diff --git a/src/.editorconfig b/.editorconfig
similarity index 100%
rename from src/.editorconfig
rename to .editorconfig
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-blank.xcf b/src/EndlessOnlinePatcher.Desktop/Resources/eo-blank.xcf
deleted file mode 100644
index 1dbee59..0000000
Binary files a/src/EndlessOnlinePatcher.Desktop/Resources/eo-blank.xcf and /dev/null differ
diff --git a/src/EndlessOnlinePatcher.sln b/src/EndlessOnlinePatcher.sln
deleted file mode 100644
index 7e8d6b0..0000000
--- a/src/EndlessOnlinePatcher.sln
+++ /dev/null
@@ -1,36 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.7.34031.279
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EndlessOnlinePatcher", "EndlessOnlinePatcher\EndlessOnlinePatcher.csproj", "{630A5F38-3B1F-4850-88E5-33E2D3766C82}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EndlessOnlinePatcher.Desktop", "EndlessOnlinePatcher.Desktop\EndlessOnlinePatcher.Desktop.csproj", "{74671C17-2B5A-4E0D-9C38-74F790D659D5}"
-EndProject
-Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "EndlessOnlinePatcherSetup", "EndlessOnlinePatcherSetup\EndlessOnlinePatcherSetup.vdproj", "{1D9DA1F0-1366-46A9-8C90-720B7DC7303F}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {630A5F38-3B1F-4850-88E5-33E2D3766C82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {630A5F38-3B1F-4850-88E5-33E2D3766C82}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {630A5F38-3B1F-4850-88E5-33E2D3766C82}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {630A5F38-3B1F-4850-88E5-33E2D3766C82}.Release|Any CPU.Build.0 = Release|Any CPU
- {74671C17-2B5A-4E0D-9C38-74F790D659D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {74671C17-2B5A-4E0D-9C38-74F790D659D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {74671C17-2B5A-4E0D-9C38-74F790D659D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {74671C17-2B5A-4E0D-9C38-74F790D659D5}.Release|Any CPU.Build.0 = Release|Any CPU
- {1D9DA1F0-1366-46A9-8C90-720B7DC7303F}.Debug|Any CPU.ActiveCfg = Debug
- {1D9DA1F0-1366-46A9-8C90-720B7DC7303F}.Release|Any CPU.ActiveCfg = Release
- {1D9DA1F0-1366-46A9-8C90-720B7DC7303F}.Release|Any CPU.Build.0 = Release
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {D1579206-4DB9-48C7-BB49-C52FE7903A74}
- EndGlobalSection
-EndGlobal
diff --git a/src/EndlessOnlinePatcher/Core/ClientVersionFetcher.cs b/src/EndlessOnlinePatcher/Core/ClientVersionFetcher.cs
deleted file mode 100644
index bd5c7a9..0000000
--- a/src/EndlessOnlinePatcher/Core/ClientVersionFetcher.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Diagnostics;
-using System.Text.RegularExpressions;
-
-namespace EndlessOnlinePatcher.Core;
-public partial class ClientVersionFetcher : IClientVersionFetcher
-{
- [GeneratedRegex("href=\"(.*EndlessOnline(\\d*)([a-zA-Z])*.zip)\"")]
- private static partial Regex EndlessOnlineZipRegex();
-
- public FileVersion GetLocal()
- {
- if (!File.Exists(EndlessOnlineDirectory.GetExe()))
- return new FileVersion(0, 0, 0, 0);
-
- var versionInfo = FileVersionInfo.GetVersionInfo(EndlessOnlineDirectory.GetExe());
-
- if (versionInfo == null)
- return new FileVersion(0, 0, 0, 0);
-
- return FileVersion.FromString(versionInfo.FileVersion ?? throw new ArgumentNullException(versionInfo.FileVersion));
- }
-
- public async Task<(string downloadLink, FileVersion)> GetRemoteAsync()
- {
- using var httpClient = new HttpClient();
- var endlessHomePage = await httpClient.GetStringAsync("https://www.endless-online.com/client/download.html");
- var regexVersionMatches = EndlessOnlineZipRegex().Match(endlessHomePage);
- var downloadLink = regexVersionMatches.Groups[1].Value;
- var major = int.Parse(regexVersionMatches.Groups[2].Value[0].ToString());
- var minor = int.Parse(regexVersionMatches.Groups[2].Value[1].ToString());
- var build = int.Parse(regexVersionMatches.Groups[2].Value[2..].ToString());
-
- if (regexVersionMatches.Groups.Count > 3 && !string.IsNullOrWhiteSpace(regexVersionMatches.Groups[3].Value))
- {
- var patchAlpha = regexVersionMatches.Groups[3].Value[0];
- var patch = patchAlpha switch
- {
- var p when patchAlpha >= 'a' && patchAlpha <= 'z' => p - 'a',
- var p when patchAlpha >= 'A' && patchAlpha <= 'Z' => p - 'A',
- _ => throw new ArgumentException("Patch must be alphabetical")
- };
-
- return (downloadLink, new FileVersion(major, minor, build, patch));
- }
-
- return (downloadLink, new FileVersion(major, minor, build, 0));
- }
-}
\ No newline at end of file
diff --git a/src/EndlessOnlinePatcher/Core/FileVersion.cs b/src/EndlessOnlinePatcher/Core/FileVersion.cs
deleted file mode 100644
index 4b62a8d..0000000
--- a/src/EndlessOnlinePatcher/Core/FileVersion.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace EndlessOnlinePatcher.Core;
-
-public record FileVersion(int Major = 0, int Minor = 0, int Build = 0, int Revision = 0)
-{
- public override string ToString() => $"{Major}.{Minor}.{Build}.{Revision}";
- public static FileVersion FromString(string s)
- {
- var parts = s.Split('.').Select(int.Parse).ToArray();
- return new FileVersion(parts[0], parts[1], parts[2], parts[3]);
- }
-
- public static bool operator >(FileVersion a, FileVersion b)
- => a.Major > b.Major ||
- a.Major == b.Major && a.Minor > a.Minor ||
- a.Major == b.Major && a.Minor == b.Minor && a.Build > b.Build ||
- a.Major == b.Major && a.Minor == b.Minor && a.Build == b.Build && a.Revision > b.Revision;
-
- public static bool operator <(FileVersion a, FileVersion b)
- => a.Major < b.Major ||
- a.Major == b.Major && a.Minor < a.Minor ||
- a.Major == b.Major && a.Minor == b.Minor && a.Build < b.Build ||
- a.Major == b.Major && a.Minor == b.Minor && a.Build == b.Build && a.Revision < b.Revision;
-}
\ No newline at end of file
diff --git a/src/EndlessOnlinePatcher/Core/IClientVersionFetcher.cs b/src/EndlessOnlinePatcher/Core/IClientVersionFetcher.cs
deleted file mode 100644
index c08583f..0000000
--- a/src/EndlessOnlinePatcher/Core/IClientVersionFetcher.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace EndlessOnlinePatcher.Core;
-
-public interface IClientVersionFetcher
-{
- FileVersion GetLocal();
- Task<(string downloadLink, FileVersion)> GetRemoteAsync();
-}
\ No newline at end of file
diff --git a/src/EndlessOnlinePatcher/Core/IPatcher.cs b/src/EndlessOnlinePatcher/Core/IPatcher.cs
deleted file mode 100644
index 195909f..0000000
--- a/src/EndlessOnlinePatcher/Core/IPatcher.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace EndlessOnlinePatcher.Core;
-
-internal interface IPatcher
-{
- public Task Patch(FileVersion version);
-}
diff --git a/src/EndlessOnlinePatcher/EndlessOnlinePatcher.csproj b/src/EndlessOnlinePatcher/EndlessOnlinePatcher.csproj
deleted file mode 100644
index 6030140..0000000
--- a/src/EndlessOnlinePatcher/EndlessOnlinePatcher.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
-
-
diff --git a/src/EoPatcher.Core/EoPatcher.Core.csproj b/src/EoPatcher.Core/EoPatcher.Core.csproj
new file mode 100644
index 0000000..f449b3c
--- /dev/null
+++ b/src/EoPatcher.Core/EoPatcher.Core.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/src/EndlessOnlinePatcher/Extensions/HttpClientExtensions.cs b/src/EoPatcher.Core/Extensions/HttpClientExtensions.cs
similarity index 96%
rename from src/EndlessOnlinePatcher/Extensions/HttpClientExtensions.cs
rename to src/EoPatcher.Core/Extensions/HttpClientExtensions.cs
index e89e460..acc783d 100644
--- a/src/EndlessOnlinePatcher/Extensions/HttpClientExtensions.cs
+++ b/src/EoPatcher.Core/Extensions/HttpClientExtensions.cs
@@ -1,4 +1,4 @@
-namespace EndlessOnlinePatcher.Extensions;
+namespace EoPatcher.Extensions;
public static class HttpClientExtensions
{
public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress? progress = null, CancellationToken cancellationToken = default)
diff --git a/src/EndlessOnlinePatcher/Extensions/StreamExtensions.cs b/src/EoPatcher.Core/Extensions/StreamExtensions.cs
similarity index 96%
rename from src/EndlessOnlinePatcher/Extensions/StreamExtensions.cs
rename to src/EoPatcher.Core/Extensions/StreamExtensions.cs
index 9a04662..8b3ec82 100644
--- a/src/EndlessOnlinePatcher/Extensions/StreamExtensions.cs
+++ b/src/EoPatcher.Core/Extensions/StreamExtensions.cs
@@ -1,6 +1,5 @@
-using System;
+namespace EoPatcher.Extensions;
-namespace EndlessOnlinePatcher.Extensions;
public static class StreamExtensions
{
public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, long contentLength, IProgress? progress = null, CancellationToken cancellationToken = default)
diff --git a/src/EndlessOnlinePatcher/Core/Windows.cs b/src/EoPatcher.Core/Interop/Windows.cs
similarity index 73%
rename from src/EndlessOnlinePatcher/Core/Windows.cs
rename to src/EoPatcher.Core/Interop/Windows.cs
index 3e4dd57..e6eb38a 100644
--- a/src/EndlessOnlinePatcher/Core/Windows.cs
+++ b/src/EoPatcher.Core/Interop/Windows.cs
@@ -1,6 +1,7 @@
using System.Runtime.InteropServices;
+using EoPatcher.Models;
-namespace EndlessOnlinePatcher.Core;
+namespace EoPatcher.Interop;
public static class Windows
{
@@ -27,7 +28,7 @@ private static void RunAsDesktopUser(string fileName)
// 6. Make a primary token with that token(DuplicateTokenEx)
// 7. Start the new process with that primary token(CreateProcessWithTokenW)
- var hProcessToken = IntPtr.Zero;
+ var hProcessToken = nint.Zero;
// Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.)
try
{
@@ -46,7 +47,7 @@ private static void RunAsDesktopUser(string fileName)
tkp.Privileges[0].Attributes = 0x00000002;
- if (!AdjustTokenPrivileges(hProcessToken, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero))
+ if (!AdjustTokenPrivileges(hProcessToken, false, ref tkp, 0, nint.Zero, nint.Zero))
return;
}
finally
@@ -59,12 +60,12 @@ private static void RunAsDesktopUser(string fileName)
// replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and
// restarted elevated.
var hwnd = GetShellWindow();
- if (hwnd == IntPtr.Zero)
+ if (hwnd == nint.Zero)
return;
- var hShellProcess = IntPtr.Zero;
- var hShellProcessToken = IntPtr.Zero;
- var hPrimaryToken = IntPtr.Zero;
+ var hShellProcess = nint.Zero;
+ var hShellProcessToken = nint.Zero;
+ var hPrimaryToken = nint.Zero;
try
{
// Get the PID of the desktop shell process.
@@ -74,7 +75,7 @@ private static void RunAsDesktopUser(string fileName)
// Open the desktop shell process in order to query it (get the token)
hShellProcess = OpenProcess(ProcessAccessFlags.QueryInformation, false, dwPID);
- if (hShellProcess == IntPtr.Zero)
+ if (hShellProcess == nint.Zero)
return;
// Get the process token of the desktop shell.
@@ -85,13 +86,13 @@ private static void RunAsDesktopUser(string fileName)
// Duplicate the shell's process token to get a primary token.
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
- if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hPrimaryToken))
+ if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, nint.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hPrimaryToken))
return;
// Start the target process with the new token.
var si = new STARTUPINFO();
var pi = new PROCESS_INFORMATION();
- if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, "", 0, IntPtr.Zero, Path.GetDirectoryName(fileName)!, ref si, out pi))
+ if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, "", 0, nint.Zero, Path.GetDirectoryName(fileName)!, ref si, out pi))
return;
}
finally
@@ -107,7 +108,7 @@ private static void RunAsDesktopUser(string fileName)
private struct TOKEN_PRIVILEGES
{
- public UInt32 PrivilegeCount;
+ public uint PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
@@ -116,7 +117,7 @@ private struct TOKEN_PRIVILEGES
private struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
- public UInt32 Attributes;
+ public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
@@ -161,8 +162,8 @@ private enum TOKEN_TYPE
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
- public IntPtr hProcess;
- public IntPtr hThread;
+ public nint hProcess;
+ public nint hThread;
public int dwProcessId;
public int dwThreadId;
}
@@ -170,57 +171,57 @@ private struct PROCESS_INFORMATION
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
- public Int32 cb;
+ public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
- public Int32 dwX;
- public Int32 dwY;
- public Int32 dwXSize;
- public Int32 dwYSize;
- public Int32 dwXCountChars;
- public Int32 dwYCountChars;
- public Int32 dwFillAttribute;
- public Int32 dwFlags;
- public Int16 wShowWindow;
- public Int16 cbReserved2;
- public IntPtr lpReserved2;
- public IntPtr hStdInput;
- public IntPtr hStdOutput;
- public IntPtr hStdError;
+ public int dwX;
+ public int dwY;
+ public int dwXSize;
+ public int dwYSize;
+ public int dwXCountChars;
+ public int dwYCountChars;
+ public int dwFillAttribute;
+ public int dwFlags;
+ public short wShowWindow;
+ public short cbReserved2;
+ public nint lpReserved2;
+ public nint hStdInput;
+ public nint hStdOutput;
+ public nint hStdError;
}
[DllImport("kernel32.dll", ExactSpelling = true)]
- private static extern IntPtr GetCurrentProcess();
+ private static extern nint GetCurrentProcess();
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
- private static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
+ private static extern bool OpenProcessToken(nint h, int acc, ref nint phtok);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LookupPrivilegeValue(string host, string name, ref LUID pluid);
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
- private static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TOKEN_PRIVILEGES newst, int len, IntPtr prev, IntPtr relen);
+ private static extern bool AdjustTokenPrivileges(nint htok, bool disall, ref TOKEN_PRIVILEGES newst, int len, nint prev, nint relen);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool CloseHandle(IntPtr hObject);
+ private static extern bool CloseHandle(nint hObject);
[DllImport("user32.dll")]
- private static extern IntPtr GetShellWindow();
+ private static extern nint GetShellWindow();
[DllImport("user32.dll", SetLastError = true)]
- private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+ private static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
- private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId);
+ private static extern nint OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- private static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr phNewToken);
+ private static extern bool DuplicateTokenEx(nint hExistingToken, uint dwDesiredAccess, nint lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out nint phNewToken);
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
- private static extern bool CreateProcessWithTokenW(IntPtr hToken, int dwLogonFlags, string lpApplicationName, string lpCommandLine, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
+ private static extern bool CreateProcessWithTokenW(nint hToken, int dwLogonFlags, string lpApplicationName, string lpCommandLine, int dwCreationFlags, nint lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
#endregion
}
diff --git a/src/EndlessOnlinePatcher/Core/EndlessOnlineDirectory.cs b/src/EoPatcher.Core/Models/EndlessOnlineDirectory.cs
similarity index 89%
rename from src/EndlessOnlinePatcher/Core/EndlessOnlineDirectory.cs
rename to src/EoPatcher.Core/Models/EndlessOnlineDirectory.cs
index df9b764..a2c9674 100644
--- a/src/EndlessOnlinePatcher/Core/EndlessOnlineDirectory.cs
+++ b/src/EoPatcher.Core/Models/EndlessOnlineDirectory.cs
@@ -1,4 +1,5 @@
-namespace EndlessOnlinePatcher.Core;
+namespace EoPatcher.Models;
+
public static class EndlessOnlineDirectory
{
public static DirectoryInfo Get()
diff --git a/src/EndlessOnlinePatcher/Core/Patcher.cs b/src/EoPatcher.Core/Services/FileService.cs
similarity index 51%
rename from src/EndlessOnlinePatcher/Core/Patcher.cs
rename to src/EoPatcher.Core/Services/FileService.cs
index 8776d52..952bbb8 100644
--- a/src/EndlessOnlinePatcher/Core/Patcher.cs
+++ b/src/EoPatcher.Core/Services/FileService.cs
@@ -1,40 +1,25 @@
-using EndlessOnlinePatcher.Extensions;
+using EoPatcher.Models;
using System.Diagnostics;
using System.IO.Compression;
-using System.Numerics;
using System.Text.RegularExpressions;
-namespace EndlessOnlinePatcher.Core;
+namespace EoPatcher.Core.Services;
-public sealed class Patcher : IPatcher, IDisposable
+public interface IFileService
{
- private string _link { get; }
+ public Task ExtractPatch(Version version);
+}
+public class FileService : IFileService
+{
private Action _setPatchTextCallback;
- public Patcher(string downloadLink, Action setPatchTextCallback)
+ public FileService(Action setPatchTextCallback)
{
- _link = downloadLink;
_setPatchTextCallback = setPatchTextCallback;
}
- public async Task Patch(FileVersion version)
- {
- Clean();
- await DownloadPatch();
- await ApplyPatch(version);
- }
-
- private async Task DownloadPatch()
- {
- var progress = new Progress(x => _setPatchTextCallback($"Downloading... {x}%"));
- using var httpClient = new HttpClient();
- using var fileStream = new FileStream("patch.zip", FileMode.Create, FileAccess.Write);
- await httpClient.DownloadAsync(_link, fileStream, progress);
- fileStream.Close();
- }
-
- private async Task ApplyPatch(FileVersion version)
+ public async Task ExtractPatch(Version version)
{
var localDirectory = EndlessOnlineDirectory.Get().FullName;
var patchFolder = $"patch-{version}/";
@@ -70,28 +55,5 @@ private async Task ApplyPatch(FileVersion version)
}
private static string GetDirectoryFrom(string filePath)
- {
- var regex = new Regex("(.*)\\\\");
- return regex.Match(filePath).Value;
- }
-
- private static void Clean()
- {
- var patchDirectories = Directory.EnumerateDirectories("./").Where(x => x.Contains("patch"));
- foreach (var directory in patchDirectories)
- {
- Directory.Delete(directory, true);
- }
-
- var patchZips = Directory.EnumerateFiles("./", "*.zip");
- foreach (var zip in patchZips)
- {
- File.Delete(zip);
- }
- }
-
- public void Dispose()
- {
- Clean();
- }
+ => new Regex("(.*)\\\\").Match(filePath).Value;
}
\ No newline at end of file
diff --git a/src/EoPatcher.Core/Services/HttpService.cs b/src/EoPatcher.Core/Services/HttpService.cs
new file mode 100644
index 0000000..ede7024
--- /dev/null
+++ b/src/EoPatcher.Core/Services/HttpService.cs
@@ -0,0 +1,64 @@
+using EoPatcher.Extensions;
+using OneOf;
+using OneOf.Types;
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+
+namespace EoPatcher.Core.Services;
+
+public interface IHttpService
+{
+ public Task>> DownloadLatestPatchAsync(Version version);
+}
+
+public partial class HttpService : IHttpService
+{
+ private readonly Action _setPatchTextCallback;
+
+ public HttpService(Action setPatchTextCallback)
+ {
+ _setPatchTextCallback = setPatchTextCallback;
+ }
+
+ private async Task>> GetLatestDownloadLinkAsync()
+ {
+ using var httpClient = new HttpClient();
+ var clientUrl = "https://www.endless-online.com/client/download.html";
+ try
+ {
+ var endlessHomePage = await httpClient.GetStringAsync(clientUrl);
+ var regexVersionMatches = new Regex("href=\"(.*EndlessOnline(\\d*)([a-zA-Z])*.zip)\"").Match(endlessHomePage);
+ var downloadLink = regexVersionMatches.Groups[1].Value;
+ return downloadLink;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Could not get the latest download link: {ex.Message}");
+ return new Error($"Could not get the latest download link from {clientUrl}");
+ }
+ }
+
+ public async Task>> DownloadLatestPatchAsync(Version version)
+ {
+ var link = await GetLatestDownloadLinkAsync();
+ if (link.IsT1)
+ {
+ return link.AsT1;
+ }
+
+ try
+ {
+ var progress = new Progress(x => _setPatchTextCallback($"Downloading... {x}%"));
+ using var httpClient = new HttpClient();
+ using var fileStream = new FileStream("patch.zip", FileMode.Create, FileAccess.Write);
+ await httpClient.DownloadAsync(link.AsT0, fileStream, progress);
+ fileStream.Close();
+ return new Success();
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine($"Could not download latest patch from {link.AsT0}: {e.Message}");
+ return new Error($"Could not download latest patch from {link.AsT0}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EoPatcher.Core/Services/PatchOrchestrator.cs b/src/EoPatcher.Core/Services/PatchOrchestrator.cs
new file mode 100644
index 0000000..e8bea92
--- /dev/null
+++ b/src/EoPatcher.Core/Services/PatchOrchestrator.cs
@@ -0,0 +1,57 @@
+using EoPatcher.Core.Services;
+using EoPatcher.Services.VersionFetchers;
+using OneOf;
+using OneOf.Types;
+
+namespace EoPatcher.Services;
+
+internal interface IPatchOrchestrator
+{
+ public Task>> Patch(Version version);
+}
+
+public sealed class PatchOrchestrator : IPatchOrchestrator, IDisposable
+{
+ private IHttpService _httpService;
+ private IFileService _fileService;
+ private ILocalVersionRepository _localVersionRepository;
+
+ public PatchOrchestrator(Action setPatchTextCallback)
+ {
+ _httpService = new HttpService(setPatchTextCallback);
+ _fileService = new FileService(setPatchTextCallback);
+ _localVersionRepository = new LocalVersionRepository();
+ }
+
+ public async Task>> Patch(Version version)
+ {
+ Clean();
+ var downloadResult = await _httpService.DownloadLatestPatchAsync(version);
+ if (downloadResult.IsT1)
+ return downloadResult.AsT1;
+
+ await _fileService.ExtractPatch(version);
+ _localVersionRepository.Save(version);
+ return new Success();
+ }
+
+ public void Dispose()
+ {
+ Clean();
+ }
+
+ private static void Clean()
+ {
+ var patchDirectories = Directory.EnumerateDirectories("./").Where(x => x.Contains("patch"));
+ foreach (var directory in patchDirectories)
+ {
+ Directory.Delete(directory, true);
+ }
+
+ var patchZips = Directory.EnumerateFiles("./", "*.zip");
+ foreach (var zip in patchZips)
+ {
+ File.Delete(zip);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EoPatcher.Core/Services/VersionFetchers/LocalClientVersionRepository.cs b/src/EoPatcher.Core/Services/VersionFetchers/LocalClientVersionRepository.cs
new file mode 100644
index 0000000..95c9d2a
--- /dev/null
+++ b/src/EoPatcher.Core/Services/VersionFetchers/LocalClientVersionRepository.cs
@@ -0,0 +1,24 @@
+namespace EoPatcher.Services.VersionFetchers;
+
+public interface ILocalVersionRepository
+{
+ Version Get();
+ void Save(Version verson);
+}
+
+public class LocalVersionRepository : ILocalVersionRepository
+{
+ public Version Get()
+ {
+ if (File.Exists("version.txt") is false)
+ return new Version(0, 0, 0);
+
+ var storedVersion = File.ReadAllText("version.txt");
+ return new Version(storedVersion);
+ }
+
+ public void Save(Version version)
+ {
+ File.WriteAllText("version.txt", $"{version.Major}.{version.Minor}.{version.Build}");
+ }
+}
diff --git a/src/EoPatcher.Core/Services/VersionFetchers/ServerVersionFetcher.cs b/src/EoPatcher.Core/Services/VersionFetchers/ServerVersionFetcher.cs
new file mode 100644
index 0000000..27e104e
--- /dev/null
+++ b/src/EoPatcher.Core/Services/VersionFetchers/ServerVersionFetcher.cs
@@ -0,0 +1,58 @@
+using Moffat.EndlessOnline.SDK.Data;
+using OneOf;
+using OneOf.Types;
+using System.Diagnostics;
+using System.Net.Sockets;
+
+namespace EoPatcher.Services.VersionFetchers;
+
+public interface IServerVersionFetcher
+{
+ OneOf> Get();
+}
+
+public partial class ServerVersionFetcher : IServerVersionFetcher
+{
+ public OneOf> Get()
+ {
+ var serverAddress = "game.endless-online.com";
+ var serverPort = 8078;
+ var client = new TcpClient();
+ Debug.WriteLine($"Connecting to {serverAddress}...");
+ try
+ {
+ client.Connect(serverAddress, serverPort);
+ Debug.WriteLine("Connected!");
+
+ byte[] buf = [0xFF, 0xFF, 0x1E, 0x12, 0xFE, 0x1, 0x5, 0x2, 0x72, 0xB, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30];
+
+ var sizeBytes = NumberEncoder.EncodeNumber(buf.Length);
+
+ byte[] payload = [sizeBytes[0], sizeBytes[1]];
+ payload = payload.Concat(buf).ToArray();
+
+ Debug.WriteLine("Sending version payload...");
+ client.Client.Send(payload);
+
+ sizeBytes = new byte[2];
+ var lengthBytes = client.Client.Receive(sizeBytes);
+
+ var size = NumberEncoder.DecodeNumber(sizeBytes);
+
+ buf = new byte[size];
+
+ client.Client.Receive(buf);
+
+ var major = NumberEncoder.DecodeNumber([buf[3]]);
+ var minor = NumberEncoder.DecodeNumber([buf[4]]);
+ var build = NumberEncoder.DecodeNumber([buf[5]]);
+
+ return new Version(major, minor, build);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine($"Exception thrown at connecting to {serverAddress}:{serverPort}: {e.Message}");
+ return new Error($"Could not connect to {serverAddress}:{serverPort}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EndlessOnlinePatcherSetup/EndlessOnlinePatcherSetup.vdproj b/src/EoPatcher.Setup/EoPatcher.Setup.vdproj
similarity index 99%
rename from src/EndlessOnlinePatcherSetup/EndlessOnlinePatcherSetup.vdproj
rename to src/EoPatcher.Setup/EoPatcher.Setup.vdproj
index a4aeefc..912d4c3 100644
--- a/src/EndlessOnlinePatcherSetup/EndlessOnlinePatcherSetup.vdproj
+++ b/src/EoPatcher.Setup/EoPatcher.Setup.vdproj
@@ -3,7 +3,7 @@
"VSVersion" = "3:800"
"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}"
"IsWebType" = "8:FALSE"
-"ProjectName" = "8:EndlessOnlinePatcherSetup"
+"ProjectName" = "8:EoPatcher.Setup"
"LanguageId" = "3:1033"
"CodePage" = "3:1252"
"UILanguageId" = "3:1033"
@@ -210,12 +210,12 @@
"Manufacturer" = "8:Endless Online"
"ARPHELPTELEPHONE" = "8:"
"ARPHELPLINK" = "8:"
- "Title" = "8:"
+ "Title" = "8:Endless Online Patcher Installer"
"Subject" = "8:"
"ARPCONTACT" = "8:Dan Oak"
"Keywords" = "8:"
"ARPCOMMENTS" = "8:Patching application for Endless Online"
- "ARPURLINFOABOUT" = "8:"
+ "ARPURLINFOABOUT" = "8:https://github.com/MrDanOak/EndlessOnlinePatcher"
"ARPPRODUCTICON" = "8:"
"ARPIconIndex" = "3:0"
"SearchPath" = "8:"
diff --git a/src/EndlessOnlinePatcher.Desktop/EndlessOnlinePatcher.Desktop.csproj b/src/EoPatcher.UI/EoPatcher.UI.csproj
similarity index 82%
rename from src/EndlessOnlinePatcher.Desktop/EndlessOnlinePatcher.Desktop.csproj
rename to src/EoPatcher.UI/EoPatcher.UI.csproj
index e527cc0..cdca2b0 100644
--- a/src/EndlessOnlinePatcher.Desktop/EndlessOnlinePatcher.Desktop.csproj
+++ b/src/EoPatcher.UI/EoPatcher.UI.csproj
@@ -12,10 +12,10 @@
OakTech
Endless Online Patcher
eo-patcher-icon.png
- 0.0.0.5
- 0.0.0.5
+ 0.0.0.6
+ 0.0.0.6
eopatcher
- EndlessOnlinePatcher.Desktop.Program
+ EoPatcher.UI.Program
@@ -23,7 +23,7 @@
-
+
diff --git a/src/EndlessOnlinePatcher.Desktop/Main.Designer.cs b/src/EoPatcher.UI/Main.Designer.cs
similarity index 89%
rename from src/EndlessOnlinePatcher.Desktop/Main.Designer.cs
rename to src/EoPatcher.UI/Main.Designer.cs
index 487e285..ea8b2e7 100644
--- a/src/EndlessOnlinePatcher.Desktop/Main.Designer.cs
+++ b/src/EoPatcher.UI/Main.Designer.cs
@@ -1,4 +1,4 @@
-namespace EndlessOnlinePatcher.Desktop;
+namespace EoPatcher.UI;
partial class Main
{
@@ -28,14 +28,16 @@ protected override void Dispose(bool disposing)
///
private void InitializeComponent()
{
+ components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main));
- label1 = new Label();
+ lblTitle = new Label();
lblMessage = new Label();
pbxLogout = new PictureBox();
pbxPatch = new PictureBox();
pbxLaunch = new PictureBox();
pbxExit = new PictureBox();
pbxSkip = new PictureBox();
+ lblMessageHover = new ToolTip(components);
((System.ComponentModel.ISupportInitialize)pbxLogout).BeginInit();
((System.ComponentModel.ISupportInitialize)pbxPatch).BeginInit();
((System.ComponentModel.ISupportInitialize)pbxLaunch).BeginInit();
@@ -43,19 +45,19 @@ private void InitializeComponent()
((System.ComponentModel.ISupportInitialize)pbxSkip).BeginInit();
SuspendLayout();
//
- // label1
+ // lblTitle
//
- label1.AutoSize = true;
- label1.BackColor = Color.Transparent;
- label1.ForeColor = Color.LemonChiffon;
- label1.Location = new Point(13, 13);
- label1.Name = "label1";
- label1.Size = new Size(127, 15);
- label1.TabIndex = 1;
- label1.Text = "Endless Online Patcher";
- label1.MouseDown += Main_MouseDown;
- label1.MouseMove += Main_MouseMove;
- label1.MouseUp += Main_MouseUp;
+ lblTitle.AutoSize = true;
+ lblTitle.BackColor = Color.Transparent;
+ lblTitle.ForeColor = Color.LemonChiffon;
+ lblTitle.Location = new Point(13, 13);
+ lblTitle.Name = "lblTitle";
+ lblTitle.Size = new Size(127, 15);
+ lblTitle.TabIndex = 1;
+ lblTitle.Text = "Endless Online Patcher";
+ lblTitle.MouseDown += Main_MouseDown;
+ lblTitle.MouseMove += Main_MouseMove;
+ lblTitle.MouseUp += Main_MouseUp;
//
// lblMessage
//
@@ -161,7 +163,7 @@ private void InitializeComponent()
Controls.Add(pbxPatch);
Controls.Add(pbxLogout);
Controls.Add(lblMessage);
- Controls.Add(label1);
+ Controls.Add(lblTitle);
FormBorderStyle = FormBorderStyle.None;
Icon = (Icon)resources.GetObject("$this.Icon");
MaximumSize = new Size(290, 125);
@@ -184,11 +186,12 @@ private void InitializeComponent()
}
#endregion
- private Label label1;
+ private Label lblTitle;
private Label lblMessage;
private PictureBox pbxLogout;
private PictureBox pbxPatch;
private PictureBox pbxLaunch;
private PictureBox pbxExit;
private PictureBox pbxSkip;
+ private ToolTip lblMessageHover;
}
diff --git a/src/EndlessOnlinePatcher.Desktop/Main.cs b/src/EoPatcher.UI/Main.cs
similarity index 56%
rename from src/EndlessOnlinePatcher.Desktop/Main.cs
rename to src/EoPatcher.UI/Main.cs
index 2f559cd..d2eaed3 100644
--- a/src/EndlessOnlinePatcher.Desktop/Main.cs
+++ b/src/EoPatcher.UI/Main.cs
@@ -1,43 +1,65 @@
-using EndlessOnlinePatcher.Core;
-using EndlessOnlinePatcher.Desktop.Properties;
+using EoPatcher.Services;
+using EoPatcher.Services.VersionFetchers;
+using OneOf;
+using OneOf.Types;
+using System.Diagnostics;
using System.Media;
+using System.Reflection;
-namespace EndlessOnlinePatcher.Desktop;
+namespace EoPatcher.UI;
public partial class Main : Form
{
- private FileVersion? _remoteVersion = default;
- private FileVersion? _localVersion = default;
private bool _patching = false;
private bool _dragging;
private Point _mouseDownLocation;
- private string _downloadLink = "";
- private readonly IClientVersionFetcher _clientVersionFetcher;
- private readonly SoundPlayer _sndClickDown = new(Resources.click_down);
- private readonly SoundPlayer _sndClickUp = new(Resources.click_up);
+ private Version _serverVersion;
+ private readonly SoundPlayer _sndClickDown = new(Properties.Resources.click_down);
+ private readonly SoundPlayer _sndClickUp = new(Properties.Resources.click_up);
+
+ private readonly ILocalVersionRepository _localVersionRepository = new LocalVersionRepository();
+ private readonly IServerVersionFetcher _serverVersionFetcher = new ServerVersionFetcher();
public Main()
{
InitializeComponent();
- _clientVersionFetcher = new ClientVersionFetcher();
}
- private async void Main_Shown(object sender, EventArgs e)
- {
- _localVersion = _clientVersionFetcher.GetLocal();
- (_downloadLink, _remoteVersion) = await _clientVersionFetcher.GetRemoteAsync();
- if (_localVersion == _remoteVersion)
- {
- lblMessage.Text = "You are already up to date with the latest version";
- pbxLaunch.Visible = true;
- }
- else
- {
- lblMessage.Text = $"A new version of the client is available{Environment.NewLine}(v{_localVersion} -> v{_remoteVersion})";
- pbxPatch.Visible = true;
- pbxSkip.Visible = true;
- }
- pbxExit.Visible = true;
+ private void Main_Shown(object sender, EventArgs e)
+ {
+ string version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion ?? "0.0.0.0";
+ lblTitle.Text = $"Endless Online Patcher v{version}";
+
+ SetPatchText("Getting local version...");
+ var localVersion = _localVersionRepository.Get();
+ SetPatchText("Getting remote version...");
+
+ _serverVersionFetcher
+ .Get()
+ .Switch(v =>
+ {
+ _serverVersion = v;
+ if (localVersion == v)
+ {
+ SetPatchText($"You are already up to date with the latest version v{localVersion}");
+ pbxLaunch.Visible = true;
+ }
+ else
+ {
+ SetPatchText($"A new version of the client is available{Environment.NewLine}(v{localVersion} -> v{_serverVersion})");
+ pbxPatch.Visible = true;
+ pbxSkip.Visible = true;
+ }
+ pbxExit.Visible = true;
+ },
+ e =>
+ {
+ SetPatchText(e.Value);
+ pbxExit.Visible = true;
+ pbxPatch.Visible = false;
+ pbxSkip.Visible = false;
+ pbxLaunch.Visible = true;
+ });
}
private void Main_MouseDown(object sender, MouseEventArgs e)
@@ -64,12 +86,12 @@ private void Main_MouseMove(object sender, MouseEventArgs e)
private void pbxLogout_MouseEnter(object sender, EventArgs e)
{
- pbxLogout.Image = Resources.eo_logout_hover;
+ pbxLogout.Image = Properties.Resources.eo_logout_hover;
}
private void pbxLogout_MouseLeave(object sender, EventArgs e)
{
- pbxLogout.Image = Resources.eo_logout;
+ pbxLogout.Image = Properties.Resources.eo_logout;
}
private void pbxLogout_Click(object sender, EventArgs e)
@@ -80,28 +102,28 @@ private void pbxLogout_Click(object sender, EventArgs e)
private void pbxPatch_MouseEnter(object sender, EventArgs e)
{
if (_patching) return;
- pbxPatch.Image = Resources.eo_patch_hover;
+ pbxPatch.Image = Properties.Resources.eo_patch_hover;
}
private void pbxPatch_MouseLeave(object sender, EventArgs e)
{
if (_patching) return;
- pbxPatch.Image = Resources.eo_patch;
+ pbxPatch.Image = Properties.Resources.eo_patch;
}
private void pbxLaunch_MouseEnter(object sender, EventArgs e)
{
- pbxLaunch.Image = Resources.eo_launch_hover;
+ pbxLaunch.Image = Properties.Resources.eo_launch_hover;
}
private void pbxLaunch_MouseLeave(object sender, EventArgs e)
{
- pbxLaunch.Image = Resources.eo_launch;
+ pbxLaunch.Image = Properties.Resources.eo_launch;
}
private async void pbxLaunch_Click(object sender, EventArgs e)
{
- await Core.Windows.StartEO();
+ await Interop.Windows.StartEO();
Close();
}
@@ -112,22 +134,22 @@ private void pbxExit_Click(object sender, EventArgs e)
private void pbxExit_MouseEnter(object sender, EventArgs e)
{
- pbxExit.Image = Resources.eo_exit_hover;
+ pbxExit.Image = Properties.Resources.eo_exit_hover;
}
private void pbxExit_MouseLeave(object sender, EventArgs e)
{
- pbxExit.Image = Resources.eo_exit;
+ pbxExit.Image = Properties.Resources.eo_exit;
}
private void pbxSkip_MouseEnter(object sender, EventArgs e)
{
- pbxSkip.Image = Resources.skip_hover;
+ pbxSkip.Image = Properties.Resources.skip_hover;
}
private void pbxSkip_MouseLeave(object sender, EventArgs e)
{
- pbxSkip.Image = Resources.skip;
+ pbxSkip.Image = Properties.Resources.skip;
}
delegate void SetPatchTextCallback(string text);
@@ -142,6 +164,7 @@ private void SetPatchText(string text)
}
lblMessage.Text = text;
+ lblMessageHover.SetToolTip(lblMessage, text);
}
private async void pbxPatch_MouseClick(object sender, MouseEventArgs e)
@@ -150,11 +173,13 @@ private async void pbxPatch_MouseClick(object sender, MouseEventArgs e)
_patching = true;
pbxSkip.Visible = false;
- pbxPatch.Image = Resources.eo_patching;
+ pbxPatch.Image = Properties.Resources.eo_patching;
- using var patcher = new Patcher(_downloadLink, SetPatchText);
+ using var patcher = new PatchOrchestrator(SetPatchText);
- await patcher.Patch(_remoteVersion!);
+ var result = await patcher.Patch(_serverVersion);
+ if (result.IsT1)
+ SetPatchText(result.AsT1.Value);
_patching = false;
pbxPatch.Visible = false;
diff --git a/src/EndlessOnlinePatcher.Desktop/Main.resx b/src/EoPatcher.UI/Main.resx
similarity index 99%
rename from src/EndlessOnlinePatcher.Desktop/Main.resx
rename to src/EoPatcher.UI/Main.resx
index 0220827..8731fdd 100644
--- a/src/EndlessOnlinePatcher.Desktop/Main.resx
+++ b/src/EoPatcher.UI/Main.resx
@@ -117,6 +117,9 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ 17, 17
+
diff --git a/src/EndlessOnlinePatcher.Desktop/Program.cs b/src/EoPatcher.UI/Program.cs
similarity index 91%
rename from src/EndlessOnlinePatcher.Desktop/Program.cs
rename to src/EoPatcher.UI/Program.cs
index f78346c..aa2d763 100644
--- a/src/EndlessOnlinePatcher.Desktop/Program.cs
+++ b/src/EoPatcher.UI/Program.cs
@@ -1,4 +1,4 @@
-namespace EndlessOnlinePatcher.Desktop;
+namespace EoPatcher.UI;
internal static class Program
{
diff --git a/src/EndlessOnlinePatcher.Desktop/Properties/PublishProfiles/FrameworkDependant.pubxml b/src/EoPatcher.UI/Properties/PublishProfiles/FrameworkDependant.pubxml
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Properties/PublishProfiles/FrameworkDependant.pubxml
rename to src/EoPatcher.UI/Properties/PublishProfiles/FrameworkDependant.pubxml
diff --git a/src/EndlessOnlinePatcher.Desktop/Properties/PublishProfiles/SelfContained.pubxml b/src/EoPatcher.UI/Properties/PublishProfiles/SelfContained.pubxml
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Properties/PublishProfiles/SelfContained.pubxml
rename to src/EoPatcher.UI/Properties/PublishProfiles/SelfContained.pubxml
diff --git a/src/EndlessOnlinePatcher.Desktop/Properties/Resources.Designer.cs b/src/EoPatcher.UI/Properties/Resources.Designer.cs
similarity index 97%
rename from src/EndlessOnlinePatcher.Desktop/Properties/Resources.Designer.cs
rename to src/EoPatcher.UI/Properties/Resources.Designer.cs
index 7cce6ca..954d71d 100644
--- a/src/EndlessOnlinePatcher.Desktop/Properties/Resources.Designer.cs
+++ b/src/EoPatcher.UI/Properties/Resources.Designer.cs
@@ -8,7 +8,7 @@
//
//------------------------------------------------------------------------------
-namespace EndlessOnlinePatcher.Desktop.Properties {
+namespace EoPatcher.UI.Properties {
using System;
@@ -39,7 +39,7 @@ internal Resources() {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EndlessOnlinePatcher.Desktop.Properties.Resources", typeof(Resources).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EoPatcher.UI.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
diff --git a/src/EndlessOnlinePatcher.Desktop/Properties/Resources.resx b/src/EoPatcher.UI/Properties/Resources.resx
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Properties/Resources.resx
rename to src/EoPatcher.UI/Properties/Resources.resx
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-blank.png b/src/EoPatcher.UI/Resources/eo-blank.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-blank.png
rename to src/EoPatcher.UI/Resources/eo-blank.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-exit-hover.png b/src/EoPatcher.UI/Resources/eo-exit-hover.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-exit-hover.png
rename to src/EoPatcher.UI/Resources/eo-exit-hover.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-exit.png b/src/EoPatcher.UI/Resources/eo-exit.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-exit.png
rename to src/EoPatcher.UI/Resources/eo-exit.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-launch-hover.png b/src/EoPatcher.UI/Resources/eo-launch-hover.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-launch-hover.png
rename to src/EoPatcher.UI/Resources/eo-launch-hover.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-launch.png b/src/EoPatcher.UI/Resources/eo-launch.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-launch.png
rename to src/EoPatcher.UI/Resources/eo-launch.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-logout-hover.png b/src/EoPatcher.UI/Resources/eo-logout-hover.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-logout-hover.png
rename to src/EoPatcher.UI/Resources/eo-logout-hover.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-logout.png b/src/EoPatcher.UI/Resources/eo-logout.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-logout.png
rename to src/EoPatcher.UI/Resources/eo-logout.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-ok-hover.png b/src/EoPatcher.UI/Resources/eo-ok-hover.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-ok-hover.png
rename to src/EoPatcher.UI/Resources/eo-ok-hover.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-ok.png b/src/EoPatcher.UI/Resources/eo-ok.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-ok.png
rename to src/EoPatcher.UI/Resources/eo-ok.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-patch-hover.png b/src/EoPatcher.UI/Resources/eo-patch-hover.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-patch-hover.png
rename to src/EoPatcher.UI/Resources/eo-patch-hover.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-patch.png b/src/EoPatcher.UI/Resources/eo-patch.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-patch.png
rename to src/EoPatcher.UI/Resources/eo-patch.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-patching.png b/src/EoPatcher.UI/Resources/eo-patching.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-patching.png
rename to src/EoPatcher.UI/Resources/eo-patching.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/eo-popup.png b/src/EoPatcher.UI/Resources/eo-popup.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/eo-popup.png
rename to src/EoPatcher.UI/Resources/eo-popup.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/sfx002.wav b/src/EoPatcher.UI/Resources/sfx002.wav
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/sfx002.wav
rename to src/EoPatcher.UI/Resources/sfx002.wav
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/sfx003.wav b/src/EoPatcher.UI/Resources/sfx003.wav
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/sfx003.wav
rename to src/EoPatcher.UI/Resources/sfx003.wav
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/skip-hover.png b/src/EoPatcher.UI/Resources/skip-hover.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/skip-hover.png
rename to src/EoPatcher.UI/Resources/skip-hover.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/skip.bmp b/src/EoPatcher.UI/Resources/skip.bmp
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/skip.bmp
rename to src/EoPatcher.UI/Resources/skip.bmp
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/skip.png b/src/EoPatcher.UI/Resources/skip.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/skip.png
rename to src/EoPatcher.UI/Resources/skip.png
diff --git a/src/EndlessOnlinePatcher.Desktop/Resources/text-window.png b/src/EoPatcher.UI/Resources/text-window.png
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/Resources/text-window.png
rename to src/EoPatcher.UI/Resources/text-window.png
diff --git a/src/EndlessOnlinePatcher.Desktop/app.manifest b/src/EoPatcher.UI/app.manifest
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/app.manifest
rename to src/EoPatcher.UI/app.manifest
diff --git a/src/EndlessOnlinePatcher.Desktop/eo-patcher-icon.ico b/src/EoPatcher.UI/eo-patcher-icon.ico
similarity index 100%
rename from src/EndlessOnlinePatcher.Desktop/eo-patcher-icon.ico
rename to src/EoPatcher.UI/eo-patcher-icon.ico
diff --git a/src/EoPatcher.sln b/src/EoPatcher.sln
new file mode 100644
index 0000000..451104a
--- /dev/null
+++ b/src/EoPatcher.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34322.80
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EoPatcher.Core", "EoPatcher.Core\EoPatcher.Core.csproj", "{AC9170EF-8400-4C14-92D1-825834A0B5F4}"
+EndProject
+Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "EoPatcher.Setup", "EoPatcher.Setup\EoPatcher.Setup.vdproj", "{6628B546-FE0D-449F-A172-71B197D16212}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EoPatcher.UI", "EoPatcher.UI\EoPatcher.UI.csproj", "{1A5F4910-5F89-42EC-B747-998778A6BAEB}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {AC9170EF-8400-4C14-92D1-825834A0B5F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AC9170EF-8400-4C14-92D1-825834A0B5F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AC9170EF-8400-4C14-92D1-825834A0B5F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AC9170EF-8400-4C14-92D1-825834A0B5F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6628B546-FE0D-449F-A172-71B197D16212}.Debug|Any CPU.ActiveCfg = Debug
+ {6628B546-FE0D-449F-A172-71B197D16212}.Debug|Any CPU.Build.0 = Debug
+ {6628B546-FE0D-449F-A172-71B197D16212}.Release|Any CPU.ActiveCfg = Release
+ {6628B546-FE0D-449F-A172-71B197D16212}.Release|Any CPU.Build.0 = Release
+ {1A5F4910-5F89-42EC-B747-998778A6BAEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A5F4910-5F89-42EC-B747-998778A6BAEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A5F4910-5F89-42EC-B747-998778A6BAEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A5F4910-5F89-42EC-B747-998778A6BAEB}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F92BCF5F-2E31-4B5C-BD00-D8F00E87F8AC}
+ EndGlobalSection
+EndGlobal