From 8225c2d98614c0832d07a7412331154774bf723f Mon Sep 17 00:00:00 2001 From: FireEmerald Date: Tue, 20 Nov 2018 17:25:45 +0100 Subject: [PATCH 1/6] Complete rewrite of the application, includes: - New: Any resolution is now supported, including 4K and custom resolutions. - New: Possibility to change the language of the game itself. - Includes translation for 14 languages (text only). - Includes full translation for German and English (text + all sound files). - New: Possibility to apply a compatibility fix for The Settlers 4 on Windows >=8 systems. - Includes multi-monitor support and border scrolling without any additonal tools. - Fixes the black terrain graphics bug when you scroll over the map. - Note: New versions of the Settlers installer from GOG do already contain a compatibility fix. - Change: Project has moved from Visual Basic to C# including WPF and MVVM. - Change: The UI uses now a dark design. - Improvement: The Readme was extended with useful information about the game. - Improvement: Removed most of hardcoded DLL's with in place binary editing. Not yet ready to be used. --- .../AssemblyInfo.cs | 20 + .../Cabinet/CabContainer.cs | 91 + .../Cabinet/CabContainerFactory.cs | 17 + .../Cabinet/CabFile.cs | 25 + .../Cabinet/Interfaces/ICabContainer.cs | 13 + .../Interfaces/ICabContainerFactory.cs | 9 + .../ExtensionMethods/CompareExtensions.cs | 20 + .../ExtensionMethods/Sha1HashExtensions.cs | 31 + .../UnitConversionExtensions.cs | 10 + .../IO/IniFileAdapter.cs | 63 + .../IO/Interfaces/IIniFileAdapter.cs | 8 + .../IO/Interfaces/IZipFileAdapter.cs | 9 + .../IO/ZipFileAdapter.cs | 15 + .../Install/Installer.cs | 75 + .../Json/Interfaces/IJsonAdapter.cs | 16 + .../Json/JsonAdapter.cs | 18 + .../Logging/Interfaces/ILogLevelConverter.cs | 9 + .../Logging/LogAdapter.cs | 10 + .../Logging/LogLevel.cs | 35 + .../Logging/LogLevelConverter.cs | 32 + .../Logging/LogManager.cs | 35 + .../Logger/Interfaces/IInternalLogger.cs | 7 + .../Logging/Logger/NullLogger.cs | 12 + .../Logging/Logger/SerilogLogger.cs | 36 + .../Logging/Sinks/ReportingSink.cs | 29 + .../Network/DownloaderBase.cs | 21 + .../Network/FileDownloader.cs | 68 + .../Network/Interfaces/IFileDownloader.cs | 14 + .../Network/Interfaces/IStringDownloader.cs | 7 + .../Network/StringDownloader.cs | 34 + .../Properties/AssemblyInfo.cs | 36 + .../Registry/Interfaces/IRegistryAdapter.cs | 7 + .../Interfaces/IRegistryKeyAdapter.cs | 17 + .../Registry/RegistryAdapter.cs | 12 + .../Registry/RegistryKeyAdapter.cs | 98 + .../Reporting/Interfaces/IReportManager.cs | 13 + .../Reporting/ReportManager.cs | 36 + .../Settlers.Toolbox.Infrastructure/Result.cs | 38 + .../Settlers.Toolbox.Infrastructure.csproj | 112 + .../packages.config | 9 + .../Addons/AddonBase.cs | 183 + .../Addons/AddonManager.cs | 56 + .../Addons/DieNeueWeltAddon.cs | 93 + .../Addons/GameAddon.cs | 11 + .../Addons/Interfaces/IAddon.cs | 22 + .../Addons/Interfaces/IAddonManager.cs | 14 + .../Compatibility/CompatibilityManager.cs | 78 + .../Interfaces/ICompatibilityManager.cs | 12 + source/Settlers.Toolbox.Model/Game.cs | 204 ++ source/Settlers.Toolbox.Model/GameModule.cs | 14 + .../Install/Installer.cs | 58 + .../Interfaces/IGame.cs | 53 + .../Languages/GameLanguage.cs | 24 + .../Languages/Interfaces/ILanguageChanger.cs | 16 + .../Languages/LanguageChanger.cs | 279 ++ .../Languages/LanguagePackLink.cs | 16 + .../Languages/LanguagePackLinks.cs | 14 + .../Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 83 + .../Properties/Resources.resx | 127 + .../Resolutions/GameResolution.cs | 25 + .../Interfaces/IResolutionChanger.cs | 12 + .../Interfaces/IResolutionFactory.cs | 12 + .../Resolutions/Resolution.cs | 18 + .../Resolutions/ResolutionChanger.cs | 155 + .../Resolutions/ResolutionFactory.cs | 63 + .../Resources/D3DImm.dll | Bin 0 -> 91648 bytes .../Resources/DDraw.dll | Bin 0 -> 112640 bytes .../Settlers.Toolbox.Model.csproj | 111 + .../Textures/Interfaces/ITextureSwapper.cs | 14 + .../Textures/TextureSwapper.cs | 124 + .../Updates/Interfaces/IUpdateChecker.cs | 9 + .../Updates/UpdateChecker.cs | 86 + .../Updates/VersionPackage.cs | 24 + .../Updates/VersionPackages.cs | 9 + source/Settlers.Toolbox.Model/packages.config | 5 + source/Settlers.Toolbox.sln | 37 + source/Settlers.Toolbox.sln.DotSettings | 205 ++ source/Settlers.Toolbox/App.config | 14 + source/Settlers.Toolbox/App.xaml | 20 + source/Settlers.Toolbox/App.xaml.cs | 11 + source/Settlers.Toolbox/FodyWeavers.xml | 4 + .../Settlers.Toolbox/Icons/roman-settler.ico | Bin 0 -> 319498 bytes source/Settlers.Toolbox/Install/Installer.cs | 42 + .../Interfaces/IWindowManager.cs | 9 + .../Properties/AssemblyInfo.cs | 55 + .../Properties/Resources.Designer.cs | 71 + .../Properties/Resources.resx | 117 + .../Properties/Settings.Designer.cs | 30 + .../Properties/Settings.settings | 7 + .../Settlers.Toolbox/Settlers.Toolbox.csproj | 206 ++ .../Themes/LeftMarginMultiplierConverter.cs | 31 + source/Settlers.Toolbox/Themes/Styles.xaml | 3096 +++++++++++++++++ .../Themes/TreeViewItemExtensions.cs | 38 + source/Settlers.Toolbox/ViewModelLocator.cs | 53 + .../ViewModels/DummyMainViewModel.cs | 48 + .../ViewModels/Interfaces/IMainViewModel.cs | 47 + .../ViewModels/MainViewModel.cs | 632 ++++ .../Interfaces/IMainViewModelValidator.cs | 11 + .../Validator/MainViewModelValidator.cs | 48 + .../Views/Converter/BaseConverter.cs | 13 + .../Views/Converter/InvertBoolConverter.cs | 23 + .../Converter/LanguageToStringConverter.cs | 58 + .../Converter/ResolutionToStringConverter.cs | 54 + .../Settlers.Toolbox/Views/Images/README.md | 16 + .../Settlers.Toolbox/Views/Images/images.zip | Bin 0 -> 1814515 bytes .../Views/Images/roman-settler.png | Bin 0 -> 269644 bytes source/Settlers.Toolbox/Views/MainWindow.xaml | 232 ++ .../Settlers.Toolbox/Views/MainWindow.xaml.cs | 15 + .../Views/Properties/AutoScrollProperty.cs | 43 + .../Properties/IsExternalLinkProperty.cs | 64 + .../Settlers.Toolbox/Views/ReadmeWindow.xaml | 207 ++ .../Views/ReadmeWindow.xaml.cs | 15 + source/Settlers.Toolbox/WindowManager.cs | 30 + source/Settlers.Toolbox/packages.config | 15 + 115 files changed, 8844 insertions(+) create mode 100644 source/Settlers.Toolbox.Infrastructure/AssemblyInfo.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainer.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainerFactory.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Cabinet/CabFile.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainer.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainerFactory.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/ExtensionMethods/CompareExtensions.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/ExtensionMethods/Sha1HashExtensions.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/ExtensionMethods/UnitConversionExtensions.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/IO/IniFileAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IIniFileAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IZipFileAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/IO/ZipFileAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Install/Installer.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Json/Interfaces/IJsonAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Json/JsonAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/Interfaces/ILogLevelConverter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/LogAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/LogLevel.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/LogLevelConverter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/LogManager.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/Logger/Interfaces/IInternalLogger.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/Logger/NullLogger.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/Logger/SerilogLogger.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Logging/Sinks/ReportingSink.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Network/DownloaderBase.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Network/FileDownloader.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IFileDownloader.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IStringDownloader.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Network/StringDownloader.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Properties/AssemblyInfo.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryKeyAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Registry/RegistryAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Registry/RegistryKeyAdapter.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Reporting/Interfaces/IReportManager.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Reporting/ReportManager.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Result.cs create mode 100644 source/Settlers.Toolbox.Infrastructure/Settlers.Toolbox.Infrastructure.csproj create mode 100644 source/Settlers.Toolbox.Infrastructure/packages.config create mode 100644 source/Settlers.Toolbox.Model/Addons/AddonBase.cs create mode 100644 source/Settlers.Toolbox.Model/Addons/AddonManager.cs create mode 100644 source/Settlers.Toolbox.Model/Addons/DieNeueWeltAddon.cs create mode 100644 source/Settlers.Toolbox.Model/Addons/GameAddon.cs create mode 100644 source/Settlers.Toolbox.Model/Addons/Interfaces/IAddon.cs create mode 100644 source/Settlers.Toolbox.Model/Addons/Interfaces/IAddonManager.cs create mode 100644 source/Settlers.Toolbox.Model/Compatibility/CompatibilityManager.cs create mode 100644 source/Settlers.Toolbox.Model/Compatibility/Interfaces/ICompatibilityManager.cs create mode 100644 source/Settlers.Toolbox.Model/Game.cs create mode 100644 source/Settlers.Toolbox.Model/GameModule.cs create mode 100644 source/Settlers.Toolbox.Model/Install/Installer.cs create mode 100644 source/Settlers.Toolbox.Model/Interfaces/IGame.cs create mode 100644 source/Settlers.Toolbox.Model/Languages/GameLanguage.cs create mode 100644 source/Settlers.Toolbox.Model/Languages/Interfaces/ILanguageChanger.cs create mode 100644 source/Settlers.Toolbox.Model/Languages/LanguageChanger.cs create mode 100644 source/Settlers.Toolbox.Model/Languages/LanguagePackLink.cs create mode 100644 source/Settlers.Toolbox.Model/Languages/LanguagePackLinks.cs create mode 100644 source/Settlers.Toolbox.Model/Properties/AssemblyInfo.cs create mode 100644 source/Settlers.Toolbox.Model/Properties/Resources.Designer.cs create mode 100644 source/Settlers.Toolbox.Model/Properties/Resources.resx create mode 100644 source/Settlers.Toolbox.Model/Resolutions/GameResolution.cs create mode 100644 source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionChanger.cs create mode 100644 source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionFactory.cs create mode 100644 source/Settlers.Toolbox.Model/Resolutions/Resolution.cs create mode 100644 source/Settlers.Toolbox.Model/Resolutions/ResolutionChanger.cs create mode 100644 source/Settlers.Toolbox.Model/Resolutions/ResolutionFactory.cs create mode 100644 source/Settlers.Toolbox.Model/Resources/D3DImm.dll create mode 100644 source/Settlers.Toolbox.Model/Resources/DDraw.dll create mode 100644 source/Settlers.Toolbox.Model/Settlers.Toolbox.Model.csproj create mode 100644 source/Settlers.Toolbox.Model/Textures/Interfaces/ITextureSwapper.cs create mode 100644 source/Settlers.Toolbox.Model/Textures/TextureSwapper.cs create mode 100644 source/Settlers.Toolbox.Model/Updates/Interfaces/IUpdateChecker.cs create mode 100644 source/Settlers.Toolbox.Model/Updates/UpdateChecker.cs create mode 100644 source/Settlers.Toolbox.Model/Updates/VersionPackage.cs create mode 100644 source/Settlers.Toolbox.Model/Updates/VersionPackages.cs create mode 100644 source/Settlers.Toolbox.Model/packages.config create mode 100644 source/Settlers.Toolbox.sln create mode 100644 source/Settlers.Toolbox.sln.DotSettings create mode 100644 source/Settlers.Toolbox/App.config create mode 100644 source/Settlers.Toolbox/App.xaml create mode 100644 source/Settlers.Toolbox/App.xaml.cs create mode 100644 source/Settlers.Toolbox/FodyWeavers.xml create mode 100644 source/Settlers.Toolbox/Icons/roman-settler.ico create mode 100644 source/Settlers.Toolbox/Install/Installer.cs create mode 100644 source/Settlers.Toolbox/Interfaces/IWindowManager.cs create mode 100644 source/Settlers.Toolbox/Properties/AssemblyInfo.cs create mode 100644 source/Settlers.Toolbox/Properties/Resources.Designer.cs create mode 100644 source/Settlers.Toolbox/Properties/Resources.resx create mode 100644 source/Settlers.Toolbox/Properties/Settings.Designer.cs create mode 100644 source/Settlers.Toolbox/Properties/Settings.settings create mode 100644 source/Settlers.Toolbox/Settlers.Toolbox.csproj create mode 100644 source/Settlers.Toolbox/Themes/LeftMarginMultiplierConverter.cs create mode 100644 source/Settlers.Toolbox/Themes/Styles.xaml create mode 100644 source/Settlers.Toolbox/Themes/TreeViewItemExtensions.cs create mode 100644 source/Settlers.Toolbox/ViewModelLocator.cs create mode 100644 source/Settlers.Toolbox/ViewModels/DummyMainViewModel.cs create mode 100644 source/Settlers.Toolbox/ViewModels/Interfaces/IMainViewModel.cs create mode 100644 source/Settlers.Toolbox/ViewModels/MainViewModel.cs create mode 100644 source/Settlers.Toolbox/ViewModels/Validator/Interfaces/IMainViewModelValidator.cs create mode 100644 source/Settlers.Toolbox/ViewModels/Validator/MainViewModelValidator.cs create mode 100644 source/Settlers.Toolbox/Views/Converter/BaseConverter.cs create mode 100644 source/Settlers.Toolbox/Views/Converter/InvertBoolConverter.cs create mode 100644 source/Settlers.Toolbox/Views/Converter/LanguageToStringConverter.cs create mode 100644 source/Settlers.Toolbox/Views/Converter/ResolutionToStringConverter.cs create mode 100644 source/Settlers.Toolbox/Views/Images/README.md create mode 100644 source/Settlers.Toolbox/Views/Images/images.zip create mode 100644 source/Settlers.Toolbox/Views/Images/roman-settler.png create mode 100644 source/Settlers.Toolbox/Views/MainWindow.xaml create mode 100644 source/Settlers.Toolbox/Views/MainWindow.xaml.cs create mode 100644 source/Settlers.Toolbox/Views/Properties/AutoScrollProperty.cs create mode 100644 source/Settlers.Toolbox/Views/Properties/IsExternalLinkProperty.cs create mode 100644 source/Settlers.Toolbox/Views/ReadmeWindow.xaml create mode 100644 source/Settlers.Toolbox/Views/ReadmeWindow.xaml.cs create mode 100644 source/Settlers.Toolbox/WindowManager.cs create mode 100644 source/Settlers.Toolbox/packages.config diff --git a/source/Settlers.Toolbox.Infrastructure/AssemblyInfo.cs b/source/Settlers.Toolbox.Infrastructure/AssemblyInfo.cs new file mode 100644 index 0000000..5ed0849 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Reflection; + +namespace Settlers.Toolbox.Infrastructure +{ + public static class AssemblyInfo + { + public static string FullName => GetAssemblyName().FullName; + + public static string VersionString => Version.ToString(); + + public static Version Version => GetAssemblyName().Version; + + + private static AssemblyName GetAssemblyName() + { + return Assembly.GetExecutingAssembly().GetName(); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainer.cs b/source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainer.cs new file mode 100644 index 0000000..cdb6bbb --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Settlers.Toolbox.Infrastructure.Cabinet.Interfaces; + +using UnshieldSharp; + +namespace Settlers.Toolbox.Infrastructure.Cabinet +{ + public class CabContainer : ICabContainer + { + private UnshieldCabinet _CabContainer; + private IReadOnlyList _CachedFileList; + + public int FileCount => _CabContainer?.FileCount ?? -1; + + public CabContainer(FileInfo cabContainerFile) + { + if (cabContainerFile == null) throw new ArgumentNullException(nameof(cabContainerFile)); + if (!cabContainerFile.Exists) throw new FileNotFoundException(nameof(cabContainerFile)); + + _CabContainer = UnshieldCabinet.Open(cabContainerFile.FullName); + } + + public IReadOnlyList FileList + { + get + { + if (_CabContainer == null) + return null; + + if (_CachedFileList != null) + return _CachedFileList; + + var fileList = new List(); + for (int i = 0; i < FileCount; i++) + { + string fileName = _CabContainer.FileName(i); + + fileList.Add(new CabFile(i, fileName)); + } + + _CachedFileList = fileList; + return fileList; + } + } + + public void Close() + { + _CabContainer = null; + } + + public bool SaveFile(CabFile file, string destinationPath) + { + if (_CabContainer == null) throw new InvalidOperationException("Unable to save file, none cab file was loaded."); + if (file.Index < 0 || file.Index > FileCount - 1) throw new IndexOutOfRangeException("Unable to save file, index of file is out of range."); + + return _CabContainer.FileSave(file.Index, destinationPath); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~CabContainer() + { + Dispose(false); + } + + /// + /// Release unmanaged and optionally managed resources. + /// + /// Releases managed resources, if true. + private void Dispose(bool disposing) + { + if (disposing) + { + // Free managed resources here. + } + + // Free unmanaged resources here. + Close(); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainerFactory.cs b/source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainerFactory.cs new file mode 100644 index 0000000..6caa4ef --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Cabinet/CabContainerFactory.cs @@ -0,0 +1,17 @@ +using System; +using System.IO; + +using Settlers.Toolbox.Infrastructure.Cabinet.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Cabinet +{ + public class CabContainerFactory : ICabContainerFactory + { + public ICabContainer Create(FileInfo cabContainerFile) + { + if (cabContainerFile == null) throw new ArgumentNullException(nameof(cabContainerFile)); + + return new CabContainer(cabContainerFile); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Cabinet/CabFile.cs b/source/Settlers.Toolbox.Infrastructure/Cabinet/CabFile.cs new file mode 100644 index 0000000..caf5277 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Cabinet/CabFile.cs @@ -0,0 +1,25 @@ +using System; + +using Settlers.Toolbox.Infrastructure.Cabinet.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Cabinet +{ + /// + /// Represents a single file inside a as data transfer object. + /// + public class CabFile + { + public int Index { get; } + + public string Name { get; } + + public CabFile(int index, string name) + { + if (index < 0) throw new ArgumentNullException(nameof(index)); + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + + Index = index; + Name = name; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainer.cs b/source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainer.cs new file mode 100644 index 0000000..889f003 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace Settlers.Toolbox.Infrastructure.Cabinet.Interfaces +{ + public interface ICabContainer : IDisposable + { + int FileCount { get; } + IReadOnlyList FileList { get; } + + bool SaveFile(CabFile file, string destinationPath); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainerFactory.cs b/source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainerFactory.cs new file mode 100644 index 0000000..3501c00 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Cabinet/Interfaces/ICabContainerFactory.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace Settlers.Toolbox.Infrastructure.Cabinet.Interfaces +{ + public interface ICabContainerFactory + { + ICabContainer Create(FileInfo cabContainerFile); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/CompareExtensions.cs b/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/CompareExtensions.cs new file mode 100644 index 0000000..9063e37 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/CompareExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.IO; + +namespace Settlers.Toolbox.Infrastructure.ExtensionMethods +{ + public static class CompareExtensions + { + public static bool HashEquals(this FileInfo fileToHash, string expectedSha1) + { + string calculatedSha1 = fileToHash.CalculateSha1Hash(); + + return expectedSha1.OrdinalEquals(calculatedSha1); + } + + public static bool OrdinalEquals(this string a, string b) + { + return string.Equals(a, b, StringComparison.Ordinal); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/Sha1HashExtensions.cs b/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/Sha1HashExtensions.cs new file mode 100644 index 0000000..3185146 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/Sha1HashExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Settlers.Toolbox.Infrastructure.ExtensionMethods +{ + public static class Sha1HashExtensions + { + public static string CalculateSha1Hash(this FileInfo file) + { + if (file == null) throw new ArgumentNullException(nameof(file)); + + using (FileStream stream = file.OpenRead()) + { + using (SHA1Managed sha1 = new SHA1Managed()) + { + var hash = sha1.ComputeHash(stream); + var sb = new StringBuilder(hash.Length * 2); + + foreach (byte b in hash) + { + sb.Append(b.ToString("X2")); // "x2" would produce a lowercase string. + } + + return sb.ToString(); + } + } + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/UnitConversionExtensions.cs b/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/UnitConversionExtensions.cs new file mode 100644 index 0000000..043c79d --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/ExtensionMethods/UnitConversionExtensions.cs @@ -0,0 +1,10 @@ +namespace Settlers.Toolbox.Infrastructure.ExtensionMethods +{ + public static class UnitConversionExtensions + { + public static double AsBytesToMegabytes(this long bytes) + { + return (bytes / 1024f) / 1024f; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/IO/IniFileAdapter.cs b/source/Settlers.Toolbox.Infrastructure/IO/IniFileAdapter.cs new file mode 100644 index 0000000..8e59d1e --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/IO/IniFileAdapter.cs @@ -0,0 +1,63 @@ +using System; +using System.Runtime.InteropServices; + +using Settlers.Toolbox.Infrastructure.IO.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.IO +{ + public class IniFileAdapter : IIniFileAdapter + { + // NOTE: FireEmerald: Both methods don't like special chars - so don't use them. Example: UTF-8 file containing 'ä' can't be read! + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileStringW(string lpAppName, string lpKeyName, string lpDefault, string lpReturnedString, int nSize, string lpFileName); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int WritePrivateProfileStringW(string lpAppName, string lpKeyName, string lpString, string lpFileName); + + /// + /// Retrieves a string from the specified section in an initialization file. + /// + /// The name of the section containing the key name. + /// The name of the key whose associated string is to be retrieved. + /// A default string. If the key cannot be found in the initialization file. + /// The name of the initialization file. + /// The return value is the number of characters copied to the buffer, not including the terminating null character. + /// The GetPrivateProfileString function searches the specified initialization file for a key that matches the name + /// specified by the lpKeyName parameter under the section heading specified by the lpAppName parameter. + /// If it finds the key, the function copies the corresponding string to the buffer. If the key does not exist, + /// the function copies the default character string specified by the lpDefault parameter. + public string ReadValueFromFile(string section, string key, string file, string defaultValue = "") + { + if (string.IsNullOrEmpty(section)) throw new ArgumentNullException(nameof(section)); + if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); + if (string.IsNullOrEmpty(file)) throw new ArgumentNullException(nameof(file)); + if (defaultValue == null) throw new ArgumentNullException(nameof(defaultValue)); + + var buffer = new string(' ', 1024); + var length = GetPrivateProfileStringW(section, key, defaultValue, buffer, buffer.Length, file); + + return buffer.Substring(0, length); + } + + /// + /// Copies a string into the specified section of an initialization file. + /// + /// The name of the section to which the string will be copied. If the section does not exist, it is created. + /// The name of the section is case-independent; the string can be any combination of uppercase and lowercase letters. + /// The name of the key to be associated with a string. If the key does not exist in the specified section, it is created. + /// If this parameter is NULL, the entire section, including all entries within the section, is deleted. + /// A null-terminated string to be written to the file. If this parameter is NULL, the key pointed to by the key parameter is deleted. + /// The name of the initialization file. + /// If the function successfully copies the string to the initialization file, the return value is nonzero. If the function fails, + /// or if it flushes the cached version of the most recently accessed initialization file, the return value is zero. + public bool WriteValueToFile(string section, string key, string value, string file) + { + if (string.IsNullOrEmpty(section)) throw new ArgumentNullException(nameof(section)); + if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); + if (value == null) throw new ArgumentNullException(nameof(value)); + if (string.IsNullOrEmpty(file)) throw new ArgumentNullException(nameof(file)); + + return WritePrivateProfileStringW(section, key, " " + value, file) != 0; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IIniFileAdapter.cs b/source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IIniFileAdapter.cs new file mode 100644 index 0000000..eb9e026 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IIniFileAdapter.cs @@ -0,0 +1,8 @@ +namespace Settlers.Toolbox.Infrastructure.IO.Interfaces +{ + public interface IIniFileAdapter + { + string ReadValueFromFile(string section, string key, string file, string defaultValue = ""); + bool WriteValueToFile(string section, string key, string value, string file); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IZipFileAdapter.cs b/source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IZipFileAdapter.cs new file mode 100644 index 0000000..1c5698b --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/IO/Interfaces/IZipFileAdapter.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace Settlers.Toolbox.Infrastructure.IO.Interfaces +{ + public interface IZipFileAdapter + { + void ExtractToDirectory(FileInfo fileToUnzip, DirectoryInfo destinationDirectory); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/IO/ZipFileAdapter.cs b/source/Settlers.Toolbox.Infrastructure/IO/ZipFileAdapter.cs new file mode 100644 index 0000000..ad3a137 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/IO/ZipFileAdapter.cs @@ -0,0 +1,15 @@ +using System.IO; +using System.IO.Compression; + +using Settlers.Toolbox.Infrastructure.IO.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.IO +{ + public class ZipFileAdapter : IZipFileAdapter + { + public void ExtractToDirectory(FileInfo fileToUnzip, DirectoryInfo destinationDirectory) + { + ZipFile.ExtractToDirectory(fileToUnzip.FullName, destinationDirectory.FullName); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Install/Installer.cs b/source/Settlers.Toolbox.Infrastructure/Install/Installer.cs new file mode 100644 index 0000000..c881601 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Install/Installer.cs @@ -0,0 +1,75 @@ +using Castle.MicroKernel.Registration; +using Castle.MicroKernel.SubSystems.Configuration; +using Castle.Windsor; + +using Serilog.Core; + +using Settlers.Toolbox.Infrastructure.Cabinet; +using Settlers.Toolbox.Infrastructure.Cabinet.Interfaces; +using Settlers.Toolbox.Infrastructure.IO; +using Settlers.Toolbox.Infrastructure.IO.Interfaces; +using Settlers.Toolbox.Infrastructure.Json; +using Settlers.Toolbox.Infrastructure.Json.Interfaces; +using Settlers.Toolbox.Infrastructure.Logging; +using Settlers.Toolbox.Infrastructure.Logging.Interfaces; +using Settlers.Toolbox.Infrastructure.Logging.Logger; +using Settlers.Toolbox.Infrastructure.Logging.Logger.Interfaces; +using Settlers.Toolbox.Infrastructure.Logging.Sinks; +using Settlers.Toolbox.Infrastructure.Network; +using Settlers.Toolbox.Infrastructure.Network.Interfaces; +using Settlers.Toolbox.Infrastructure.Registry; +using Settlers.Toolbox.Infrastructure.Registry.Interfaces; +using Settlers.Toolbox.Infrastructure.Reporting; +using Settlers.Toolbox.Infrastructure.Reporting.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Install +{ + public class Installer : IWindsorInstaller + { + /// + /// Performs the installation in the . + /// + /// The container. + /// The configuration store. + public void Install(IWindsorContainer container, IConfigurationStore store) + { + // Cabinet + container.Register( + Component.For().ImplementedBy().LifestyleTransient() + ); + + // IO + container.Register( + Component.For().ImplementedBy().LifestyleTransient(), + Component.For().ImplementedBy().LifestyleTransient() + ); + + // Json + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + + // Logging + container.Register( + Component.For().ImplementedBy().LifestyleTransient(), + Component.For().ImplementedBy().LifestyleTransient() + ); + + // Sinks + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + + // Network + container.Register( + Component.For().ImplementedBy().LifestyleTransient(), + Component.For().ImplementedBy().LifestyleTransient() + ); + + // Registry + container.Register( + Component.For().ImplementedBy().LifestyleTransient(), + Component.For().ImplementedBy().LifestyleTransient() + ); + + // Reporting + container.Register(Component.For().ImplementedBy().LifestyleSingleton()); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Json/Interfaces/IJsonAdapter.cs b/source/Settlers.Toolbox.Infrastructure/Json/Interfaces/IJsonAdapter.cs new file mode 100644 index 0000000..9141acc --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Json/Interfaces/IJsonAdapter.cs @@ -0,0 +1,16 @@ +namespace Settlers.Toolbox.Infrastructure.Json.Interfaces +{ + /// + /// Defines methods to deserialize JSON objects. + /// + public interface IJsonAdapter + { + /// + /// Deserializes the JSON to the specified .NET type. + /// + /// The type of the object to deserialize to. + /// The JSON to deserialize. + /// The deserialized object from the JSON string. + T DeserializeObject(string json); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Json/JsonAdapter.cs b/source/Settlers.Toolbox.Infrastructure/Json/JsonAdapter.cs new file mode 100644 index 0000000..bae281b --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Json/JsonAdapter.cs @@ -0,0 +1,18 @@ +using System; + +using Newtonsoft.Json; + +using Settlers.Toolbox.Infrastructure.Json.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Json +{ + public class JsonAdapter : IJsonAdapter + { + public T DeserializeObject(string json) + { + if (string.IsNullOrEmpty(json)) throw new ArgumentNullException(nameof(json)); + + return JsonConvert.DeserializeObject(json); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/Interfaces/ILogLevelConverter.cs b/source/Settlers.Toolbox.Infrastructure/Logging/Interfaces/ILogLevelConverter.cs new file mode 100644 index 0000000..4372cf7 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/Interfaces/ILogLevelConverter.cs @@ -0,0 +1,9 @@ +using Serilog.Events; + +namespace Settlers.Toolbox.Infrastructure.Logging.Interfaces +{ + public interface ILogLevelConverter + { + LogEventLevel Convert(LogLevel logLevel); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/LogAdapter.cs b/source/Settlers.Toolbox.Infrastructure/Logging/LogAdapter.cs new file mode 100644 index 0000000..b67d85b --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/LogAdapter.cs @@ -0,0 +1,10 @@ +namespace Settlers.Toolbox.Infrastructure.Logging +{ + public static class LogAdapter + { + public static void Log(LogLevel logLevel, string message) + { + LogManager.Instance.CurrentLogger.Log(logLevel, message); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/LogLevel.cs b/source/Settlers.Toolbox.Infrastructure/Logging/LogLevel.cs new file mode 100644 index 0000000..4c0b1aa --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/LogLevel.cs @@ -0,0 +1,35 @@ +namespace Settlers.Toolbox.Infrastructure.Logging +{ + public enum LogLevel + { + /// + /// Tracing information and debugging minutiae. Generally only switched on in unusual situations. + /// + Verbose, + + /// + /// Internal control flow and diagnostic state dumps to facilitate pinpointing of recognised problems. + /// + Debug, + + /// + /// Events of interest or that have relevance to outside observers. The default enabled minimum logging level. + /// + Information, + + /// + /// Indicators of possible issues or service/functionality degradation. + /// + Warning, + + /// + /// Indicating a failure within the application or connected system. + /// + Error, + + /// + /// Critical errors causing complete failure of the application. + /// + Fatal + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/LogLevelConverter.cs b/source/Settlers.Toolbox.Infrastructure/Logging/LogLevelConverter.cs new file mode 100644 index 0000000..f1c8d38 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/LogLevelConverter.cs @@ -0,0 +1,32 @@ +using System; + +using Serilog.Events; + +using Settlers.Toolbox.Infrastructure.Logging.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Logging +{ + public class LogLevelConverter : ILogLevelConverter + { + public LogEventLevel Convert(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Verbose: + return LogEventLevel.Verbose; + case LogLevel.Debug: + return LogEventLevel.Debug; + case LogLevel.Information: + return LogEventLevel.Information; + case LogLevel.Warning: + return LogEventLevel.Warning; + case LogLevel.Error: + return LogEventLevel.Error; + case LogLevel.Fatal: + return LogEventLevel.Fatal; + default: + throw new InvalidOperationException($"Unable to convert {logLevel}, not implemented."); + } + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/LogManager.cs b/source/Settlers.Toolbox.Infrastructure/Logging/LogManager.cs new file mode 100644 index 0000000..28d7d3c --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/LogManager.cs @@ -0,0 +1,35 @@ +using System; + +using Settlers.Toolbox.Infrastructure.Logging.Logger; +using Settlers.Toolbox.Infrastructure.Logging.Logger.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Logging +{ + /// + /// Manager for the currently used . + /// + public sealed class LogManager + { + private IInternalLogger _CurrentLogger = new NullLogger(); + + /// + /// Gets the instance as a singleton. + /// + public static LogManager Instance { get; } = new LogManager(); + + /// + /// Gets or sets the current logger. + /// + /// Use a if you don't want any logging. + public IInternalLogger CurrentLogger + { + get => _CurrentLogger; + set + { + if (value == null) throw new ArgumentNullException(nameof(value)); + + _CurrentLogger = value; + } + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/Logger/Interfaces/IInternalLogger.cs b/source/Settlers.Toolbox.Infrastructure/Logging/Logger/Interfaces/IInternalLogger.cs new file mode 100644 index 0000000..979f8cc --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/Logger/Interfaces/IInternalLogger.cs @@ -0,0 +1,7 @@ +namespace Settlers.Toolbox.Infrastructure.Logging.Logger.Interfaces +{ + public interface IInternalLogger + { + void Log(LogLevel logLevel, string message); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/Logger/NullLogger.cs b/source/Settlers.Toolbox.Infrastructure/Logging/Logger/NullLogger.cs new file mode 100644 index 0000000..b09f846 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/Logger/NullLogger.cs @@ -0,0 +1,12 @@ +using Settlers.Toolbox.Infrastructure.Logging.Logger.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Logging.Logger +{ + public class NullLogger : IInternalLogger + { + public void Log(LogLevel logLevel, string message) + { + // Nothing to do. + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/Logger/SerilogLogger.cs b/source/Settlers.Toolbox.Infrastructure/Logging/Logger/SerilogLogger.cs new file mode 100644 index 0000000..0752740 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/Logger/SerilogLogger.cs @@ -0,0 +1,36 @@ +using System; + +using Serilog; +using Serilog.Core; +using Serilog.Events; + +using Settlers.Toolbox.Infrastructure.Logging.Interfaces; +using Settlers.Toolbox.Infrastructure.Logging.Logger.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Logging.Logger +{ + public class SerilogLogger : IInternalLogger + { + private readonly Serilog.Core.Logger _Logger; + private readonly ILogLevelConverter _LogLevelConverter; + + public SerilogLogger(ILogEventSink usedSink, ILogLevelConverter logLevelConverter) + { + if (usedSink == null) throw new ArgumentNullException(nameof(usedSink)); + if (logLevelConverter == null) throw new ArgumentNullException(nameof(logLevelConverter)); + + _LogLevelConverter = logLevelConverter; + + _Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .WriteTo.Sink(usedSink) + .CreateLogger(); + } + + public void Log(LogLevel logLevel, string message) + { + LogEventLevel logEventLevel = _LogLevelConverter.Convert(logLevel); + _Logger.Write(logEventLevel, message); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Logging/Sinks/ReportingSink.cs b/source/Settlers.Toolbox.Infrastructure/Logging/Sinks/ReportingSink.cs new file mode 100644 index 0000000..0d0c587 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Logging/Sinks/ReportingSink.cs @@ -0,0 +1,29 @@ +using System; + +using Serilog.Core; +using Serilog.Events; + +using Settlers.Toolbox.Infrastructure.Reporting.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Logging.Sinks +{ + public class ReportingSink : ILogEventSink + { + private readonly IReportManager _ReportManager; + private readonly IFormatProvider _FormatProvider; + + public ReportingSink(IReportManager reportManager, IFormatProvider formatProvider = null) + { + if (reportManager == null) throw new ArgumentNullException(nameof(reportManager)); + + _ReportManager = reportManager; + _FormatProvider = formatProvider; + } + + public void Emit(LogEvent logEvent) + { + string message = logEvent.RenderMessage(_FormatProvider); + _ReportManager.ReportMessage(message); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Network/DownloaderBase.cs b/source/Settlers.Toolbox.Infrastructure/Network/DownloaderBase.cs new file mode 100644 index 0000000..5000105 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Network/DownloaderBase.cs @@ -0,0 +1,21 @@ +using System.Net; +using System.Text; + +namespace Settlers.Toolbox.Infrastructure.Network +{ + public abstract class DownloaderBase + { + protected WebClient InitializeWebClient() + { + var client = new WebClient + { + Encoding = Encoding.UTF8, + UseDefaultCredentials = true + }; + + client.Headers.Add(HttpRequestHeader.UserAgent, AssemblyInfo.FullName); + + return client; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Network/FileDownloader.cs b/source/Settlers.Toolbox.Infrastructure/Network/FileDownloader.cs new file mode 100644 index 0000000..db1deb3 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Network/FileDownloader.cs @@ -0,0 +1,68 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Net; + +using Settlers.Toolbox.Infrastructure.ExtensionMethods; +using Settlers.Toolbox.Infrastructure.Network.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Network +{ + public class FileDownloader : DownloaderBase, IFileDownloader + { + public event Action DownloadSuccessful; + public event Action DownloadFailed; + public event Action DownloadProgressUpdate; + + private int _LastProgressPercentage; + + public FileDownloader() + { + _LastProgressPercentage = 0; + } + + public void DownloadFile(string fileUrl, FileInfo destinationFile) + { + if (string.IsNullOrEmpty(fileUrl)) throw new ArgumentNullException(nameof(fileUrl)); + if (destinationFile == null) throw new ArgumentNullException(nameof(destinationFile)); + + if (destinationFile.Exists) + { + throw new IOException($"Unable to download file '{fileUrl}', destination file does already exist '{destinationFile.FullName}'."); + } + + WebClient client = InitializeWebClient(); + + client.DownloadFileCompleted += HandleDownloadFileCompleted; + client.DownloadProgressChanged += HandleDownloadProgressChanged; + + _LastProgressPercentage = 0; + + var fileUri = new Uri(fileUrl); + client.DownloadFileAsync(fileUri, destinationFile.FullName); + } + + private void HandleDownloadFileCompleted(object sender, AsyncCompletedEventArgs e) + { + if (e.Error != null) + { + DownloadFailed?.Invoke(e.Error.Message); + } + else + { + DownloadSuccessful?.Invoke(); + } + } + + private void HandleDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) + { + // Only trigger on 1% steps + if (_LastProgressPercentage != e.ProgressPercentage) + { + _LastProgressPercentage = e.ProgressPercentage; + + DownloadProgressUpdate?.Invoke($"{e.ProgressPercentage}% Downloaded, {e.BytesReceived.AsBytesToMegabytes():0.00} MB of {e.TotalBytesToReceive.AsBytesToMegabytes():0.00} MB."); + } + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IFileDownloader.cs b/source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IFileDownloader.cs new file mode 100644 index 0000000..4dc29b5 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IFileDownloader.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace Settlers.Toolbox.Infrastructure.Network.Interfaces +{ + public interface IFileDownloader + { + event Action DownloadSuccessful; + event Action DownloadFailed; + event Action DownloadProgressUpdate; + + void DownloadFile(string fileUrl, FileInfo destinationFile); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IStringDownloader.cs b/source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IStringDownloader.cs new file mode 100644 index 0000000..3cffd0e --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Network/Interfaces/IStringDownloader.cs @@ -0,0 +1,7 @@ +namespace Settlers.Toolbox.Infrastructure.Network.Interfaces +{ + public interface IStringDownloader + { + string DownloadString(string stringUrl); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Network/StringDownloader.cs b/source/Settlers.Toolbox.Infrastructure/Network/StringDownloader.cs new file mode 100644 index 0000000..32830ef --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Network/StringDownloader.cs @@ -0,0 +1,34 @@ +using System; +using System.Net; + +using Settlers.Toolbox.Infrastructure.Network.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Network +{ + public class StringDownloader : DownloaderBase, IStringDownloader + { + public string DownloadString(string stringUrl) + { + if (string.IsNullOrEmpty(stringUrl)) throw new ArgumentNullException(nameof(stringUrl)); + + WebClient client = InitializeWebClient(); + + var retrievedString = string.Empty; + try + { + retrievedString = client.DownloadString(stringUrl); + } + catch (WebException) + { + // Returning string.Empty is enough. + // LogAdapter.Log(LogLevel.Error, ex.Message); + } + finally + { + client.Dispose(); + } + + return retrievedString; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Properties/AssemblyInfo.cs b/source/Settlers.Toolbox.Infrastructure/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..de0b841 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Settlers.Toolbox.Infrastructure")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Settlers.Toolbox.Infrastructure")] +[assembly: AssemblyCopyright("Copyright © FireEmerald 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("56bc2857-0984-4316-ae93-4ebfa86b0503")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryAdapter.cs b/source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryAdapter.cs new file mode 100644 index 0000000..90771ad --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryAdapter.cs @@ -0,0 +1,7 @@ +namespace Settlers.Toolbox.Infrastructure.Registry.Interfaces +{ + public interface IRegistryAdapter + { + IRegistryKeyAdapter LocalMachine(); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryKeyAdapter.cs b/source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryKeyAdapter.cs new file mode 100644 index 0000000..ece111d --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Registry/Interfaces/IRegistryKeyAdapter.cs @@ -0,0 +1,17 @@ +namespace Settlers.Toolbox.Infrastructure.Registry.Interfaces +{ + public interface IRegistryKeyAdapter + { + string Name { get; } + + IRegistryKeyAdapter OpenSubKey(string name, bool writable); + + IRegistryKeyAdapter CreateSubKey(string subkey); + + Result DeleteSubKeyTree(string subkey); + + object GetValue(string name, object defaultValue = null); + + void SetValue(string name, object value); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Registry/RegistryAdapter.cs b/source/Settlers.Toolbox.Infrastructure/Registry/RegistryAdapter.cs new file mode 100644 index 0000000..6367674 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Registry/RegistryAdapter.cs @@ -0,0 +1,12 @@ +using Settlers.Toolbox.Infrastructure.Registry.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Registry +{ + public class RegistryAdapter : IRegistryAdapter + { + public IRegistryKeyAdapter LocalMachine() + { + return new RegistryKeyAdapter(Microsoft.Win32.Registry.LocalMachine); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Registry/RegistryKeyAdapter.cs b/source/Settlers.Toolbox.Infrastructure/Registry/RegistryKeyAdapter.cs new file mode 100644 index 0000000..1b46216 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Registry/RegistryKeyAdapter.cs @@ -0,0 +1,98 @@ +using System; + +using Microsoft.Win32; + +using Settlers.Toolbox.Infrastructure.Registry.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Registry +{ + public class RegistryKeyAdapter : IRegistryKeyAdapter + { + private readonly RegistryKey _RegistryKey; + + public string Name => _RegistryKey.Name; + + public RegistryKeyAdapter(RegistryKey registryKey) + { + if (registryKey == null) throw new ArgumentNullException(nameof(registryKey)); + + _RegistryKey = registryKey; + } + + public IRegistryKeyAdapter OpenSubKey(string name, bool writable) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + + RegistryKey openedKey; + + try + { + openedKey = _RegistryKey.OpenSubKey(name, writable); + } + catch (Exception) + { + return null; + } + + return openedKey != null ? new RegistryKeyAdapter(openedKey) : null; + } + + public IRegistryKeyAdapter CreateSubKey(string subkey) + { + if (string.IsNullOrEmpty(subkey)) throw new ArgumentNullException(nameof(subkey)); + + RegistryKey createdKey; + + try + { + createdKey = _RegistryKey.CreateSubKey(subkey); + } + catch (Exception) + { + return null; + } + + return createdKey != null ? new RegistryKeyAdapter(createdKey) : null; + + } + + public Result DeleteSubKeyTree(string subkey) + { + if (string.IsNullOrEmpty(subkey)) throw new ArgumentNullException(nameof(subkey)); + + if (OpenSubKey(subkey, true) != null) + { + _RegistryKey.DeleteSubKeyTree(subkey); + return new Result(); + } + + return new Result($"Unable to delete sub key tree '{subkey}', unknown error."); + } + + public object GetValue(string name, object defaultValue = null) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + + object retrievedValue; + + try + { + retrievedValue = _RegistryKey.GetValue(name, defaultValue); + } + catch (Exception) + { + return defaultValue; + } + + return retrievedValue; + } + + public void SetValue(string name, object value) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + if (value == null) throw new ArgumentNullException(nameof(value)); + + _RegistryKey.SetValue(name, value); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Reporting/Interfaces/IReportManager.cs b/source/Settlers.Toolbox.Infrastructure/Reporting/Interfaces/IReportManager.cs new file mode 100644 index 0000000..b652d25 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Reporting/Interfaces/IReportManager.cs @@ -0,0 +1,13 @@ +using System; + +namespace Settlers.Toolbox.Infrastructure.Reporting.Interfaces +{ + public interface IReportManager + { + event Action ReportReceived; + event Action StatusReportReceived; + + void ReportMessage(string message); + void ReportStatus(string status); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Reporting/ReportManager.cs b/source/Settlers.Toolbox.Infrastructure/Reporting/ReportManager.cs new file mode 100644 index 0000000..18b2a6a --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Reporting/ReportManager.cs @@ -0,0 +1,36 @@ +using System; + +using Settlers.Toolbox.Infrastructure.Reporting.Interfaces; + +namespace Settlers.Toolbox.Infrastructure.Reporting +{ + public class ReportManager : IReportManager + { + public event Action ReportReceived; + public event Action StatusReportReceived; + + public void ReportMessage(string message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + + RaiseReportReceived(message); + } + + public void ReportStatus(string status) + { + if (status == null) throw new ArgumentNullException(nameof(status)); + + RaiseStatusReportReceived(status); + } + + private void RaiseReportReceived(string message) + { + ReportReceived?.Invoke(message); + } + + private void RaiseStatusReportReceived(string statusMessage) + { + StatusReportReceived?.Invoke(statusMessage); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Result.cs b/source/Settlers.Toolbox.Infrastructure/Result.cs new file mode 100644 index 0000000..ef03993 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Result.cs @@ -0,0 +1,38 @@ +using System; + +namespace Settlers.Toolbox.Infrastructure +{ + public class Result + { + public bool Success { get; private set; } + public string ErrorMessage { get; private set; } + public Exception ThrownException { get; private set; } + + public Result() + { + SetProperties(true, string.Empty, null); + } + + public Result(string errorMessage) + { + if (string.IsNullOrEmpty(errorMessage)) throw new ArgumentNullException(nameof(errorMessage)); + + SetProperties(false, errorMessage, null); + } + + public Result(string errorMessage, Exception thrownException) + { + if (string.IsNullOrEmpty(errorMessage)) throw new ArgumentNullException(nameof(errorMessage)); + if (thrownException == null) throw new ArgumentNullException(nameof(thrownException)); + + SetProperties(false, errorMessage, thrownException); + } + + private void SetProperties(bool success, string errorMessage, Exception thrownException) + { + Success = success; + ErrorMessage = errorMessage; + ThrownException = thrownException; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/Settlers.Toolbox.Infrastructure.csproj b/source/Settlers.Toolbox.Infrastructure/Settlers.Toolbox.Infrastructure.csproj new file mode 100644 index 0000000..c778909 --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/Settlers.Toolbox.Infrastructure.csproj @@ -0,0 +1,112 @@ + + + + + Debug + AnyCPU + {56BC2857-0984-4316-AE93-4EBFA86B0503} + Library + Properties + Settlers.Toolbox.Infrastructure + Settlers.Toolbox.Infrastructure + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + + + ..\packages\Castle.Windsor.4.1.0\lib\net45\Castle.Windsor.dll + + + ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Serilog.2.7.1\lib\net46\Serilog.dll + + + + + + + + + + + + + + + + + ..\packages\UnshieldSharp.1.4.2.2\lib\net461\UnshieldSharp.dll + + + ..\packages\zlib.net.1.0.4.0\lib\zlib.net.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/Settlers.Toolbox.Infrastructure/packages.config b/source/Settlers.Toolbox.Infrastructure/packages.config new file mode 100644 index 0000000..69b735d --- /dev/null +++ b/source/Settlers.Toolbox.Infrastructure/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Addons/AddonBase.cs b/source/Settlers.Toolbox.Model/Addons/AddonBase.cs new file mode 100644 index 0000000..6d2311c --- /dev/null +++ b/source/Settlers.Toolbox.Model/Addons/AddonBase.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Settlers.Toolbox.Infrastructure; +using Settlers.Toolbox.Infrastructure.Cabinet; +using Settlers.Toolbox.Infrastructure.Cabinet.Interfaces; +using Settlers.Toolbox.Infrastructure.ExtensionMethods; +using Settlers.Toolbox.Model.Addons.Interfaces; + +namespace Settlers.Toolbox.Model.Addons +{ + public abstract class AddonBase : IAddon + { + private const string DISK_MAIN_DIRECTORY_PATH = "S4"; + private const string DISK_DATA1_CAB_FILE_NAME = "data1.cab"; + + private readonly ICabContainerFactory _CabContainerFactory; + + protected AddonBase(ICabContainerFactory cabContainerFactory) + { + if (cabContainerFactory == null) throw new ArgumentNullException(nameof(cabContainerFactory)); + + _CabContainerFactory = cabContainerFactory; + } + + public abstract GameAddon Id { get; } + + public abstract IDictionary FileMappings { get; } + + public bool IsInstalled(DirectoryInfo installDir) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + if (!UnlockRegistryKeysExist()) + return false; + + foreach (KeyValuePair fileMapping in FileMappings) + { + string fullFilePath = Path.Combine(installDir.FullName, fileMapping.Value); + + if (!File.Exists(fullFilePath)) + return false; + } + + return true; + } + + public bool IsAddonDisk(DriveInfo driveInfo) + { + if (driveInfo == null) throw new ArgumentNullException(nameof(driveInfo)); + + DirectoryInfo diskMainDirectory = GetDiskMainDirectory(driveInfo); + if (!diskMainDirectory.Exists) + return false; + + FileInfo cabContainerFile = GetCabContainerFile(diskMainDirectory); + if (!cabContainerFile.Exists) + return false; + + IReadOnlyList filesInMainDirOnDisk = diskMainDirectory.GetFiles("*", SearchOption.AllDirectories); + IReadOnlyList filesInCabFileOnDisk = GetFileListFromCabContainerFile(cabContainerFile); + + foreach (KeyValuePair fileMapping in FileMappings) + { + if (!filesInMainDirOnDisk.Any(file => file.Name.OrdinalEquals(fileMapping.Key)) && + !filesInCabFileOnDisk.Any(cabFile => cabFile.Name.OrdinalEquals(fileMapping.Key))) + return false; + } + + return true; + } + + private DirectoryInfo GetDiskMainDirectory(DriveInfo driveInfo) + { + string diskMainDirectoryPath = Path.Combine(driveInfo.RootDirectory.FullName, DISK_MAIN_DIRECTORY_PATH); + + return new DirectoryInfo(diskMainDirectoryPath); + } + + private FileInfo GetCabContainerFile(DirectoryInfo diskMainDirectory) + { + string cabContainerFilePath = Path.Combine(diskMainDirectory.FullName, DISK_DATA1_CAB_FILE_NAME); + + return new FileInfo(cabContainerFilePath); + } + + private IReadOnlyList GetFileListFromCabContainerFile(FileInfo cabContainerFile) + { + using (ICabContainer cabContainer = _CabContainerFactory.Create(cabContainerFile)) + { + return cabContainer.FileList; + } + } + + public Result InstallFromDisk(DirectoryInfo installDir, DriveInfo addonDisk) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + if (addonDisk == null) throw new ArgumentNullException(nameof(addonDisk)); + + DirectoryInfo diskMainDirectory = GetDiskMainDirectory(addonDisk); + FileInfo cabContainerFile = GetCabContainerFile(diskMainDirectory); + + IReadOnlyList filesInMainDirOnDisk = diskMainDirectory.GetFiles("*", SearchOption.AllDirectories); + + Result addonFilesCopied; + using (ICabContainer cabContainer = _CabContainerFactory.Create(cabContainerFile)) + { + addonFilesCopied = CopyAddonFilesToInstallDir(installDir, filesInMainDirOnDisk, cabContainer); + } + + Result addonInRegistryUnlocked = CreateUnlockRegistryKeys(); + + if (addonFilesCopied.Success) + { + if (addonInRegistryUnlocked.Success) + return new Result(); + } + + // Revert partial changes + RemoveAddonFilesFromInstallDir(installDir); + + return addonFilesCopied.Success ? addonInRegistryUnlocked : addonFilesCopied; + } + + private Result CopyAddonFilesToInstallDir(DirectoryInfo installDir, IReadOnlyList filesInMainDirOnDisk, ICabContainer cabContainer) + { + foreach (KeyValuePair fileMapping in FileMappings) + { + string fullDestinationFilePath = Path.Combine(installDir.FullName, fileMapping.Value); + + if (File.Exists(fullDestinationFilePath)) + RemoveReadOnlyFileAttribute(fullDestinationFilePath); + + FileInfo matchingMainDirFile = filesInMainDirOnDisk.FirstOrDefault(file => file.Name.OrdinalEquals(fileMapping.Key)); + if (matchingMainDirFile != null) + { + matchingMainDirFile.CopyTo(fullDestinationFilePath, true); // TODO: FireEmerald: Crashes if the dir does not exist. + continue; + } + + CabFile matchingCabFile = cabContainer.FileList.FirstOrDefault(cabFile => cabFile.Name.OrdinalEquals(fileMapping.Key)); + if (matchingCabFile != null) + { + if (cabContainer.SaveFile(matchingCabFile, fullDestinationFilePath)) + continue; + + return new Result($"Unable to copy file '{matchingCabFile.Name}' from cab container to install directory, unknown error."); + } + + return new Result($"Unable to copy file '{fileMapping.Key}' from disk to install directory, source file not found."); + } + + return new Result(); + } + + private void RemoveReadOnlyFileAttribute(string fullFilePath) + { + FileAttributes attributes = File.GetAttributes(fullFilePath); + + if (attributes.HasFlag(FileAttributes.ReadOnly)) + File.SetAttributes(fullFilePath, attributes &~ FileAttributes.ReadOnly); + } + + private void RemoveAddonFilesFromInstallDir(DirectoryInfo installDir) + { + foreach (KeyValuePair fileMapping in FileMappings) + { + string fullFilePath = Path.Combine(installDir.FullName, fileMapping.Value); + + if (File.Exists(fullFilePath)) + { + RemoveReadOnlyFileAttribute(fullFilePath); + File.Delete(fullFilePath); + } + } + } + + protected abstract bool UnlockRegistryKeysExist(); + protected abstract Result CreateUnlockRegistryKeys(); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Addons/AddonManager.cs b/source/Settlers.Toolbox.Model/Addons/AddonManager.cs new file mode 100644 index 0000000..6db8f4d --- /dev/null +++ b/source/Settlers.Toolbox.Model/Addons/AddonManager.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Settlers.Toolbox.Infrastructure; +using Settlers.Toolbox.Model.Addons.Interfaces; + +namespace Settlers.Toolbox.Model.Addons +{ + public class AddonManager : IAddonManager + { + private readonly IReadOnlyList _AllAddons; + + public AddonManager(IReadOnlyList allAddons) + { + if (allAddons == null || !allAddons.Any()) throw new ArgumentNullException(nameof(allAddons)); + + _AllAddons = allAddons; + } + + public GameAddon DetectInstalledAddons(DirectoryInfo installDir) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + GameAddon installedAddons = GameAddon.None; + + foreach (IAddon addon in _AllAddons) + { + if (addon.IsInstalled(installDir)) + installedAddons |= addon.Id; + } + + return installedAddons; + } + + public bool IsAddonDisk(GameAddon gameAddon, DriveInfo driveInfo) + { + if (driveInfo == null) throw new ArgumentNullException(nameof(driveInfo)); + + IAddon addonToCheck = _AllAddons.Single(addon => addon.Id == gameAddon); + + return addonToCheck.IsAddonDisk(driveInfo); + } + + public Result InstallAddon(GameAddon gameAddon, DirectoryInfo installDir, DriveInfo addonDisk) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + if (addonDisk == null) throw new ArgumentNullException(nameof(addonDisk)); + + IAddon addonToInstall = _AllAddons.Single(addon => addon.Id == gameAddon); + + return addonToInstall.InstallFromDisk(installDir, addonDisk); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Addons/DieNeueWeltAddon.cs b/source/Settlers.Toolbox.Model/Addons/DieNeueWeltAddon.cs new file mode 100644 index 0000000..662295f --- /dev/null +++ b/source/Settlers.Toolbox.Model/Addons/DieNeueWeltAddon.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; + +using Settlers.Toolbox.Infrastructure; +using Settlers.Toolbox.Infrastructure.Cabinet.Interfaces; +using Settlers.Toolbox.Infrastructure.Registry.Interfaces; + +namespace Settlers.Toolbox.Model.Addons +{ + public class DieNeueWeltAddon : AddonBase + { + private const string ADDON_REGKEY = @"SOFTWARE\BlueByte\Settlers4\Extra3"; + private const string CDVERSION_VALUE_NAME = "CDVersion"; + + private readonly IRegistryAdapter _RegistryAdapter; + + public override GameAddon Id { get; } + + public override IDictionary FileMappings { get; } + + public DieNeueWeltAddon(ICabContainerFactory cabContainerFactory, IRegistryAdapter registryAdapter) : base(cabContainerFactory) + { + if (registryAdapter == null) throw new ArgumentNullException(nameof(registryAdapter)); + + _RegistryAdapter = registryAdapter; + + Id = GameAddon.DieNeueWelt; + + FileMappings = new Dictionary + { + // Game Menu and Graphics + { "40.gfx", @"Gfx\40.gfx" }, + { "41.gh5", @"Gfx\41.gh5" }, + { "41.gh6", @"Gfx\41.gh6" }, + { "41.gl5", @"Gfx\41.gl5" }, + { "41.gl6", @"Gfx\41.gl6" }, + + // Maps + { "MCD2_maya1.map", @"Map\Campaign\MCD2_maya1.map" }, + { "MCD2_maya2.map", @"Map\Campaign\MCD2_maya2.map" }, + { "MCD2_maya3.map", @"Map\Campaign\MCD2_maya3.map" }, + { "MCD2_maya4.map", @"Map\Campaign\MCD2_maya4.map" }, + { "MCD2_maya5.map", @"Map\Campaign\MCD2_maya5.map" }, + { "MCD2_roman1.map", @"Map\Campaign\MCD2_roman1.map" }, + { "MCD2_roman2.map", @"Map\Campaign\MCD2_roman2.map" }, + { "MCD2_roman3.map", @"Map\Campaign\MCD2_roman3.map" }, + { "MCD2_roman4.map", @"Map\Campaign\MCD2_roman4.map" }, + { "MCD2_roman5.map", @"Map\Campaign\MCD2_roman5.map" }, + { "MCD2_trojan1.map", @"Map\Campaign\MCD2_trojan1.map" }, + { "MCD2_trojan2.map", @"Map\Campaign\MCD2_trojan2.map" }, + { "MCD2_trojan3.map", @"Map\Campaign\MCD2_trojan3.map" }, + { "MCD2_trojan4.map", @"Map\Campaign\MCD2_trojan4.map" }, + { "MCD2_trojan5.map", @"Map\Campaign\MCD2_trojan5.map" }, + { "MCD2_viking1.map", @"Map\Campaign\MCD2_viking1.map" }, + { "MCD2_viking2.map", @"Map\Campaign\MCD2_viking2.map" }, + { "MCD2_viking3.map", @"Map\Campaign\MCD2_viking3.map" }, + { "MCD2_viking4.map", @"Map\Campaign\MCD2_viking4.map" }, + { "MCD2_viking5.map", @"Map\Campaign\MCD2_viking5.map" }, + }; + } + + // Works without special privileges + protected override bool UnlockRegistryKeysExist() + { + IRegistryKeyAdapter hklm = _RegistryAdapter.LocalMachine(); + + IRegistryKeyAdapter addonKey = hklm.OpenSubKey(ADDON_REGKEY, false); + + if (addonKey == null) + return false; + + object cdVersionValue = addonKey.GetValue(CDVERSION_VALUE_NAME); + + return cdVersionValue != null; + } + + // Requires administrator privileges + protected override Result CreateUnlockRegistryKeys() + { + return new Result(); // TODO: FireEmerald: REMOVE + + IRegistryKeyAdapter hklm = _RegistryAdapter.LocalMachine(); + + IRegistryKeyAdapter addonKey = hklm.CreateSubKey(ADDON_REGKEY); + + if (addonKey == null) + return new Result($"Unable to create registry key '{hklm.Name}\\{ADDON_REGKEY}'."); + + addonKey.SetValue(CDVERSION_VALUE_NAME, 1508); + return new Result(); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Addons/GameAddon.cs b/source/Settlers.Toolbox.Model/Addons/GameAddon.cs new file mode 100644 index 0000000..76321e3 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Addons/GameAddon.cs @@ -0,0 +1,11 @@ +using System; + +namespace Settlers.Toolbox.Model.Addons +{ + [Flags] + public enum GameAddon + { + None = 0, + DieNeueWelt = 1 + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Addons/Interfaces/IAddon.cs b/source/Settlers.Toolbox.Model/Addons/Interfaces/IAddon.cs new file mode 100644 index 0000000..1a1511c --- /dev/null +++ b/source/Settlers.Toolbox.Model/Addons/Interfaces/IAddon.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.IO; + +using Settlers.Toolbox.Infrastructure; + +namespace Settlers.Toolbox.Model.Addons.Interfaces +{ + public interface IAddon + { + GameAddon Id { get; } + + /// + /// Dictionary <FileName, RelativeFilePathInInstallDir> + /// + IDictionary FileMappings { get; } + + bool IsInstalled(DirectoryInfo installDir); + bool IsAddonDisk(DriveInfo driveInfo); + + Result InstallFromDisk(DirectoryInfo installDir, DriveInfo addonDisk); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Addons/Interfaces/IAddonManager.cs b/source/Settlers.Toolbox.Model/Addons/Interfaces/IAddonManager.cs new file mode 100644 index 0000000..aa8cf18 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Addons/Interfaces/IAddonManager.cs @@ -0,0 +1,14 @@ +using System.IO; + +using Settlers.Toolbox.Infrastructure; + +namespace Settlers.Toolbox.Model.Addons.Interfaces +{ + public interface IAddonManager + { + GameAddon DetectInstalledAddons(DirectoryInfo installDir); + + bool IsAddonDisk(GameAddon gameAddon, DriveInfo driveInfo); + Result InstallAddon(GameAddon gameAddon, DirectoryInfo installDir, DriveInfo addonDisk); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Compatibility/CompatibilityManager.cs b/source/Settlers.Toolbox.Model/Compatibility/CompatibilityManager.cs new file mode 100644 index 0000000..5733e2f --- /dev/null +++ b/source/Settlers.Toolbox.Model/Compatibility/CompatibilityManager.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Settlers.Toolbox.Infrastructure.Reporting.Interfaces; +using Settlers.Toolbox.Model.Compatibility.Interfaces; + +namespace Settlers.Toolbox.Model.Compatibility +{ + public class CompatibilityManager : ICompatibilityManager + { + private const string D3DIMM_RELATIVE_PATH = @"Exe\D3DImm.dll"; + private const string DDRAW_RELATIVE_PATH = @"Exe\DDraw.dll"; + + private readonly IReportManager _ReportManager; + + public CompatibilityManager(IReportManager reportManager) + { + if (reportManager == null) throw new ArgumentNullException(nameof(reportManager)); + + _ReportManager = reportManager; + } + + public bool IsFixApplied(DirectoryInfo installDir) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + IReadOnlyList compatibilityDllFiles = GetCompatibilityDllFiles(installDir); + + return compatibilityDllFiles.All(file => file.Exists); + } + + public void ApplyFix(DirectoryInfo installDir) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + var d3DImmFile = new FileInfo(Path.Combine(installDir.FullName, D3DIMM_RELATIVE_PATH)); + var dDrawFile = new FileInfo(Path.Combine(installDir.FullName, DDRAW_RELATIVE_PATH)); + + if (d3DImmFile.Exists) + d3DImmFile.Delete(); + + File.WriteAllBytes(d3DImmFile.FullName, Properties.Resources.D3DImm); + + if (dDrawFile.Exists) + dDrawFile.Delete(); + + File.WriteAllBytes(dDrawFile.FullName, Properties.Resources.DDraw); + + _ReportManager.ReportMessage("Compatibility fix applied ... OK"); + } + + public void RemoveFix(DirectoryInfo installDir) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + IReadOnlyList compatibilityDllFiles = GetCompatibilityDllFiles(installDir); + + foreach (FileInfo file in compatibilityDllFiles) + { + if (file.Exists) + file.Delete(); + } + + _ReportManager.ReportMessage("Compatibility fix removed ... OK"); + } + + private IReadOnlyList GetCompatibilityDllFiles(DirectoryInfo installDir) + { + return new List + { + new FileInfo(Path.Combine(installDir.FullName, D3DIMM_RELATIVE_PATH)), + new FileInfo(Path.Combine(installDir.FullName, DDRAW_RELATIVE_PATH)) + }; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Compatibility/Interfaces/ICompatibilityManager.cs b/source/Settlers.Toolbox.Model/Compatibility/Interfaces/ICompatibilityManager.cs new file mode 100644 index 0000000..cfacba3 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Compatibility/Interfaces/ICompatibilityManager.cs @@ -0,0 +1,12 @@ +using System.IO; + +namespace Settlers.Toolbox.Model.Compatibility.Interfaces +{ + public interface ICompatibilityManager + { + bool IsFixApplied(DirectoryInfo installDir); + + void ApplyFix(DirectoryInfo installDir); + void RemoveFix(DirectoryInfo installDir); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Game.cs b/source/Settlers.Toolbox.Model/Game.cs new file mode 100644 index 0000000..2021beb --- /dev/null +++ b/source/Settlers.Toolbox.Model/Game.cs @@ -0,0 +1,204 @@ +using System; +using System.Diagnostics; +using System.IO; + +using Settlers.Toolbox.Infrastructure; +using Settlers.Toolbox.Infrastructure.ExtensionMethods; +using Settlers.Toolbox.Infrastructure.Logging; +using Settlers.Toolbox.Model.Addons; +using Settlers.Toolbox.Model.Addons.Interfaces; +using Settlers.Toolbox.Model.Compatibility.Interfaces; +using Settlers.Toolbox.Model.Interfaces; +using Settlers.Toolbox.Model.Languages; +using Settlers.Toolbox.Model.Languages.Interfaces; +using Settlers.Toolbox.Model.Resolutions; +using Settlers.Toolbox.Model.Resolutions.Interfaces; +using Settlers.Toolbox.Model.Textures.Interfaces; + +namespace Settlers.Toolbox.Model +{ + public class Game : IGame + { + private const string SETTLERS_PROC_NAME = "S4"; + private const string SETTLERS_S4_EXE = SETTLERS_PROC_NAME + ".exe"; + + private const string SETTLERS_S4_MAIN_RELATIVE_PATH = @"Exe\S4_Main.exe"; + + private const string S4_MAIN_GOG_SHA1 = "E23CD82EDD8783368BA8BE0DDD322E1AE44B5E6C"; + private const int S4_MAIN_RETAIL_MAJOR_VERSION_NUMBER = 1; + + // Cached values + private GameAddon _CachedInstalledAddons; + + private readonly IAddonManager _AddonManager; + private readonly IResolutionChanger _ResolutionChanger; + private readonly ILanguageChanger _LanguageChanger; + private readonly ICompatibilityManager _CompatibilityManager; + private readonly ITextureSwapper _TextureSwapper; + + public event Action LanguageChangeCompleted; + + public Game(IAddonManager addonManager, IResolutionChanger resolutionChanger, ILanguageChanger languageChanger, + ICompatibilityManager compatibilityManager, ITextureSwapper textureSwapper) + { + if (resolutionChanger == null) throw new ArgumentNullException(nameof(resolutionChanger)); + if (languageChanger == null) throw new ArgumentNullException(nameof(languageChanger)); + if (compatibilityManager == null) throw new ArgumentNullException(nameof(compatibilityManager)); + if (textureSwapper == null) throw new ArgumentNullException(nameof(textureSwapper)); + + _AddonManager = addonManager; + _ResolutionChanger = resolutionChanger; + _LanguageChanger = languageChanger; + _CompatibilityManager = compatibilityManager; + _TextureSwapper = textureSwapper; + + LatestVersion = new Version("2.50.1516.0"); + + // Link events + _LanguageChanger.LanguageChangeCompleted += HandleLanguageChangeCompleted; + } + + ~Game() + { + // Unlink events + _LanguageChanger.LanguageChangeCompleted -= HandleLanguageChangeCompleted; + } + + public DirectoryInfo InstallDir { get; set; } + + public GameLanguage Language => IsInstalled ? _LanguageChanger.DetectLanguage(InstallDir) : GameLanguage.Unknown; + + public Resolution Resolution => IsInstalled ? _ResolutionChanger.DetectResolution(InstallDir) : null; + + public GameAddon InstalledAddons => IsInstalled ? _CachedInstalledAddons : GameAddon.None; + + public GameModule SupportedModules + { + get + { + if (!IsInstalled) + return GameModule.None; + + var modules = GameModule.Resolution | GameModule.CompatibilityFix; // Modules which are always enabled. + + if (IsGogVersion) + modules |= GameModule.Language; + + if (InstalledAddons.HasFlag(GameAddon.DieNeueWelt)) + modules |= GameModule.TexturePack; + + return modules; + } + } + + public Version LatestVersion { get; } + + public Version InstalledVersion + { + get + { + if (!IsInstalled) + return null; + + string s4MainFilePath = Path.Combine(InstallDir.FullName, SETTLERS_S4_MAIN_RELATIVE_PATH); + var s4MainVersionInfo = FileVersionInfo.GetVersionInfo(s4MainFilePath); + + return new Version(s4MainVersionInfo.FileMajorPart, s4MainVersionInfo.FileMinorPart, + s4MainVersionInfo.FileBuildPart, s4MainVersionInfo.FilePrivatePart); + } + } + + public bool IsGogVersion + { + get + { + if (!IsInstalled) + return false; + + string s4MainFilePath = Path.Combine(InstallDir.FullName, SETTLERS_S4_MAIN_RELATIVE_PATH); + var s4MainFile = new FileInfo(s4MainFilePath); + + return s4MainFile.HashEquals(S4_MAIN_GOG_SHA1); + } + } + + public bool IsRetailVersion => InstalledVersion != null && InstalledVersion.Major == S4_MAIN_RETAIL_MAJOR_VERSION_NUMBER; + + public bool IsCompatibilityFixApplied => IsInstalled && _CompatibilityManager.IsFixApplied(InstallDir); + + public bool IsTexturePackActive => IsInstalled && _TextureSwapper.IsTropicalTexturePackActive(InstallDir); + + public bool IsInstalled => InstallDir != null && InstallDir.Exists; + + public bool IsRunning => Process.GetProcessesByName(SETTLERS_PROC_NAME).Length > 0; + + public void RefreshInstalledAddons() + { + if (IsInstalled) + _CachedInstalledAddons = _AddonManager.DetectInstalledAddons(InstallDir); + } + + public bool IsAddonDisk(GameAddon gameAddon, DriveInfo driveInfo) + { + return _AddonManager.IsAddonDisk(gameAddon, driveInfo); + } + + public Result InstallAddon(GameAddon gameAddon,DriveInfo addonDisk) + { + return _AddonManager.InstallAddon(gameAddon, InstallDir, addonDisk); + } + + public void ApplyCompatibilityFix() + { + _CompatibilityManager.ApplyFix(InstallDir); + } + + public void RemoveCompatibilityFix() + { + _CompatibilityManager.RemoveFix(InstallDir); + } + + public void ActivateTropicalTextures() + { + _TextureSwapper.ActivateTropicalTextures(InstallDir); + } + + public void DeactivateTropicalTextures() + { + _TextureSwapper.DeactivateTropicalTextures(InstallDir); + } + + public void ChangeResolution(GameResolution gameResolution) + { + _ResolutionChanger.ChangeResolution(InstallDir, gameResolution); + } + + public void ChangeResolution(ushort width, ushort height) + { + _ResolutionChanger.ChangeResolution(InstallDir, width, height); + } + + public void ChangeLanguage(GameLanguage language) + { + _LanguageChanger.ChangeLanguage(InstallDir, language); + } + + private void HandleLanguageChangeCompleted(Result result) + { + LanguageChangeCompleted?.Invoke(result); + } + + public void Start() + { + try + { + Process.Start(Path.Combine(InstallDir.FullName, SETTLERS_S4_EXE)); + } + catch (Exception ex) + { + LogAdapter.Log(LogLevel.Error, ex.Message); + LogAdapter.Log(LogLevel.Warning, "Unable to start the game, a error occurred."); + } + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/GameModule.cs b/source/Settlers.Toolbox.Model/GameModule.cs new file mode 100644 index 0000000..9b993e7 --- /dev/null +++ b/source/Settlers.Toolbox.Model/GameModule.cs @@ -0,0 +1,14 @@ +using System; + +namespace Settlers.Toolbox.Model +{ + [Flags] + public enum GameModule + { + None = 0, + Language = 1, + Resolution = 2, + CompatibilityFix = 4, + TexturePack = 8 + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Install/Installer.cs b/source/Settlers.Toolbox.Model/Install/Installer.cs new file mode 100644 index 0000000..a9dbb89 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Install/Installer.cs @@ -0,0 +1,58 @@ +using Castle.MicroKernel.Registration; +using Castle.MicroKernel.SubSystems.Configuration; +using Castle.Windsor; + +using Settlers.Toolbox.Model.Addons; +using Settlers.Toolbox.Model.Addons.Interfaces; +using Settlers.Toolbox.Model.Compatibility; +using Settlers.Toolbox.Model.Compatibility.Interfaces; +using Settlers.Toolbox.Model.Interfaces; +using Settlers.Toolbox.Model.Languages; +using Settlers.Toolbox.Model.Languages.Interfaces; +using Settlers.Toolbox.Model.Resolutions; +using Settlers.Toolbox.Model.Resolutions.Interfaces; +using Settlers.Toolbox.Model.Textures; +using Settlers.Toolbox.Model.Textures.Interfaces; +using Settlers.Toolbox.Model.Updates; +using Settlers.Toolbox.Model.Updates.Interfaces; + +namespace Settlers.Toolbox.Model.Install +{ + public class Installer : IWindsorInstaller + { + /// + /// Performs the installation in the . + /// + /// The container. + /// The configuration store. + public void Install(IWindsorContainer container, IConfigurationStore store) + { + // Addons + container.Register(Classes.FromAssemblyContaining() + .BasedOn().WithService.FromInterface().LifestyleTransient()); + + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + + // Compatibility + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + + // Languages + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + + // Resolutions + container.Register( + Component.For().ImplementedBy().LifestyleTransient(), + Component.For().ImplementedBy().LifestyleTransient() + ); + + // Textures + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + + // Updates + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + + // Root + container.Register(Component.For().ImplementedBy().LifestyleTransient()); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Interfaces/IGame.cs b/source/Settlers.Toolbox.Model/Interfaces/IGame.cs new file mode 100644 index 0000000..f7de88d --- /dev/null +++ b/source/Settlers.Toolbox.Model/Interfaces/IGame.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; + +using Settlers.Toolbox.Infrastructure; +using Settlers.Toolbox.Model.Addons; +using Settlers.Toolbox.Model.Languages; +using Settlers.Toolbox.Model.Resolutions; + +namespace Settlers.Toolbox.Model.Interfaces +{ + public interface IGame + { + event Action LanguageChangeCompleted; + + DirectoryInfo InstallDir { get; set; } + + GameLanguage Language { get; } + Resolution Resolution { get; } + + GameAddon InstalledAddons { get; } + GameModule SupportedModules { get; } + + Version LatestVersion { get; } + Version InstalledVersion { get; } + + bool IsGogVersion { get; } + bool IsRetailVersion { get; } + + bool IsCompatibilityFixApplied { get; } + bool IsTexturePackActive { get; } + + bool IsInstalled { get; } + bool IsRunning { get; } + + void RefreshInstalledAddons(); + + bool IsAddonDisk(GameAddon gameAddon, DriveInfo driveInfo); + Result InstallAddon(GameAddon gameAddon, DriveInfo addonDisk); + + void ApplyCompatibilityFix(); + void RemoveCompatibilityFix(); + + void ActivateTropicalTextures(); + void DeactivateTropicalTextures(); + + void ChangeResolution(GameResolution gameResolution); + void ChangeResolution(ushort width, ushort height); + + void ChangeLanguage(GameLanguage language); + + void Start(); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Languages/GameLanguage.cs b/source/Settlers.Toolbox.Model/Languages/GameLanguage.cs new file mode 100644 index 0000000..602aedb --- /dev/null +++ b/source/Settlers.Toolbox.Model/Languages/GameLanguage.cs @@ -0,0 +1,24 @@ +namespace Settlers.Toolbox.Model.Languages +{ + /// + /// The assigned integer values are equal to the Language property inside MiscData1.cfg. + /// + public enum GameLanguage + { + Unknown = -1, + English = 0, + German = 1, + French = 2, + Spanish = 3, + Italian = 4, + Polish = 5, + Korean = 6, + EnglishAsianWesternFont = 7, + Swedish = 8, + Danish = 9, + Norwegian = 10, + Hungarian = 11, + Thai = 12, + Czech = 13 + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Languages/Interfaces/ILanguageChanger.cs b/source/Settlers.Toolbox.Model/Languages/Interfaces/ILanguageChanger.cs new file mode 100644 index 0000000..d94423c --- /dev/null +++ b/source/Settlers.Toolbox.Model/Languages/Interfaces/ILanguageChanger.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; + +using Settlers.Toolbox.Infrastructure; + +namespace Settlers.Toolbox.Model.Languages.Interfaces +{ + public interface ILanguageChanger + { + event Action LanguageChangeCompleted; + + GameLanguage DetectLanguage(DirectoryInfo installDir); + + void ChangeLanguage(DirectoryInfo installDir, GameLanguage languageToInstall); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Languages/LanguageChanger.cs b/source/Settlers.Toolbox.Model/Languages/LanguageChanger.cs new file mode 100644 index 0000000..0c14298 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Languages/LanguageChanger.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Settlers.Toolbox.Infrastructure; +using Settlers.Toolbox.Infrastructure.ExtensionMethods; +using Settlers.Toolbox.Infrastructure.IO.Interfaces; +using Settlers.Toolbox.Infrastructure.Json.Interfaces; +using Settlers.Toolbox.Infrastructure.Logging; +using Settlers.Toolbox.Infrastructure.Network.Interfaces; +using Settlers.Toolbox.Infrastructure.Reporting.Interfaces; +using Settlers.Toolbox.Model.Languages.Interfaces; + +namespace Settlers.Toolbox.Model.Languages +{ + public class LanguageChanger : ILanguageChanger + { + private const string JSON_LANGUAGE_DEFINITIONS_URL = @""; // TODO: FireEmerald: Add URL. + + private const string MISCDATA1_RELATIVE_PATH = @"Config\MiscData1.cfg"; + private const string MISCDATA1_SECTION = "MISCDATA1"; + private const string MISCDATA1_KEY_LANGUAGE = "Language"; + + // TODO: FireEmerald: If the downloader would store this any downloaded file would be cached. + private readonly IList<(GameLanguage language, FileInfo languagePackFile)> _DownloadedLanguagePacks; + + private readonly IReportManager _ReportManager; + private readonly IStringDownloader _StringDownloader; + private readonly IFileDownloader _FileDownloader; + private readonly IJsonAdapter _JsonAdapter; + private readonly IIniFileAdapter _IniFileAdapter; + private readonly IZipFileAdapter _ZipFileAdapter; + + public event Action LanguageChangeCompleted; + + public LanguageChanger(IReportManager reportManager, IStringDownloader stringDownloader, IFileDownloader fileDownloader, IJsonAdapter jsonAdapter, IIniFileAdapter iniFileAdapter, IZipFileAdapter zipFileAdapter) + { + if (reportManager == null) throw new ArgumentNullException(nameof(reportManager)); + if (stringDownloader == null) throw new ArgumentNullException(nameof(stringDownloader)); + if (fileDownloader == null) throw new ArgumentNullException(nameof(fileDownloader)); + if (jsonAdapter == null) throw new ArgumentNullException(nameof(jsonAdapter)); + if (iniFileAdapter == null) throw new ArgumentNullException(nameof(iniFileAdapter)); + if (zipFileAdapter == null) throw new ArgumentNullException(nameof(zipFileAdapter)); + + _ReportManager = reportManager; + _StringDownloader = stringDownloader; + _FileDownloader = fileDownloader; + _JsonAdapter = jsonAdapter; + _IniFileAdapter = iniFileAdapter; + _ZipFileAdapter = zipFileAdapter; + + _DownloadedLanguagePacks = new List<(GameLanguage language, FileInfo languagePackFile)>(); + } + + ~LanguageChanger() + { + foreach ((GameLanguage language, FileInfo languagePackFile) downloadedLanguagePack in _DownloadedLanguagePacks) + { + downloadedLanguagePack.languagePackFile.Refresh(); + + if (downloadedLanguagePack.languagePackFile.Exists) + downloadedLanguagePack.languagePackFile.Delete(); + } + } + + public GameLanguage DetectLanguage(DirectoryInfo installDir) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + FileInfo miscData1File = GetMiscData1File(installDir); + + string miscData1LanguageValue = _IniFileAdapter.ReadValueFromFile(MISCDATA1_SECTION, MISCDATA1_KEY_LANGUAGE, miscData1File.FullName); + + if (Enum.TryParse(miscData1LanguageValue, out GameLanguage gameLanguage)) + { + return gameLanguage; + } + + return GameLanguage.Unknown; + } + + public void ChangeLanguage(DirectoryInfo installDir, GameLanguage languageToInstall) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + ChangeTextLanguage(installDir, languageToInstall); + + RaiseLanguageChangeCompleted(new Result()); // TODO: FireEmerald: REMOVE TESTING CODE + + //Result voiceLanguageChanged = ChangeVoiceLanguage(installDir, languageToInstall); + //if (!voiceLanguageChanged.Success) + // RaiseLanguageChangeCompleted(voiceLanguageChanged); + } + + private void ChangeTextLanguage(DirectoryInfo installDir, GameLanguage languageToInstall) + { + FileInfo miscData1File = GetMiscData1File(installDir); + + int miscData1LanguageValue = GetLanguageId(languageToInstall); + + _IniFileAdapter.WriteValueToFile(MISCDATA1_SECTION, MISCDATA1_KEY_LANGUAGE, miscData1LanguageValue.ToString(), miscData1File.FullName); + + LogAdapter.Log(LogLevel.Information, "Text language changed ... OK"); + } + + private FileInfo GetMiscData1File(DirectoryInfo installDir) + { + var miscData1File = new FileInfo(Path.Combine(installDir.FullName, MISCDATA1_RELATIVE_PATH)); + + if (!miscData1File.Exists) + { + throw new FileNotFoundException($"Unable to access '{MISCDATA1_RELATIVE_PATH}', file does not exist."); // TODO: FireEmerald: Replace + } + + return miscData1File; + } + + private Result ChangeVoiceLanguage(DirectoryInfo installDir, GameLanguage languageToInstall) + { + LogAdapter.Log(LogLevel.Information, "Changing audio and video files..."); + + string languagesJson = _StringDownloader.DownloadString(JSON_LANGUAGE_DEFINITIONS_URL); + if (string.IsNullOrEmpty(languagesJson)) + { + return new Result("Download of sound pack links failed, skipped."); + } + + var languageLinks = _JsonAdapter.DeserializeObject(languagesJson); + + LanguagePackLink languagePackLink = GetLanguagePackLinkOrNull(languageToInstall, languageLinks); + if (languagePackLink == null) + { + return new Result("None sound pack for selected language exists, skipped."); + } + + FileInfo alreadyDownloadedLanguagePackFileOrNull = GetAlreadyDownloadedLanguagePackFileOrNull(languageToInstall, languagePackLink.Sha1); + if (alreadyDownloadedLanguagePackFileOrNull != null) + { + InstallLanguagePack(installDir, alreadyDownloadedLanguagePackFileOrNull); + } + else + { + DownloadLanguagePack(installDir, languagePackLink); + } + + return new Result(); + } + + + private FileInfo GetAlreadyDownloadedLanguagePackFileOrNull(GameLanguage language, string sha1) + { + (GameLanguage, FileInfo languagePackFile) downloadedLanguagePack = _DownloadedLanguagePacks.SingleOrDefault(pack => pack.language == language); + if (!downloadedLanguagePack.Equals(default(ValueTuple))) + { + downloadedLanguagePack.languagePackFile.Refresh(); + + if (downloadedLanguagePack.languagePackFile.Exists && downloadedLanguagePack.languagePackFile.HashEquals(sha1)) + { + LogAdapter.Log(LogLevel.Information, "Download of sound pack skipped, already downloaded."); + + return downloadedLanguagePack.languagePackFile; + } + + if (downloadedLanguagePack.languagePackFile.Exists) + downloadedLanguagePack.languagePackFile.Delete(); + + _DownloadedLanguagePacks.Remove(downloadedLanguagePack); + } + + return null; + } + + private void DownloadLanguagePack(DirectoryInfo installDir, LanguagePackLink languagePackLink) + { + //await Task.Delay(5000); // TODO: FireEmerald: REMOVE TEST CODE + //LogAdapter.Log(LogLevel.Information, "DONE"); + //RaiseLanguageChangeCompleted(new Result()); + //return; + + var tempFile = new FileInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".tmp")); + + // Events + _FileDownloader.DownloadProgressUpdate += (statusMessage) => _ReportManager.ReportStatus(statusMessage); + _FileDownloader.DownloadFailed += (errorMessage) => HandleDownloadFailed(tempFile, errorMessage); + _FileDownloader.DownloadSuccessful += () => HandleDownloadSuccessfule(installDir, tempFile, languagePackLink.Sha1, languagePackLink.Id); + + _FileDownloader.DownloadFile(languagePackLink.Url, tempFile); + } + + private void HandleDownloadFailed(FileInfo languagePackFile, string errorMessage) + { + LogAdapter.Log(LogLevel.Error, errorMessage); + RaiseLanguageChangeCompleted(new Result("Download of language pack ... FAILED")); + + languagePackFile.Refresh(); + + if (languagePackFile.Exists) + languagePackFile.Delete(); + } + + private void HandleDownloadSuccessfule(DirectoryInfo installDir, FileInfo languagePackFile, string sha1, int languageId) + { + LogAdapter.Log(LogLevel.Information, "Download of language pack ... OK"); + + languagePackFile.Refresh(); + + if (languagePackFile.Exists && languagePackFile.HashEquals(sha1)) + { + LogAdapter.Log(LogLevel.Information, "Validating language pack ... OK"); + + _DownloadedLanguagePacks.Add(((GameLanguage) languageId, languagePackFile)); + + InstallLanguagePack(installDir, languagePackFile); + } + else + { + RaiseLanguageChangeCompleted(new Result("Validating language pack ... FAILED")); + + if (languagePackFile.Exists) + languagePackFile.Delete(); + } + } + + private void InstallLanguagePack(DirectoryInfo installDir, FileInfo languagePackFile) + { + var tempDir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + tempDir.Create(); + + _ZipFileAdapter.ExtractToDirectory(languagePackFile, tempDir); + + CopyAndReplace(tempDir, installDir); + tempDir.Delete(true); + + LogAdapter.Log(LogLevel.Information, "Applying language pack ... OK"); + RaiseLanguageChangeCompleted(new Result()); + } + + private void CopyAndReplace(DirectoryInfo sourceDir, DirectoryInfo targetDir) + { + foreach (FileInfo file in sourceDir.EnumerateFiles()) + { + string targetFilePath = Path.Combine(targetDir.FullName, file.Name); + file.CopyTo(targetFilePath, true); + } + + foreach (DirectoryInfo directory in sourceDir.EnumerateDirectories()) + { + DirectoryInfo createdDir = targetDir.CreateSubdirectory(directory.Name); + CopyAndReplace(directory, createdDir); + } + } + + private int GetLanguageId(GameLanguage gameLanguage) + { + switch (gameLanguage) + { + case GameLanguage.Unknown: + throw new InvalidOperationException($"Enumeration type {gameLanguage} does not hold a valid language identifier."); + default: + return (int) gameLanguage; + } + } + + private LanguagePackLink GetLanguagePackLinkOrNull(GameLanguage gameLanguage, LanguagePackLinks languagePackLinks) + { + int languageId = GetLanguageId(gameLanguage); + + return languagePackLinks.Languages.SingleOrDefault(x => x.Id == languageId); + } + + private void RaiseLanguageChangeCompleted(Result result) + { + LanguageChangeCompleted?.Invoke(result); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Languages/LanguagePackLink.cs b/source/Settlers.Toolbox.Model/Languages/LanguagePackLink.cs new file mode 100644 index 0000000..bd9f69e --- /dev/null +++ b/source/Settlers.Toolbox.Model/Languages/LanguagePackLink.cs @@ -0,0 +1,16 @@ +namespace Settlers.Toolbox.Model.Languages +{ + public class LanguagePackLink + { + public int Id { get; } + public string Url { get; } + public string Sha1 { get; } + + public LanguagePackLink(int id, string url, string sha1) + { + Id = id; + Url = url; + Sha1 = sha1; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Languages/LanguagePackLinks.cs b/source/Settlers.Toolbox.Model/Languages/LanguagePackLinks.cs new file mode 100644 index 0000000..b64144c --- /dev/null +++ b/source/Settlers.Toolbox.Model/Languages/LanguagePackLinks.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Settlers.Toolbox.Model.Languages +{ + public class LanguagePackLinks + { + public IReadOnlyList Languages { get; } + + public LanguagePackLinks(IReadOnlyList languages) + { + Languages = languages; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Properties/AssemblyInfo.cs b/source/Settlers.Toolbox.Model/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d3fd510 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Settlers.Toolbox.Model")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Settlers.Toolbox.Model")] +[assembly: AssemblyCopyright("Copyright © FireEmerald 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f3213d60-0b96-4a0d-827e-603c8d76e16a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/source/Settlers.Toolbox.Model/Properties/Resources.Designer.cs b/source/Settlers.Toolbox.Model/Properties/Resources.Designer.cs new file mode 100644 index 0000000..8f1b0d8 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Settlers.Toolbox.Model.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Settlers.Toolbox.Model.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] D3DImm { + get { + object obj = ResourceManager.GetObject("D3DImm", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] DDraw { + get { + object obj = ResourceManager.GetObject("DDraw", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/source/Settlers.Toolbox.Model/Properties/Resources.resx b/source/Settlers.Toolbox.Model/Properties/Resources.resx new file mode 100644 index 0000000..ad67231 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\D3DImm.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\DDraw.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Resolutions/GameResolution.cs b/source/Settlers.Toolbox.Model/Resolutions/GameResolution.cs new file mode 100644 index 0000000..5b5d652 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Resolutions/GameResolution.cs @@ -0,0 +1,25 @@ +namespace Settlers.Toolbox.Model.Resolutions +{ + /// + /// The supported resolutions. + /// + /// + /// See https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam for global usage. + /// + public enum GameResolution + { + Unknown = -1, + Default = 0, + R1024_600 = 1, + R1280_720 = 2, + R1280_800 = 3, + R1366_768 = 4, + R1440_900 = 5, + R1680_1050 = 6, + R1920_1080 = 7, + R1920_1200 = 8, + R2560_1440 = 9, + R3840_2160 = 10, + Custom = 11 + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionChanger.cs b/source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionChanger.cs new file mode 100644 index 0000000..6407def --- /dev/null +++ b/source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionChanger.cs @@ -0,0 +1,12 @@ +using System.IO; + +namespace Settlers.Toolbox.Model.Resolutions.Interfaces +{ + public interface IResolutionChanger + { + Resolution DetectResolution(DirectoryInfo installDir); + + void ChangeResolution(DirectoryInfo installDir, GameResolution gameResolution); + void ChangeResolution(DirectoryInfo installDir, ushort width, ushort height); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionFactory.cs b/source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionFactory.cs new file mode 100644 index 0000000..1d18604 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Resolutions/Interfaces/IResolutionFactory.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Settlers.Toolbox.Model.Resolutions.Interfaces +{ + public interface IResolutionFactory + { + Resolution Create(GameResolution gameResolution); + Resolution CreateCustom(ushort width, ushort height); + + IReadOnlyList CreateAllPresets(); + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Resolutions/Resolution.cs b/source/Settlers.Toolbox.Model/Resolutions/Resolution.cs new file mode 100644 index 0000000..c2efb55 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Resolutions/Resolution.cs @@ -0,0 +1,18 @@ +namespace Settlers.Toolbox.Model.Resolutions +{ + public class Resolution + { + public GameResolution Id { get; } + + public ushort Width { get; } + public ushort Height { get; } + + public Resolution(GameResolution id, ushort width, ushort height) + { + Id = id; + + Width = width; + Height = height; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Resolutions/ResolutionChanger.cs b/source/Settlers.Toolbox.Model/Resolutions/ResolutionChanger.cs new file mode 100644 index 0000000..27d228a --- /dev/null +++ b/source/Settlers.Toolbox.Model/Resolutions/ResolutionChanger.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Settlers.Toolbox.Infrastructure.IO.Interfaces; +using Settlers.Toolbox.Infrastructure.Logging; +using Settlers.Toolbox.Model.Resolutions.Interfaces; + +namespace Settlers.Toolbox.Model.Resolutions +{ + public class ResolutionChanger : IResolutionChanger + { + private const string GFX_ENGINE_RELATIVE_PATH = @"Exe\GfxEngine.dll"; + + private const int GFX_ENGINE_POS_WIDTH = 0x0001068E; + private const int GFX_ENGINE_POS_HEIGHT = 0x00010693; + + private const string GAMESETTINGS_RELATIVE_PATH = @"Config\GameSettings.cfg"; + private const string GAMESETTINGS_SECTION = "GAMESETTINGS"; + private const string GAMESETTINGS_KEY_BORDERSCROLL = "BorderScroll"; + private const string GAMESETTINGS_KEY_FULLSCREEN = "Fullscreen"; + private const string GAMESETTINGS_KEY_SCREENMODE = "Screenmode"; + private const string GAMESETTINGS_KEY_WINDOWHEIGHT = "WindowHeight"; + private const string GAMESETTINGS_KEY_WINDOWWIDTH = "WindowWidth"; + + private readonly IIniFileAdapter _IniFileAdapter; + private readonly IResolutionFactory _ResolutionFactory; + + public ResolutionChanger(IIniFileAdapter iniFileAdapter, IResolutionFactory resolutionFactory) + { + if (iniFileAdapter == null) throw new ArgumentNullException(nameof(iniFileAdapter)); + if (resolutionFactory == null) throw new ArgumentNullException(nameof(resolutionFactory)); + + _IniFileAdapter = iniFileAdapter; + _ResolutionFactory = resolutionFactory; + } + + public Resolution DetectResolution(DirectoryInfo installDir) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + FileInfo gfxEngineFile = GetGfxEngineFile(installDir); + + ushort detectedWidth = ReadData(gfxEngineFile, GFX_ENGINE_POS_WIDTH); + ushort detectedHeight = ReadData(gfxEngineFile, GFX_ENGINE_POS_HEIGHT); + + IReadOnlyList allPresetResolutions = _ResolutionFactory.CreateAllPresets(); + + Resolution detectedResolution = allPresetResolutions.SingleOrDefault(res => res.Width == detectedWidth && res.Height == detectedHeight); + + return detectedResolution ?? _ResolutionFactory.CreateCustom(detectedWidth, detectedHeight); + } + + private ushort ReadData(FileInfo file, int position) + { + byte[] buffer = new byte[2]; + + using (Stream stream = file.OpenRead()) + { + stream.Position = position; + stream.Read(buffer, 0, buffer.Length); + } + + buffer = SwitchBytes(buffer); + return BitConverter.ToUInt16(buffer, 0); + } + + public void ChangeResolution(DirectoryInfo installDir, GameResolution gameResolution) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + Resolution resolution = _ResolutionFactory.Create(gameResolution); + + ChangeResolution(installDir, resolution); + } + + public void ChangeResolution(DirectoryInfo installDir, ushort width, ushort height) + { + if (installDir == null) throw new ArgumentNullException(nameof(installDir)); + + Resolution resolution = _ResolutionFactory.CreateCustom(width, height); + + ChangeResolution(installDir, resolution); + } + + private void ChangeResolution(DirectoryInfo installDir, Resolution resolution) + { + PatchGfxEngineFile(installDir, resolution.Width, resolution.Height); + UpdateGameSettingsResolution(installDir, resolution.Width, resolution.Height); + + LogAdapter.Log(LogLevel.Information, "Resolution changed ... OK"); + } + + private FileInfo GetGfxEngineFile(DirectoryInfo installDir) + { + var gfxEngineFile = new FileInfo(Path.Combine(installDir.FullName, GFX_ENGINE_RELATIVE_PATH)); + + if (!gfxEngineFile.Exists) + { + throw new FileNotFoundException($"Unable to access '{GFX_ENGINE_RELATIVE_PATH}', file does not exist."); + } + + return gfxEngineFile; + } + + private void PatchGfxEngineFile(DirectoryInfo installDir, ushort width, ushort height) + { + FileInfo gfxEngineFile = GetGfxEngineFile(installDir); + + byte[] widthBytes = BitConverter.GetBytes(width); + byte[] heightBytes = BitConverter.GetBytes(height); + + widthBytes = SwitchBytes(widthBytes); + heightBytes = SwitchBytes(heightBytes); + + ReplaceData(gfxEngineFile, GFX_ENGINE_POS_WIDTH, widthBytes); + ReplaceData(gfxEngineFile, GFX_ENGINE_POS_HEIGHT, heightBytes); + } + + private byte[] SwitchBytes(byte[] bytes) + { + // The width/height of the resolution is stored in a block of 2 byte. + // Before/after the width/height is written/read the high-byte must be switched with the low-byte. + // 0x1234 -> 0x3412 + return new[] { bytes[1], bytes[0] }; + } + + private void ReplaceData(FileInfo file, int position, byte[] data) + { + using (Stream stream = file.OpenWrite()) + { + stream.Position = position; + stream.Write(data, 0, data.Length); + } + } + + private void UpdateGameSettingsResolution(DirectoryInfo installDir, int width, int height) + { + var gameSettingsFile = new FileInfo(Path.Combine(installDir.FullName, GAMESETTINGS_RELATIVE_PATH)); + + if (!gameSettingsFile.Exists) + { + throw new FileNotFoundException($"Unable to access '{GAMESETTINGS_RELATIVE_PATH}', file does not exist."); + } + + // TODO: FireEmerald: Validate values and move perhaps to const/enum e.g. + _IniFileAdapter.WriteValueToFile(GAMESETTINGS_SECTION, GAMESETTINGS_KEY_BORDERSCROLL, "1", gameSettingsFile.FullName); + _IniFileAdapter.WriteValueToFile(GAMESETTINGS_SECTION, GAMESETTINGS_KEY_FULLSCREEN, "1", gameSettingsFile.FullName); + _IniFileAdapter.WriteValueToFile(GAMESETTINGS_SECTION, GAMESETTINGS_KEY_SCREENMODE, "2", gameSettingsFile.FullName); + _IniFileAdapter.WriteValueToFile(GAMESETTINGS_SECTION, GAMESETTINGS_KEY_WINDOWHEIGHT, height.ToString(), gameSettingsFile.FullName); + _IniFileAdapter.WriteValueToFile(GAMESETTINGS_SECTION, GAMESETTINGS_KEY_WINDOWWIDTH, width.ToString(), gameSettingsFile.FullName); + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Resolutions/ResolutionFactory.cs b/source/Settlers.Toolbox.Model/Resolutions/ResolutionFactory.cs new file mode 100644 index 0000000..0d5e5c1 --- /dev/null +++ b/source/Settlers.Toolbox.Model/Resolutions/ResolutionFactory.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; + +using Settlers.Toolbox.Model.Resolutions.Interfaces; + +namespace Settlers.Toolbox.Model.Resolutions +{ + public class ResolutionFactory : IResolutionFactory + { + public Resolution Create(GameResolution gameResolution) + { + switch (gameResolution) + { + case GameResolution.Default: + return new Resolution(gameResolution, 1024, 768); + case GameResolution.R1024_600: + return new Resolution(gameResolution, 1024, 600); + case GameResolution.R1280_720: + return new Resolution(gameResolution, 1280, 720); + case GameResolution.R1280_800: + return new Resolution(gameResolution, 1280, 800); + case GameResolution.R1366_768: + return new Resolution(gameResolution, 1366, 768); + case GameResolution.R1440_900: + return new Resolution(gameResolution, 1440, 900); + case GameResolution.R1680_1050: + return new Resolution(gameResolution, 1680, 1050); + case GameResolution.R1920_1080: + return new Resolution(gameResolution, 1920, 1080); + case GameResolution.R1920_1200: + return new Resolution(gameResolution, 1920, 1200); + case GameResolution.R2560_1440: + return new Resolution(gameResolution, 2560, 1440); + case GameResolution.R3840_2160: + return new Resolution(gameResolution, 3840, 2160); + + default: + throw new InvalidOperationException($"Unable to create {nameof(Resolution)}, {gameResolution} is not implemented."); + } + } + + public Resolution CreateCustom(ushort width, ushort height) + { + return new Resolution(GameResolution.Custom, width, height); + } + + public IReadOnlyList CreateAllPresets() + { + var allPresetResolutions = new List(); + + foreach (var gameResolution in (GameResolution[])Enum.GetValues(typeof(GameResolution))) + { + if (gameResolution == GameResolution.Unknown || gameResolution == GameResolution.Custom) + continue; + + var presetResolution = Create(gameResolution); + allPresetResolutions.Add(presetResolution); + } + + return allPresetResolutions; + } + } +} \ No newline at end of file diff --git a/source/Settlers.Toolbox.Model/Resources/D3DImm.dll b/source/Settlers.Toolbox.Model/Resources/D3DImm.dll new file mode 100644 index 0000000000000000000000000000000000000000..bf2d623753d62212c8d93f22f58c4e43e44c2203 GIT binary patch literal 91648 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%0A51X@vP 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 =69b1pb0qE=5kd|j|)Vf`&G+5 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®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