mirror of
https://gitlab.com/2009scape/Saradomin-Launcher.git
synced 2026-08-01 14:19:14 -06:00
Merge branch 'singleplayerlauncher' into 'master'
Add Singleplayer Support to Launcher See merge request 2009scape/Saradomin-Launcher!48
This commit is contained in:
commit
6562434594
28 changed files with 1214 additions and 145 deletions
|
|
@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Saradomin", "Saradomin\Sara
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Glitonea", "Glitonea\Glitonea.csproj", "{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{6A66E5A7-9297-410F-B061-EB386679E430}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -18,5 +20,9 @@ Global
|
|||
{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6A66E5A7-9297-410F-B061-EB386679E430}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6A66E5A7-9297-410F-B061-EB386679E430}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6A66E5A7-9297-410F-B061-EB386679E430}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6A66E5A7-9297-410F-B061-EB386679E430}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
3
Saradomin.sln.DotSettings
Normal file
3
Saradomin.sln.DotSettings
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Saradomin/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Singleplayer/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">/usr/share/dotnet/sdk/7.0.100/MSBuild.dll</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/SolBuilderDuo/UseMsbuildSolutionBuilder/@EntryValue">No</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/SolBuilderDuo/UseMsbuildSolutionBuilder/@EntryValue">No</s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=7a951a1e_002Dc4b2_002D4516_002Db0cc_002D72fd4df57b19/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from FindMostRecentBackupDirectory.cs" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<Project Location="\home\xacket\Projects\Saradomin-Launcher\UnitTests" Presentation="&lt;UnitTests&gt;" />
|
||||
</SessionState></s:String>
|
||||
</wpf:ResourceDictionary>
|
||||
|
|
@ -37,9 +37,9 @@ namespace Saradomin.Infrastructure.Services
|
|||
Arguments =
|
||||
$"-Dsun.java2d.uiScale={_settingsService.Client.UiScale} "
|
||||
+ $"-DclientFps={_settingsService.Client.Fps} "
|
||||
+ $"-DclientHomeOverride=\"{_settingsService.Launcher.InstallationDirectory}/\" "
|
||||
+ $"-DclientHomeOverride=\"{CrossPlatform.Get2009scapeHome()}/\" "
|
||||
+ $"-jar \"{_clientUpdateService.PreferredTargetFilePath}\"",
|
||||
WorkingDirectory = $"{_settingsService.Launcher.InstallationDirectory}",
|
||||
WorkingDirectory = $"{CrossPlatform.Get2009scapeHome()}",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
public string ClientHashURL => "https://gitlab.com/2009scape/rt4-client/-/jobs/artifacts/master/raw/client/build/libs/rt4-client.jar.sha256?job=build";
|
||||
|
||||
public string PreferredTargetFilePath =>
|
||||
CrossPlatform.Locate2009scapeExecutable(_settingsService.Launcher.InstallationDirectory);
|
||||
CrossPlatform.Get2009scapeExecutable();
|
||||
|
||||
public event EventHandler<float> DownloadProgressChanged;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
{
|
||||
public interface IJavaUpdateService : IService
|
||||
{
|
||||
event EventHandler<float> JavaDownloadProgressChanged;
|
||||
event EventHandler<Tuple<float, bool>> JavaDownloadProgressChanged;
|
||||
Task DownloadAndSetJava11(ISettingsService settingsService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Glitonea.Mvvm;
|
||||
|
||||
namespace Saradomin.Infrastructure.Services
|
||||
{
|
||||
public interface ISingleplayerUpdateService : IService
|
||||
{
|
||||
event EventHandler<Tuple<float, bool>> SingleplayerDownloadProgressChanged;
|
||||
Task DownloadSingleplayer();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,18 +10,18 @@ namespace Saradomin.Infrastructure.Services
|
|||
{
|
||||
public class JavaUpdateService : IJavaUpdateService
|
||||
{
|
||||
public event EventHandler<float> JavaDownloadProgressChanged;
|
||||
public event EventHandler<Tuple<float, bool>> JavaDownloadProgressChanged;
|
||||
|
||||
public async Task DownloadAndSetJava11(ISettingsService settingsService)
|
||||
{
|
||||
string downloadUrl = CrossPlatform.GetJava11DownloadUrl();
|
||||
|
||||
string downloadPath = Path.Combine(
|
||||
CrossPlatform.LocateDefault2009scapeHome(),
|
||||
CrossPlatform.Get2009scapeHome(),
|
||||
"jre11" + Path.GetExtension(downloadUrl)
|
||||
);
|
||||
string extractedPath = Path.Combine(
|
||||
CrossPlatform.LocateDefault2009scapeHome(),
|
||||
CrossPlatform.Get2009scapeHome(),
|
||||
"jre11"
|
||||
);
|
||||
|
||||
|
|
@ -47,19 +47,20 @@ namespace Saradomin.Infrastructure.Services
|
|||
await fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||
|
||||
var progress = (float)totalRead / contentLength;
|
||||
JavaDownloadProgressChanged?.Invoke(this, progress);
|
||||
JavaDownloadProgressChanged?.Invoke(this, new Tuple<float, bool>(progress, false));
|
||||
} while (bytesRead > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JavaDownloadProgressChanged?.Invoke(this, 1f);
|
||||
JavaDownloadProgressChanged?.Invoke(this, new Tuple<float, bool>(1f, false));
|
||||
|
||||
if (Directory.Exists(extractedPath)) Directory.Delete(extractedPath, true);
|
||||
|
||||
if (Path.GetExtension(downloadUrl) == ".zip")
|
||||
{
|
||||
string tempDir = Path.Combine(Path.GetTempPath(), "jre11_temp");
|
||||
// Don't use /tmp because Directory.Move doesn't work cross-partition
|
||||
string tempDir = Path.Combine(CrossPlatform.Get2009scapeHome(), "jre11_temp");
|
||||
if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true);
|
||||
await Task.Run(() => ZipFile.ExtractToDirectory(downloadPath, tempDir));
|
||||
Directory.Move(Directory.GetDirectories(tempDir)[0], extractedPath);
|
||||
|
|
@ -96,6 +97,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
);
|
||||
}
|
||||
settingsService.SaveAll();
|
||||
JavaDownloadProgressChanged?.Invoke(this, new Tuple<float, bool>(1f, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Saradomin.Utilities;
|
||||
|
||||
namespace Saradomin.Infrastructure.Services
|
||||
{
|
||||
|
|
@ -13,7 +14,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
|
||||
public PluginManagementService(ISettingsService settings)
|
||||
{
|
||||
PluginRepositoryPath = Path.Combine(settings.Launcher.InstallationDirectory, "plugins");
|
||||
PluginRepositoryPath = Path.Combine(CrossPlatform.Get2009scapeHome(), "plugins");
|
||||
}
|
||||
|
||||
public Task<List<string>> EnumerateInstalledPlugins()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Saradomin.Model.Settings.Client;
|
||||
|
|
@ -12,10 +13,10 @@ namespace Saradomin.Infrastructure.Services
|
|||
public ClientSettings Client { get; private set; } = new();
|
||||
|
||||
private string ClientSettingsPath
|
||||
=> Path.Combine(Launcher.InstallationDirectory, ClientSettings.FileName);
|
||||
=> Path.Combine(CrossPlatform.Get2009scapeHome(), ClientSettings.FileName);
|
||||
|
||||
private string LauncherSettingsPath
|
||||
=> Path.Combine(CrossPlatform.LocateSaradominHome(), LauncherSettings.FileName);
|
||||
=> Path.Combine(CrossPlatform.GetSaradominHome(), LauncherSettings.FileName);
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
|
|
@ -30,7 +31,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
|
||||
private void TryReadConfigurationData()
|
||||
{
|
||||
Directory.CreateDirectory(CrossPlatform.LocateSaradominHome());
|
||||
Directory.CreateDirectory(CrossPlatform.GetSaradominHome());
|
||||
|
||||
if (File.Exists(LauncherSettingsPath))
|
||||
{
|
||||
|
|
@ -44,7 +45,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
SaveLauncherSettings();
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Launcher!.InstallationDirectory);
|
||||
Directory.CreateDirectory(CrossPlatform.Get2009scapeHome());
|
||||
if (File.Exists(ClientSettingsPath))
|
||||
{
|
||||
using (var stream = File.OpenRead(ClientSettingsPath))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Saradomin.Utilities;
|
||||
|
||||
namespace Saradomin.Infrastructure.Services
|
||||
{
|
||||
public class SingleplayerUpdateService : ISingleplayerUpdateService
|
||||
{
|
||||
public event EventHandler<Tuple<float, bool>> SingleplayerDownloadProgressChanged;
|
||||
|
||||
public async Task DownloadSingleplayer()
|
||||
{
|
||||
SingleplayerDownloadProgressChanged?.Invoke(this, new Tuple<float, bool>(0f, false));
|
||||
string downloadUrl =
|
||||
"https://gitlab.com/2009scape/singleplayer/windows/-/archive/master/windows-master.zip";
|
||||
|
||||
string downloadPath = Path.Combine(
|
||||
CrossPlatform.Get2009scapeHome(),
|
||||
"singleplayer" + Path.GetExtension(downloadUrl)
|
||||
);
|
||||
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
var response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||
var contentLength = response.Content.Headers.ContentLength ?? 40 * 1024 * 1024L;
|
||||
var totalRead = 0L;
|
||||
var buffer = new byte[8192];
|
||||
|
||||
// Create a FileStream to write the downloaded bytes to
|
||||
await using (var fileStream =
|
||||
new FileStream(downloadPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
{
|
||||
int bytesRead;
|
||||
do
|
||||
{
|
||||
bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
totalRead += bytesRead;
|
||||
|
||||
// Write the bytes to the FileStream
|
||||
await fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||
|
||||
var progress = (float)totalRead / contentLength;
|
||||
SingleplayerDownloadProgressChanged?.Invoke(this,
|
||||
new Tuple<float, bool>(progress, false));
|
||||
} while (bytesRead > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SingleplayerDownloadProgressChanged?.Invoke(this, new Tuple<float, bool>(1f, false));
|
||||
|
||||
if (Directory.Exists(CrossPlatform.GetSingleplayerHome())) Directory.Delete(CrossPlatform.GetSingleplayerHome(), true);
|
||||
|
||||
// Don't use /tmp because Directory.Move doesn't work cross-partition
|
||||
string tempDir = Path.Combine(CrossPlatform.Get2009scapeHome(), "singleplayer_temp");
|
||||
if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true);
|
||||
await Task.Run(() => ZipFile.ExtractToDirectory(downloadPath, tempDir));
|
||||
Directory.Move(Directory.GetDirectories(tempDir)[0], CrossPlatform.GetSingleplayerHome());
|
||||
Directory.Delete(tempDir, true);
|
||||
|
||||
File.Delete(downloadPath);
|
||||
SingleplayerDownloadProgressChanged?.Invoke(this, new Tuple<float, bool>(1f, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,12 +9,9 @@ namespace Saradomin.Model.Settings.Launcher
|
|||
public bool PlaceCloseButtonOnLeft { get; set; } = false;
|
||||
public bool ExitAfterLaunchingClient { get; set; } = true;
|
||||
public bool AllowMultiboxing { get; set; } = false;
|
||||
|
||||
public bool CheckForClientUpdatesOnLaunch { get; set; } = true;
|
||||
public bool CheckForServerProfilesOnLaunch { get; set; } = true;
|
||||
|
||||
public string JavaExecutableLocation { get; set; }
|
||||
public string InstallationDirectory { get; set; } = CrossPlatform.LocateDefault2009scapeHome();
|
||||
|
||||
protected override void OnSettingsModified(string propertyName)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,4 +23,9 @@
|
|||
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource DarkForegroundBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.OutsideNavigator:disabled">
|
||||
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SemiDarkForegroundBrush}" />
|
||||
</Style>
|
||||
</Styles>
|
||||
|
|
@ -3,18 +3,10 @@
|
|||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>1.6.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetPlatform>x64</TargetPlatform>
|
||||
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
|
||||
<NoWarn>IL2026;CS0067</NoWarn>
|
||||
|
||||
<ApplicationIcon>Resources\Icons\saradomin.ico</ApplicationIcon>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<StartupObject>Saradomin.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
@ -66,6 +58,10 @@
|
|||
<DependentUpon>NotificationBox.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\Controls\SingleplayerView.axaml.cs">
|
||||
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\Controls\SettingsView.axaml.cs">
|
||||
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
|
|
@ -77,6 +73,10 @@
|
|||
<Compile Update="View\Controls\PluginManagerView.axaml.cs">
|
||||
<DependentUpon>PluginManagerView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="View\Controls\SingleplayerView.axaml.cs">
|
||||
<DependentUpon>SingleplayerView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Win32;
|
||||
using Mono.Unix;
|
||||
|
||||
|
|
@ -27,6 +28,22 @@ namespace Saradomin.Utilities
|
|||
}
|
||||
}
|
||||
|
||||
public static void OpenFolder(string path)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Process.Start("xdg-open", path);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
Process.Start("open", path);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsJavaExecutableValid(string location)
|
||||
{
|
||||
try
|
||||
|
|
@ -165,13 +182,12 @@ namespace Saradomin.Utilities
|
|||
);
|
||||
}
|
||||
|
||||
public static string LocateDefault2009scapeHome()
|
||||
public static string Get2009scapeHome()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
|| RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
||||
{
|
||||
return Path.Combine(
|
||||
// Get the XDG_DATA_HOME environment variable, or if it doesn't exist, use the default ~/.local/share
|
||||
LocateUnixUserHome(),
|
||||
"2009scape"
|
||||
);
|
||||
|
|
@ -185,7 +201,7 @@ namespace Saradomin.Utilities
|
|||
}
|
||||
}
|
||||
|
||||
public static string LocateSaradominHome()
|
||||
public static string GetSaradominHome()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
|| RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
||||
|
|
@ -197,33 +213,44 @@ namespace Saradomin.Utilities
|
|||
"saradomin"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"2009scape",
|
||||
"saradomin"
|
||||
);
|
||||
}
|
||||
|
||||
return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"2009scape",
|
||||
"saradomin"
|
||||
);
|
||||
}
|
||||
|
||||
public static string GetSingleplayerBackupsHome()
|
||||
{
|
||||
return Path.Combine(Get2009scapeHome(), "singleplayer_backups");
|
||||
}
|
||||
|
||||
public static string GetSingleplayerHome()
|
||||
{
|
||||
return Path.Combine(Get2009scapeHome(), "singleplayer");
|
||||
}
|
||||
|
||||
public static string LocateSingleplayerExecutable()
|
||||
{
|
||||
return Path.Combine(GetSingleplayerHome(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "launch.bat" : "launch.sh");
|
||||
}
|
||||
|
||||
public static string Locate2009scapeExecutable(string baseDirectory)
|
||||
public static string Get2009scapeExecutable()
|
||||
{
|
||||
baseDirectory ??= LocateDefault2009scapeHome();
|
||||
return Path.Combine(baseDirectory, "2009scape.jar");
|
||||
return Path.Combine(Get2009scapeHome(), "2009scape.jar");
|
||||
}
|
||||
|
||||
public static string LocateServerProfilesPath(string baseDirectory)
|
||||
public static string GetServerProfilePath(string baseDirectory)
|
||||
{
|
||||
baseDirectory ??= LocateDefault2009scapeHome();
|
||||
baseDirectory ??= Get2009scapeHome();
|
||||
return Path.Combine(baseDirectory, "server_profiles.json");
|
||||
}
|
||||
|
||||
public static string RunCommandAndGetOutput(string command)
|
||||
public static string RunCommandAndGetOutput(string command, Action<string> onOutputReceived = null, Action<string> onErrorReceived = null)
|
||||
{
|
||||
Process process = new Process();
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
process.StartInfo = new ProcessStartInfo("cmd.exe", "/c " + command)
|
||||
|
|
@ -250,12 +277,16 @@ namespace Saradomin.Utilities
|
|||
|
||||
process.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null) output.AppendLine(e.Data);
|
||||
if (e.Data == null) return;
|
||||
output.AppendLine(e.Data);
|
||||
onOutputReceived?.Invoke(e.Data);
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null) output.AppendLine(e.Data);
|
||||
if (e.Data == null) return;
|
||||
output.AppendLine(e.Data);
|
||||
onErrorReceived?.Invoke(e.Data);
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
|
|
|||
160
Saradomin/Utilities/Singleplayer/SingleplayerManagement.cs
Normal file
160
Saradomin/Utilities/Singleplayer/SingleplayerManagement.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace Saradomin.Utilities;
|
||||
|
||||
public static class SingleplayerManagement
|
||||
{
|
||||
private static readonly string[] DirsToBackup =
|
||||
{
|
||||
"game/data/players",
|
||||
"game/data/serverstore",
|
||||
"game/worldprops"
|
||||
};
|
||||
|
||||
private static readonly string[] FilesToBackup =
|
||||
{
|
||||
"game/data/eco/grandexchange.db"
|
||||
};
|
||||
|
||||
public static void MakeBackup(Action<string> log)
|
||||
{
|
||||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
string newBackupDir = Path.Combine(
|
||||
CrossPlatform.GetSingleplayerBackupsHome(),
|
||||
timestamp
|
||||
);
|
||||
|
||||
foreach (string dir in DirsToBackup)
|
||||
{
|
||||
string sourcePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), dir);
|
||||
if (!Directory.Exists(sourcePath))
|
||||
{
|
||||
log($" Skipping backup of {dir} because it doesn't exist (Full path attempted: {sourcePath})");
|
||||
continue;
|
||||
}
|
||||
|
||||
string destPath = Path.Combine(newBackupDir, dir);
|
||||
CopyDirectory(sourcePath, destPath);
|
||||
}
|
||||
|
||||
foreach (string file in FilesToBackup)
|
||||
{
|
||||
string sourceFilePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), file);
|
||||
if (!File.Exists(sourceFilePath))
|
||||
{
|
||||
log($" Skipping backup of {file} because it doesn't exist (Full path attempted: {sourceFilePath})");
|
||||
continue;
|
||||
}
|
||||
|
||||
string destFilePath = Path.Combine(newBackupDir, file);
|
||||
string directoryForFile = Path.GetDirectoryName(destFilePath);
|
||||
if (!Directory.Exists(directoryForFile))
|
||||
Directory.CreateDirectory(directoryForFile);
|
||||
File.Copy(sourceFilePath, destFilePath, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
if (!Directory.Exists(destinationDir))
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
foreach (string filePath in Directory.GetFiles(sourceDir))
|
||||
{
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
string destFilePath = Path.Combine(destinationDir, fileName);
|
||||
File.Copy(filePath, destFilePath, true);
|
||||
}
|
||||
|
||||
foreach (string subDirPath in Directory.GetDirectories(sourceDir))
|
||||
{
|
||||
string subDirName = Path.GetFileName(subDirPath);
|
||||
string destSubDirPath = Path.Combine(destinationDir, subDirName);
|
||||
// Recurse!
|
||||
CopyDirectory(subDirPath, destSubDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static string FindMostRecentBackupDirectory(IEnumerable<string> directories)
|
||||
{
|
||||
return directories.Select(Path.GetFileName).MaxBy(name => name);
|
||||
}
|
||||
|
||||
public static void ApplyLatestBackup(Action<string> log)
|
||||
{
|
||||
var backupHome = CrossPlatform.GetSingleplayerBackupsHome();
|
||||
var singleplayerHome = CrossPlatform.GetSingleplayerHome();
|
||||
|
||||
// Get all backup directories
|
||||
string[] backupDirectories = Directory.Exists(backupHome) ? Directory.GetDirectories(backupHome) : Array.Empty<string>();
|
||||
|
||||
string mostRecentBackupDirName = FindMostRecentBackupDirectory(backupDirectories);
|
||||
|
||||
if (mostRecentBackupDirName == null)
|
||||
{
|
||||
log(" No backups found.");
|
||||
return;
|
||||
}
|
||||
|
||||
string mostRecentBackupFullPath = Path.Combine(backupHome, mostRecentBackupDirName);
|
||||
|
||||
// Get the list of files in the most recent backup
|
||||
var filesInBackup = Directory.GetFiles(
|
||||
mostRecentBackupFullPath,
|
||||
"*",
|
||||
SearchOption.AllDirectories
|
||||
);
|
||||
|
||||
foreach (var file in filesInBackup)
|
||||
{
|
||||
// Calculate the file's destination path in the singleplayer home directory
|
||||
var relativePath = file.Substring(mostRecentBackupFullPath.Length)
|
||||
.TrimStart(Path.DirectorySeparatorChar);
|
||||
var destFilePath = Path.Combine(singleplayerHome, relativePath);
|
||||
|
||||
// Ensure the destination directory exists
|
||||
var destDirectory = Path.GetDirectoryName(destFilePath);
|
||||
if (!Directory.Exists(destDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(destDirectory);
|
||||
}
|
||||
|
||||
// Copy the file to the destination, overwriting any existing file
|
||||
File.Copy(file, destFilePath, true);
|
||||
}
|
||||
|
||||
log($" Backup from {mostRecentBackupDirName} has been successfully applied.");
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> _confCache;
|
||||
private static string ConfPath => Path.Combine(CrossPlatform.GetSingleplayerHome(), "game", "worldprops", "default.conf");
|
||||
public static Dictionary<string, string> GrabConfCache()
|
||||
{
|
||||
return File.ReadLines(ConfPath)
|
||||
.Where(line => line.Contains('=') && !line.TrimStart().StartsWith("#"))
|
||||
.Select(l => l.Split(new[] { '#', '=' }, 3))
|
||||
.ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
|
||||
}
|
||||
|
||||
public static T ParseConf<T>(string key, T defaultValue = default)
|
||||
{
|
||||
if (!File.Exists(ConfPath)) return defaultValue;
|
||||
_confCache ??= GrabConfCache();
|
||||
if (!_confCache.TryGetValue(key, out var value)) return defaultValue;
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
public static void WriteConf(string key, object value)
|
||||
{
|
||||
_confCache[key] = value.ToString().ToLowerInvariant(); // Update cache
|
||||
var lines = File.ReadAllLines(ConfPath).Select(l =>
|
||||
l.StartsWith(key + " =")
|
||||
? $"{key} = {_confCache[key]}" + (l.Contains("#") ? " " + l.Substring(l.IndexOf('#')) : "")
|
||||
: l);
|
||||
File.WriteAllLines(ConfPath, lines);
|
||||
}
|
||||
}
|
||||
77
Saradomin/Utilities/Singleplayer/Windows.cs
Normal file
77
Saradomin/Utilities/Singleplayer/Windows.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading;
|
||||
|
||||
namespace Saradomin.Utilities.Singleplayer;
|
||||
|
||||
public static class Windows
|
||||
{
|
||||
private static bool IsPortInUse(int port)
|
||||
{
|
||||
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpListeners();
|
||||
|
||||
return tcpConnInfoArray.Any(endpoint => endpoint.Port == port);
|
||||
}
|
||||
|
||||
private static Process StartJavaProcess(string javaExecutable, string jarPath, string memoryAllocation, Action<string> outputHandler, Action onExit)
|
||||
{
|
||||
Process process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = javaExecutable,
|
||||
Arguments = $"-Xmx{memoryAllocation} -Xms{memoryAllocation} -jar \"{jarPath}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
WorkingDirectory = Path.Combine(CrossPlatform.GetSingleplayerHome(), "game")
|
||||
};
|
||||
|
||||
process.OutputDataReceived += (_, args) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(args.Data)) return;
|
||||
outputHandler?.Invoke(args.Data);
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (_, args) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(args.Data)) return;
|
||||
outputHandler?.Invoke(args.Data);
|
||||
};
|
||||
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += (_, _) => onExit?.Invoke();
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
return process;
|
||||
}
|
||||
|
||||
public static void WindowsLaunchServerAndClient(string javaExecutableLocation, Action<string> log)
|
||||
{
|
||||
string serverJar = CrossPlatform.GetSingleplayerHome() + @"\game\server.jar";
|
||||
string clientJar = CrossPlatform.GetSingleplayerHome() + @"\game\client.jar";
|
||||
|
||||
if (IsPortInUse(43595))
|
||||
{
|
||||
log("Port 43595 is in use. Cannot start the server again.");
|
||||
return;
|
||||
}
|
||||
|
||||
Process serverProcess = StartJavaProcess(javaExecutableLocation, serverJar, "2G", log, null);
|
||||
|
||||
while (!IsPortInUse(43595)) Thread.Sleep(1000);
|
||||
|
||||
StartJavaProcess(javaExecutableLocation, clientJar, "1G", null, () =>
|
||||
{
|
||||
serverProcess.Kill();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@
|
|||
Width="300"
|
||||
Margin="2,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
IsReadOnly="True"
|
||||
Classes="NormalTextBox"
|
||||
Text="{Binding Launcher.JavaExecutableLocation, Mode=TwoWay}"
|
||||
Watermark="Enter a path to java or java.exe..." />
|
||||
|
|
@ -59,40 +60,6 @@
|
|||
ToolTip.Tip="Browse..." />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,2,0,4"
|
||||
ColumnDefinitions="*,Auto"
|
||||
RowDefinitions="Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="3,0,0,2"
|
||||
Foreground="{StaticResource DarkForegroundBrush}"
|
||||
Text="2009scape installation directory" />
|
||||
|
||||
<!--
|
||||
Don't let the user edit it directly to avoid creating a ton of directories,
|
||||
since we get a notification every time the textbox is updated.
|
||||
-->
|
||||
<TextBox Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Width="300"
|
||||
Margin="2,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Classes="NormalTextBox"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding Launcher.InstallationDirectory, Mode=TwoWay}"
|
||||
Watermark="Enter a path to your preferred installation directory..." />
|
||||
|
||||
<Button Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="22"
|
||||
Height="24"
|
||||
Margin="2,0,0,0"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding BrowseForInstallationDirectory}"
|
||||
Content="..."
|
||||
ToolTip.Tip="Browse..." />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,4"
|
||||
RowDefinitions="Auto, Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
|
|
|
|||
197
Saradomin/View/Controls/SingleplayerView.axaml
Normal file
197
Saradomin/View/Controls/SingleplayerView.axaml
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<UserControl x:Class="Saradomin.View.Controls.SingleplayerView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Saradomin.View.Controls"
|
||||
xmlns:converters="clr-namespace:Glitonea.Mvvm.Converters;assembly=Glitonea"
|
||||
xmlns:glitonea="clr-namespace:Glitonea;assembly=Glitonea"
|
||||
xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
|
||||
xmlns:vm="clr-namespace:Saradomin.ViewModel.Controls"
|
||||
DataContext="{mvvm:DataContextSource vm:SingleplayerViewModel}">
|
||||
<Grid Margin="0,0,2,2"
|
||||
ColumnDefinitions="400,*">
|
||||
<DockPanel Grid.Column="0">
|
||||
<HeaderedContentControl Classes="GroupBox"
|
||||
DockPanel.Dock="Top"
|
||||
Header="singleplayer logs"
|
||||
IsVisible="{Binding ShowLogPanel}"
|
||||
>
|
||||
<ScrollViewer HorizontalAlignment="Left"
|
||||
ClipToBounds="True"
|
||||
Height="300"
|
||||
Width="390"
|
||||
Content="{Binding SingleplayerLogsTextBox}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible" />
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Classes="GroupBox"
|
||||
DockPanel.Dock="Top"
|
||||
Header="singleplayer info"
|
||||
IsVisible="{Binding !ShowLogPanel}"
|
||||
>
|
||||
<StackPanel Orientation="Vertical" Margin="10">
|
||||
<TextBlock Width="380" TextWrapping="Wrap" Foreground="Black">
|
||||
Singleplayer is provided by the 2009scape team for advanced users only.
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Width="380" TextWrapping="Wrap" Margin="0,10,0,0" Foreground="Black">
|
||||
2009scape official servers offer free membership, multiple XP rates, and are hosted 24/7. Your player save file can be exported from the multiplayer server if you wish to switch to singleplayer.
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Width="380" TextWrapping="Wrap" Foreground="Black">
|
||||
However, we will not import your singleplayer save file into the multiplayer world. If you ever want to play with others, we recommend the multiplayer server.
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Width="380" TextWrapping="Wrap" Margin="0,10,0,0" Foreground="Black">
|
||||
If you have any questions, feel free to ask on our Discord or Forums.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Classes="GroupBox"
|
||||
DockPanel.Dock="Top"
|
||||
Header="singleplayer links">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Button
|
||||
Margin="0,0,0,4"
|
||||
Width="140"
|
||||
Height="25"
|
||||
BorderBrush="{StaticResource DarkBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding LaunchFaq}"
|
||||
Opacity="1"
|
||||
FontSize="11"
|
||||
Content="singleplayer faq"
|
||||
ZIndex="9999" />
|
||||
|
||||
<Button
|
||||
Margin="0,0,0,4"
|
||||
Width="140"
|
||||
Height="25"
|
||||
BorderBrush="{StaticResource DarkBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding LaunchForums}"
|
||||
Opacity="1"
|
||||
FontSize="11"
|
||||
Content="support forums"
|
||||
ZIndex="9999" />
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
</DockPanel>
|
||||
<DockPanel Grid.Column="1">
|
||||
<HeaderedContentControl Classes="GroupBox"
|
||||
DockPanel.Dock="Top"
|
||||
Header="singleplayer launch">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Button
|
||||
Margin="0,0,0,4"
|
||||
Width="140"
|
||||
Height="25"
|
||||
BorderBrush="{StaticResource DarkBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding DownloadSingleplayer}"
|
||||
Opacity="1"
|
||||
FontSize="11"
|
||||
Content="{Binding SingleplayerDownloadText}"
|
||||
ZIndex="9999"
|
||||
IsEnabled="{Binding CanDownload}" />
|
||||
|
||||
<Button
|
||||
Margin="0,0,0,4"
|
||||
Width="140"
|
||||
Height="25"
|
||||
BorderBrush="{StaticResource DarkBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding LaunchSingleplayer}"
|
||||
Opacity="1"
|
||||
FontSize="11"
|
||||
Content="Start Singleplayer"
|
||||
ZIndex="9999"
|
||||
IsEnabled="{Binding CanLaunch}" />
|
||||
|
||||
<Button
|
||||
Margin="0,0,0,4"
|
||||
Width="140"
|
||||
Height="25"
|
||||
BorderBrush="{StaticResource DarkBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding MakeBackup}"
|
||||
Opacity="1"
|
||||
FontSize="11"
|
||||
Content="Make Backup"
|
||||
ZIndex="9999"/>
|
||||
|
||||
<Button
|
||||
Margin="0,0,0,4"
|
||||
Width="140"
|
||||
Height="25"
|
||||
BorderBrush="{StaticResource DarkBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding OpenBackupFolder}"
|
||||
Opacity="1"
|
||||
FontSize="11"
|
||||
Content="Open Backups"
|
||||
ZIndex="9999" />
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Margin="2,2,0,0"
|
||||
Classes="GroupBox"
|
||||
DockPanel.Dock="Top"
|
||||
Header="singleplayer config">
|
||||
<HeaderedContentControl.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Text="{Binding Header}" />
|
||||
</DataTemplate>
|
||||
</HeaderedContentControl.HeaderTemplate>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Foreground="{StaticResource DarkForegroundBrush}"
|
||||
/>
|
||||
|
||||
<CheckBox Content="Cheats"
|
||||
IsChecked="{Binding Cheats, Mode=TwoWay}"
|
||||
IsEnabled="{Binding CanLaunch}"
|
||||
/>
|
||||
|
||||
<CheckBox Content="Fake Players"
|
||||
IsChecked="{Binding FakePlayers, Mode=TwoWay}"
|
||||
IsEnabled="{Binding CanLaunch}"
|
||||
/>
|
||||
|
||||
<CheckBox Content="GE Auto Buy and Sell"
|
||||
IsChecked="{Binding GEAutoBuySell, Mode=TwoWay}"
|
||||
IsEnabled="{Binding CanLaunch}"
|
||||
/>
|
||||
|
||||
<CheckBox Content="Debug"
|
||||
IsChecked="{Binding Debug, Mode=TwoWay}"
|
||||
IsEnabled="{Binding CanLaunch}"
|
||||
/>
|
||||
|
||||
<Button
|
||||
Margin="0,0,0,0"
|
||||
Width="140"
|
||||
Height="25"
|
||||
BorderBrush="{StaticResource DarkBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Classes="OutsideNavigator"
|
||||
Opacity="1"
|
||||
FontSize="11"
|
||||
Content="Reset Config"
|
||||
Command="{Binding ResetToDefaults}"
|
||||
IsEnabled="{Binding CanLaunch}"
|
||||
ZIndex="9999"/>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
20
Saradomin/View/Controls/SingleplayerView.axaml.cs
Normal file
20
Saradomin/View/Controls/SingleplayerView.axaml.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using PropertyChanged;
|
||||
|
||||
namespace Saradomin.View.Controls
|
||||
{
|
||||
[DoNotNotify]
|
||||
public class SingleplayerView : UserControl
|
||||
{
|
||||
public SingleplayerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
|
||||
<TabItem Width="95"
|
||||
HorizontalContentAlignment="Center"
|
||||
CornerRadius="0,0,8,0"
|
||||
CornerRadius="0,0,0,0"
|
||||
Header="settings">
|
||||
<Border Margin="4,0,4,0"
|
||||
Background="{StaticResource MediumBackgroundBrush}"
|
||||
|
|
@ -125,6 +125,23 @@
|
|||
</ScrollViewer>
|
||||
</Border>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Width="95"
|
||||
HorizontalContentAlignment="Center"
|
||||
CornerRadius="0,0,8,0"
|
||||
Header="singleplayer">
|
||||
<Border Margin="4,0,4,0"
|
||||
Background="{StaticResource MediumBackgroundBrush}"
|
||||
BorderBrush="{StaticResource SemiLightMediumBorderBrush}"
|
||||
BorderThickness="1,1,1,0"
|
||||
CornerRadius="6,6,0,0">
|
||||
<ScrollViewer Classes="MainViewScrollViewer"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Visible">
|
||||
<controls:SingleplayerView VerticalAlignment="Stretch" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<Border Grid.Row="2"
|
||||
|
|
|
|||
|
|
@ -120,32 +120,5 @@ namespace Saradomin.ViewModel.Controls
|
|||
Launcher.JavaExecutableLocation = paths[0];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BrowseForInstallationDirectory()
|
||||
{
|
||||
var ofd = new OpenFolderDialog
|
||||
{
|
||||
Title = "Browse for Installation Directory...",
|
||||
Directory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
|
||||
};
|
||||
|
||||
var path = await ofd.ShowAsync(Application.Current.GetMainWindow());
|
||||
|
||||
if (path != null)
|
||||
{
|
||||
if (CrossPlatform.IsDirectoryWritable(path))
|
||||
{
|
||||
Launcher.InstallationDirectory = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
NotificationBox.DisplayNotification(
|
||||
"Access denied",
|
||||
"The location you have selected is not writable. Select the one you have permissions for.",
|
||||
Application.Current.GetMainWindow()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
208
Saradomin/ViewModel/Controls/SingleplayerViewModel.cs
Normal file
208
Saradomin/ViewModel/Controls/SingleplayerViewModel.cs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using Glitonea.Mvvm;
|
||||
using Saradomin.Infrastructure.Services;
|
||||
using Saradomin.Model.Settings.Launcher;
|
||||
using Saradomin.Utilities;
|
||||
using Saradomin.View.Windows;
|
||||
using static Saradomin.Utilities.SingleplayerManagement;
|
||||
|
||||
namespace Saradomin.ViewModel.Controls;
|
||||
|
||||
public class SingleplayerViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ISingleplayerUpdateService _singleplayerUpdateService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IJavaUpdateService _javaUpdateService;
|
||||
|
||||
public string SingleplayerDownloadText { get; private set; } =
|
||||
Directory.Exists(CrossPlatform.GetSingleplayerHome()) ? "Update Singleplayer" : "Download Singleplayer";
|
||||
public bool CanLaunch { get; private set; } = File.Exists(CrossPlatform.LocateSingleplayerExecutable());
|
||||
public TextBox SingleplayerLogsTextBox { get; }
|
||||
public bool ShowLogPanel { get; private set; }
|
||||
public LauncherSettings Launcher => _settingsService.Launcher;
|
||||
|
||||
public SingleplayerViewModel(ISettingsService settingsService,
|
||||
IJavaUpdateService javaUpdateService,
|
||||
ISingleplayerUpdateService iSingleplayerUpdateService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_javaUpdateService = javaUpdateService;
|
||||
_singleplayerUpdateService = iSingleplayerUpdateService;
|
||||
_singleplayerUpdateService.SingleplayerDownloadProgressChanged += OnSingleplayerDownloadProgressChanged;
|
||||
|
||||
SingleplayerLogsTextBox = new TextBox
|
||||
{
|
||||
Foreground = new SolidColorBrush(Colors.Black),
|
||||
FontSize = 12,
|
||||
Margin = new Thickness(8, 0, 0, 0),
|
||||
IsReadOnly = true,
|
||||
BorderThickness = new Thickness(0),
|
||||
Background = Brushes.Transparent,
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnSingleplayerDownloadProgressChanged(object sender, Tuple<float, bool> e)
|
||||
{
|
||||
float progress = e.Item1;
|
||||
bool finished = e.Item2;
|
||||
if (finished)
|
||||
{
|
||||
SingleplayerDownloadText = "Update Singleplayer";
|
||||
ApplyLatestBackup(PrintLog);
|
||||
PrintLog($"Singleplayer Download complete");
|
||||
PrintLog($"");
|
||||
CanLaunch = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress >= 1f)
|
||||
{
|
||||
SingleplayerDownloadText = "Extracting...";
|
||||
return;
|
||||
}
|
||||
|
||||
SingleplayerDownloadText = $"Downloading... {progress * 100:F2}%";
|
||||
}
|
||||
|
||||
public void DownloadSingleplayer()
|
||||
{
|
||||
if (File.Exists(CrossPlatform.LocateSingleplayerExecutable()) && !CanLaunch) return; // While the button could be disabled, this looks nicer visually (since we update the download progress in the button text)
|
||||
CanLaunch = false;
|
||||
MakeBackup();
|
||||
PrintLog($"Starting singleplayer download...");
|
||||
_singleplayerUpdateService.DownloadSingleplayer();
|
||||
}
|
||||
|
||||
private void MakeBackup()
|
||||
{
|
||||
PrintLog("Starting backup of player saves and economy files..");
|
||||
SingleplayerManagement.MakeBackup(PrintLog);
|
||||
PrintLog($"Done backing up player saves and economy files");
|
||||
PrintLog($"");
|
||||
}
|
||||
|
||||
public void OpenBackupFolder()
|
||||
{
|
||||
if (!Directory.Exists(CrossPlatform.GetSingleplayerBackupsHome()))
|
||||
Directory.CreateDirectory(CrossPlatform.GetSingleplayerBackupsHome());
|
||||
CrossPlatform.OpenFolder(CrossPlatform.GetSingleplayerBackupsHome());
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public static bool IsServerTerminationLog(string log)
|
||||
{
|
||||
return Regex.IsMatch(log, @"^\[\d{2}:\d{2}:\d{2}\]: \[SystemTermination\] Server successfully terminated!\s*$");
|
||||
}
|
||||
|
||||
private void PrintLog(string message)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) message = Regex.Replace(message, @"\e\[[0-9;]*m", string.Empty);
|
||||
|
||||
ShowLogPanel = true;
|
||||
Dispatcher.UIThread.Post(() => { SingleplayerLogsTextBox.Text += message + Environment.NewLine; },
|
||||
DispatcherPriority.Background);
|
||||
|
||||
if (IsServerTerminationLog(message)) CanLaunch = true;
|
||||
}
|
||||
|
||||
public async void LaunchSingleplayer()
|
||||
{
|
||||
string javaVersionOutput = CrossPlatform.RunCommandAndGetOutput(
|
||||
$"\"{Launcher.JavaExecutableLocation}\" -version"
|
||||
);
|
||||
if (!javaVersionOutput.Contains("11"))
|
||||
{
|
||||
PrintLog("You don't have Java 11 set! Saradomin will grab it's own copy..");
|
||||
await _javaUpdateService.DownloadAndSetJava11(_settingsService);
|
||||
}
|
||||
CanLaunch = false;
|
||||
PrintLog("Starting Singleplayer.. The Singleplayer client will launch when 2009scape is ready. Sit tight!");
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
new Task(() => Utilities.Singleplayer.Windows.WindowsLaunchServerAndClient(Launcher.JavaExecutableLocation, PrintLog)).Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
new Task(() =>
|
||||
CrossPlatform.RunCommandAndGetOutput(
|
||||
$"{CrossPlatform.LocateSingleplayerExecutable()} \"{Launcher.JavaExecutableLocation}\"",
|
||||
PrintLog,
|
||||
PrintLog)
|
||||
).Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchFaq()
|
||||
{
|
||||
CrossPlatform.LaunchURL("https://2009scape.org/site/game_guide/singleplayer.html");
|
||||
}
|
||||
|
||||
private void LaunchForums()
|
||||
{
|
||||
CrossPlatform.LaunchURL("https://forum.2009scape.org/viewforum.php?f=8-support");
|
||||
}
|
||||
|
||||
public bool Cheats
|
||||
{
|
||||
get => ParseConf<bool>("noauth_default_admin");
|
||||
set => WriteConf("noauth_default_admin", value);
|
||||
}
|
||||
|
||||
public bool FakePlayers
|
||||
{
|
||||
get => ParseConf<bool>("enable_bots", true);
|
||||
set => WriteConf("enable_bots", value);
|
||||
}
|
||||
|
||||
public bool GEAutoBuySell
|
||||
{
|
||||
get => ParseConf<bool>("i_want_to_cheat");
|
||||
set => WriteConf("i_want_to_cheat", value);
|
||||
}
|
||||
|
||||
public bool Debug
|
||||
{
|
||||
get => ParseConf<bool>("debug");
|
||||
set => WriteConf("debug", value);
|
||||
}
|
||||
|
||||
public async void ResetToDefaults()
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(5);
|
||||
MakeBackup();
|
||||
PrintLog("Grabbing the default config from the latest singleplayer codebase..");
|
||||
try
|
||||
{
|
||||
string defaultConf = await httpClient.GetStringAsync("https://gitlab.com/2009scape/singleplayer/windows/-/raw/master/game/worldprops/default.conf");
|
||||
await File.WriteAllTextAsync(CrossPlatform.GetSingleplayerHome() + "/game/worldprops/default.conf",
|
||||
defaultConf);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PrintLog($"Couldn't reset game properties");
|
||||
PrintLog(ex.Message);
|
||||
return;
|
||||
}
|
||||
Cheats = GEAutoBuySell = Debug = false; // Force update UI
|
||||
FakePlayers = true;
|
||||
PrintLog("Saved new config.");
|
||||
PrintLog("");
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,6 @@ namespace Saradomin.ViewModel.Windows
|
|||
private readonly IRemoteConfigService _remoteConfigService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private bool JavaExecutableValid
|
||||
=> CrossPlatform.IsJavaExecutableValid(_settingsService.Launcher.JavaExecutableLocation);
|
||||
|
||||
private LauncherSettings Launcher { get; }
|
||||
|
||||
public string Title { get; set; } = "2009scape launcher";
|
||||
|
|
@ -50,9 +47,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
_remoteConfigService = remoteConfigService;
|
||||
_javaUpdateService = javaUpdateService;
|
||||
_javaUpdateService.JavaDownloadProgressChanged += OnJavaDownloadProgressUpdated;
|
||||
|
||||
|
||||
|
||||
_settingsService = settingsService;
|
||||
Launcher = _settingsService.Launcher;
|
||||
|
||||
|
|
@ -62,13 +57,9 @@ namespace Saradomin.ViewModel.Windows
|
|||
};
|
||||
|
||||
Message.Subscribe<MainViewLoadedMessage>(this, MainViewLoaded);
|
||||
Message.Subscribe<SettingsModifiedMessage>(this, SettingsModified);
|
||||
Message.Subscribe<NotificationBoxStateChangedMessage>(this, NotificatationBoxStateChanged);
|
||||
|
||||
if (!JavaExecutableValid)
|
||||
{
|
||||
_settingsService.Launcher.JavaExecutableLocation = CrossPlatform.LocateJavaExecutable();
|
||||
}
|
||||
_settingsService.Launcher.JavaExecutableLocation ??= CrossPlatform.LocateJavaExecutable();
|
||||
}
|
||||
|
||||
public void ExitApplication()
|
||||
|
|
@ -102,23 +93,6 @@ namespace Saradomin.ViewModel.Windows
|
|||
}
|
||||
}
|
||||
|
||||
public void SettingsModified(SettingsModifiedMessage msg)
|
||||
{
|
||||
if (msg.SettingName == nameof(LauncherSettings.JavaExecutableLocation))
|
||||
{
|
||||
if (!JavaExecutableValid)
|
||||
{
|
||||
CanLaunch = false;
|
||||
LaunchText = "Unable to locate Java. Find Java executable using Settings page.";
|
||||
}
|
||||
else
|
||||
{
|
||||
CanLaunch = true;
|
||||
LaunchText = "Play!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NotificatationBoxStateChanged(NotificationBoxStateChangedMessage msg)
|
||||
{
|
||||
DimContent = msg.WasOpened;
|
||||
|
|
@ -174,7 +148,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
}
|
||||
|
||||
|
||||
if (!File.Exists(CrossPlatform.LocateServerProfilesPath(Launcher.InstallationDirectory)) ||
|
||||
if (!File.Exists(CrossPlatform.GetServerProfilePath(CrossPlatform.Get2009scapeHome())) ||
|
||||
_settingsService.Launcher.CheckForServerProfilesOnLaunch)
|
||||
await AttemptServerProfileUpdate();
|
||||
|
||||
|
|
@ -205,7 +179,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
|
||||
private async Task AttemptServerProfileUpdate()
|
||||
{
|
||||
var serverProfilePath = CrossPlatform.LocateServerProfilesPath(Launcher.InstallationDirectory);
|
||||
var serverProfilePath = CrossPlatform.GetServerProfilePath(CrossPlatform.Get2009scapeHome());
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -269,7 +243,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
{
|
||||
|
||||
LaunchText = $"Updating... (Downloading client: 0%)";
|
||||
Directory.CreateDirectory(Launcher.InstallationDirectory);
|
||||
Directory.CreateDirectory(CrossPlatform.Get2009scapeHome());
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -291,7 +265,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
private bool IsJavaVersion11()
|
||||
{
|
||||
string javaVersionOutput = CrossPlatform.RunCommandAndGetOutput(
|
||||
$"\"{_settingsService.Launcher.JavaExecutableLocation}\" -version"
|
||||
$"\"{Launcher.JavaExecutableLocation}\" -version"
|
||||
);
|
||||
return javaVersionOutput.Contains("11");
|
||||
}
|
||||
|
|
@ -300,14 +274,19 @@ namespace Saradomin.ViewModel.Windows
|
|||
{
|
||||
LaunchText = $"Updating... (Downloading client - {e * 100:F2}%)";
|
||||
}
|
||||
private void OnJavaDownloadProgressUpdated(object sender, float e)
|
||||
private void OnJavaDownloadProgressUpdated(object sender, Tuple<float, bool> e)
|
||||
{
|
||||
if (e >= 0.999f)
|
||||
if (e.Item2)
|
||||
{
|
||||
LaunchText = "Play! (Multiplayer)";
|
||||
return;
|
||||
}
|
||||
if (e.Item1 >= 0.999f)
|
||||
{
|
||||
LaunchText = "Updating... (Extracting Java 11)";
|
||||
return;
|
||||
}
|
||||
LaunchText = $"Updating... (Downloading Java 11 - {e * 100:F2}%)";
|
||||
LaunchText = $"Updating... (Downloading Java 11 - {e.Item1 * 100:F2}%)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,295 @@
|
|||
namespace UnitTests.Saradomin.Utilities.SingleplayerManagement;
|
||||
|
||||
[TestFixture]
|
||||
public class BackupManagerTests
|
||||
{
|
||||
[Test]
|
||||
public void WithValidDirectories_ReturnsMostRecent()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230101_120000",
|
||||
"/backups/20230102_120000",
|
||||
"/backups/20230103_120000"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230103_120000".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithDifferentYears_ReturnsMostRecent()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20210101_120000",
|
||||
"/backups/20220101_120000",
|
||||
"/backups/20230101_120000"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230101_120000".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithDifferentMonths_ReturnsMostRecent()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230101_120000",
|
||||
"/backups/20230201_120000",
|
||||
"/backups/20230301_120000"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230301_120000".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithDifferentDays_ReturnsMostRecent()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230301_120000",
|
||||
"/backups/20230302_120000",
|
||||
"/backups/20230303_120000"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230303_120000".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithDifferentHours_ReturnsMostRecent()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230301_110000",
|
||||
"/backups/20230301_120000",
|
||||
"/backups/20230301_130000"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230301_130000".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithDifferentMinutes_ReturnsMostRecent()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230301_120100",
|
||||
"/backups/20230301_120200",
|
||||
"/backups/20230301_120300"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230301_120300".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithDifferentSeconds_ReturnsMostRecent()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230301_120000",
|
||||
"/backups/20230301_120015",
|
||||
"/backups/20230301_120030"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230301_120030".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithMixedDateTimes_ReturnsMostRecent_Case1()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20210101_235959",
|
||||
"/backups/20201231_235959",
|
||||
"/backups/20220228_235959"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20220228_235959".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithMixedDateTimes_ReturnsMostRecent_Case2()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20211001_000001",
|
||||
"/backups/20211001_000002",
|
||||
"/backups/20210930_235959"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20211001_000002".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithMixedDateTimes_ReturnsMostRecent_Case3()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20211231_235959",
|
||||
"/backups/20220101_000000",
|
||||
"/backups/20211231_235958"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20220101_000000".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithMixedDateTimes_ReturnsMostRecent_Case4()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230530_123456",
|
||||
"/backups/20230529_123456",
|
||||
"/backups/20220530_123457"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230530_123456".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithMixedDateTimes_ReturnsMostRecent_Case5()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20230102_010101",
|
||||
"/backups/20230101_222222",
|
||||
"/backups/20221231_235959"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20230102_010101".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithMixedDateTimes_ReturnsMostRecent_Case6()
|
||||
{
|
||||
// Arrange
|
||||
var directories = new List<string>
|
||||
{
|
||||
"/backups/20240101_120000",
|
||||
"/backups/20231231_235959",
|
||||
"/backups/20231231_235958"
|
||||
};
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That("20240101_120000".Equals(mostRecent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WithNoDirectories_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
// ReSharper disable once CollectionNeverUpdated.Local
|
||||
var directories = new List<string>();
|
||||
|
||||
// Act
|
||||
var mostRecent =
|
||||
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
|
||||
directories
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.That(mostRecent, Is.EqualTo(null));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
namespace UnitTests.Saradomin.ViewModel.Controls.SingleplayerViewModel;
|
||||
|
||||
[TestFixture]
|
||||
public class IsServerTerminationLogTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase("[23:43:26]: [SystemTermination] Server successfully terminated!", true)]
|
||||
[TestCase("[23:00:26]: [SystemTermination] Server successfully terminated!", true)]
|
||||
[TestCase("[23:43:26]: lol said [SystemTermination] Server successfully terminated!", false)]
|
||||
[TestCase("[23:00:26]: [SystemTermination] Server successfully terminated! weee exploit", false)]
|
||||
[TestCase("Gotcha [23:00:26]: [SystemTermination] Server successfully terminated!", false)]
|
||||
public void ServerTerminationLogTests(string log, bool expectedOutcome)
|
||||
{
|
||||
// Act
|
||||
var result = global::Saradomin.ViewModel.Controls.SingleplayerViewModel.IsServerTerminationLog(log);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedOutcome), $"Failed for log: {log}");
|
||||
}
|
||||
}
|
||||
23
UnitTests/UnitTests.csproj
Normal file
23
UnitTests/UnitTests.csproj
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
|
||||
<PackageReference Include="NUnit" Version="3.13.3"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.3.0"/>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Saradomin\Saradomin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
1
UnitTests/Usings.cs
Normal file
1
UnitTests/Usings.cs
Normal file
|
|
@ -0,0 +1 @@
|
|||
global using NUnit.Framework;
|
||||
Loading…
Add table
Add a link
Reference in a new issue