From 6f9716779b05b4adb002c1b82746704e19d4eb35 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 4 Jul 2025 01:41:09 +0300 Subject: [PATCH 01/81] Add migration tool project --- DXClient.sln | 66 ++++++++++++++++++++++++++++++ MigrationTool/MigrationTool.csproj | 14 +++++++ MigrationTool/Program.cs | 9 ++++ 3 files changed, 89 insertions(+) create mode 100644 MigrationTool/MigrationTool.csproj create mode 100644 MigrationTool/Program.cs diff --git a/DXClient.sln b/DXClient.sln index d5070253c..caea03960 100644 --- a/DXClient.sln +++ b/DXClient.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecondStageUpdater", "Secon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientUpdater", "ClientUpdater\ClientUpdater.csproj", "{551D080B-5624-4793-AC31-69D77C62F6B1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrationTool", "MigrationTool\MigrationTool.csproj", "{6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution UniversalGLDebug|Any CPU = UniversalGLDebug|Any CPU @@ -513,6 +515,70 @@ Global {551D080B-5624-4793-AC31-69D77C62F6B1}.WindowsXNARelease|x64.Build.0 = WindowsXNARelease|x64 {551D080B-5624-4793-AC31-69D77C62F6B1}.WindowsXNARelease|x86.ActiveCfg = WindowsXNARelease|x86 {551D080B-5624-4793-AC31-69D77C62F6B1}.WindowsXNARelease|x86.Build.0 = WindowsXNARelease|x86 + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|Any CPU.ActiveCfg = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|Any CPU.Build.0 = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|ARM64.ActiveCfg = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|ARM64.Build.0 = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x64.ActiveCfg = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x64.Build.0 = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x86.ActiveCfg = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x86.Build.0 = UniversalGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|Any CPU.ActiveCfg = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|Any CPU.Build.0 = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|ARM64.ActiveCfg = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|ARM64.Build.0 = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x64.ActiveCfg = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x64.Build.0 = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x86.ActiveCfg = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x86.Build.0 = UniversalGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|Any CPU.ActiveCfg = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|Any CPU.Build.0 = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|ARM64.ActiveCfg = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|ARM64.Build.0 = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x64.ActiveCfg = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x64.Build.0 = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x86.ActiveCfg = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x86.Build.0 = WindowsDXDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|Any CPU.ActiveCfg = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|Any CPU.Build.0 = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|ARM64.ActiveCfg = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|ARM64.Build.0 = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x64.ActiveCfg = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x64.Build.0 = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x86.ActiveCfg = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x86.Build.0 = WindowsDXRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|Any CPU.ActiveCfg = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|Any CPU.Build.0 = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|ARM64.ActiveCfg = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|ARM64.Build.0 = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x64.ActiveCfg = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x64.Build.0 = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x86.ActiveCfg = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x86.Build.0 = WindowsGLDebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|Any CPU.ActiveCfg = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|Any CPU.Build.0 = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|ARM64.ActiveCfg = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|ARM64.Build.0 = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x64.ActiveCfg = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x64.Build.0 = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x86.ActiveCfg = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x86.Build.0 = WindowsGLRelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|Any CPU.ActiveCfg = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|Any CPU.Build.0 = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|ARM64.ActiveCfg = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|ARM64.Build.0 = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x64.ActiveCfg = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x64.Build.0 = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x86.ActiveCfg = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x86.Build.0 = WindowsXNADebug|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|Any CPU.ActiveCfg = WindowsXNARelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|Any CPU.Build.0 = WindowsXNARelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|ARM64.ActiveCfg = WindowsXNARelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|ARM64.Build.0 = WindowsXNARelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x64.ActiveCfg = WindowsXNARelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x64.Build.0 = WindowsXNARelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x86.ActiveCfg = WindowsXNARelease|Any CPU + {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x86.Build.0 = WindowsXNARelease|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj new file mode 100644 index 000000000..6302adef8 --- /dev/null +++ b/MigrationTool/MigrationTool.csproj @@ -0,0 +1,14 @@ + + + Exe + false + + + CnCNet.MigratioTool + CnCNet Client Migration Tool + CnCNet.MigratioTool + + + + + diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs new file mode 100644 index 000000000..cf0a45893 --- /dev/null +++ b/MigrationTool/Program.cs @@ -0,0 +1,9 @@ +namespace MigrationTool; + +public class Program +{ + public static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } +} From d6c3c618e5aa9af720ffbcf2ec33e7f0c38e55c8 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 4 Jul 2025 18:48:07 +0300 Subject: [PATCH 02/81] Test implementation --- MigrationTool/Program.cs | 147 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 143 insertions(+), 4 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index cf0a45893..62ad0a4b0 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -1,9 +1,148 @@ -namespace MigrationTool; +using System.IO; +using Rampastring.Tools; -public class Program +namespace MigrationTool; + +internal sealed class Program { - public static void Main(string[] args) + private enum Version + { + Begin, + V_2_8, + V_2_9, + V_2_, + V_2_12_1, + End + } + + private enum ClientGameType + { + TS, + YR, + Ares + } + + private static Version currentConfigVersion = Version.V_2_8; + private static ConsoleColor defaultColor = Console.ForegroundColor; + + private static void Main(string[] args) + { + // Initialize logger + DirectoryInfo baseDirectory = SafePath.GetDirectory(Directory.GetCurrentDirectory()); + FileInfo logFile = SafePath.GetFile(SafePath.CombineFilePath(baseDirectory.FullName, "MigrationTool.log")); + Logger.Initialize(logFile.DirectoryName, logFile.Name); + Logger.WriteLogFile = true; + Logger.WriteToConsole = false; + Logger.Log("CnCNet Client Migration Tool"); + Logger.Log("Version: " + GitVersionInformation.AssemblySemVer); + + // Check arguments + switch (args.Length) + { + case 1: + string arg = args[0].Trim(); + + if (arg == "-h" + || arg == "--help" + || arg == "-?" + || arg == "/?" + || arg == "/h") + { + PrintHelp(); + return; + } + + if (!SafePath.GetDirectory(arg).Exists) + { + PrintArgsError(); + return; + } + + if (!SafePath.GetFile(SafePath.CombineFilePath(arg, "Resources", "ClientDefinitions.ini")).Exists) + { + Log("Unable to find Resources/ClientDefinitions.ini. Migration aborted.", ConsoleColor.Red); + return; + } + + try + { + Migrate(arg); + } + catch (Exception ex) + { + Log("Unable to migrate client configs due to internal error. Message: " + ex.Message, ConsoleColor.Red); + } + break; + case 0: + default: + PrintArgsError(); + break; + } + } + + private static void Log(string text, ConsoleColor? color = null, bool echoToConsole = true) { - Console.WriteLine("Hello, World!"); + Console.ForegroundColor = color ?? defaultColor; + Logger.Log(text); + + if (echoToConsole) + Console.WriteLine(text); + } + + private static void PrintArgsError() + => Log("Unknown arguments detected. Use -h argument to print help information", ConsoleColor.Red); + + private static void PrintHelp() + { + string text = + """ + CnCNet Client Migration Tool. + + Execute this file with path to the unmigrated client directory as first argument. + """; + + Console.WriteLine(text); + } + + private static void Migrate(string path) + { + DirectoryInfo clientDir = SafePath.GetDirectory(path); + DirectoryInfo resouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(path, "Resources")); + + for (int i = (int)Version.Begin; i != (int)Version.End; i++) + { + switch ((Version)i) + { + case (Version.V_2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 + // Predict client type by guessing game engine files + var clientGameType = ClientGameType.TS; + + if (!SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists + && SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "gamemd-spawn.exe")).Exists) + { + clientGameType = ClientGameType.YR; + } + else if (SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists) + { + clientGameType = ClientGameType.Ares; + } + + var clientDefsIni = new IniFile(SafePath.CombineFilePath(path, "ClientDefinitions.ini")); + var value = clientDefsIni.GetStringValue("Settings", "ClientGameType", string.Empty); + + value = !string.IsNullOrEmpty(value.Trim()) ? value : clientGameType switch + { + ClientGameType.Ares => "Ares", + ClientGameType.YR => "YR", + _ => "TS" + }; + + clientDefsIni.GetSection("Settings").AddKey("ClientGameType", value); + continue; + default: + continue; + } + } } + } From d9a7e585567d4c26f29c760b0c2eb4f2f8eef70d Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 4 Jul 2025 22:06:37 +0300 Subject: [PATCH 03/81] Add more migration patches --- MigrationTool/Program.cs | 97 +++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 62ad0a4b0..7ccfba352 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using Rampastring.Tools; namespace MigrationTool; @@ -8,10 +9,13 @@ internal sealed class Program private enum Version { Begin, - V_2_8, - V_2_9, - V_2_, + V_2_8_x_x, + V_2_11_0, + V_2_11_1, + V_2_11_2, + V_2_12_0, V_2_12_1, + V_2_12_5, End } @@ -22,7 +26,7 @@ private enum ClientGameType Ares } - private static Version currentConfigVersion = Version.V_2_8; + private static Version currentConfigVersion = Version.V_2_8_x_x; private static ConsoleColor defaultColor = Console.ForegroundColor; private static void Main(string[] args) @@ -109,12 +113,71 @@ private static void Migrate(string path) DirectoryInfo clientDir = SafePath.GetDirectory(path); DirectoryInfo resouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(path, "Resources")); + IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "ClientDefinitions.ini")); + for (int i = (int)Version.Begin; i != (int)Version.End; i++) { switch ((Version)i) { + case (Version.V_2_11_0): + continue; + + case (Version.V_2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 + // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions + if (clientDefsIni.KeyExists("Settings", "RecommendedResolutions")) + { + Log($"Update ClientDefinitions.ini: Skip add [Settings]->RecommendedResolutions, reason: already exist"); + continue; + } + + var rr = "1280x720"; + Log($"Update ClientDefinitions.ini: Add [Settings]->RecommendedResolutions={rr}"); + clientDefsIni.GetSection("Settings").AddKey("RecommendedResolutions", rr); + clientDefsIni.WriteIniFile(); + continue; + + case (Version.V_2_11_2): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.2.0 + // Remove ClientUpdater.xml and SecondStageUpdater.xml + var listExtraXMLs = new List(2) { "ClientUpdater.xml", "SecondStageUpdater.xml" }; + Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); + + foreach (var item in listExtraXMLs) + { + Directory.GetFiles(resouresDir.FullName, item, SearchOption.AllDirectories) + .ToList() + .ForEach(elem => SafePath.DeleteFileIfExists(elem)); + } + + // Add ClientDefinitions.ini->[Settings]->ShowDevelopmentBuildWarnings + if (clientDefsIni.KeyExists("Settings", "ShowDevelopmentBuildWarnings")) + { + Log($"Update ClientDefinitions.ini: Skip add [Settings]->ShowDevelopmentBuildWarnings, reason: already exist"); + continue; + } + + var sdbw = true; + Log($"Update ClientDefinitions.ini: Add [Settings]->ShowDevelopmentBuildWarnings={sdbw.ToString()}"); + clientDefsIni.GetSection("Settings").AddKey("ShowDevelopmentBuildWarnings", sdbw.ToString()); + clientDefsIni.WriteIniFile(); + continue; + + case (Version.V_2_12_0): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.0 + // Remove Rampastring.Tools from Resources directory (not recursive) + Log("Remove Resources/Rampastring.Tools.* (* -- dll, pdb, xml)"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.pdb"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.xml"); + break; + case (Version.V_2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 // Predict client type by guessing game engine files + // And add ClientDefinitions.ini->[Settings]->ClientGameType + if (clientDefsIni.KeyExists("Settings", "ClientGameType")) + { + Log($"Update ClientDefinitions.ini: Skip add [Settings]->ClientGameType, reason: already exist"); + continue; + } + var clientGameType = ClientGameType.TS; if (!SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists @@ -127,22 +190,36 @@ private static void Migrate(string path) clientGameType = ClientGameType.Ares; } - var clientDefsIni = new IniFile(SafePath.CombineFilePath(path, "ClientDefinitions.ini")); - var value = clientDefsIni.GetStringValue("Settings", "ClientGameType", string.Empty); - - value = !string.IsNullOrEmpty(value.Trim()) ? value : clientGameType switch + clientDefsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "ClientDefinitions.ini")); + string cgt = clientGameType switch { ClientGameType.Ares => "Ares", ClientGameType.YR => "YR", _ => "TS" }; - clientDefsIni.GetSection("Settings").AddKey("ClientGameType", value); + Log($"Update ClientDefinitions.ini: Add [Settings]->ClientGameType={cgt}"); + clientDefsIni.GetSection("Settings").AddKey("ClientGameType", cgt); + clientDefsIni.WriteIniFile(); continue; + + case (Version.V_2_12_5): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.5 + // Add ClientDefinitions.ini->[Settings]->TrustedDomains + if (clientDefsIni.KeyExists("Settings", "TrustedDomains")) + { + Log($"Update ClientDefinitions.ini: Skip add [Settings]->TrustedDomains, reason: already exist"); + continue; + } + + var td = "moddb.com"; + Log($"Update ClientDefinitions.ini: Add [Settings]->TrustedDomains={td}"); + clientDefsIni.GetSection("Settings").AddKey("TrustedDomains", td); + clientDefsIni.WriteIniFile(); + continue; + default: continue; } } } - } From 40e6e28ae7662b9411328ba7695ad5db8dbc024f Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 4 Jul 2025 22:21:52 +0300 Subject: [PATCH 04/81] Attempt to make VS2022 compile migration tool --- Directory.Build.props | 5 +++++ Directory.Build.targets | 29 +++++++++++++++++++++++++++++ Directory.Packages.props | 4 ++-- MigrationTool/MigrationTool.csproj | 24 ++++++++++++------------ MigrationTool/Program.cs | 7 ++++--- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f8b019ee8..2db800d50 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -50,6 +50,11 @@ net8.0;net48 AnyCPU + + + net8.0;net48 + AnyCPU + diff --git a/Directory.Build.targets b/Directory.Build.targets index f43472c3e..93dc3991d 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -23,6 +23,19 @@ Projects="$(MSBuildThisFileDirectory)SecondStageUpdater\SecondStageUpdater.csproj" Properties="TargetFramework=$(TargetFramework.Split('-')[0]);Platform=AnyCPU;RuntimeIdentifier=" /> + + + + + + + + @@ -138,6 +151,22 @@ + + + $(PublishDir)\..\MigrationTool\ + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 13c9d2529..3bfee61b3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -37,8 +37,8 @@ - - + + diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index 6302adef8..8bf4b2a72 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -1,14 +1,14 @@  - - Exe - false - - - CnCNet.MigratioTool - CnCNet Client Migration Tool - CnCNet.MigratioTool - - - - + + Exe + false + + + CnCNet.MigratioTool + CnCNet Client Migration Tool + CnCNet.MigratioTool + + + + diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 7ccfba352..92016a5f5 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -1,5 +1,8 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; using System.Linq; + using Rampastring.Tools; namespace MigrationTool; @@ -9,7 +12,6 @@ internal sealed class Program private enum Version { Begin, - V_2_8_x_x, V_2_11_0, V_2_11_1, V_2_11_2, @@ -26,7 +28,6 @@ private enum ClientGameType Ares } - private static Version currentConfigVersion = Version.V_2_8_x_x; private static ConsoleColor defaultColor = Console.ForegroundColor; private static void Main(string[] args) From d3e046c78a6767ba1cf9cf490a59461b4ef25a2e Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 14:30:17 +0300 Subject: [PATCH 05/81] Add latest client migration notes --- MigrationTool/Program.cs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 92016a5f5..ac13db01b 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -18,6 +18,7 @@ private enum Version V_2_12_0, V_2_12_1, V_2_12_5, + Latest, End } @@ -115,6 +116,7 @@ private static void Migrate(string path) DirectoryInfo resouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(path, "Resources")); IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "ClientDefinitions.ini")); + IniFile gmLobbyBaseIni = null; for (int i = (int)Version.Begin; i != (int)Version.End; i++) { @@ -142,9 +144,9 @@ private static void Migrate(string path) var listExtraXMLs = new List(2) { "ClientUpdater.xml", "SecondStageUpdater.xml" }; Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); - foreach (var item in listExtraXMLs) + foreach (var extraXml in listExtraXMLs) { - Directory.GetFiles(resouresDir.FullName, item, SearchOption.AllDirectories) + Directory.GetFiles(resouresDir.FullName, extraXml, SearchOption.AllDirectories) .ToList() .ForEach(elem => SafePath.DeleteFileIfExists(elem)); } @@ -208,7 +210,7 @@ private static void Migrate(string path) // Add ClientDefinitions.ini->[Settings]->TrustedDomains if (clientDefsIni.KeyExists("Settings", "TrustedDomains")) { - Log($"Update ClientDefinitions.ini: Skip add [Settings]->TrustedDomains, reason: already exist"); + Log("Update ClientDefinitions.ini: Skip add [Settings]->TrustedDomains, reason: already exist"); continue; } @@ -218,6 +220,28 @@ private static void Migrate(string path) clientDefsIni.WriteIniFile(); continue; + case (Version.Latest): + // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode + gmLobbyBaseIni ??= new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GameLobbyBase.ini")); + string ddPlayerColor = nameof(ddPlayerColor); + foreach (var n in new int[] { 0, 1, 2, 3, 4, 5, 6, 7 }) + { + if (gmLobbyBaseIni.KeyExists(ddPlayerColor + n, "ItemsDrawMode")) + { + Log($"Update GameLobbyBase.ini: Skip add [{ddPlayerColor + n}]->ItemsDrawMode, reason: already exist"); + continue; + } + + Log($"Update GameLobbyBase.ini: Add [{ddPlayerColor + n}]->ItemsDrawMode=Text"); + + if (!gmLobbyBaseIni.SectionExists(ddPlayerColor + n)) + gmLobbyBaseIni.AddSection(ddPlayerColor + n); + + gmLobbyBaseIni.GetSection(ddPlayerColor + n).AddKey("ItemsDrawMode", "Text"); + gmLobbyBaseIni.WriteIniFile(); + } + continue; + default: continue; } From 028f8de7cf441404daadaccf2d71c7cfaf45eaf0 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 15:56:32 +0300 Subject: [PATCH 06/81] Add `MaximumRenderWidth` and `MaximumRenderHeight` keys into migration --- MigrationTool/Program.cs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index ac13db01b..01909772d 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -123,6 +123,8 @@ private static void Migrate(string path) switch ((Version)i) { case (Version.V_2_11_0): + + continue; case (Version.V_2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 @@ -130,13 +132,38 @@ private static void Migrate(string path) if (clientDefsIni.KeyExists("Settings", "RecommendedResolutions")) { Log($"Update ClientDefinitions.ini: Skip add [Settings]->RecommendedResolutions, reason: already exist"); - continue; + } + else + { + var rr = "1280x720"; + Log($"Update ClientDefinitions.ini: Add [Settings]->RecommendedResolutions={rr}"); + clientDefsIni.GetSection("Settings").AddKey("RecommendedResolutions", rr); + } + + if (clientDefsIni.KeyExists("Settings", "MaximumRenderWidth")) + { + Log($"Update ClientDefinitions.ini: Skip add [Settings]->MaximumRenderWidth, reason: already exist"); + } + else + { + var mrw = 1280; + Log($"Update ClientDefinitions.ini: Add [Settings]->MaximumRenderWidth={mrw}"); + clientDefsIni.GetSection("Settings").AddKey("MaximumRenderWidth", mrw.ToString()); + } + + if (clientDefsIni.KeyExists("Settings", "MaximumRenderHeight")) + { + Log($"Update ClientDefinitions.ini: Skip add [Settings]->MaximumRenderHeight, reason: already exist"); + } + else + { + var mrh = 720; + Log($"Update ClientDefinitions.ini: Add [Settings]->MaximumRenderHeight={mrh}"); + clientDefsIni.GetSection("Settings").AddKey("MaximumRenderHeight", mrh.ToString()); } - var rr = "1280x720"; - Log($"Update ClientDefinitions.ini: Add [Settings]->RecommendedResolutions={rr}"); - clientDefsIni.GetSection("Settings").AddKey("RecommendedResolutions", rr); clientDefsIni.WriteIniFile(); + continue; case (Version.V_2_11_2): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.2.0 From 6b0ee569b122b8415f2a62fc50897cb04f40eda4 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 16:43:17 +0300 Subject: [PATCH 07/81] Refactor key writing and remove v2.12.5 version migration --- MigrationTool/Program.cs | 150 ++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 88 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 01909772d..a59583cc9 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -17,7 +17,6 @@ private enum Version V_2_11_2, V_2_12_0, V_2_12_1, - V_2_12_5, Latest, End } @@ -110,6 +109,19 @@ Execute this file with path to the unmigrated client directory as first argument Console.WriteLine(text); } + private static void AddKeyWithLog(IniFile src, string section, string key, string value) + { + if (src.KeyExists(section, key)) + { + Log($"Update {src.FileName}.ini: Skip add [{section}]->{key}, reason: already exist"); + } + else + { + Log($"Update {src.FileName}.ini: Add [{section}]->{key}={value}"); + src.GetSection(section).AddKey(key, value); + } + } + private static void Migrate(string path) { DirectoryInfo clientDir = SafePath.GetDirectory(path); @@ -118,50 +130,63 @@ private static void Migrate(string path) IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "ClientDefinitions.ini")); IniFile gmLobbyBaseIni = null; + // Predict client type by guessing game engine files + var clientGameType = ClientGameType.TS; + if (!SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists + && SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "gamemd-spawn.exe")).Exists) + { + clientGameType = ClientGameType.YR; + } + else if (SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists) + { + clientGameType = ClientGameType.Ares; + } + for (int i = (int)Version.Begin; i != (int)Version.End; i++) { switch ((Version)i) { case (Version.V_2_11_0): + // Add GlobalThemeSettings.ini + IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GlobalThemeSettings.ini")); + Dictionary gtsKeys = new() + { + { "DEFAULT_LBL_HEIGHT", 12 }, + { "DEFAULT_CONTROL_HEIGHT", 21 }, + { "DEFAULT_BUTTON_HEIGHT", 23 }, + { "BUTTON_WIDTH_133", 133 }, + { "OPEN_BUTTON_WIDTH", 18 }, + { "OPEN_BUTTON_HEIGHT", 22 }, + { "EMPTY_SPACE_TOP", 12 }, + { "EMPTY_SPACE_BOTTOM", 12 }, + { "EMPTY_SPACE_SIDES", 12 }, + { "BUTTON_SPACING", 12 }, + { "LABEL_SPACING", 6 }, + { "CHECKBOX_SPACING", 24 }, + { "LOBBY_EMPTY_SPACE_SIDES", 12 }, + { "LOBBY_PANEL_SPACING", 10 }, + { "GAME_OPTION_COLUMN_SPACING", 160 }, + { "GAME_OPTION_ROW_SPACING", 6 }, + { "GAME_OPTION_DD_WIDTH", 132 }, + { "GAME_OPTION_DD_HEIGHT", 22 } + }; - continue; + if (!globalThemeSettingsIni.SectionExists("ParserConstants")) + globalThemeSettingsIni.AddSection("ParserConstants"); - case (Version.V_2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 - // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions - if (clientDefsIni.KeyExists("Settings", "RecommendedResolutions")) - { - Log($"Update ClientDefinitions.ini: Skip add [Settings]->RecommendedResolutions, reason: already exist"); - } - else - { - var rr = "1280x720"; - Log($"Update ClientDefinitions.ini: Add [Settings]->RecommendedResolutions={rr}"); - clientDefsIni.GetSection("Settings").AddKey("RecommendedResolutions", rr); - } + foreach (var key in gtsKeys) + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", key.Key, key.Value.ToString()); - if (clientDefsIni.KeyExists("Settings", "MaximumRenderWidth")) - { - Log($"Update ClientDefinitions.ini: Skip add [Settings]->MaximumRenderWidth, reason: already exist"); - } - else - { - var mrw = 1280; - Log($"Update ClientDefinitions.ini: Add [Settings]->MaximumRenderWidth={mrw}"); - clientDefsIni.GetSection("Settings").AddKey("MaximumRenderWidth", mrw.ToString()); - } + globalThemeSettingsIni.WriteIniFile(); - if (clientDefsIni.KeyExists("Settings", "MaximumRenderHeight")) - { - Log($"Update ClientDefinitions.ini: Skip add [Settings]->MaximumRenderHeight, reason: already exist"); - } - else - { - var mrh = 720; - Log($"Update ClientDefinitions.ini: Add [Settings]->MaximumRenderHeight={mrh}"); - clientDefsIni.GetSection("Settings").AddKey("MaximumRenderHeight", mrh.ToString()); - } + continue; + case (Version.V_2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 + // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions + AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", "1280x720"); + AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderWidth", "1280"); + AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderHeight", "720"); clientDefsIni.WriteIniFile(); continue; @@ -179,15 +204,7 @@ private static void Migrate(string path) } // Add ClientDefinitions.ini->[Settings]->ShowDevelopmentBuildWarnings - if (clientDefsIni.KeyExists("Settings", "ShowDevelopmentBuildWarnings")) - { - Log($"Update ClientDefinitions.ini: Skip add [Settings]->ShowDevelopmentBuildWarnings, reason: already exist"); - continue; - } - - var sdbw = true; - Log($"Update ClientDefinitions.ini: Add [Settings]->ShowDevelopmentBuildWarnings={sdbw.ToString()}"); - clientDefsIni.GetSection("Settings").AddKey("ShowDevelopmentBuildWarnings", sdbw.ToString()); + AddKeyWithLog(clientDefsIni, "Settings", "ShowDevelopmentBuildWarnings", "true"); clientDefsIni.WriteIniFile(); continue; @@ -200,27 +217,7 @@ private static void Migrate(string path) break; case (Version.V_2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 - // Predict client type by guessing game engine files // And add ClientDefinitions.ini->[Settings]->ClientGameType - if (clientDefsIni.KeyExists("Settings", "ClientGameType")) - { - Log($"Update ClientDefinitions.ini: Skip add [Settings]->ClientGameType, reason: already exist"); - continue; - } - - var clientGameType = ClientGameType.TS; - - if (!SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists - && SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "gamemd-spawn.exe")).Exists) - { - clientGameType = ClientGameType.YR; - } - else if (SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists) - { - clientGameType = ClientGameType.Ares; - } - - clientDefsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "ClientDefinitions.ini")); string cgt = clientGameType switch { ClientGameType.Ares => "Ares", @@ -228,22 +225,7 @@ private static void Migrate(string path) _ => "TS" }; - Log($"Update ClientDefinitions.ini: Add [Settings]->ClientGameType={cgt}"); - clientDefsIni.GetSection("Settings").AddKey("ClientGameType", cgt); - clientDefsIni.WriteIniFile(); - continue; - - case (Version.V_2_12_5): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.5 - // Add ClientDefinitions.ini->[Settings]->TrustedDomains - if (clientDefsIni.KeyExists("Settings", "TrustedDomains")) - { - Log("Update ClientDefinitions.ini: Skip add [Settings]->TrustedDomains, reason: already exist"); - continue; - } - - var td = "moddb.com"; - Log($"Update ClientDefinitions.ini: Add [Settings]->TrustedDomains={td}"); - clientDefsIni.GetSection("Settings").AddKey("TrustedDomains", td); + AddKeyWithLog(clientDefsIni, "Settings", "ClientGameType", cgt); clientDefsIni.WriteIniFile(); continue; @@ -253,18 +235,10 @@ private static void Migrate(string path) string ddPlayerColor = nameof(ddPlayerColor); foreach (var n in new int[] { 0, 1, 2, 3, 4, 5, 6, 7 }) { - if (gmLobbyBaseIni.KeyExists(ddPlayerColor + n, "ItemsDrawMode")) - { - Log($"Update GameLobbyBase.ini: Skip add [{ddPlayerColor + n}]->ItemsDrawMode, reason: already exist"); - continue; - } - - Log($"Update GameLobbyBase.ini: Add [{ddPlayerColor + n}]->ItemsDrawMode=Text"); - if (!gmLobbyBaseIni.SectionExists(ddPlayerColor + n)) gmLobbyBaseIni.AddSection(ddPlayerColor + n); - gmLobbyBaseIni.GetSection(ddPlayerColor + n).AddKey("ItemsDrawMode", "Text"); + AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + n, "ItemsDrawMode", "Text"); gmLobbyBaseIni.WriteIniFile(); } continue; From 18aca658e5ba0cd83df1af6c8c45014da023c7ef Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 17:10:35 +0300 Subject: [PATCH 08/81] Add `PlayerExtraOptionsPanel.ini` migration --- MigrationTool/Program.cs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index a59583cc9..f5afb7b08 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -142,9 +142,9 @@ private static void Migrate(string path) clientGameType = ClientGameType.Ares; } - for (int i = (int)Version.Begin; i != (int)Version.End; i++) + for (int currentVersion = (int)Version.Begin; currentVersion != (int)Version.End; currentVersion++) { - switch ((Version)i) + switch ((Version)currentVersion) { case (Version.V_2_11_0): @@ -180,6 +180,35 @@ private static void Migrate(string path) globalThemeSettingsIni.WriteIniFile(); + // Add PlayerExtraOptionsPanel.ini + IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "PlayerExtraOptionsPanel.ini")); + var peopSections = new List() + { + "btnClose", "lblHeader", "chkBoxForceRandomSides", + "chkBoxForceRandomColors", "chkBoxForceRandomTeams", + "chkBoxForceRandomStarts", "chkBoxUseTeamStartMappings", + "btnHelp", "lblPreset", "ddTeamStartMappingPreset", + "teamStartMappingsPanel" + }; + + foreach (var section in peopSections) + if (!playerExtraOptionsPanelIni.SectionExists(section)) + playerExtraOptionsPanelIni.AddSection(section); + + AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Location", "220,0"); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Size", "18,18"); + AddKeyWithLog(playerExtraOptionsPanelIni, "lblHeader", "Location", "12,6"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomSides", "Location", "12,28"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomColors", "Location", "12,50"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomTeams", "Location", "12,72"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomStarts", "Location", "12,94"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxUseTeamStartMappings", "Location", "12,130"); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnHelp", "Location", "160,130"); + AddKeyWithLog(playerExtraOptionsPanelIni, "lblPreset", "Location", "12,156"); + AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Location", "65,154"); + AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Size", "157,21"); + AddKeyWithLog(playerExtraOptionsPanelIni, "teamStartMappingsPanel", "Location", "12,189"); + continue; case (Version.V_2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 @@ -188,7 +217,6 @@ private static void Migrate(string path) AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderWidth", "1280"); AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderHeight", "720"); clientDefsIni.WriteIniFile(); - continue; case (Version.V_2_11_2): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.2.0 @@ -214,7 +242,7 @@ private static void Migrate(string path) SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.pdb"); SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.xml"); - break; + continue; case (Version.V_2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 // And add ClientDefinitions.ini->[Settings]->ClientGameType From ae8ed1618fbd57948a230173ca7fcceac3cd113c Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 18:26:10 +0300 Subject: [PATCH 09/81] Fix typo --- MigrationTool/MigrationTool.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index 8bf4b2a72..082f823a2 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -4,9 +4,9 @@ false - CnCNet.MigratioTool + CnCNet.MigrationTool CnCNet Client Migration Tool - CnCNet.MigratioTool + CnCNet.MigrationTool From b7ddb2f16a7d169b06390d01bf52425aefc4e2fa Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 18:28:11 +0300 Subject: [PATCH 10/81] Move changes for v2.12.0 to patch for 2.11.0 --- MigrationTool/Program.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index f5afb7b08..427749cd1 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -15,7 +15,6 @@ private enum Version V_2_11_0, V_2_11_1, V_2_11_2, - V_2_12_0, V_2_12_1, Latest, End @@ -148,6 +147,12 @@ private static void Migrate(string path) { case (Version.V_2_11_0): + // Remove Rampastring.Tools from Resources directory (not recursive) + Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.pdb"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.xml"); + // Add GlobalThemeSettings.ini IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GlobalThemeSettings.ini")); Dictionary gtsKeys = new() @@ -236,14 +241,6 @@ private static void Migrate(string path) clientDefsIni.WriteIniFile(); continue; - case (Version.V_2_12_0): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.0 - // Remove Rampastring.Tools from Resources directory (not recursive) - Log("Remove Resources/Rampastring.Tools.* (* -- dll, pdb, xml)"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.pdb"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.xml"); - continue; - case (Version.V_2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 // And add ClientDefinitions.ini->[Settings]->ClientGameType string cgt = clientGameType switch From 96d9225521de5f3b0296c177551f0925f6757530 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 18:30:33 +0300 Subject: [PATCH 11/81] Refactoring and rewording --- MigrationTool/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 427749cd1..e13a1a27d 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -112,7 +112,7 @@ private static void AddKeyWithLog(IniFile src, string section, string key, strin { if (src.KeyExists(section, key)) { - Log($"Update {src.FileName}.ini: Skip add [{section}]->{key}, reason: already exist"); + Log($"Update {src.FileName}.ini: Skip adding [{section}]->{key}, reason: already exist"); } else { @@ -258,7 +258,7 @@ private static void Migrate(string path) // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode gmLobbyBaseIni ??= new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GameLobbyBase.ini")); string ddPlayerColor = nameof(ddPlayerColor); - foreach (var n in new int[] { 0, 1, 2, 3, 4, 5, 6, 7 }) + foreach (var n in Enumerable.Range(0, 8)) { if (!gmLobbyBaseIni.SectionExists(ddPlayerColor + n)) gmLobbyBaseIni.AddSection(ddPlayerColor + n); From 0122a2f85e7246c1f65c20d4e353531de8870d1e Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 5 Jul 2025 18:43:29 +0300 Subject: [PATCH 12/81] Rework v2.11.1 patch --- MigrationTool/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index e13a1a27d..7beec2a0c 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -217,10 +217,12 @@ private static void Migrate(string path) continue; case (Version.V_2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 - // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions - AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", "1280x720"); + // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions, MaximumRenderWidth, MaximumRenderHeight AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderWidth", "1280"); AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderHeight", "720"); + var width = clientDefsIni.GetStringValue("Settings", "MaximumRenderWidth", "1280"); + var height = clientDefsIni.GetStringValue("Settings", "MaximumRenderHeight", "720"); + AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", $"{width}x{height}"); clientDefsIni.WriteIniFile(); continue; From 5e2661d8040a08daebe6398b1ce7c12c6b440484 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 02:18:51 +0300 Subject: [PATCH 13/81] Switch expression optimization and add `GenericWindow.ini` migration --- MigrationTool/Program.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 7beec2a0c..26027ce6a 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -146,7 +146,6 @@ private static void Migrate(string path) switch ((Version)currentVersion) { case (Version.V_2_11_0): - // Remove Rampastring.Tools from Resources directory (not recursive) Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); @@ -213,6 +212,13 @@ private static void Migrate(string path) AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Location", "65,154"); AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Size", "157,21"); AddKeyWithLog(playerExtraOptionsPanelIni, "teamStartMappingsPanel", "Location", "12,189"); + playerExtraOptionsPanelIni.WriteIniFile(); + + // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false + var genericWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GenericWindow.ini")); + AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); + + continue; @@ -245,12 +251,7 @@ private static void Migrate(string path) case (Version.V_2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 // And add ClientDefinitions.ini->[Settings]->ClientGameType - string cgt = clientGameType switch - { - ClientGameType.Ares => "Ares", - ClientGameType.YR => "YR", - _ => "TS" - }; + string cgt = clientGameType.ToString(); AddKeyWithLog(clientDefsIni, "Settings", "ClientGameType", cgt); clientDefsIni.WriteIniFile(); From 8104dde2f529abe420743c9c2277a4b4de901fe1 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 12:06:44 +0300 Subject: [PATCH 14/81] Add patch rename `CustomSettingFile*` to `FileSetting*` --- MigrationTool/Program.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 26027ce6a..2e9cc4527 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -218,7 +218,25 @@ private static void Migrate(string path) var genericWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GenericWindow.ini")); AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); - + // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} + IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "OptionsWindow.ini")); + foreach (var section in optionsWindowIni.GetSections()) + foreach (var pair in optionsWindowIni.GetSection(section).Keys) + { + if (pair.Value.Contains(":CustomSettingFileCheckBox")) + { + pair.Value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox"); + continue; + } + + if (pair.Value.Contains(":CustomSettingFileDropDown")) + { + pair.Value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown"); + continue; + } + } + + // Add new texture files continue; From b7a3c48f7c8d9daa5c667ed58813fd98daa2fec6 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 14:06:20 +0300 Subject: [PATCH 15/81] Fix bug with potential null reference exception when section does not exist --- MigrationTool/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 2e9cc4527..11335a281 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -117,6 +117,8 @@ private static void AddKeyWithLog(IniFile src, string section, string key, strin else { Log($"Update {src.FileName}.ini: Add [{section}]->{key}={value}"); + if (!src.SectionExists(section)) + src.AddSection(section); src.GetSection(section).AddKey(key, value); } } @@ -235,7 +237,7 @@ private static void Migrate(string path) continue; } } - + optionsWindowIni.WriteIniFile(); // Add new texture files continue; From 540ea08554d2b9e56aa54cb058f342bc9316576d Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 14:46:40 +0300 Subject: [PATCH 16/81] Adjustments (new keys for `OptionsWindow.ini`, bug fix, improvemetns) --- Docs/Migration-INI.md | 2 + MigrationTool/Program.cs | 83 ++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/Docs/Migration-INI.md b/Docs/Migration-INI.md index 79a038373..c5dc92e0b 100644 --- a/Docs/Migration-INI.md +++ b/Docs/Migration-INI.md @@ -806,6 +806,8 @@ Location=470,137 Location=0,200 [btnForceUpdate] +Location=407,213 +Size=133,23 ``` 2. **OPTIONAL** Add sections: diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 11335a281..dcdd6ebe4 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -112,13 +112,12 @@ private static void AddKeyWithLog(IniFile src, string section, string key, strin { if (src.KeyExists(section, key)) { - Log($"Update {src.FileName}.ini: Skip adding [{section}]->{key}, reason: already exist"); + Log($"Update {src.FileName}: Skip adding [{section}]->{key}, reason: already exist", ConsoleColor.Red); } else { - Log($"Update {src.FileName}.ini: Add [{section}]->{key}={value}"); - if (!src.SectionExists(section)) - src.AddSection(section); + Log($"Update {src.FileName}: Add [{section}]->{key}={value}", ConsoleColor.Green); + if (!src.SectionExists(section)) src.AddSection(section); src.GetSection(section).AddKey(key, value); } } @@ -156,51 +155,28 @@ private static void Migrate(string path) // Add GlobalThemeSettings.ini IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GlobalThemeSettings.ini")); - Dictionary gtsKeys = new() - { - { "DEFAULT_LBL_HEIGHT", 12 }, - { "DEFAULT_CONTROL_HEIGHT", 21 }, - { "DEFAULT_BUTTON_HEIGHT", 23 }, - { "BUTTON_WIDTH_133", 133 }, - { "OPEN_BUTTON_WIDTH", 18 }, - { "OPEN_BUTTON_HEIGHT", 22 }, - { "EMPTY_SPACE_TOP", 12 }, - { "EMPTY_SPACE_BOTTOM", 12 }, - { "EMPTY_SPACE_SIDES", 12 }, - { "BUTTON_SPACING", 12 }, - { "LABEL_SPACING", 6 }, - { "CHECKBOX_SPACING", 24 }, - { "LOBBY_EMPTY_SPACE_SIDES", 12 }, - { "LOBBY_PANEL_SPACING", 10 }, - { "GAME_OPTION_COLUMN_SPACING", 160 }, - { "GAME_OPTION_ROW_SPACING", 6 }, - { "GAME_OPTION_DD_WIDTH", 132 }, - { "GAME_OPTION_DD_HEIGHT", 22 } - }; - - if (!globalThemeSettingsIni.SectionExists("ParserConstants")) - globalThemeSettingsIni.AddSection("ParserConstants"); - - foreach (var key in gtsKeys) - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", key.Key, key.Value.ToString()); - + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_LBL_HEIGHT", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_CONTROL_HEIGHT", "21"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_BUTTON_HEIGHT", "23"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_WIDTH_133", "133"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_WIDTH", "18"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_HEIGHT", "22"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_TOP", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_BOTTOM", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_SIDES", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_SPACING", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LABEL_SPACING", "6"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "CHECKBOX_SPACING", "24"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_EMPTY_SPACE_SIDES", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_PANEL_SPACING", "10"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_COLUMN_SPACING", "160"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_ROW_SPACING", "6"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_WIDTH", "132"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_HEIGHT", "22"); globalThemeSettingsIni.WriteIniFile(); // Add PlayerExtraOptionsPanel.ini IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "PlayerExtraOptionsPanel.ini")); - var peopSections = new List() - { - "btnClose", "lblHeader", "chkBoxForceRandomSides", - "chkBoxForceRandomColors", "chkBoxForceRandomTeams", - "chkBoxForceRandomStarts", "chkBoxUseTeamStartMappings", - "btnHelp", "lblPreset", "ddTeamStartMappingPreset", - "teamStartMappingsPanel" - }; - - foreach (var section in peopSections) - if (!playerExtraOptionsPanelIni.SectionExists(section)) - playerExtraOptionsPanelIni.AddSection(section); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Location", "220,0"); AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Size", "18,18"); AddKeyWithLog(playerExtraOptionsPanelIni, "lblHeader", "Location", "12,6"); @@ -219,6 +195,7 @@ private static void Migrate(string path) // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false var genericWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GenericWindow.ini")); AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); + genericWindowIni.WriteIniFile(); // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "OptionsWindow.ini")); @@ -237,6 +214,22 @@ private static void Migrate(string path) continue; } } + + // Add new sections into OptionsWindow.ini + AddKeyWithLog(optionsWindowIni, "lblPlayerName", "Location", "12,195"); + AddKeyWithLog(optionsWindowIni, "tbPlayerName", "Location", "113,193"); + AddKeyWithLog(optionsWindowIni, "lblNotice", "Location", "12,220"); + AddKeyWithLog(optionsWindowIni, "btnConfigureHotkeys", "Location", "12,290"); + AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Location", "12,138"); + AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); + AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); + AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); + AddKeyWithLog(optionsWindowIni, "lblAllPrivateMessagesFrom", "Location", "276,138"); + AddKeyWithLog(optionsWindowIni, "ddAllowPrivateMessagesFrom", "Location", "470,137"); + AddKeyWithLog(optionsWindowIni, "gameListPanel", "Location", "0,200"); + AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Location", "407,213"); + AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Size", "133,23"); + optionsWindowIni.WriteIniFile(); // Add new texture files From e3f72ba36b91579fe270795f81181f447e37eac8 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 15:01:23 +0300 Subject: [PATCH 17/81] Rework exception catch --- MigrationTool/Program.cs | 298 ++++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 148 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index dcdd6ebe4..a0c057886 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -12,10 +12,10 @@ internal sealed class Program private enum Version { Begin, - V_2_11_0, - V_2_11_1, - V_2_11_2, - V_2_12_1, + v2_11_0, + v2_11_1, + v2_11_2, + v2_12_1, Latest, End } @@ -68,14 +68,7 @@ private static void Main(string[] args) return; } - try - { - Migrate(arg); - } - catch (Exception ex) - { - Log("Unable to migrate client configs due to internal error. Message: " + ex.Message, ConsoleColor.Red); - } + Migrate(arg); break; case 0: default: @@ -144,148 +137,157 @@ private static void Migrate(string path) for (int currentVersion = (int)Version.Begin; currentVersion != (int)Version.End; currentVersion++) { - switch ((Version)currentVersion) + try { - case (Version.V_2_11_0): - // Remove Rampastring.Tools from Resources directory (not recursive) - Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.pdb"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.xml"); - - // Add GlobalThemeSettings.ini - IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GlobalThemeSettings.ini")); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_LBL_HEIGHT", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_CONTROL_HEIGHT", "21"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_BUTTON_HEIGHT", "23"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_WIDTH_133", "133"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_WIDTH", "18"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_HEIGHT", "22"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_TOP", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_BOTTOM", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_SIDES", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_SPACING", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LABEL_SPACING", "6"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "CHECKBOX_SPACING", "24"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_EMPTY_SPACE_SIDES", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_PANEL_SPACING", "10"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_COLUMN_SPACING", "160"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_ROW_SPACING", "6"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_WIDTH", "132"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_HEIGHT", "22"); - globalThemeSettingsIni.WriteIniFile(); - - // Add PlayerExtraOptionsPanel.ini - IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "PlayerExtraOptionsPanel.ini")); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Location", "220,0"); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Size", "18,18"); - AddKeyWithLog(playerExtraOptionsPanelIni, "lblHeader", "Location", "12,6"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomSides", "Location", "12,28"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomColors", "Location", "12,50"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomTeams", "Location", "12,72"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomStarts", "Location", "12,94"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxUseTeamStartMappings", "Location", "12,130"); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnHelp", "Location", "160,130"); - AddKeyWithLog(playerExtraOptionsPanelIni, "lblPreset", "Location", "12,156"); - AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Location", "65,154"); - AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Size", "157,21"); - AddKeyWithLog(playerExtraOptionsPanelIni, "teamStartMappingsPanel", "Location", "12,189"); - playerExtraOptionsPanelIni.WriteIniFile(); - - // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false - var genericWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GenericWindow.ini")); - AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); - genericWindowIni.WriteIniFile(); - - // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} - IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "OptionsWindow.ini")); - foreach (var section in optionsWindowIni.GetSections()) - foreach (var pair in optionsWindowIni.GetSection(section).Keys) - { - if (pair.Value.Contains(":CustomSettingFileCheckBox")) + switch ((Version)currentVersion) + { + case (Version.v2_11_0): + // Remove Rampastring.Tools from Resources directory (not recursive) + Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.pdb"); + SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.xml"); + + // Add GlobalThemeSettings.ini + IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GlobalThemeSettings.ini")); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_LBL_HEIGHT", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_CONTROL_HEIGHT", "21"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_BUTTON_HEIGHT", "23"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_WIDTH_133", "133"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_WIDTH", "18"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_HEIGHT", "22"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_TOP", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_BOTTOM", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_SIDES", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_SPACING", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LABEL_SPACING", "6"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "CHECKBOX_SPACING", "24"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_EMPTY_SPACE_SIDES", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_PANEL_SPACING", "10"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_COLUMN_SPACING", "160"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_ROW_SPACING", "6"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_WIDTH", "132"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_HEIGHT", "22"); + globalThemeSettingsIni.WriteIniFile(); + + // Add PlayerExtraOptionsPanel.ini + IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "PlayerExtraOptionsPanel.ini")); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Location", "220,0"); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Size", "18,18"); + AddKeyWithLog(playerExtraOptionsPanelIni, "lblHeader", "Location", "12,6"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomSides", "Location", "12,28"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomColors", "Location", "12,50"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomTeams", "Location", "12,72"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomStarts", "Location", "12,94"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxUseTeamStartMappings", "Location", "12,130"); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnHelp", "Location", "160,130"); + AddKeyWithLog(playerExtraOptionsPanelIni, "lblPreset", "Location", "12,156"); + AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Location", "65,154"); + AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Size", "157,21"); + AddKeyWithLog(playerExtraOptionsPanelIni, "teamStartMappingsPanel", "Location", "12,189"); + playerExtraOptionsPanelIni.WriteIniFile(); + + // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false + var genericWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GenericWindow.ini")); + AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); + genericWindowIni.WriteIniFile(); + + // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} + IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "OptionsWindow.ini")); + foreach (var section in optionsWindowIni.GetSections()) + foreach (var pair in optionsWindowIni.GetSection(section).Keys) { - pair.Value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox"); - continue; + if (pair.Value.Contains(":CustomSettingFileCheckBox")) + { + pair.Value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox"); + continue; + } + + if (pair.Value.Contains(":CustomSettingFileDropDown")) + { + pair.Value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown"); + continue; + } } - if (pair.Value.Contains(":CustomSettingFileDropDown")) - { - pair.Value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown"); - continue; - } + // Add new sections into OptionsWindow.ini + AddKeyWithLog(optionsWindowIni, "lblPlayerName", "Location", "12,195"); + AddKeyWithLog(optionsWindowIni, "tbPlayerName", "Location", "113,193"); + AddKeyWithLog(optionsWindowIni, "lblNotice", "Location", "12,220"); + AddKeyWithLog(optionsWindowIni, "btnConfigureHotkeys", "Location", "12,290"); + AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Location", "12,138"); + AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); + AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); + AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); + AddKeyWithLog(optionsWindowIni, "lblAllPrivateMessagesFrom", "Location", "276,138"); + AddKeyWithLog(optionsWindowIni, "ddAllowPrivateMessagesFrom", "Location", "470,137"); + AddKeyWithLog(optionsWindowIni, "gameListPanel", "Location", "0,200"); + AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Location", "407,213"); + AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Size", "133,23"); + + optionsWindowIni.WriteIniFile(); + // Add new texture files + + continue; + + case (Version.v2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 + // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions, MaximumRenderWidth, MaximumRenderHeight + AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderWidth", "1280"); + AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderHeight", "720"); + var width = clientDefsIni.GetStringValue("Settings", "MaximumRenderWidth", "1280"); + var height = clientDefsIni.GetStringValue("Settings", "MaximumRenderHeight", "720"); + AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", $"{width}x{height}"); + clientDefsIni.WriteIniFile(); + continue; + + case (Version.v2_11_2): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.2.0 + // Remove ClientUpdater.xml and SecondStageUpdater.xml + var listExtraXMLs = new List(2) { "ClientUpdater.xml", "SecondStageUpdater.xml" }; + Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); + + foreach (var extraXml in listExtraXMLs) + { + Directory.GetFiles(resouresDir.FullName, extraXml, SearchOption.AllDirectories) + .ToList() + .ForEach(elem => SafePath.DeleteFileIfExists(elem)); + } + + // Add ClientDefinitions.ini->[Settings]->ShowDevelopmentBuildWarnings + AddKeyWithLog(clientDefsIni, "Settings", "ShowDevelopmentBuildWarnings", "true"); + clientDefsIni.WriteIniFile(); + continue; + + case (Version.v2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 + // And add ClientDefinitions.ini->[Settings]->ClientGameType + string cgt = clientGameType.ToString(); + + AddKeyWithLog(clientDefsIni, "Settings", "ClientGameType", cgt); + clientDefsIni.WriteIniFile(); + continue; + + case (Version.Latest): + // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode + gmLobbyBaseIni ??= new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GameLobbyBase.ini")); + string ddPlayerColor = nameof(ddPlayerColor); + foreach (var n in Enumerable.Range(0, 8)) + { + if (!gmLobbyBaseIni.SectionExists(ddPlayerColor + n)) + gmLobbyBaseIni.AddSection(ddPlayerColor + n); + + AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + n, "ItemsDrawMode", "Text"); + gmLobbyBaseIni.WriteIniFile(); } + continue; - // Add new sections into OptionsWindow.ini - AddKeyWithLog(optionsWindowIni, "lblPlayerName", "Location", "12,195"); - AddKeyWithLog(optionsWindowIni, "tbPlayerName", "Location", "113,193"); - AddKeyWithLog(optionsWindowIni, "lblNotice", "Location", "12,220"); - AddKeyWithLog(optionsWindowIni, "btnConfigureHotkeys", "Location", "12,290"); - AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Location", "12,138"); - AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); - AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); - AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); - AddKeyWithLog(optionsWindowIni, "lblAllPrivateMessagesFrom", "Location", "276,138"); - AddKeyWithLog(optionsWindowIni, "ddAllowPrivateMessagesFrom", "Location", "470,137"); - AddKeyWithLog(optionsWindowIni, "gameListPanel", "Location", "0,200"); - AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Location", "407,213"); - AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Size", "133,23"); - - optionsWindowIni.WriteIniFile(); - // Add new texture files - - continue; - - case (Version.V_2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 - // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions, MaximumRenderWidth, MaximumRenderHeight - AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderWidth", "1280"); - AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderHeight", "720"); - var width = clientDefsIni.GetStringValue("Settings", "MaximumRenderWidth", "1280"); - var height = clientDefsIni.GetStringValue("Settings", "MaximumRenderHeight", "720"); - AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", $"{width}x{height}"); - clientDefsIni.WriteIniFile(); - continue; - - case (Version.V_2_11_2): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.2.0 - // Remove ClientUpdater.xml and SecondStageUpdater.xml - var listExtraXMLs = new List(2) { "ClientUpdater.xml", "SecondStageUpdater.xml" }; - Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); - - foreach (var extraXml in listExtraXMLs) - { - Directory.GetFiles(resouresDir.FullName, extraXml, SearchOption.AllDirectories) - .ToList() - .ForEach(elem => SafePath.DeleteFileIfExists(elem)); - } - - // Add ClientDefinitions.ini->[Settings]->ShowDevelopmentBuildWarnings - AddKeyWithLog(clientDefsIni, "Settings", "ShowDevelopmentBuildWarnings", "true"); - clientDefsIni.WriteIniFile(); - continue; - - case (Version.V_2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 - // And add ClientDefinitions.ini->[Settings]->ClientGameType - string cgt = clientGameType.ToString(); - - AddKeyWithLog(clientDefsIni, "Settings", "ClientGameType", cgt); - clientDefsIni.WriteIniFile(); - continue; - - case (Version.Latest): - // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode - gmLobbyBaseIni ??= new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GameLobbyBase.ini")); - string ddPlayerColor = nameof(ddPlayerColor); - foreach (var n in Enumerable.Range(0, 8)) - { - if (!gmLobbyBaseIni.SectionExists(ddPlayerColor + n)) - gmLobbyBaseIni.AddSection(ddPlayerColor + n); - - AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + n, "ItemsDrawMode", "Text"); - gmLobbyBaseIni.WriteIniFile(); - } - continue; - - default: - continue; + default: + continue; + } + } + catch (Exception ex) + { + Log($"Unable to apply migration patch for client version {((Version)currentVersion).ToString().Replace('_', '.')} due to internal error. Message: " + ex.Message, ConsoleColor.Red); + Log("Migration to the latest client version has been failed", ConsoleColor.Red); + break; } } } From 6e2535ffc00eb8b5aa6da1d49f7a6fa6289585f3 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 16:06:41 +0300 Subject: [PATCH 18/81] Rework patch apply mechanism --- MigrationTool/Patch.cs | 77 +++++++++++ MigrationTool/Patch_Latest.cs | 27 ++++ MigrationTool/Patch_v2_11_0.cs | 110 +++++++++++++++ MigrationTool/Patch_v2_11_1.cs | 27 ++++ MigrationTool/Patch_v2_11_2.cs | 38 ++++++ MigrationTool/Patch_v2_12_1.cs | 28 ++++ MigrationTool/Program.cs | 241 +++++---------------------------- 7 files changed, 339 insertions(+), 209 deletions(-) create mode 100644 MigrationTool/Patch.cs create mode 100644 MigrationTool/Patch_Latest.cs create mode 100644 MigrationTool/Patch_v2_11_0.cs create mode 100644 MigrationTool/Patch_v2_11_1.cs create mode 100644 MigrationTool/Patch_v2_11_2.cs create mode 100644 MigrationTool/Patch_v2_12_1.cs diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs new file mode 100644 index 000000000..00c64b8ce --- /dev/null +++ b/MigrationTool/Patch.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Text.RegularExpressions; + +using Rampastring.Tools; +namespace MigrationTool; + +internal abstract class Patch +{ + public Version ClientVersion { get; protected set; } + public ClientGameType Game { get; protected set; } + public DirectoryInfo ClientDir { get; protected set; } + public DirectoryInfo ResouresDir { get; protected set; } + + protected static ConsoleColor defaultColor = Console.ForegroundColor; + + public Patch(string clientPath) + { + ClientDir = SafePath.GetDirectory(clientPath); + ResouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(clientPath, "Resources")); + + // Predict client type by guessing game engine files + var Game = ClientGameType.TS; + if (!SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "Ares.dll")).Exists + && SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "gamemd-spawn.exe")).Exists) + { + Game = ClientGameType.YR; + } + else if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "Ares.dll")).Exists) + { + Game = ClientGameType.Ares; + } + } + + public virtual Patch Apply() + { + Log($"Applying patch for client version {ClientVersion.ToString().Replace('_', '.')}...", ConsoleColor.White); + return this; + } + + public void AddKeyWithLog(IniFile src, string section, string key, string value) + { + if (src.KeyExists(section, key)) + { + Log($"Update {src.FileName}: Skip adding [{section}]->{key}, reason: already exist", ConsoleColor.Red); + } + else + { + Log($"Update {src.FileName}: Add [{section}]->{key}={value}", ConsoleColor.Green); + if (!src.SectionExists(section)) src.AddSection(section); + src.GetSection(section).AddKey(key, value); + } + } + + protected void Log(string text, ConsoleColor? color = null, bool echoToConsole = true) + { + Console.ForegroundColor = color ?? defaultColor; + Logger.Log(text); + + if (echoToConsole) + Console.WriteLine(text); + } + + public bool TryApply() + { + try + { + Apply(); + return true; + } + catch + { + return false; + } + } +} + diff --git a/MigrationTool/Patch_Latest.cs b/MigrationTool/Patch_Latest.cs new file mode 100644 index 000000000..8f8f1cdef --- /dev/null +++ b/MigrationTool/Patch_Latest.cs @@ -0,0 +1,27 @@ +using System.Linq; + +using Rampastring.Tools; + +namespace MigrationTool; + +internal class Patch_Latest : Patch +{ + public Patch_Latest(string clientPath) : base(clientPath) + { + ClientVersion = Version.Latest; + } + + public override Patch Apply() + { + base.Apply(); + + // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode + IniFile gmLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini")); + string ddPlayerColor = nameof(ddPlayerColor); + Enumerable.Range(0, 8).ToList().ForEach(i => AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + i, "ItemsDrawMode", "Text")); + gmLobbyBaseIni.WriteIniFile(); + + return this; + } +} + diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs new file mode 100644 index 000000000..78a9a2550 --- /dev/null +++ b/MigrationTool/Patch_v2_11_0.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Rampastring.Tools; + +namespace MigrationTool; + +internal class Patch_v2_11_0 : Patch +{ + public Patch_v2_11_0 (string clientPath) : base (clientPath) + { + ClientVersion = Version.v2_11_0; + } + + public override Patch Apply() + { + base.Apply(); + + // Remove Rampastring.Tools from Resources directory (not recursive) + Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); + SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.dll"); + SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.pdb"); + SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.xml"); + + // Add GlobalThemeSettings.ini + IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GlobalThemeSettings.ini")); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_LBL_HEIGHT", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_CONTROL_HEIGHT", "21"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_BUTTON_HEIGHT", "23"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_WIDTH_133", "133"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_WIDTH", "18"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_HEIGHT", "22"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_TOP", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_BOTTOM", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_SIDES", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_SPACING", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LABEL_SPACING", "6"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "CHECKBOX_SPACING", "24"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_EMPTY_SPACE_SIDES", "12"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_PANEL_SPACING", "10"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_COLUMN_SPACING", "160"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_ROW_SPACING", "6"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_WIDTH", "132"); + AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_HEIGHT", "22"); + globalThemeSettingsIni.WriteIniFile(); + + // Add PlayerExtraOptionsPanel.ini + IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "PlayerExtraOptionsPanel.ini")); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Location", "220,0"); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Size", "18,18"); + AddKeyWithLog(playerExtraOptionsPanelIni, "lblHeader", "Location", "12,6"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomSides", "Location", "12,28"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomColors", "Location", "12,50"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomTeams", "Location", "12,72"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomStarts", "Location", "12,94"); + AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxUseTeamStartMappings", "Location", "12,130"); + AddKeyWithLog(playerExtraOptionsPanelIni, "btnHelp", "Location", "160,130"); + AddKeyWithLog(playerExtraOptionsPanelIni, "lblPreset", "Location", "12,156"); + AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Location", "65,154"); + AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Size", "157,21"); + AddKeyWithLog(playerExtraOptionsPanelIni, "teamStartMappingsPanel", "Location", "12,189"); + playerExtraOptionsPanelIni.WriteIniFile(); + + // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false + var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); + AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); + genericWindowIni.WriteIniFile(); + + // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} + IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "OptionsWindow.ini")); + foreach (var section in optionsWindowIni.GetSections()) + foreach (var pair in optionsWindowIni.GetSection(section).Keys) + { + if (pair.Value.Contains(":CustomSettingFileCheckBox")) + { + pair.Value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox"); + continue; + } + + if (pair.Value.Contains(":CustomSettingFileDropDown")) + { + pair.Value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown"); + continue; + } + } + + // Add new sections into OptionsWindow.ini + AddKeyWithLog(optionsWindowIni, "lblPlayerName", "Location", "12,195"); + AddKeyWithLog(optionsWindowIni, "tbPlayerName", "Location", "113,193"); + AddKeyWithLog(optionsWindowIni, "lblNotice", "Location", "12,220"); + AddKeyWithLog(optionsWindowIni, "btnConfigureHotkeys", "Location", "12,290"); + AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Location", "12,138"); + AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); + AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); + AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); + AddKeyWithLog(optionsWindowIni, "lblAllPrivateMessagesFrom", "Location", "276,138"); + AddKeyWithLog(optionsWindowIni, "ddAllowPrivateMessagesFrom", "Location", "470,137"); + AddKeyWithLog(optionsWindowIni, "gameListPanel", "Location", "0,200"); + AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Location", "407,213"); + AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Size", "133,23"); + + optionsWindowIni.WriteIniFile(); + // Add new texture files + + return this; + } +} diff --git a/MigrationTool/Patch_v2_11_1.cs b/MigrationTool/Patch_v2_11_1.cs new file mode 100644 index 000000000..706cbb825 --- /dev/null +++ b/MigrationTool/Patch_v2_11_1.cs @@ -0,0 +1,27 @@ +using Rampastring.Tools; + +namespace MigrationTool; + +internal class Patch_v2_11_1 : Patch +{ + public Patch_v2_11_1 (string clientPath) : base (clientPath) + { + ClientVersion = Version.v2_11_1; + } + + public override Patch Apply() + { + base.Apply(); + + // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions, MaximumRenderWidth, MaximumRenderHeight + IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "ClientDefinitions.ini")); + AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderWidth", "1280"); + AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderHeight", "720"); + var width = clientDefsIni.GetStringValue("Settings", "MaximumRenderWidth", "1280"); + var height = clientDefsIni.GetStringValue("Settings", "MaximumRenderHeight", "720"); + AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", $"{width}x{height}"); + clientDefsIni.WriteIniFile(); + + return this; + } +} diff --git a/MigrationTool/Patch_v2_11_2.cs b/MigrationTool/Patch_v2_11_2.cs new file mode 100644 index 000000000..6073589bf --- /dev/null +++ b/MigrationTool/Patch_v2_11_2.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Rampastring.Tools; + +namespace MigrationTool; + +internal class Patch_v2_11_2 : Patch +{ + public Patch_v2_11_2(string clientPath) : base(clientPath) + { + ClientVersion = Version.v2_11_2; + } + + public override Patch Apply() + { + base.Apply(); + + // Remove ClientUpdater.xml and SecondStageUpdater.xml + IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "ClientDefinitions.ini")); + var listExtraXMLs = new List(2) { "ClientUpdater.xml", "SecondStageUpdater.xml" }; + Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); + + foreach (var extraXml in listExtraXMLs) + { + Directory.GetFiles(ResouresDir.FullName, extraXml, SearchOption.AllDirectories) + .ToList() + .ForEach(elem => SafePath.DeleteFileIfExists(elem)); + } + + // Add ClientDefinitions.ini->[Settings]->ShowDevelopmentBuildWarnings + AddKeyWithLog(clientDefsIni, "Settings", "ShowDevelopmentBuildWarnings", "true"); + clientDefsIni.WriteIniFile(); + + return this; + } +} diff --git a/MigrationTool/Patch_v2_12_1.cs b/MigrationTool/Patch_v2_12_1.cs new file mode 100644 index 000000000..e5be64755 --- /dev/null +++ b/MigrationTool/Patch_v2_12_1.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Rampastring.Tools; + +namespace MigrationTool; + +internal class Patch_v2_12_1 : Patch +{ + public Patch_v2_12_1(string clientPath) : base(clientPath) + { + ClientVersion = Version.v2_12_1; + } + public override Patch Apply() + { + base.Apply(); + + // And add ClientDefinitions.ini->[Settings]->ClientGameType + IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "ClientDefinitions.ini")); + AddKeyWithLog(clientDefsIni, "Settings", "ClientGameType", Game.ToString()); + clientDefsIni.WriteIniFile(); + + return this; + } +} diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index a0c057886..b270fad62 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -7,26 +7,26 @@ namespace MigrationTool; -internal sealed class Program +public enum Version { - private enum Version - { - Begin, - v2_11_0, - v2_11_1, - v2_11_2, - v2_12_1, - Latest, - End - } + Begin, + v2_11_0, + v2_11_1, + v2_11_2, + v2_12_1, + Latest, + End +} - private enum ClientGameType - { - TS, - YR, - Ares - } +public enum ClientGameType +{ + TS, + YR, + Ares +} +internal sealed class Program +{ private static ConsoleColor defaultColor = Console.ForegroundColor; private static void Main(string[] args) @@ -68,7 +68,21 @@ private static void Main(string[] args) return; } - Migrate(arg); + Patch patch = null; + try + { + patch = new Patch_v2_11_0(arg).Apply(); + patch = new Patch_v2_11_1(arg).Apply(); + patch = new Patch_v2_11_2(arg).Apply(); + patch = new Patch_v2_12_1(arg).Apply(); + patch = new Patch_Latest(arg).Apply(); + } + catch (Exception ex) + { + Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: " + ex.Message, ConsoleColor.Red); + Log("Migration to the latest client version has been failed", ConsoleColor.Red); + } + break; case 0: default: @@ -100,195 +114,4 @@ Execute this file with path to the unmigrated client directory as first argument Console.WriteLine(text); } - - private static void AddKeyWithLog(IniFile src, string section, string key, string value) - { - if (src.KeyExists(section, key)) - { - Log($"Update {src.FileName}: Skip adding [{section}]->{key}, reason: already exist", ConsoleColor.Red); - } - else - { - Log($"Update {src.FileName}: Add [{section}]->{key}={value}", ConsoleColor.Green); - if (!src.SectionExists(section)) src.AddSection(section); - src.GetSection(section).AddKey(key, value); - } - } - - private static void Migrate(string path) - { - DirectoryInfo clientDir = SafePath.GetDirectory(path); - DirectoryInfo resouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(path, "Resources")); - - IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "ClientDefinitions.ini")); - IniFile gmLobbyBaseIni = null; - - // Predict client type by guessing game engine files - var clientGameType = ClientGameType.TS; - if (!SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists - && SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "gamemd-spawn.exe")).Exists) - { - clientGameType = ClientGameType.YR; - } - else if (SafePath.GetFile(SafePath.CombineFilePath(clientDir.FullName, "Ares.dll")).Exists) - { - clientGameType = ClientGameType.Ares; - } - - for (int currentVersion = (int)Version.Begin; currentVersion != (int)Version.End; currentVersion++) - { - try - { - switch ((Version)currentVersion) - { - case (Version.v2_11_0): - // Remove Rampastring.Tools from Resources directory (not recursive) - Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.dll"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.pdb"); - SafePath.DeleteFileIfExists(resouresDir.FullName, "Rampastring.Tools.xml"); - - // Add GlobalThemeSettings.ini - IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GlobalThemeSettings.ini")); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_LBL_HEIGHT", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_CONTROL_HEIGHT", "21"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_BUTTON_HEIGHT", "23"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_WIDTH_133", "133"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_WIDTH", "18"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_HEIGHT", "22"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_TOP", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_BOTTOM", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_SIDES", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_SPACING", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LABEL_SPACING", "6"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "CHECKBOX_SPACING", "24"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_EMPTY_SPACE_SIDES", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_PANEL_SPACING", "10"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_COLUMN_SPACING", "160"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_ROW_SPACING", "6"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_WIDTH", "132"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_HEIGHT", "22"); - globalThemeSettingsIni.WriteIniFile(); - - // Add PlayerExtraOptionsPanel.ini - IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "PlayerExtraOptionsPanel.ini")); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Location", "220,0"); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Size", "18,18"); - AddKeyWithLog(playerExtraOptionsPanelIni, "lblHeader", "Location", "12,6"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomSides", "Location", "12,28"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomColors", "Location", "12,50"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomTeams", "Location", "12,72"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomStarts", "Location", "12,94"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxUseTeamStartMappings", "Location", "12,130"); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnHelp", "Location", "160,130"); - AddKeyWithLog(playerExtraOptionsPanelIni, "lblPreset", "Location", "12,156"); - AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Location", "65,154"); - AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Size", "157,21"); - AddKeyWithLog(playerExtraOptionsPanelIni, "teamStartMappingsPanel", "Location", "12,189"); - playerExtraOptionsPanelIni.WriteIniFile(); - - // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false - var genericWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GenericWindow.ini")); - AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); - genericWindowIni.WriteIniFile(); - - // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} - IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "OptionsWindow.ini")); - foreach (var section in optionsWindowIni.GetSections()) - foreach (var pair in optionsWindowIni.GetSection(section).Keys) - { - if (pair.Value.Contains(":CustomSettingFileCheckBox")) - { - pair.Value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox"); - continue; - } - - if (pair.Value.Contains(":CustomSettingFileDropDown")) - { - pair.Value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown"); - continue; - } - } - - // Add new sections into OptionsWindow.ini - AddKeyWithLog(optionsWindowIni, "lblPlayerName", "Location", "12,195"); - AddKeyWithLog(optionsWindowIni, "tbPlayerName", "Location", "113,193"); - AddKeyWithLog(optionsWindowIni, "lblNotice", "Location", "12,220"); - AddKeyWithLog(optionsWindowIni, "btnConfigureHotkeys", "Location", "12,290"); - AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Location", "12,138"); - AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); - AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); - AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); - AddKeyWithLog(optionsWindowIni, "lblAllPrivateMessagesFrom", "Location", "276,138"); - AddKeyWithLog(optionsWindowIni, "ddAllowPrivateMessagesFrom", "Location", "470,137"); - AddKeyWithLog(optionsWindowIni, "gameListPanel", "Location", "0,200"); - AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Location", "407,213"); - AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Size", "133,23"); - - optionsWindowIni.WriteIniFile(); - // Add new texture files - - continue; - - case (Version.v2_11_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.1.0 - // Add ClientDefinitions.ini->[Settings]->RecommendedResolutions, MaximumRenderWidth, MaximumRenderHeight - AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderWidth", "1280"); - AddKeyWithLog(clientDefsIni, "Settings", "MaximumRenderHeight", "720"); - var width = clientDefsIni.GetStringValue("Settings", "MaximumRenderWidth", "1280"); - var height = clientDefsIni.GetStringValue("Settings", "MaximumRenderHeight", "720"); - AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", $"{width}x{height}"); - clientDefsIni.WriteIniFile(); - continue; - - case (Version.v2_11_2): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.11.2.0 - // Remove ClientUpdater.xml and SecondStageUpdater.xml - var listExtraXMLs = new List(2) { "ClientUpdater.xml", "SecondStageUpdater.xml" }; - Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); - - foreach (var extraXml in listExtraXMLs) - { - Directory.GetFiles(resouresDir.FullName, extraXml, SearchOption.AllDirectories) - .ToList() - .ForEach(elem => SafePath.DeleteFileIfExists(elem)); - } - - // Add ClientDefinitions.ini->[Settings]->ShowDevelopmentBuildWarnings - AddKeyWithLog(clientDefsIni, "Settings", "ShowDevelopmentBuildWarnings", "true"); - clientDefsIni.WriteIniFile(); - continue; - - case (Version.v2_12_1): // https://github.com/CnCNet/xna-cncnet-client/releases/tag/2.12.1 - // And add ClientDefinitions.ini->[Settings]->ClientGameType - string cgt = clientGameType.ToString(); - - AddKeyWithLog(clientDefsIni, "Settings", "ClientGameType", cgt); - clientDefsIni.WriteIniFile(); - continue; - - case (Version.Latest): - // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode - gmLobbyBaseIni ??= new IniFile(SafePath.CombineFilePath(resouresDir.FullName, "GameLobbyBase.ini")); - string ddPlayerColor = nameof(ddPlayerColor); - foreach (var n in Enumerable.Range(0, 8)) - { - if (!gmLobbyBaseIni.SectionExists(ddPlayerColor + n)) - gmLobbyBaseIni.AddSection(ddPlayerColor + n); - - AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + n, "ItemsDrawMode", "Text"); - gmLobbyBaseIni.WriteIniFile(); - } - continue; - - default: - continue; - } - } - catch (Exception ex) - { - Log($"Unable to apply migration patch for client version {((Version)currentVersion).ToString().Replace('_', '.')} due to internal error. Message: " + ex.Message, ConsoleColor.Red); - Log("Migration to the latest client version has been failed", ConsoleColor.Red); - break; - } - } - } } From 6281d7d055147f10e902f396e3e78b465119d9b6 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 16:08:02 +0300 Subject: [PATCH 19/81] Adjustments in enums --- MigrationTool/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index b270fad62..70585f7e9 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -9,20 +9,18 @@ namespace MigrationTool; public enum Version { - Begin, v2_11_0, v2_11_1, v2_11_2, v2_12_1, Latest, - End } public enum ClientGameType { TS, YR, - Ares + Ares, } internal sealed class Program From e7d8a1f4855ce14886061a5ca1661a09ac748b2e Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 16:27:23 +0300 Subject: [PATCH 20/81] Rework logging --- MigrationTool/Patch.cs | 17 ++--------- MigrationTool/Patch_v2_11_0.cs | 2 +- MigrationTool/Patch_v2_11_2.cs | 2 +- MigrationTool/Program.cs | 54 ++++++++++++---------------------- 4 files changed, 24 insertions(+), 51 deletions(-) diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 00c64b8ce..35e39edc6 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -12,8 +12,6 @@ internal abstract class Patch public DirectoryInfo ClientDir { get; protected set; } public DirectoryInfo ResouresDir { get; protected set; } - protected static ConsoleColor defaultColor = Console.ForegroundColor; - public Patch(string clientPath) { ClientDir = SafePath.GetDirectory(clientPath); @@ -34,7 +32,7 @@ public Patch(string clientPath) public virtual Patch Apply() { - Log($"Applying patch for client version {ClientVersion.ToString().Replace('_', '.')}...", ConsoleColor.White); + Logger.Log($"Applying patch for client version {ClientVersion.ToString().Replace('_', '.')}..."); return this; } @@ -42,25 +40,16 @@ public void AddKeyWithLog(IniFile src, string section, string key, string value) { if (src.KeyExists(section, key)) { - Log($"Update {src.FileName}: Skip adding [{section}]->{key}, reason: already exist", ConsoleColor.Red); + Logger.Log($"Update {src.FileName}: Skip adding [{section}]->{key}, reason: already exist"); } else { - Log($"Update {src.FileName}: Add [{section}]->{key}={value}", ConsoleColor.Green); + Logger.Log($"Update {src.FileName}: Add [{section}]->{key}={value}"); if (!src.SectionExists(section)) src.AddSection(section); src.GetSection(section).AddKey(key, value); } } - protected void Log(string text, ConsoleColor? color = null, bool echoToConsole = true) - { - Console.ForegroundColor = color ?? defaultColor; - Logger.Log(text); - - if (echoToConsole) - Console.WriteLine(text); - } - public bool TryApply() { try diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 78a9a2550..e9ec9b9a1 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -20,7 +20,7 @@ public override Patch Apply() base.Apply(); // Remove Rampastring.Tools from Resources directory (not recursive) - Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); + Logger.Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.dll"); SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.pdb"); SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.xml"); diff --git a/MigrationTool/Patch_v2_11_2.cs b/MigrationTool/Patch_v2_11_2.cs index 6073589bf..49032aea3 100644 --- a/MigrationTool/Patch_v2_11_2.cs +++ b/MigrationTool/Patch_v2_11_2.cs @@ -20,7 +20,7 @@ public override Patch Apply() // Remove ClientUpdater.xml and SecondStageUpdater.xml IniFile clientDefsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "ClientDefinitions.ini")); var listExtraXMLs = new List(2) { "ClientUpdater.xml", "SecondStageUpdater.xml" }; - Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); + Logger.Log("Remove ClientUpdater.xml and SecondStageUpdater.xml"); foreach (var extraXml in listExtraXMLs) { diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 70585f7e9..5c48a8a1b 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -25,7 +25,13 @@ public enum ClientGameType internal sealed class Program { - private static ConsoleColor defaultColor = Console.ForegroundColor; + private const string errMsg = "Unknown arguments detected. Use -h argument to print help information."; + private const string helpMsg = + """ + CnCNet Client Migration Tool. + + Execute this file with path to the unmigrated client directory as first argument. + """; private static void Main(string[] args) { @@ -37,6 +43,7 @@ private static void Main(string[] args) Logger.WriteToConsole = false; Logger.Log("CnCNet Client Migration Tool"); Logger.Log("Version: " + GitVersionInformation.AssemblySemVer); + Logger.WriteToConsole = true; // Check arguments switch (args.Length) @@ -50,66 +57,43 @@ private static void Main(string[] args) || arg == "/?" || arg == "/h") { - PrintHelp(); + Console.WriteLine(helpMsg); return; } if (!SafePath.GetDirectory(arg).Exists) { - PrintArgsError(); + Console.WriteLine(errMsg); return; } if (!SafePath.GetFile(SafePath.CombineFilePath(arg, "Resources", "ClientDefinitions.ini")).Exists) { - Log("Unable to find Resources/ClientDefinitions.ini. Migration aborted.", ConsoleColor.Red); + Logger.Log("Unable to find Resources/ClientDefinitions.ini. Migration aborted."); return; } Patch patch = null; try { - patch = new Patch_v2_11_0(arg).Apply(); - patch = new Patch_v2_11_1(arg).Apply(); - patch = new Patch_v2_11_2(arg).Apply(); - patch = new Patch_v2_12_1(arg).Apply(); + patch = new Patch_v2_11_0(arg).Apply(); Console.WriteLine(""); + patch = new Patch_v2_11_1(arg).Apply(); Console.WriteLine(""); + patch = new Patch_v2_11_2(arg).Apply(); Console.WriteLine(""); + patch = new Patch_v2_12_1(arg).Apply(); Console.WriteLine(""); patch = new Patch_Latest(arg).Apply(); } catch (Exception ex) { - Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: " + ex.Message, ConsoleColor.Red); - Log("Migration to the latest client version has been failed", ConsoleColor.Red); + Logger.Log(""); + Logger.Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: " + ex.Message); + Logger.Log("Migration to the latest client version has been failed"); } break; case 0: default: - PrintArgsError(); + Console.WriteLine(errMsg); break; } } - - private static void Log(string text, ConsoleColor? color = null, bool echoToConsole = true) - { - Console.ForegroundColor = color ?? defaultColor; - Logger.Log(text); - - if (echoToConsole) - Console.WriteLine(text); - } - - private static void PrintArgsError() - => Log("Unknown arguments detected. Use -h argument to print help information", ConsoleColor.Red); - - private static void PrintHelp() - { - string text = - """ - CnCNet Client Migration Tool. - - Execute this file with path to the unmigrated client directory as first argument. - """; - - Console.WriteLine(text); - } } From 6edabf152313886301553c5704f1deb9eed4522f Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 17:09:05 +0300 Subject: [PATCH 21/81] Refactoring patch v2.11.0 --- MigrationTool/Patch_v2_11_0.cs | 121 ++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index e9ec9b9a1..a060279ee 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; @@ -25,48 +26,9 @@ public override Patch Apply() SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.pdb"); SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.xml"); - // Add GlobalThemeSettings.ini - IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GlobalThemeSettings.ini")); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_LBL_HEIGHT", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_CONTROL_HEIGHT", "21"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "DEFAULT_BUTTON_HEIGHT", "23"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_WIDTH_133", "133"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_WIDTH", "18"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "OPEN_BUTTON_HEIGHT", "22"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_TOP", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_BOTTOM", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "EMPTY_SPACE_SIDES", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "BUTTON_SPACING", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LABEL_SPACING", "6"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "CHECKBOX_SPACING", "24"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_EMPTY_SPACE_SIDES", "12"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "LOBBY_PANEL_SPACING", "10"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_COLUMN_SPACING", "160"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_ROW_SPACING", "6"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_WIDTH", "132"); - AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", "GAME_OPTION_DD_HEIGHT", "22"); - globalThemeSettingsIni.WriteIniFile(); - - // Add PlayerExtraOptionsPanel.ini - IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "PlayerExtraOptionsPanel.ini")); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Location", "220,0"); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnClose", "Size", "18,18"); - AddKeyWithLog(playerExtraOptionsPanelIni, "lblHeader", "Location", "12,6"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomSides", "Location", "12,28"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomColors", "Location", "12,50"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomTeams", "Location", "12,72"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxForceRandomStarts", "Location", "12,94"); - AddKeyWithLog(playerExtraOptionsPanelIni, "chkBoxUseTeamStartMappings", "Location", "12,130"); - AddKeyWithLog(playerExtraOptionsPanelIni, "btnHelp", "Location", "160,130"); - AddKeyWithLog(playerExtraOptionsPanelIni, "lblPreset", "Location", "12,156"); - AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Location", "65,154"); - AddKeyWithLog(playerExtraOptionsPanelIni, "ddTeamStartMappingPreset", "Size", "157,21"); - AddKeyWithLog(playerExtraOptionsPanelIni, "teamStartMappingsPanel", "Location", "12,189"); - playerExtraOptionsPanelIni.WriteIniFile(); - // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); - AddKeyWithLog(playerExtraOptionsPanelIni, "GenericWindow", "DrawBorders", "false"); + AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "false"); genericWindowIni.WriteIniFile(); // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} @@ -86,23 +48,74 @@ public override Patch Apply() continue; } } - + // Add new sections into OptionsWindow.ini - AddKeyWithLog(optionsWindowIni, "lblPlayerName", "Location", "12,195"); - AddKeyWithLog(optionsWindowIni, "tbPlayerName", "Location", "113,193"); - AddKeyWithLog(optionsWindowIni, "lblNotice", "Location", "12,220"); - AddKeyWithLog(optionsWindowIni, "btnConfigureHotkeys", "Location", "12,290"); - AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Location", "12,138"); - AddKeyWithLog(optionsWindowIni, "chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); - AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); - AddKeyWithLog(optionsWindowIni, "chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); - AddKeyWithLog(optionsWindowIni, "lblAllPrivateMessagesFrom", "Location", "276,138"); - AddKeyWithLog(optionsWindowIni, "ddAllowPrivateMessagesFrom", "Location", "470,137"); - AddKeyWithLog(optionsWindowIni, "gameListPanel", "Location", "0,200"); - AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Location", "407,213"); - AddKeyWithLog(optionsWindowIni, "btnForceUpdate", "Size", "133,23"); + { + var addKey = (string section, string key, string value) => AddKeyWithLog(optionsWindowIni, section, key, value); + addKey("lblPlayerName", "Location", "12,195"); + addKey("tbPlayerName", "Location", "113,193"); + addKey("lblNotice", "Location", "12,220"); + addKey("btnConfigureHotkeys", "Location", "12,290"); + addKey("chkDisablePrivateMessagePopup", "Location", "12,138"); + addKey("chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); + addKey("chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); + addKey("chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); + addKey("lblAllPrivateMessagesFrom", "Location", "276,138"); + addKey("ddAllowPrivateMessagesFrom", "Location", "470,137"); + addKey("gameListPanel", "Location", "0,200"); + addKey("btnForceUpdate", "Location", "407,213"); + addKey("btnForceUpdate", "Size", "133,23"); + } optionsWindowIni.WriteIniFile(); + + // Add GlobalThemeSettings.ini + if (!File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, "GlobalThemeSettings.ini"))) + { + IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GlobalThemeSettings.ini")); + var addKey = (string key, string value) => AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", key, value); + addKey("DEFAULT_LBL_HEIGHT", "12"); + addKey("DEFAULT_CONTROL_HEIGHT", "21"); + addKey("DEFAULT_BUTTON_HEIGHT", "23"); + addKey("BUTTON_WIDTH_133", "133"); + addKey("OPEN_BUTTON_WIDTH", "18"); + addKey("OPEN_BUTTON_HEIGHT", "22"); + addKey("EMPTY_SPACE_TOP", "12"); + addKey("EMPTY_SPACE_BOTTOM", "12"); + addKey("EMPTY_SPACE_SIDES", "12"); + addKey("BUTTON_SPACING", "12"); + addKey("LABEL_SPACING", "6"); + addKey("CHECKBOX_SPACING", "24"); + addKey("LOBBY_EMPTY_SPACE_SIDES", "12"); + addKey("LOBBY_PANEL_SPACING", "10"); + addKey("GAME_OPTION_COLUMN_SPACING", "160"); + addKey("GAME_OPTION_ROW_SPACING", "6"); + addKey("GAME_OPTION_DD_WIDTH", "132"); + addKey("GAME_OPTION_DD_HEIGHT", "22"); + globalThemeSettingsIni.WriteIniFile(); + } + + // Add PlayerExtraOptionsPanel.ini + if (!File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, "PlayerExtraOptionsPanel.ini"))) + { + IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "PlayerExtraOptionsPanel.ini")); + var addKey = (string section, string key, string value) => AddKeyWithLog(playerExtraOptionsPanelIni, section, key, value); + addKey("btnClose", "Location", "220,0"); + addKey("btnClose", "Size", "18,18"); + addKey("lblHeader", "Location", "12,6"); + addKey("chkBoxForceRandomSides", "Location", "12,28"); + addKey("chkBoxForceRandomColors", "Location", "12,50"); + addKey("chkBoxForceRandomTeams", "Location", "12,72"); + addKey("chkBoxForceRandomStarts", "Location", "12,94"); + addKey("chkBoxUseTeamStartMappings", "Location", "12,130"); + addKey("btnHelp", "Location", "160,130"); + addKey("lblPreset", "Location", "12,156"); + addKey("ddTeamStartMappingPreset", "Location", "65,154"); + addKey("ddTeamStartMappingPreset", "Size", "157,21"); + addKey("teamStartMappingsPanel", "Location", "12,189"); + playerExtraOptionsPanelIni.WriteIniFile(); + } + // Add new texture files return this; From 82a7765accfd8c6c1715a514902dd350ac322efb Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 18:03:34 +0300 Subject: [PATCH 22/81] Start migration configs from 2.8.x.x --- MigrationTool/Patch_v2_11_0.cs | 39 ++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index a060279ee..b36353dd8 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -66,7 +66,6 @@ public override Patch Apply() addKey("btnForceUpdate", "Location", "407,213"); addKey("btnForceUpdate", "Size", "133,23"); } - optionsWindowIni.WriteIniFile(); // Add GlobalThemeSettings.ini @@ -115,7 +114,43 @@ public override Patch Apply() addKey("teamStartMappingsPanel", "Location", "12,189"); playerExtraOptionsPanelIni.WriteIniFile(); } - + + // Rework skirmish/lan/cncnet lobbies ini's + if (!File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini"))) + { + // Old configs + IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "SkirmishLobby.ini")); + IniFile multiplayerGameLobby_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "MultiplayerGameLobby.ini")); + IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); + + // New configs + IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini")); + IniFile skirmishLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "SkirmishLobby_New.ini")); + IniFile multiplayerGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "MultiplayerGameLobby_New.ini")); + IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); + IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); + + // Add inheritance + AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow"); + AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", "GameLobbyBase"); + AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", "GameLobbyBase"); + AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", "MultiplayerGameLobby"); + AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", "MultiplayerGameLobby"); + + // Configure CnCNetGameLobby.ini + AddKeyWithLog(cncnetGameLobbyIni, "MultiplayerGameLobby", "$CCMP99", "btnChangeTunnel:XNAClientButton"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); + + // Replace new configs and delete placeholders + skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); + multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobby_old.FileName)); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, "SkirmishLobby_New.ini")); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, "MultiplayerGameLobby_New.ini")); + } + // Add new texture files return this; From 60fccd087b7f9b97479803831606ba41db78dfc3 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 6 Jul 2025 23:36:02 +0300 Subject: [PATCH 23/81] Add experimental config transfer from `GameOptions.ini` to `GameLobbyBase.ini` --- MigrationTool/Patch_v2_11_0.cs | 56 ++++++++++++++++++++++++++++++++-- MigrationTool/Program.cs | 2 +- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index b36353dd8..33e78c7fb 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -29,6 +29,8 @@ public override Patch Apply() // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "false"); + if (genericWindowIni.SectionExists("ExtraControls")) + genericWindowIni.GetSection("ExtraControls").SectionName = "$ExtraControls"; genericWindowIni.WriteIniFile(); // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} @@ -69,7 +71,6 @@ public override Patch Apply() optionsWindowIni.WriteIniFile(); // Add GlobalThemeSettings.ini - if (!File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, "GlobalThemeSettings.ini"))) { IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GlobalThemeSettings.ini")); var addKey = (string key, string value) => AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", key, value); @@ -95,7 +96,6 @@ public override Patch Apply() } // Add PlayerExtraOptionsPanel.ini - if (!File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, "PlayerExtraOptionsPanel.ini"))) { IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "PlayerExtraOptionsPanel.ini")); var addKey = (string section, string key, string value) => AddKeyWithLog(playerExtraOptionsPanelIni, section, key, value); @@ -130,6 +130,9 @@ public override Patch Apply() IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); + // Add random color to the GameOptions.ini + AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); + // Add inheritance AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow"); AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", "GameLobbyBase"); @@ -137,6 +140,52 @@ public override Patch Apply() AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", "MultiplayerGameLobby"); AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", "MultiplayerGameLobby"); + // Configure GameLobbyBase.ini + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionLocationX", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionLocationX", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionLocationY", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionLocationY", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionVerticalMargin", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionVerticalMargin", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionHorizontalMargin", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionHorizontalMargin", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionCaptionLocationY", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionCaptionLocationY", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerNameWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerNameWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "SideWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "SideWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "ColorWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "ColorWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "StartWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "StartWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "TeamWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "TeamWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "$CC-GOP", "GameOptionsPanel:XNAPanel"); + + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "SolidColorBackgroundTexture", "0,0,0,192"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "DrawBorders", "yes"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Width", "427"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Height", "266"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$X", "getWidth($ParentControl) - getWidth($Self) - EMPTY_SPACE_SIDES"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Y", "EMPTY_SPACE_TOP"); + + int outerIndex = 0; + foreach (var itemName in new string[] { "CheckBoxes", "DropDowns", "Labels" }) + { + string itemType = itemName switch + { + "CheckBoxes" => "GameLobbyCheckBox", + "DropDowns" => "GameLobbyDropDown", + "Labels" => "XNALabel", + _ => throw new Exception($"Unknown type of elements {itemName}") + }; + + var items = gameOptionsIni.GetStringValue("SkirmishLobby", itemName, string.Empty).Split(','); + for (int i = 0; i < items.Length; i++) + { + var item = items[i]; + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", $"$CC_{i + outerIndex}", $"{item}:{itemType}"); + gameOptionsIni.GetSectionKeys(item) + .ForEach(key => AddKeyWithLog(gameLobbyBaseIni, item, key, gameOptionsIni.GetStringValue(item, key, string.Empty))); + } + + outerIndex += items.Length; + } + + // Configure MultiplayerGameLobby.ini + + // Configure CnCNetGameLobby.ini AddKeyWithLog(cncnetGameLobbyIni, "MultiplayerGameLobby", "$CCMP99", "btnChangeTunnel:XNAClientButton"); AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); @@ -147,6 +196,9 @@ public override Patch Apply() // Replace new configs and delete placeholders skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobby_old.FileName)); + gameLobbyBaseIni.WriteIniFile(); + lanGameLobbyIni.WriteIniFile(); + cncnetGameLobbyIni.WriteIniFile(); SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, "SkirmishLobby_New.ini")); SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, "MultiplayerGameLobby_New.ini")); } diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 5c48a8a1b..d3020a0dd 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -85,7 +85,7 @@ private static void Main(string[] args) catch (Exception ex) { Logger.Log(""); - Logger.Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: " + ex.Message); + Logger.Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: {ex.Message}"); Logger.Log("Migration to the latest client version has been failed"); } From 40e92db41f5da5fe36ebc77e06ce78cef82997c6 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 7 Jul 2025 11:13:30 +0300 Subject: [PATCH 24/81] Transfering keys optimization --- MigrationTool/Patch.cs | 20 ++++++++++++++++---- MigrationTool/Patch_v2_11_0.cs | 9 ++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 35e39edc6..9dc37a5c7 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -1,10 +1,10 @@ -using System; -using System.IO; -using System.Text.RegularExpressions; +using System.IO; using Rampastring.Tools; namespace MigrationTool; +#nullable enable + internal abstract class Patch { public Version ClientVersion { get; protected set; } @@ -36,7 +36,7 @@ public virtual Patch Apply() return this; } - public void AddKeyWithLog(IniFile src, string section, string key, string value) + public Patch AddKeyWithLog(IniFile src, string section, string key, string value) { if (src.KeyExists(section, key)) { @@ -48,6 +48,18 @@ public void AddKeyWithLog(IniFile src, string section, string key, string value) if (!src.SectionExists(section)) src.AddSection(section); src.GetSection(section).AddKey(key, value); } + + return this; + } + + public Patch TransferKeys(IniFile srcIni, string srcSection, IniFile desIni, string? desSection = null) + { + desSection ??= srcSection; + + srcIni.GetSectionKeys(srcSection) + .ForEach(key => AddKeyWithLog(desIni, desSection, key, srcIni.GetStringValue(srcSection, key, string.Empty))); + + return this; } public bool TryApply() diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 33e78c7fb..9f1cdd8c4 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Rampastring.Tools; - namespace MigrationTool; internal class Patch_v2_11_0 : Patch @@ -176,13 +175,17 @@ public override Patch Apply() { var item = items[i]; AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", $"$CC_{i + outerIndex}", $"{item}:{itemType}"); - gameOptionsIni.GetSectionKeys(item) - .ForEach(key => AddKeyWithLog(gameLobbyBaseIni, item, key, gameOptionsIni.GetStringValue(item, key, string.Empty))); + TransferKeys(gameOptionsIni, item, gameLobbyBaseIni); } outerIndex += items.Length; } + foreach (var section in skirmishLobbyIni_old.GetSections()) + { + + } + // Configure MultiplayerGameLobby.ini From 9ee8b297c6cd439df27af2a04d5e1b664086c2d5 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 7 Jul 2025 12:07:30 +0300 Subject: [PATCH 25/81] Refactoring ini-files naming --- MigrationTool/Patch_v2_11_0.cs | 99 +++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 9f1cdd8c4..e5e7e959f 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -114,43 +114,52 @@ public override Patch Apply() playerExtraOptionsPanelIni.WriteIniFile(); } + string MultiplayerGameLobby = nameof(MultiplayerGameLobby); + string SkirmishLobby = nameof(SkirmishLobby); + string ExtraControls = nameof(ExtraControls); + string GameLobbyBase = nameof(GameLobbyBase); + // Rework skirmish/lan/cncnet lobbies ini's - if (!File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini"))) + if (File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini"))) + { + Logger.Log($"Update lobbies has been aborted, {GameLobbyBase}.ini already exists"); + } + else { // Old configs - IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "SkirmishLobby.ini")); - IniFile multiplayerGameLobby_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "MultiplayerGameLobby.ini")); - IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); + IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); + IniFile multiplayerGameLobby_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); + IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); // New configs - IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini")); - IniFile skirmishLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "SkirmishLobby_New.ini")); - IniFile multiplayerGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "MultiplayerGameLobby_New.ini")); - IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); - IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); + IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini")); + IniFile skirmishLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}_New.ini")); + IniFile multiplayerGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}_New.ini")); + IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); + IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); // Add random color to the GameOptions.ini AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); // Add inheritance AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow"); - AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", "GameLobbyBase"); - AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", "GameLobbyBase"); - AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", "MultiplayerGameLobby"); - AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", "MultiplayerGameLobby"); + AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}"); + AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}"); + AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}"); + AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}"); // Configure GameLobbyBase.ini - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionLocationX", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionLocationX", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionLocationY", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionLocationY", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionVerticalMargin", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionVerticalMargin", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionHorizontalMargin", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionHorizontalMargin", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerOptionCaptionLocationY", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerOptionCaptionLocationY", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "PlayerNameWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "PlayerNameWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "SideWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "SideWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "ColorWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "ColorWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "StartWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "StartWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "TeamWidth", gameOptionsIni.GetStringValue("SkirmishLobby", "TeamWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, "SkirmishLobby", "$CC-GOP", "GameOptionsPanel:XNAPanel"); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationX", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationX", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationY", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionVerticalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionVerticalMargin", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionHorizontalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionHorizontalMargin", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionCaptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionCaptionLocationY", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerNameWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerNameWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "SideWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "SideWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "ColorWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "ColorWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "StartWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "StartWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "TeamWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "TeamWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-GOP", "GameOptionsPanel:XNAPanel"); AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "SolidColorBackgroundTexture", "0,0,0,192"); AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "DrawBorders", "yes"); @@ -159,6 +168,7 @@ public override Patch Apply() AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$X", "getWidth($ParentControl) - getWidth($Self) - EMPTY_SPACE_SIDES"); AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Y", "EMPTY_SPACE_TOP"); + // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[SkirmishLobby] int outerIndex = 0; foreach (var itemName in new string[] { "CheckBoxes", "DropDowns", "Labels" }) { @@ -170,7 +180,7 @@ public override Patch Apply() _ => throw new Exception($"Unknown type of elements {itemName}") }; - var items = gameOptionsIni.GetStringValue("SkirmishLobby", itemName, string.Empty).Split(','); + var items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split(','); for (int i = 0; i < items.Length; i++) { var item = items[i]; @@ -181,29 +191,46 @@ public override Patch Apply() outerIndex += items.Length; } - foreach (var section in skirmishLobbyIni_old.GetSections()) - { + // Transfer SkirmishLobby.ini->[ExtraControls] to SkirmishLobby.ini->[$ExtraControls] + if (skirmishLobbyIni_old.SectionExists($"{ExtraControls}")) + skirmishLobbyIni_old.GetSection($"{ExtraControls}").SectionName = $"${ExtraControls}"; + + TransferKeys(skirmishLobbyIni_old, $"${ExtraControls}", skirmishLobbyIni); + foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) + { + var value = skirmishLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; + TransferKeys(skirmishLobbyIni_old, value, skirmishLobbyIni); } // Configure MultiplayerGameLobby.ini + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$BaseSection", $"{SkirmishLobby}"); + foreach (var key in gameLobbyBaseIni.GetSectionKeys($"{SkirmishLobby}").Where(elem => !elem.StartsWith("$"))) + { + var valueSkirmish = gameLobbyBaseIni.GetStringValue($"{SkirmishLobby}", key, string.Empty); + var valueMultiplayer = gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", key, string.Empty); + if (valueMultiplayer != valueSkirmish) + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", key, valueMultiplayer); + } // Configure CnCNetGameLobby.ini - AddKeyWithLog(cncnetGameLobbyIni, "MultiplayerGameLobby", "$CCMP99", "btnChangeTunnel:XNAClientButton"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); - - // Replace new configs and delete placeholders + AddKeyWithLog(cncnetGameLobbyIni, $"{MultiplayerGameLobby}", "$CCMP99", "btnChangeTunnel:XNAClientButton"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); + + // Replace old configs with new one, delete placeholders, delete redundant sections + gameLobbyBaseIni.RemoveSection($"{SkirmishLobby}"); + gameLobbyBaseIni.RemoveSection($"{MultiplayerGameLobby}"); skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobby_old.FileName)); gameLobbyBaseIni.WriteIniFile(); lanGameLobbyIni.WriteIniFile(); cncnetGameLobbyIni.WriteIniFile(); - SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, "SkirmishLobby_New.ini")); - SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, "MultiplayerGameLobby_New.ini")); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}_New.ini")); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}_New.ini")); } // Add new texture files From 0122f8aa2d453c69411219e9b56f2db0d9f545be Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 7 Jul 2025 12:42:27 +0300 Subject: [PATCH 26/81] Optimizations --- MigrationTool/Patch_v2_11_0.cs | 125 +++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 51 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index e5e7e959f..8c673c4ee 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -114,9 +114,6 @@ public override Patch Apply() playerExtraOptionsPanelIni.WriteIniFile(); } - string MultiplayerGameLobby = nameof(MultiplayerGameLobby); - string SkirmishLobby = nameof(SkirmishLobby); - string ExtraControls = nameof(ExtraControls); string GameLobbyBase = nameof(GameLobbyBase); // Rework skirmish/lan/cncnet lobbies ini's @@ -126,6 +123,11 @@ public override Patch Apply() } else { + string MultiplayerGameLobby = nameof(MultiplayerGameLobby); + string SkirmishLobby = nameof(SkirmishLobby); + string ExtraControls = nameof(ExtraControls); + List keysWithControlsInGameOptionsIni = new() { "CheckBoxes", "DropDowns", "Labels" }; + // Old configs IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); IniFile multiplayerGameLobby_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); @@ -149,53 +151,53 @@ public override Patch Apply() AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}"); // Configure GameLobbyBase.ini - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationX", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationX", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationY", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionVerticalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionVerticalMargin", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionHorizontalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionHorizontalMargin", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionCaptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionCaptionLocationY", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerNameWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerNameWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "SideWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "SideWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "ColorWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "ColorWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "StartWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "StartWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "TeamWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "TeamWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-GOP", "GameOptionsPanel:XNAPanel"); - - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "SolidColorBackgroundTexture", "0,0,0,192"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "DrawBorders", "yes"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Width", "427"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Height", "266"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$X", "getWidth($ParentControl) - getWidth($Self) - EMPTY_SPACE_SIDES"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Y", "EMPTY_SPACE_TOP"); - - // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[SkirmishLobby] - int outerIndex = 0; - foreach (var itemName in new string[] { "CheckBoxes", "DropDowns", "Labels" }) { - string itemType = itemName switch - { - "CheckBoxes" => "GameLobbyCheckBox", - "DropDowns" => "GameLobbyDropDown", - "Labels" => "XNALabel", - _ => throw new Exception($"Unknown type of elements {itemName}") - }; - - var items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split(','); - for (int i = 0; i < items.Length; i++) + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationX", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationX", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationY", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionVerticalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionVerticalMargin", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionHorizontalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionHorizontalMargin", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionCaptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionCaptionLocationY", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerNameWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerNameWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "SideWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "SideWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "ColorWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "ColorWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "StartWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "StartWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "TeamWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "TeamWidth", string.Empty)); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-GOP", "GameOptionsPanel:XNAPanel"); + + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "SolidColorBackgroundTexture", "0,0,0,192"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "DrawBorders", "yes"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Width", "427"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Height", "266"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$X", "getWidth($ParentControl) - getWidth($Self) - EMPTY_SPACE_SIDES"); + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Y", "EMPTY_SPACE_TOP"); + + // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[SkirmishLobby] + int outerIndex = 0; + foreach (var itemName in keysWithControlsInGameOptionsIni) { - var item = items[i]; - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", $"$CC_{i + outerIndex}", $"{item}:{itemType}"); - TransferKeys(gameOptionsIni, item, gameLobbyBaseIni); - } + string itemType = itemName switch + { + "CheckBoxes" => "GameLobbyCheckBox", + "DropDowns" => "GameLobbyDropDown", + "Labels" => "XNALabel", + _ => throw new Exception($"Unknown type of elements {itemName}") + }; + + var items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split(','); + for (int i = 0; i < items.Length; i++) + { + var item = items[i]; + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", $"$CC_{i + outerIndex}", $"{item}:{itemType}"); + TransferKeys(gameOptionsIni, item, gameLobbyBaseIni); + } - outerIndex += items.Length; + outerIndex += items.Length; + } } // Transfer SkirmishLobby.ini->[ExtraControls] to SkirmishLobby.ini->[$ExtraControls] if (skirmishLobbyIni_old.SectionExists($"{ExtraControls}")) - skirmishLobbyIni_old.GetSection($"{ExtraControls}").SectionName = $"${ExtraControls}"; - - TransferKeys(skirmishLobbyIni_old, $"${ExtraControls}", skirmishLobbyIni); + TransferKeys(skirmishLobbyIni_old, $"{ExtraControls}", skirmishLobbyIni, $"${ExtraControls}"); foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) { @@ -204,14 +206,21 @@ public override Patch Apply() } // Configure MultiplayerGameLobby.ini - AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$BaseSection", $"{SkirmishLobby}"); - foreach (var key in gameLobbyBaseIni.GetSectionKeys($"{SkirmishLobby}").Where(elem => !elem.StartsWith("$"))) { - var valueSkirmish = gameLobbyBaseIni.GetStringValue($"{SkirmishLobby}", key, string.Empty); - var valueMultiplayer = gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", key, string.Empty); + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$BaseSection", $"{SkirmishLobby}"); + + // Add keys if into [MultiplayerGameLobby] if values are new + foreach (var key in gameLobbyBaseIni.GetSectionKeys($"{SkirmishLobby}").Where(elem => !elem.StartsWith("$"))) + { + var valueSkirmish = gameLobbyBaseIni.GetStringValue($"{SkirmishLobby}", key, string.Empty); + var valueMultiplayer = gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", key, string.Empty); + + if (valueMultiplayer != valueSkirmish) + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", key, valueMultiplayer); + } + + // Add new controls for [MultiplayerGameLobby] only - if (valueMultiplayer != valueSkirmish) - AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", key, valueMultiplayer); } // Configure CnCNetGameLobby.ini @@ -222,10 +231,24 @@ public override Patch Apply() AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); // Replace old configs with new one, delete placeholders, delete redundant sections - gameLobbyBaseIni.RemoveSection($"{SkirmishLobby}"); - gameLobbyBaseIni.RemoveSection($"{MultiplayerGameLobby}"); + var sb = new StringBuilder(); + keysWithControlsInGameOptionsIni + .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty)).Append(',')); + keysWithControlsInGameOptionsIni + .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty)).Append(',')); + sb.ToString() + .Split(',') + .ToHashSet() + .Select(x => x = x.Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .ToList() + .ForEach(x => gameOptionsIni.RemoveSection(x)); + + gameOptionsIni.RemoveSection($"{SkirmishLobby}"); + gameOptionsIni.RemoveSection($"{MultiplayerGameLobby}"); skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobby_old.FileName)); + gameOptionsIni.WriteIniFile(); gameLobbyBaseIni.WriteIniFile(); lanGameLobbyIni.WriteIniFile(); cncnetGameLobbyIni.WriteIniFile(); From a66d2511d44aecf38068ea4990dc9ef41613b9e4 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 7 Jul 2025 17:43:32 +0300 Subject: [PATCH 27/81] Add more config transfer for `MultiplayerGameLobby.ini` --- MigrationTool/Patch_v2_11_0.cs | 39 ++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 8c673c4ee..e4a13a2da 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -126,7 +126,7 @@ public override Patch Apply() string MultiplayerGameLobby = nameof(MultiplayerGameLobby); string SkirmishLobby = nameof(SkirmishLobby); string ExtraControls = nameof(ExtraControls); - List keysWithControlsInGameOptionsIni = new() { "CheckBoxes", "DropDowns", "Labels" }; + List gameOptionsIniControlKeys = new() { "CheckBoxes", "DropDowns", "Labels" }; // Old configs IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); @@ -173,7 +173,7 @@ public override Patch Apply() // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[SkirmishLobby] int outerIndex = 0; - foreach (var itemName in keysWithControlsInGameOptionsIni) + foreach (var itemName in gameOptionsIniControlKeys) { string itemType = itemName switch { @@ -209,7 +209,7 @@ public override Patch Apply() { AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$BaseSection", $"{SkirmishLobby}"); - // Add keys if into [MultiplayerGameLobby] if values are new + // Add keys into [MultiplayerGameLobby] if values are changed with comparison to [SkirmishLobby] foreach (var key in gameLobbyBaseIni.GetSectionKeys($"{SkirmishLobby}").Where(elem => !elem.StartsWith("$"))) { var valueSkirmish = gameLobbyBaseIni.GetStringValue($"{SkirmishLobby}", key, string.Empty); @@ -219,12 +219,37 @@ public override Patch Apply() AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", key, valueMultiplayer); } - // Add new controls for [MultiplayerGameLobby] only + // Find controls to exclude and include + List skirmishControls = new(); + List multiplayerControls = new(); + gameOptionsIniControlKeys.ForEach(x => skirmishControls.AddRange(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty).Split(','))); + gameOptionsIniControlKeys.ForEach(x => multiplayerControls.AddRange(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty).Split(','))); + var excludeControls = skirmishControls.Except(multiplayerControls).ToList(); + var addControls = multiplayerControls.Except(skirmishControls).ToList(); + // Disable skirmish lobby only controls + excludeControls.ForEach(x => + AddKeyWithLog(multiplayerGameLobbyIni, x, "Visible", "false") + .AddKeyWithLog(multiplayerGameLobbyIni, x, "Enabled", "false")); + + // Add multiplayer lobby only controls + addControls.ForEach(x => + AddKeyWithLog( + multiplayerGameLobbyIni, + "GameOptionsPanel", + $"$CC-M{addControls.IndexOf(x)}", + x + ':' + x.Substring(0, 3) switch + { + "chk" => "GameLobbyCheckBox", + "cmb" => "GameLobbyDropDown", + "lbl" => "XNALabel", + _ => throw new Exception($"GameOptions.ini contains unknown type of contol with name {x}") + }) + .TransferKeys(gameOptionsIni, x, multiplayerGameLobbyIni)); } // Configure CnCNetGameLobby.ini - AddKeyWithLog(cncnetGameLobbyIni, $"{MultiplayerGameLobby}", "$CCMP99", "btnChangeTunnel:XNAClientButton"); + AddKeyWithLog(cncnetGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP99", "btnChangeTunnel:XNAClientButton"); AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); @@ -232,9 +257,9 @@ public override Patch Apply() // Replace old configs with new one, delete placeholders, delete redundant sections var sb = new StringBuilder(); - keysWithControlsInGameOptionsIni + gameOptionsIniControlKeys .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty)).Append(',')); - keysWithControlsInGameOptionsIni + gameOptionsIniControlKeys .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty)).Append(',')); sb.ToString() .Split(',') From c06116a213629100eeb470fbbb1acdcb1e4b4e88 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 7 Jul 2025 19:55:54 +0300 Subject: [PATCH 28/81] Fix inheritance --- MigrationTool/Patch_v2_11_0.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index e4a13a2da..52b72b013 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Collections.Generic; using System.Linq; @@ -144,11 +144,11 @@ public override Patch Apply() AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); // Add inheritance - AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow"); - AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}"); - AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}"); - AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}"); - AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}"); + AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow.ini"); + AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); + AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); + AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); + AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); // Configure GameLobbyBase.ini { From 88c15e560dcc2ee2f5ad3160520772a7a2eeb992 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 01:54:00 +0300 Subject: [PATCH 29/81] Refactoring and finilizing `GameLobbyBase.ini` creation --- MigrationTool/Patch_v2_11_0.cs | 130 +++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 38 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 52b72b013..9567f5645 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -1,11 +1,13 @@ -using System; -using System.IO; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Rampastring.Tools; + +using static System.Collections.Specialized.BitVector32; namespace MigrationTool; internal class Patch_v2_11_0 : Patch @@ -129,9 +131,9 @@ public override Patch Apply() List gameOptionsIniControlKeys = new() { "CheckBoxes", "DropDowns", "Labels" }; // Old configs - IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); - IniFile multiplayerGameLobby_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); - IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); + IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); + IniFile multiplayerGameLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); + IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); // New configs IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini")); @@ -150,28 +152,44 @@ public override Patch Apply() AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); + // Transfer old SkirmishLobby.ini->[ExtraControls] to new SkirmishLobby.ini->[$ExtraControls] + if (skirmishLobbyIni_old.SectionExists($"{ExtraControls}")) + { + TransferKeys(skirmishLobbyIni_old, $"{ExtraControls}", skirmishLobbyIni, $"${ExtraControls}"); + skirmishLobbyIni_old.RemoveSection($"{ExtraControls}"); + } + + foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) + { + var value = skirmishLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; + TransferKeys(skirmishLobbyIni_old, value, skirmishLobbyIni); + skirmishLobbyIni_old.RemoveSection(value); + } + // Configure GameLobbyBase.ini { - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationX", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationX", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionLocationY", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionVerticalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionVerticalMargin", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionHorizontalMargin", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionHorizontalMargin", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerOptionCaptionLocationY", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerOptionCaptionLocationY", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "PlayerNameWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "PlayerNameWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "SideWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "SideWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "ColorWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "ColorWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "StartWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "StartWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "TeamWidth", gameOptionsIni.GetStringValue($"{SkirmishLobby}", "TeamWidth", string.Empty)); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-GOP", "GameOptionsPanel:XNAPanel"); - - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "SolidColorBackgroundTexture", "0,0,0,192"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "DrawBorders", "yes"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Width", "427"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Height", "266"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$X", "getWidth($ParentControl) - getWidth($Self) - EMPTY_SPACE_SIDES"); - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", "$Y", "EMPTY_SPACE_TOP"); - - // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[SkirmishLobby] + // Add [SkirmishLobby] + { + var addKey = (string key) => AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", key, gameOptionsIni.GetStringValue($"{SkirmishLobby}", key, string.Empty)); + addKey("PlayerOptionLocationX"); + addKey("PlayerOptionLocationY"); + addKey("PlayerOptionVerticalMargin"); + addKey("PlayerOptionHorizontalMargin"); + addKey("PlayerOptionCaptionLocationY"); + addKey("PlayerNameWidth"); + addKey("SideWidth"); + addKey("ColorWidth"); + addKey("StartWidth"); + addKey("TeamWidth"); + } + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK-GOP", "GameOptionsPanel:XNAPanel"); + TransferKeys(skirmishLobbyIni_old, $"{SkirmishLobby}", gameLobbyBaseIni); + skirmishLobbyIni_old.RemoveSection($"{SkirmishLobby}"); + + TransferKeys(skirmishLobbyIni_old, "GameOptionsPanel", gameLobbyBaseIni); + skirmishLobbyIni_old.RemoveSection("GameOptionsPanel"); + + // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[GameOptionsPanel] int outerIndex = 0; foreach (var itemName in gameOptionsIniControlKeys) { @@ -193,16 +211,51 @@ public override Patch Apply() outerIndex += items.Length; } + + // Add other elements in GameLobbyBase.ini->[SkirmishLobby] + { + var addControl = (string controlKey, string section, string controlType) => + { + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", controlKey, $"{section}:{controlType}"); + TransferKeys(skirmishLobbyIni_old, section, gameLobbyBaseIni); + skirmishLobbyIni_old.RemoveSection(section); + }; + + addControl("$CC-SK00", "btnLaunchGame", "GameLaunchButton"); + addControl("$CC-SK01", "MapPreviewBox", "MapPreviewBox"); + addControl("$CC-SK02", "PlayerOptionsPanel", "XNAPanel"); + addControl("$CC-SK03", "ddGameMode", "XNAClientDropDown"); + addControl("$CC-SK04", "tbMapSearch", "XNASuggestionTextBox"); + addControl("$CC-SK05", "btnPickRandomMap", "XNAClientButton"); + addControl("$CC-SK06", "lblGameModeSelect", "XNALabel"); + addControl("$CC-SK07", "lbMapList", "XNAMultiColumnListBox"); + addControl("$CC-SK08", "lblMapSize", "XNALabel"); + addControl("$CC-SK09", "lblGameMode", "XNALabel"); + addControl("$CC-SK10", "lblMapAuthor", "XNALabel"); + addControl("$CC-SK11", "lblMapName", "XNALabel"); + addControl("$CC-SK12", "btnLeaveGame", "XNAClientButton"); + + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK13", "btnSaveLoadGameOptions:XNAClientButton"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "IdleTexture", "comboBoxArrow.png"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "HoverTexture", "comboBoxArrow.png"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Width", "18"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Height", "21"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$X", "getRight(GameOptionsPanel) - getWidth($Self) - 1"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Y", "getY(GameOptionsPanel) + 1"); + + skirmishLobbyIni_old.GetSections().ForEach(x => TransferKeys(skirmishLobbyIni_old, x, gameLobbyBaseIni)); + } } - // Transfer SkirmishLobby.ini->[ExtraControls] to SkirmishLobby.ini->[$ExtraControls] - if (skirmishLobbyIni_old.SectionExists($"{ExtraControls}")) - TransferKeys(skirmishLobbyIni_old, $"{ExtraControls}", skirmishLobbyIni, $"${ExtraControls}"); + // Transfer old MultiplayerGameLobby.ini->[ExtraControls] to new MultiplayerGameLobby.ini->[$ExtraControls] + if (multiplayerGameLobbyIni_old.SectionExists($"{ExtraControls}")) + TransferKeys(multiplayerGameLobbyIni_old, $"{ExtraControls}", multiplayerGameLobbyIni, $"${ExtraControls}"); - foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) + foreach (var key in multiplayerGameLobbyIni.GetSectionKeys($"${ExtraControls}")) { - var value = skirmishLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; - TransferKeys(skirmishLobbyIni_old, value, skirmishLobbyIni); + var value = multiplayerGameLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; + TransferKeys(multiplayerGameLobbyIni_old, value, multiplayerGameLobbyIni); + multiplayerGameLobbyIni_old.RemoveSection(value); } // Configure MultiplayerGameLobby.ini @@ -250,10 +303,10 @@ public override Patch Apply() // Configure CnCNetGameLobby.ini AddKeyWithLog(cncnetGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP99", "btnChangeTunnel:XNAClientButton"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); // Replace old configs with new one, delete placeholders, delete redundant sections var sb = new StringBuilder(); @@ -271,14 +324,15 @@ public override Patch Apply() gameOptionsIni.RemoveSection($"{SkirmishLobby}"); gameOptionsIni.RemoveSection($"{MultiplayerGameLobby}"); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); - multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobby_old.FileName)); + multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobbyIni_old.FileName)); + gameOptionsIni.WriteIniFile(); gameLobbyBaseIni.WriteIniFile(); lanGameLobbyIni.WriteIniFile(); cncnetGameLobbyIni.WriteIniFile(); - SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}_New.ini")); - SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}_New.ini")); } // Add new texture files From ecea40786d25b3c36dd9a2f31eaa964f4651fc5f Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 02:31:37 +0300 Subject: [PATCH 30/81] Refactoring --- MigrationTool/Patch_v2_11_0.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 9567f5645..31c9cd0a4 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -26,13 +26,15 @@ public override Patch Apply() SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.dll"); SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.pdb"); SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.xml"); - + // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false - var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); - AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "false"); - if (genericWindowIni.SectionExists("ExtraControls")) - genericWindowIni.GetSection("ExtraControls").SectionName = "$ExtraControls"; - genericWindowIni.WriteIniFile(); + { + var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); + AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "false"); + if (genericWindowIni.SectionExists("ExtraControls")) + genericWindowIni.GetSection("ExtraControls").SectionName = "$ExtraControls"; + genericWindowIni.WriteIniFile(); + } // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "OptionsWindow.ini")); @@ -157,13 +159,13 @@ public override Patch Apply() { TransferKeys(skirmishLobbyIni_old, $"{ExtraControls}", skirmishLobbyIni, $"${ExtraControls}"); skirmishLobbyIni_old.RemoveSection($"{ExtraControls}"); - } - foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) - { - var value = skirmishLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; - TransferKeys(skirmishLobbyIni_old, value, skirmishLobbyIni); - skirmishLobbyIni_old.RemoveSection(value); + foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) + { + var section = skirmishLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; + TransferKeys(skirmishLobbyIni_old, section, skirmishLobbyIni); + skirmishLobbyIni_old.RemoveSection(section); + } } // Configure GameLobbyBase.ini From 27b3c134bbef6a6aa51a0c599ac5603716e90b40 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 11:42:00 +0300 Subject: [PATCH 31/81] Migrate other controls for `MultiplayerGameLobby.ini` --- MigrationTool/Patch_v2_11_0.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 31c9cd0a4..aac97997b 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -148,7 +148,7 @@ public override Patch Apply() AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); // Add inheritance - AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow.ini"); + //AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow.ini"); AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); @@ -184,8 +184,8 @@ public override Patch Apply() addKey("StartWidth"); addKey("TeamWidth"); } - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK-GOP", "GameOptionsPanel:XNAPanel"); TransferKeys(skirmishLobbyIni_old, $"{SkirmishLobby}", gameLobbyBaseIni); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK-GOP", "GameOptionsPanel:XNAPanel"); skirmishLobbyIni_old.RemoveSection($"{SkirmishLobby}"); TransferKeys(skirmishLobbyIni_old, "GameOptionsPanel", gameLobbyBaseIni); @@ -301,6 +301,25 @@ public override Patch Apply() _ => throw new Exception($"GameOptions.ini contains unknown type of contol with name {x}") }) .TransferKeys(gameOptionsIni, x, multiplayerGameLobbyIni)); + + // Add other elements in MultiplayerGameLobby.ini->[MultiplayerGameLobby] + { + var addControl = (string controlKey, string section, string controlType) => + { + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", controlKey, $"{section}:{controlType}"); + TransferKeys(multiplayerGameLobbyIni_old, section, multiplayerGameLobbyIni); + multiplayerGameLobbyIni_old.RemoveSection(section); + }; + + addControl("$CC-MP01", "btnLockGame", "XNAClientButton"); + addControl("$CC-MP02", "lbChatMessages_Host", "ChatListBox"); + addControl("$CC-MP03", "lbChatMessages_Player", "ChatListBox"); + addControl("$CC-MP04", "tbChatInput_Host", "XNAChatTextBox"); + addControl("$CC-MP05", "tbChatInput_Player", "XNAChatTextBox"); + addControl("$CC-MP06", "chkAutoReady", "XNAClientCheckBox"); + + multiplayerGameLobbyIni_old.GetSections().ForEach(x => TransferKeys(multiplayerGameLobbyIni_old, x, multiplayerGameLobbyIni)); + } } // Configure CnCNetGameLobby.ini From 31425ef2188c76a7ec31a542d6efd5eac3b8b03f Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 12:29:06 +0300 Subject: [PATCH 32/81] Fix mistake for writing client parser constants --- MigrationTool/Patch_v2_11_0.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index aac97997b..d6ddc4c7f 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -72,11 +72,11 @@ public override Patch Apply() addKey("btnForceUpdate", "Size", "133,23"); } optionsWindowIni.WriteIniFile(); - - // Add GlobalThemeSettings.ini + + // Add DTACnCNetClient.ini { - IniFile globalThemeSettingsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GlobalThemeSettings.ini")); - var addKey = (string key, string value) => AddKeyWithLog(globalThemeSettingsIni, "ParserConstants", key, value); + IniFile dtaCnCNetClientIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "DTACnCNetClient.ini")); + var addKey = (string key, string value) => AddKeyWithLog(dtaCnCNetClientIni, "ParserConstants", key, value); addKey("DEFAULT_LBL_HEIGHT", "12"); addKey("DEFAULT_CONTROL_HEIGHT", "21"); addKey("DEFAULT_BUTTON_HEIGHT", "23"); @@ -95,7 +95,7 @@ public override Patch Apply() addKey("GAME_OPTION_ROW_SPACING", "6"); addKey("GAME_OPTION_DD_WIDTH", "132"); addKey("GAME_OPTION_DD_HEIGHT", "22"); - globalThemeSettingsIni.WriteIniFile(); + dtaCnCNetClientIni.WriteIniFile(); } // Add PlayerExtraOptionsPanel.ini From 6509dbf57de9a89ceb381ca9c63cb9d90371a649 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 12:59:41 +0300 Subject: [PATCH 33/81] Fix potential client crash if `lbChatMessages` and `tbChatInput` and empty localizable keys exist --- MigrationTool/Patch_v2_11_0.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index d6ddc4c7f..199502849 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -318,6 +318,11 @@ public override Patch Apply() addControl("$CC-MP05", "tbChatInput_Player", "XNAChatTextBox"); addControl("$CC-MP06", "chkAutoReady", "XNAClientCheckBox"); + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP07", "lbChatMessages:ChatListBox"); + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP08", "tbChatInput:XNAChatTextBox"); + TransferKeys(multiplayerGameLobbyIni, "lbChatMessages_Player", multiplayerGameLobbyIni, "lbChatMessages"); + TransferKeys(multiplayerGameLobbyIni, "tbChatInput_Player", multiplayerGameLobbyIni, "tbChatInput"); + multiplayerGameLobbyIni_old.GetSections().ForEach(x => TransferKeys(multiplayerGameLobbyIni_old, x, multiplayerGameLobbyIni)); } } @@ -329,6 +334,18 @@ public override Patch Apply() AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); + // Remove empty keys + foreach (var ini in new List() { gameLobbyBaseIni, multiplayerGameLobbyIni, skirmishLobbyIni, lanGameLobbyIni, cncnetGameLobbyIni }) + { + foreach (var section in ini.GetSections()) + { + ini.GetSectionKeys(section) + .Where(key => string.IsNullOrWhiteSpace(ini.GetStringValue(section, key, string.Empty))) + .ToList() + .ForEach(key => ini.RemoveKey(section, key)); + } + } + // Replace old configs with new one, delete placeholders, delete redundant sections var sb = new StringBuilder(); gameOptionsIniControlKeys From 0466e03213f0b55a26302e1a929375d2babebd68 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 13:46:15 +0300 Subject: [PATCH 34/81] Improve game guessing --- MigrationTool/Patch.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 9dc37a5c7..49d404fe9 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -18,15 +18,15 @@ public Patch(string clientPath) ResouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(clientPath, "Resources")); // Predict client type by guessing game engine files - var Game = ClientGameType.TS; - if (!SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "Ares.dll")).Exists - && SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "gamemd-spawn.exe")).Exists) + Game = ClientGameType.TS; + + if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "Ares.dll")).Exists) { - Game = ClientGameType.YR; + Game = ClientGameType.Ares; } - else if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "Ares.dll")).Exists) + else if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "gamemd-spawn.dll")).Exists) { - Game = ClientGameType.Ares; + Game = ClientGameType.YR; } } From 937747d895824dc4b97a502ff447f08e62b784b8 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 14:04:24 +0300 Subject: [PATCH 35/81] Add status images --- MigrationTool/MigrationTool.csproj | 10 ++++++++++ MigrationTool/Pictures/statusAI.png | Bin 0 -> 552 bytes MigrationTool/Pictures/statusClear.png | Bin 0 -> 145 bytes MigrationTool/Pictures/statusEmpty.png | Bin 0 -> 145 bytes MigrationTool/Pictures/statusError.png | Bin 0 -> 209 bytes MigrationTool/Pictures/statusInProgress.png | Bin 0 -> 9358 bytes MigrationTool/Pictures/statusOk.png | Bin 0 -> 211 bytes MigrationTool/Pictures/statusUnavailable.png | Bin 0 -> 179 bytes MigrationTool/Pictures/statusWarning.png | Bin 0 -> 7862 bytes MigrationTool/statusAI.png | Bin 0 -> 552 bytes MigrationTool/statusClear.png | Bin 0 -> 145 bytes MigrationTool/statusEmpty.png | Bin 0 -> 145 bytes MigrationTool/statusError.png | Bin 0 -> 209 bytes MigrationTool/statusInProgress.png | Bin 0 -> 9358 bytes MigrationTool/statusOk.png | Bin 0 -> 211 bytes MigrationTool/statusUnavailable.png | Bin 0 -> 179 bytes MigrationTool/statusWarning.png | Bin 0 -> 7862 bytes 17 files changed, 10 insertions(+) create mode 100644 MigrationTool/Pictures/statusAI.png create mode 100644 MigrationTool/Pictures/statusClear.png create mode 100644 MigrationTool/Pictures/statusEmpty.png create mode 100644 MigrationTool/Pictures/statusError.png create mode 100644 MigrationTool/Pictures/statusInProgress.png create mode 100644 MigrationTool/Pictures/statusOk.png create mode 100644 MigrationTool/Pictures/statusUnavailable.png create mode 100644 MigrationTool/Pictures/statusWarning.png create mode 100644 MigrationTool/statusAI.png create mode 100644 MigrationTool/statusClear.png create mode 100644 MigrationTool/statusEmpty.png create mode 100644 MigrationTool/statusError.png create mode 100644 MigrationTool/statusInProgress.png create mode 100644 MigrationTool/statusOk.png create mode 100644 MigrationTool/statusUnavailable.png create mode 100644 MigrationTool/statusWarning.png diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index 082f823a2..4be005ebc 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -8,6 +8,16 @@ CnCNet Client Migration Tool CnCNet.MigrationTool + + + + + + + + + + diff --git a/MigrationTool/Pictures/statusAI.png b/MigrationTool/Pictures/statusAI.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8a9399d5b53d2884b397145dcfd71bf2a95f9c GIT binary patch literal 552 zcmV+@0@wYCP)0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$i(@Kj}9PFUtkfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRo)_Ex7zKj6K&xTf-^aGyIsyF8z?IhV*P6iWC+Urj z7Ci#`w}Ff6jwbH`mpj1VlP(#OBl&3x#Uk*2M&FbN25y1gHMh6+K29HiEOoVf0~{Oz zV+9=*O!2b^`GFK?fk$L90|Vb75M~tB@M-`Gig>y>hDcmac3|si hGBBvQS#aP01H-~o-1%+$>+}9L&M-8-6l<|L=cp$!!i@VXotv7Y}96$=9u6QB*v3HRh2^K>M@R(lt-YUTH3O wu(fpj`^5YGy=S(^Kh>_k|G50$pQqXn*qB|pbStkbd;nSQ>FVdQ&MBb@0Gql+UjP6A literal 0 HcmV?d00001 diff --git a/MigrationTool/Pictures/statusInProgress.png b/MigrationTool/Pictures/statusInProgress.png new file mode 100644 index 0000000000000000000000000000000000000000..bc0b8be2d561aeff2621c80ec7db4d6da7b0d22b GIT binary patch literal 9358 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 A&Hw-a literal 0 HcmV?d00001 diff --git a/MigrationTool/Pictures/statusUnavailable.png b/MigrationTool/Pictures/statusUnavailable.png new file mode 100644 index 0000000000000000000000000000000000000000..aea07d0bb67ba3138d8eb98bb92c5a33a4c5c037 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw|3?!p1cPs@`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$1AIbU>+9=*%(S#L3D(LdAi)BU$YKTtzC$3)D5~Mr02DO#ba4!kxSZ_3 z*3)Fra4sx>MOh%a!&9KCA&1d*1&@b}7SBa)LAK2crC#Ln@$Emr$jr>(ugG)$;+LJX Qf!Y~7UHx3vIVCg!0QB-LkpKVy literal 0 HcmV?d00001 diff --git a/MigrationTool/Pictures/statusWarning.png b/MigrationTool/Pictures/statusWarning.png new file mode 100644 index 0000000000000000000000000000000000000000..bf046b98e00a42454fe6f52a1501ed3b20867b74 GIT binary patch literal 7862 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 MPF1!>+9c$E0Mr>SrvLx| literal 0 HcmV?d00001 diff --git a/MigrationTool/statusAI.png b/MigrationTool/statusAI.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8a9399d5b53d2884b397145dcfd71bf2a95f9c GIT binary patch literal 552 zcmV+@0@wYCP)0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$i(@Kj}9PFUtkfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRo)_Ex7zKj6K&xTf-^aGyIsyF8z?IhV*P6iWC+Urj z7Ci#`w}Ff6jwbH`mpj1VlP(#OBl&3x#Uk*2M&FbN25y1gHMh6+K29HiEOoVf0~{Oz zV+9=*O!2b^`GFK?fk$L90|Vb75M~tB@M-`Gig>y>hDcmac3|si hGBBvQS#aP01H-~o-1%+$>+}9L&M-8-6l<|L=cp$!!i@VXotv7Y}96$=9u6QB*v3HRh2^K>M@R(lt-YUTH3O wu(fpj`^5YGy=S(^Kh>_k|G50$pQqXn*qB|pbStkbd;nSQ>FVdQ&MBb@0Gql+UjP6A literal 0 HcmV?d00001 diff --git a/MigrationTool/statusInProgress.png b/MigrationTool/statusInProgress.png new file mode 100644 index 0000000000000000000000000000000000000000..bc0b8be2d561aeff2621c80ec7db4d6da7b0d22b GIT binary patch literal 9358 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 A&Hw-a literal 0 HcmV?d00001 diff --git a/MigrationTool/statusUnavailable.png b/MigrationTool/statusUnavailable.png new file mode 100644 index 0000000000000000000000000000000000000000..aea07d0bb67ba3138d8eb98bb92c5a33a4c5c037 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw|3?!p1cPs@`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$1AIbU>+9=*%(S#L3D(LdAi)BU$YKTtzC$3)D5~Mr02DO#ba4!kxSZ_3 z*3)Fra4sx>MOh%a!&9KCA&1d*1&@b}7SBa)LAK2crC#Ln@$Emr$jr>(ugG)$;+LJX Qf!Y~7UHx3vIVCg!0QB-LkpKVy literal 0 HcmV?d00001 diff --git a/MigrationTool/statusWarning.png b/MigrationTool/statusWarning.png new file mode 100644 index 0000000000000000000000000000000000000000..bf046b98e00a42454fe6f52a1501ed3b20867b74 GIT binary patch literal 7862 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 MPF1!>+9c$E0Mr>SrvLx| literal 0 HcmV?d00001 From 9951138b314e6e8023f71fd312bb3f7d46c0a9e9 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 14:37:09 +0300 Subject: [PATCH 36/81] Compile and copy absent images --- MigrationTool/Patch_v2_11_0.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 199502849..3e047b72e 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -2,12 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; -using System.Threading.Tasks; using Rampastring.Tools; -using static System.Collections.Specialized.BitVector32; namespace MigrationTool; internal class Patch_v2_11_0 : Patch @@ -374,6 +373,22 @@ public override Patch Apply() } // Add new texture files + var assembly = Assembly.GetExecutingAssembly(); + foreach (var exefile in assembly.GetManifestResourceNames()) + using (Stream resourceStream = assembly.GetManifestResourceStream(exefile)) + { + var filename = exefile.Replace($"{nameof(MigrationTool)}.Pictures.", string.Empty); + var filepath = SafePath.CombineFilePath(ResouresDir.FullName, filename); + + if (!File.Exists(filepath)) + { + using (FileStream fileStream = new FileStream(filepath, FileMode.Create)) + { + Logger.Log($"Copy {filename} to the {ResouresDir.FullName}"); + resourceStream.CopyTo(fileStream); + } + } + } return this; } From fbb308990b56dd89728a3c77c17decf2d80ecf40 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 14:50:09 +0300 Subject: [PATCH 37/81] Update mistake in docs --- Docs/Migration-INI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/Migration-INI.md b/Docs/Migration-INI.md index c5dc92e0b..0fad12545 100644 --- a/Docs/Migration-INI.md +++ b/Docs/Migration-INI.md @@ -724,7 +724,7 @@ DrawBorders=true Size=1230,750 ``` -## Edit `GlobalThemeSettings.ini` +## Edit `DTACnCNetClient.ini` This file now also contains the `ParserConstants` section, which lists user-defined constants used for positioning controls within panels and windows. **Without this section, the client will crash with new `GameLobbyBase.ini` layout**. From 38e42dd0baabad820bee6a8384005efaa6a0fc28 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 15:00:21 +0300 Subject: [PATCH 38/81] Add more images --- MigrationTool/MigrationTool.csproj | 9 +++++++++ MigrationTool/Pictures/favActive.png | Bin 0 -> 11942 bytes MigrationTool/Pictures/favActive_c.png | Bin 0 -> 520 bytes MigrationTool/Pictures/favInactive.png | Bin 0 -> 14122 bytes MigrationTool/Pictures/noMapPreview.png | Bin 0 -> 92515 bytes MigrationTool/Pictures/sortAlphaActive.png | Bin 0 -> 1194 bytes MigrationTool/Pictures/sortAlphaAsc.png | Bin 0 -> 1194 bytes MigrationTool/Pictures/sortAlphaDesc.png | Bin 0 -> 1333 bytes MigrationTool/Pictures/sortAlphaInactive.png | Bin 0 -> 1173 bytes MigrationTool/Pictures/sortAlphaNone.png | Bin 0 -> 1173 bytes 10 files changed, 9 insertions(+) create mode 100644 MigrationTool/Pictures/favActive.png create mode 100644 MigrationTool/Pictures/favActive_c.png create mode 100644 MigrationTool/Pictures/favInactive.png create mode 100644 MigrationTool/Pictures/noMapPreview.png create mode 100644 MigrationTool/Pictures/sortAlphaActive.png create mode 100644 MigrationTool/Pictures/sortAlphaAsc.png create mode 100644 MigrationTool/Pictures/sortAlphaDesc.png create mode 100644 MigrationTool/Pictures/sortAlphaInactive.png create mode 100644 MigrationTool/Pictures/sortAlphaNone.png diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index 4be005ebc..ea258680c 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -9,6 +9,15 @@ CnCNet.MigrationTool + + + + + + + + + diff --git a/MigrationTool/Pictures/favActive.png b/MigrationTool/Pictures/favActive.png new file mode 100644 index 0000000000000000000000000000000000000000..545be40b4d0c30a999aebd35be09dda778b559ea GIT binary patch literal 11942 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 NDhitNHL~WR{|Bu$tQ!CT literal 0 HcmV?d00001 diff --git a/MigrationTool/Pictures/favActive_c.png b/MigrationTool/Pictures/favActive_c.png new file mode 100644 index 0000000000000000000000000000000000000000..23080d5761ace80ca659fb4def06cf59d6d9d46f GIT binary patch literal 520 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%0AP&&{P_a_xO?}muIsN~ zzy9{^+v@7-#>R%GX@n3$NV!~Q7zRdBgfT7@3WtY>JkPh=ZH8eer5IyD5JscX-rnBt z-@g?_`S9Vx+}zxiD_6dL{W=^DlO*}{=@ZQJJc=To=PzBl6a+!JTn@u92!i?fd5+_{ z-LB&}zVCA!hY;G`-Q{^+mgUpa)4zZJG7JMDgp^W25HQAyqRjNGs+uH8p-@PYWHcJt zwq31OYqi>VJXTfp`}gk;5UN(I z&z?OqP1CZhYPA|gkzp8>N(H*EJ06eIG(A2(7DchyY)+?BN@<#=g+c)#)MzyFJb(K1 z>F3X%-@SXM>v|l=^?E%FLrN)#<5-fUdcCfyYOB@Kb$vJ--n)0N+wK1R`SbAbFp8ol zPo4k(_V@QWj$>Jt<2a7vFvgZ;ajUDV?RHz!G{Z19H#b*SRzy+!@#Dv2GAWfxNs?@B zZ3%+#^5x57u_y=vA%s$D7{>MM*JW9TXV0D`gsiWxOOn*<^+ZveUF81#`v{>oZ{EyW zU}tA%mi&(&KT=8ug8{}^*LC*&`}Y8VX0zGpbcVyBWmz1@ojG%cQaYJT+U<6y(=koc zb={Sfm43h9>-8Q!e7L{AFN&h3X{_ToGvdKu!2EB7VK^K90g}u!fS}=W<^TWy07*qo IM6N<$f=V7!ssI20 literal 0 HcmV?d00001 diff --git a/MigrationTool/Pictures/sortAlphaAsc.png b/MigrationTool/Pictures/sortAlphaAsc.png new file mode 100644 index 0000000000000000000000000000000000000000..9243fd4e08c4fc2661184c0d54bd7d7a88b40641 GIT binary patch literal 1194 zcmV;b1XcTqP)cWoz!H*~dFm)(m zLa-r0nV=Ms5`V;w<2KihCa}2M19UO{ddZ zttN`1<2YHCVT{8tL%Cf9&+pg=LoSa;}diC<<%dpewsH%GL;>G9BpR+8hX$;X@G5_Jhg$o-S8yI7Db8}PEG?rymRV9RAjCr2-eZSxD z>$;vKiD{aI5LuSLeEFhj8qf1dk|>IjB#9(R0Aoxk^*oQ`xY1~oB#Ex;ilS(mHXe_A zy`E)R0D!f%HA#|Mt(GK7lgR{QObGFPpHo#8Ap{}xeIEdD>(;H?w{O3F`&O3aAPA0* zj0(lt!a58jWhTTAHR=mN5*25K7aO z+5T4Ye5h|2=hG07-Ni~=Xp^S&CSit_85i%0AP&&{P_a_xO?}muIsN~ zzy9{^+v@7-#>R%GX@n3$NV!~Q7zRdBgfT7@3WtY>JkPh=ZH8eer5IyD5JscX-rnBt z-@g?_`S9Vx+}zxiD_6dL{W=^DlO*}{=@ZQJJc=To=PzBl6a+!JTn@u92!i?fd5+_{ z-LB&}zVCA!hY;G`-Q{^+mgUpa)4zZJG7JMDgp^W25HQAyqRjNGs+uH8p-@PYWHcJt zwq31OYqi>VJXTfp`}gk;5UN(I z&z?OqP1CZhYPA|gkzp8>N(H*EJ06eIG(A2(7DchyY)+?BN@<#=g+c)#)MzyFJb(K1 z>F3X%-@SXM>v|l=^?E%FLrN)#<5-fUdcCfyYOB@Kb$vJ--n)0N+wK1R`SbAbFp8ol zPo4k(_V@QWj$>Jt<2a7vFvgZ;ajUDV?RHz!G{Z19H#b*SRzy+!@#Dv2GAWfxNs?@B zZ3%+#^5x57u_y=vA%s$D7{>MM*JW9TXV0D`gsiWxOOn*<^+ZveUF81#`v{>oZ{EyW zU}tA%mi&(&KT=8ug8{}^*LC*&`}Y8VX0zGpbcVyBWmz1@ojG%cQaYJT+U<6y(=koc zb={Sfm43h9>-8Q!e7L{AFN&h3X{_ToGvdKu!2EB7VK^K90g}u!fS}=W<^TWy07*qo IM6N<$f=V7!ssI20 literal 0 HcmV?d00001 diff --git a/MigrationTool/Pictures/sortAlphaDesc.png b/MigrationTool/Pictures/sortAlphaDesc.png new file mode 100644 index 0000000000000000000000000000000000000000..ac1389e46c043ac3b9cc1e5d811441b68cd4880c GIT binary patch literal 1333 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 Date: Tue, 8 Jul 2025 17:13:35 +0300 Subject: [PATCH 39/81] Fix possible bugs with transefing section keys and OptionsWindow new FileSetting* keys --- MigrationTool/Patch_v2_11_0.cs | 60 ++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 3e047b72e..fa8f44af0 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -38,21 +38,25 @@ public override Patch Apply() // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "OptionsWindow.ini")); foreach (var section in optionsWindowIni.GetSections()) - foreach (var pair in optionsWindowIni.GetSection(section).Keys) + { + foreach (var key in optionsWindowIni.GetSectionKeys(section)) { - if (pair.Value.Contains(":CustomSettingFileCheckBox")) + var value = optionsWindowIni.GetStringValue(section, key, string.Empty); + + if (value.Contains(":CustomSettingFileCheckBox")) { - pair.Value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox"); + optionsWindowIni.SetStringValue(section, key, value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox")); continue; } - - if (pair.Value.Contains(":CustomSettingFileDropDown")) + + if (value.Contains(":CustomSettingFileDropDown")) { - pair.Value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown"); + optionsWindowIni.SetStringValue(section, key, value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown")); continue; } } - + } + // Add new sections into OptionsWindow.ini { var addKey = (string section, string key, string value) => AddKeyWithLog(optionsWindowIni, section, key, value); @@ -146,6 +150,12 @@ public override Patch Apply() // Add random color to the GameOptions.ini AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); + // Delete old inheritance + if (skirmishLobbyIni_old.SectionExists("INISystem")) + skirmishLobbyIni_old.RemoveSection("INISystem"); + if (multiplayerGameLobbyIni_old.SectionExists("INISystem")) + multiplayerGameLobbyIni_old.RemoveSection("INISystem"); + // Add inheritance //AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow.ini"); AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); @@ -218,7 +228,14 @@ public override Patch Apply() var addControl = (string controlKey, string section, string controlType) => { AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", controlKey, $"{section}:{controlType}"); - TransferKeys(skirmishLobbyIni_old, section, gameLobbyBaseIni); + try + { + TransferKeys(skirmishLobbyIni_old, section, gameLobbyBaseIni); + } + catch + { + gameLobbyBaseIni.AddSection(section); + } skirmishLobbyIni_old.RemoveSection(section); }; @@ -250,13 +267,15 @@ public override Patch Apply() // Transfer old MultiplayerGameLobby.ini->[ExtraControls] to new MultiplayerGameLobby.ini->[$ExtraControls] if (multiplayerGameLobbyIni_old.SectionExists($"{ExtraControls}")) - TransferKeys(multiplayerGameLobbyIni_old, $"{ExtraControls}", multiplayerGameLobbyIni, $"${ExtraControls}"); - - foreach (var key in multiplayerGameLobbyIni.GetSectionKeys($"${ExtraControls}")) { - var value = multiplayerGameLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; - TransferKeys(multiplayerGameLobbyIni_old, value, multiplayerGameLobbyIni); - multiplayerGameLobbyIni_old.RemoveSection(value); + TransferKeys(multiplayerGameLobbyIni_old, $"{ExtraControls}", multiplayerGameLobbyIni, $"${ExtraControls}"); + multiplayerGameLobbyIni_old.RemoveSection($"{ExtraControls}"); + foreach (var key in multiplayerGameLobbyIni.GetSectionKeys($"${ExtraControls}")) + { + var value = multiplayerGameLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; + TransferKeys(multiplayerGameLobbyIni_old, value, multiplayerGameLobbyIni); + multiplayerGameLobbyIni_old.RemoveSection(value); + } } // Configure MultiplayerGameLobby.ini @@ -281,12 +300,12 @@ public override Patch Apply() var excludeControls = skirmishControls.Except(multiplayerControls).ToList(); var addControls = multiplayerControls.Except(skirmishControls).ToList(); - // Disable skirmish lobby only controls + // Disable skirmish lobby only controls from GameOptions.ini excludeControls.ForEach(x => AddKeyWithLog(multiplayerGameLobbyIni, x, "Visible", "false") .AddKeyWithLog(multiplayerGameLobbyIni, x, "Enabled", "false")); - // Add multiplayer lobby only controls + // Add multiplayer lobby only controls from GameOptions.ini addControls.ForEach(x => AddKeyWithLog( multiplayerGameLobbyIni, @@ -306,7 +325,14 @@ public override Patch Apply() var addControl = (string controlKey, string section, string controlType) => { AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", controlKey, $"{section}:{controlType}"); - TransferKeys(multiplayerGameLobbyIni_old, section, multiplayerGameLobbyIni); + try + { + TransferKeys(multiplayerGameLobbyIni_old, section, multiplayerGameLobbyIni); + } + catch + { + multiplayerGameLobbyIni.AddSection(section); + } multiplayerGameLobbyIni_old.RemoveSection(section); }; From c7415501d012d5282e81c8a4aef2f2bed1bd074f Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 19:46:31 +0300 Subject: [PATCH 40/81] Add `GameOptionsPanel` child controls position recalculation --- MigrationTool/Patch.cs | 62 ++++++++++++++++++++++++++++++++++ MigrationTool/Patch_v2_11_0.cs | 12 ++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 49d404fe9..1d42f28b9 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Linq; +using System.Collections.Generic; using Rampastring.Tools; namespace MigrationTool; @@ -52,6 +54,66 @@ public Patch AddKeyWithLog(IniFile src, string section, string key, string value return this; } + public void CalculatePositions(IniFile ini, string parent, string child) + { + int parentX, parentY, childX, childY; + parentX = parentY = childX = childY = 0; + + var parentKeys = ini.GetSectionKeys(parent); + var childKeys = ini.GetSectionKeys(child); + + var positionKeys = new List() { "$X", "$Y", "X", "Y", "Location" }; + + Logger.Log($"Update {ini.FileName}: Fix position for {child} control in {parent}"); + + foreach (var control in new List>() { parentKeys, childKeys }) + { + int tmpX, tmpY; + tmpX = tmpY = 0; + + foreach (var key in control.Where(key => positionKeys.Contains(key))) + { + switch (key) + { + case ("$X"): + case ("X"): + tmpX = ini.GetIntValue(control == parentKeys ? parent : child, key, tmpX); + continue; + case ("$Y"): + case ("Y"): + tmpY = ini.GetIntValue(control == parentKeys ? parent : child, key, tmpY); + continue; + case ("Location"): + var value = ini.GetStringValue(control == parentKeys ? parent : child, key, string.Empty).Split(','); + tmpX = Conversions.IntFromString(value[0], tmpX); + tmpY = Conversions.IntFromString(value[1], tmpY); + break; + default: + break; + } + } + + if (control == parentKeys) + { + parentX = tmpX; + parentY = tmpY; + } + else + { + childX = tmpX; + childY = tmpY; + } + } + + positionKeys.ForEach(key => ini.RemoveKey(child, key)); + + childX = childX - parentX; + childY = childY - parentY; + + ini.GetSection(child).AddKey("$X", $"{childX}"); + ini.GetSection(child).AddKey("$Y", $"{childY}"); + } + public Patch TransferKeys(IniFile srcIni, string srcSection, IniFile desIni, string? desSection = null) { desSection ??= srcSection; diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index fa8f44af0..50d85cbad 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -218,6 +218,7 @@ public override Patch Apply() var item = items[i]; AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", $"$CC_{i + outerIndex}", $"{item}:{itemType}"); TransferKeys(gameOptionsIni, item, gameLobbyBaseIni); + CalculatePositions(gameLobbyBaseIni, "GameOptionsPanel", item); } outerIndex += items.Length; @@ -306,19 +307,20 @@ public override Patch Apply() .AddKeyWithLog(multiplayerGameLobbyIni, x, "Enabled", "false")); // Add multiplayer lobby only controls from GameOptions.ini - addControls.ForEach(x => + addControls.ForEach(control => AddKeyWithLog( multiplayerGameLobbyIni, "GameOptionsPanel", - $"$CC-M{addControls.IndexOf(x)}", - x + ':' + x.Substring(0, 3) switch + $"$CC-M{addControls.IndexOf(control)}", + control + ':' + control.Substring(0, 3) switch { "chk" => "GameLobbyCheckBox", "cmb" => "GameLobbyDropDown", "lbl" => "XNALabel", - _ => throw new Exception($"GameOptions.ini contains unknown type of contol with name {x}") + _ => throw new Exception($"GameOptions.ini contains unknown type of contol with name {control}") }) - .TransferKeys(gameOptionsIni, x, multiplayerGameLobbyIni)); + .TransferKeys(gameOptionsIni, control, multiplayerGameLobbyIni) + .CalculatePositions(multiplayerGameLobbyIni, "GameOptionsPanel", control)); // Add other elements in MultiplayerGameLobby.ini->[MultiplayerGameLobby] { From ac8e5c38dd9b52cd6e8b7c6fe821a4a4d145bc40 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 9 Jul 2025 04:26:06 +0800 Subject: [PATCH 41/81] Show detailed error message --- MigrationTool/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index d3020a0dd..763545c5d 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -85,7 +85,7 @@ private static void Main(string[] args) catch (Exception ex) { Logger.Log(""); - Logger.Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: {ex.Message}"); + Logger.Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: {ex.ToString()}"); Logger.Log("Migration to the latest client version has been failed"); } From bdbe183dc0c41ab2ae9968a0f0193103f300a1d5 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 9 Jul 2025 04:26:39 +0800 Subject: [PATCH 42/81] Fix null reference on catching the first failed patch --- MigrationTool/Program.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 763545c5d..b23effca3 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -76,11 +76,11 @@ private static void Main(string[] args) Patch patch = null; try { - patch = new Patch_v2_11_0(arg).Apply(); Console.WriteLine(""); - patch = new Patch_v2_11_1(arg).Apply(); Console.WriteLine(""); - patch = new Patch_v2_11_2(arg).Apply(); Console.WriteLine(""); - patch = new Patch_v2_12_1(arg).Apply(); Console.WriteLine(""); - patch = new Patch_Latest(arg).Apply(); + patch = new Patch_v2_11_0(arg); patch.Apply(); Console.WriteLine(""); + patch = new Patch_v2_11_1(arg); patch.Apply(); Console.WriteLine(""); + patch = new Patch_v2_11_2(arg); patch.Apply(); Console.WriteLine(""); + patch = new Patch_v2_12_1(arg); patch.Apply(); Console.WriteLine(""); + patch = new Patch_Latest(arg); patch.Apply(); } catch (Exception ex) { From ed521dc880b3f90f6d1ef9311ac964d51c9303d8 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 23:51:24 +0300 Subject: [PATCH 43/81] Remove images --- MigrationTool/statusAI.png | Bin 552 -> 0 bytes MigrationTool/statusClear.png | Bin 145 -> 0 bytes MigrationTool/statusEmpty.png | Bin 145 -> 0 bytes MigrationTool/statusError.png | Bin 209 -> 0 bytes MigrationTool/statusInProgress.png | Bin 9358 -> 0 bytes MigrationTool/statusOk.png | Bin 211 -> 0 bytes MigrationTool/statusUnavailable.png | Bin 179 -> 0 bytes MigrationTool/statusWarning.png | Bin 7862 -> 0 bytes 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 MigrationTool/statusAI.png delete mode 100644 MigrationTool/statusClear.png delete mode 100644 MigrationTool/statusEmpty.png delete mode 100644 MigrationTool/statusError.png delete mode 100644 MigrationTool/statusInProgress.png delete mode 100644 MigrationTool/statusOk.png delete mode 100644 MigrationTool/statusUnavailable.png delete mode 100644 MigrationTool/statusWarning.png diff --git a/MigrationTool/statusAI.png b/MigrationTool/statusAI.png deleted file mode 100644 index 2b8a9399d5b53d2884b397145dcfd71bf2a95f9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 552 zcmV+@0@wYCP)0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$i(@Kj}9PFUtkfAzR5EXHhDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRo)_Ex7zKj6K&xTf-^aGyIsyF8z?IhV*P6iWC+Urj z7Ci#`w}Ff6jwbH`mpj1VlP(#OBl&3x#Uk*2M&FbN25y1gHMh6+K29HiEOoVf0~{Oz zV+9=*O!2b^`GFK?fk$L90|Vb75M~tB@M-`Gig>y>hDcmac3|si hGBBvQS#aP01H-~o-1%+$>+}9L&M-8-6l<|L=cp$!!i@VXotv7Y}96$=9u6QB*v3HRh2^K>M@R(lt-YUTH3O wu(fpj`^5YGy=S(^Kh>_k|G50$pQqXn*qB|pbStkbd;nSQ>FVdQ&MBb@0Gql+UjP6A diff --git a/MigrationTool/statusInProgress.png b/MigrationTool/statusInProgress.png deleted file mode 100644 index bc0b8be2d561aeff2621c80ec7db4d6da7b0d22b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9358 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 A&Hw-a diff --git a/MigrationTool/statusUnavailable.png b/MigrationTool/statusUnavailable.png deleted file mode 100644 index aea07d0bb67ba3138d8eb98bb92c5a33a4c5c037..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw|3?!p1cPs@`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$1AIbU>+9=*%(S#L3D(LdAi)BU$YKTtzC$3)D5~Mr02DO#ba4!kxSZ_3 z*3)Fra4sx>MOh%a!&9KCA&1d*1&@b}7SBa)LAK2crC#Ln@$Emr$jr>(ugG)$;+LJX Qf!Y~7UHx3vIVCg!0QB-LkpKVy diff --git a/MigrationTool/statusWarning.png b/MigrationTool/statusWarning.png deleted file mode 100644 index bf046b98e00a42454fe6f52a1501ed3b20867b74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7862 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 MPF1!>+9c$E0Mr>SrvLx| From fe4b62edc80c4ae9b757cad400feb08ce7f00a25 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 8 Jul 2025 23:55:18 +0300 Subject: [PATCH 44/81] Add force enabling borders to draw --- MigrationTool/Patch_v2_11_0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 50d85cbad..afb5b9695 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -29,7 +29,7 @@ public override Patch Apply() // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false { var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); - AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "false"); + AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "true"); if (genericWindowIni.SectionExists("ExtraControls")) genericWindowIni.GetSection("ExtraControls").SectionName = "$ExtraControls"; genericWindowIni.WriteIniFile(); From 7bf1626966a86b3ad7d3b18d0df7f75e637685ac Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 11 Jul 2025 14:23:08 +0300 Subject: [PATCH 45/81] Add patch for client version v2.12.6 --- MigrationTool/Patch_Latest.cs | 6 +----- MigrationTool/Patch_v2_12_6.cs | 27 +++++++++++++++++++++++++++ MigrationTool/Program.cs | 2 ++ 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 MigrationTool/Patch_v2_12_6.cs diff --git a/MigrationTool/Patch_Latest.cs b/MigrationTool/Patch_Latest.cs index 8f8f1cdef..92f62fc28 100644 --- a/MigrationTool/Patch_Latest.cs +++ b/MigrationTool/Patch_Latest.cs @@ -15,11 +15,7 @@ public override Patch Apply() { base.Apply(); - // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode - IniFile gmLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini")); - string ddPlayerColor = nameof(ddPlayerColor); - Enumerable.Range(0, 8).ToList().ForEach(i => AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + i, "ItemsDrawMode", "Text")); - gmLobbyBaseIni.WriteIniFile(); + // Write latest patch there return this; } diff --git a/MigrationTool/Patch_v2_12_6.cs b/MigrationTool/Patch_v2_12_6.cs new file mode 100644 index 000000000..e4e6915ca --- /dev/null +++ b/MigrationTool/Patch_v2_12_6.cs @@ -0,0 +1,27 @@ +using System.Linq; + +using Rampastring.Tools; + +namespace MigrationTool; + +internal class Patch_v2_12_6 : Patch +{ + public Patch_v2_12_6(string clientPath) : base(clientPath) + { + ClientVersion = Version.v2_12_6; + } + + public override Patch Apply() + { + base.Apply(); + + // Add GameLobbyBase.ini->[ddPlayerColorX]->ItemsDrawMode + IniFile gmLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini")); + string ddPlayerColor = nameof(ddPlayerColor); + Enumerable.Range(0, 8).ToList().ForEach(i => AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + i, "ItemsDrawMode", "Text")); + gmLobbyBaseIni.WriteIniFile(); + + return this; + } +} + diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index b23effca3..758a93072 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -13,6 +13,7 @@ public enum Version v2_11_1, v2_11_2, v2_12_1, + v2_12_6, Latest, } @@ -80,6 +81,7 @@ private static void Main(string[] args) patch = new Patch_v2_11_1(arg); patch.Apply(); Console.WriteLine(""); patch = new Patch_v2_11_2(arg); patch.Apply(); Console.WriteLine(""); patch = new Patch_v2_12_1(arg); patch.Apply(); Console.WriteLine(""); + patch = new Patch_v2_12_6(arg); patch.Apply(); Console.WriteLine(""); patch = new Patch_Latest(arg); patch.Apply(); } catch (Exception ex) From 30929bbaf7e2d74ae229a7d06d7a57e81b6a5c09 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 12 Jul 2025 20:19:08 +0300 Subject: [PATCH 46/81] Replace cast to hash set with `Distinct()` method --- MigrationTool/Patch_v2_11_0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index afb5b9695..7d15587a8 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -381,7 +381,7 @@ public override Patch Apply() .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty)).Append(',')); sb.ToString() .Split(',') - .ToHashSet() + .Distinct() .Select(x => x = x.Trim()) .Where(x => !string.IsNullOrEmpty(x)) .ToList() From 6cf7ae09722c8efa75700308a9ade9b33b4f87fb Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sun, 13 Jul 2025 17:03:55 +0300 Subject: [PATCH 47/81] Apply 2.12.6 release migration notes --- MigrationTool/Patch_v2_12_6.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MigrationTool/Patch_v2_12_6.cs b/MigrationTool/Patch_v2_12_6.cs index e4e6915ca..ec72e3fd4 100644 --- a/MigrationTool/Patch_v2_12_6.cs +++ b/MigrationTool/Patch_v2_12_6.cs @@ -21,6 +21,12 @@ public override Patch Apply() Enumerable.Range(0, 8).ToList().ForEach(i => AddKeyWithLog(gmLobbyBaseIni, ddPlayerColor + i, "ItemsDrawMode", "Text")); gmLobbyBaseIni.WriteIniFile(); + // Remove GenericWindow.ini->{ GameCreationWindow; GameCreationWindow_Advanced }->Size + IniFile genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); + genericWindowIni.GetSection("GameCreationWindow").RemoveKey("Size"); + genericWindowIni.GetSection("GameCreationWindow_Advanced").RemoveKey("Size"); + genericWindowIni.WriteIniFile(); + return this; } } From ce74dc50519d949881a64ff330bac266f410c8ae Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 14 Jul 2025 12:56:21 +0300 Subject: [PATCH 48/81] Add patch for renamed in PR#534 btnSaveLoadGameOptions --- MigrationTool/Patch_v2_11_1.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/MigrationTool/Patch_v2_11_1.cs b/MigrationTool/Patch_v2_11_1.cs index 706cbb825..1d81f6e3c 100644 --- a/MigrationTool/Patch_v2_11_1.cs +++ b/MigrationTool/Patch_v2_11_1.cs @@ -22,6 +22,24 @@ public override Patch Apply() AddKeyWithLog(clientDefsIni, "Settings", "RecommendedResolutions", $"{width}x{height}"); clientDefsIni.WriteIniFile(); + // Rename GameLobbyBase.ini->[SkirmishLobby]->BtnSaveLoadGameOptions to btnSaveLoadGameOptions + Logger.Log("Update name from BtnSaveLoadGameOptions to btnSaveLoadGameOptions in GameLobbyBase.ini->[SkirmishLobby]"); + IniFile glb = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameLobbyBase.ini")); + var presets = glb.GetSection("BtnSaveLoadGameOptions"); + + if (presets == null) + return this; + + presets.SectionName = "btnSaveLoadGameOptions"; + + foreach (var pair in glb.GetSection("SkirmishLobby").Keys) + { + if (pair.Value.Contains("BtnSaveLoadGameOptions")) + pair.Value.Replace("BtnSaveLoadGameOptions", "btnSaveLoadGameOptions"); + } + + glb.WriteIniFile(); + return this; } } From a6da43d007cc934d10ed507e7dd175738022f6ec Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 13:27:32 +0800 Subject: [PATCH 49/81] Enable nullable globally for MigrationTool --- MigrationTool/MigrationTool.csproj | 1 + MigrationTool/Patch.cs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index ea258680c..0972f00c2 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -2,6 +2,7 @@ Exe false + Enable CnCNet.MigrationTool diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 1d42f28b9..90d57aeba 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -5,8 +5,6 @@ using Rampastring.Tools; namespace MigrationTool; -#nullable enable - internal abstract class Patch { public Version ClientVersion { get; protected set; } From 317bf78bc57f1363a0777e267e60dae8edd3f67f Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 13:33:58 +0800 Subject: [PATCH 50/81] Address the nullable issue in Program.cs --- MigrationTool/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index b23effca3..9a1afeca3 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -73,7 +73,7 @@ private static void Main(string[] args) return; } - Patch patch = null; + Patch? patch = null; try { patch = new Patch_v2_11_0(arg); patch.Apply(); Console.WriteLine(""); @@ -85,7 +85,7 @@ private static void Main(string[] args) catch (Exception ex) { Logger.Log(""); - Logger.Log($"Unable to apply migration patch for client version {patch.ClientVersion.ToString().Replace('_', '.')} due to internal error. Message: {ex.ToString()}"); + Logger.Log($"Unable to apply migration patch for client version {patch?.ClientVersion.ToString().Replace('_', '.')} due to an internal error. Message: {ex.ToString()}"); Logger.Log("Migration to the latest client version has been failed"); } From cd3fa393b5da51983a5650b6cfdfbad88185630e Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:04:30 +0800 Subject: [PATCH 51/81] Fix handling SkirmishLobby in gameOptionsIni --- MigrationTool/Patch_v2_11_0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 7d15587a8..fa49b81d0 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -212,7 +212,7 @@ public override Patch Apply() _ => throw new Exception($"Unknown type of elements {itemName}") }; - var items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split(','); + string[] items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split([","], StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < items.Length; i++) { var item = items[i]; From 95c75c8c583acdbe16b799df7d1f9734f1d62385 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:06:14 +0800 Subject: [PATCH 52/81] GenericWindow.ini: DrawBorders = false --- MigrationTool/Patch_v2_11_0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index fa49b81d0..bd1f00f22 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -29,7 +29,7 @@ public override Patch Apply() // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false { var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); - AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "true"); + AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "false"); if (genericWindowIni.SectionExists("ExtraControls")) genericWindowIni.GetSection("ExtraControls").SectionName = "$ExtraControls"; genericWindowIni.WriteIniFile(); From 434a2180b1aac33332a703282d8fb97d56567387 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:08:33 +0800 Subject: [PATCH 53/81] Use new C# syntax to determine the `arg` --- MigrationTool/Program.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 70705e5ba..e307133c9 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -52,11 +52,11 @@ private static void Main(string[] args) case 1: string arg = args[0].Trim(); - if (arg == "-h" - || arg == "--help" - || arg == "-?" - || arg == "/?" - || arg == "/h") + if (arg is "-h" + or "--help" + or "-?" + or "/?" + or "/h") { Console.WriteLine(helpMsg); return; From 5b26734e3f9b2df59481c33f53283615501e425e Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:09:32 +0800 Subject: [PATCH 54/81] Format codes in Visual Studio --- MigrationTool/Patch_v2_11_0.cs | 182 ++++++++++++++++----------------- MigrationTool/Patch_v2_11_1.cs | 2 +- 2 files changed, 92 insertions(+), 92 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index bd1f00f22..adee7e1df 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -11,7 +11,7 @@ namespace MigrationTool; internal class Patch_v2_11_0 : Patch { - public Patch_v2_11_0 (string clientPath) : base (clientPath) + public Patch_v2_11_0(string clientPath) : base(clientPath) { ClientVersion = Version.v2_11_0; } @@ -34,7 +34,7 @@ public override Patch Apply() genericWindowIni.GetSection("ExtraControls").SectionName = "$ExtraControls"; genericWindowIni.WriteIniFile(); } - + // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "OptionsWindow.ini")); foreach (var section in optionsWindowIni.GetSections()) @@ -56,23 +56,23 @@ public override Patch Apply() } } } - + // Add new sections into OptionsWindow.ini { var addKey = (string section, string key, string value) => AddKeyWithLog(optionsWindowIni, section, key, value); - addKey("lblPlayerName", "Location", "12,195"); - addKey("tbPlayerName", "Location", "113,193"); - addKey("lblNotice", "Location", "12,220"); - addKey("btnConfigureHotkeys", "Location", "12,290"); - addKey("chkDisablePrivateMessagePopup", "Location", "12,138"); - addKey("chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); + addKey("lblPlayerName", "Location", "12,195"); + addKey("tbPlayerName", "Location", "113,193"); + addKey("lblNotice", "Location", "12,220"); + addKey("btnConfigureHotkeys", "Location", "12,290"); + addKey("chkDisablePrivateMessagePopup", "Location", "12,138"); + addKey("chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); addKey("chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); - addKey("chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); - addKey("lblAllPrivateMessagesFrom", "Location", "276,138"); - addKey("ddAllowPrivateMessagesFrom", "Location", "470,137"); - addKey("gameListPanel", "Location", "0,200"); - addKey("btnForceUpdate", "Location", "407,213"); - addKey("btnForceUpdate", "Size", "133,23"); + addKey("chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); + addKey("lblAllPrivateMessagesFrom", "Location", "276,138"); + addKey("ddAllowPrivateMessagesFrom", "Location", "470,137"); + addKey("gameListPanel", "Location", "0,200"); + addKey("btnForceUpdate", "Location", "407,213"); + addKey("btnForceUpdate", "Size", "133,23"); } optionsWindowIni.WriteIniFile(); @@ -80,24 +80,24 @@ public override Patch Apply() { IniFile dtaCnCNetClientIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "DTACnCNetClient.ini")); var addKey = (string key, string value) => AddKeyWithLog(dtaCnCNetClientIni, "ParserConstants", key, value); - addKey("DEFAULT_LBL_HEIGHT", "12"); - addKey("DEFAULT_CONTROL_HEIGHT", "21"); - addKey("DEFAULT_BUTTON_HEIGHT", "23"); - addKey("BUTTON_WIDTH_133", "133"); - addKey("OPEN_BUTTON_WIDTH", "18"); - addKey("OPEN_BUTTON_HEIGHT", "22"); - addKey("EMPTY_SPACE_TOP", "12"); - addKey("EMPTY_SPACE_BOTTOM", "12"); - addKey("EMPTY_SPACE_SIDES", "12"); - addKey("BUTTON_SPACING", "12"); - addKey("LABEL_SPACING", "6"); - addKey("CHECKBOX_SPACING", "24"); - addKey("LOBBY_EMPTY_SPACE_SIDES", "12"); - addKey("LOBBY_PANEL_SPACING", "10"); + addKey("DEFAULT_LBL_HEIGHT", "12"); + addKey("DEFAULT_CONTROL_HEIGHT", "21"); + addKey("DEFAULT_BUTTON_HEIGHT", "23"); + addKey("BUTTON_WIDTH_133", "133"); + addKey("OPEN_BUTTON_WIDTH", "18"); + addKey("OPEN_BUTTON_HEIGHT", "22"); + addKey("EMPTY_SPACE_TOP", "12"); + addKey("EMPTY_SPACE_BOTTOM", "12"); + addKey("EMPTY_SPACE_SIDES", "12"); + addKey("BUTTON_SPACING", "12"); + addKey("LABEL_SPACING", "6"); + addKey("CHECKBOX_SPACING", "24"); + addKey("LOBBY_EMPTY_SPACE_SIDES", "12"); + addKey("LOBBY_PANEL_SPACING", "10"); addKey("GAME_OPTION_COLUMN_SPACING", "160"); - addKey("GAME_OPTION_ROW_SPACING", "6"); - addKey("GAME_OPTION_DD_WIDTH", "132"); - addKey("GAME_OPTION_DD_HEIGHT", "22"); + addKey("GAME_OPTION_ROW_SPACING", "6"); + addKey("GAME_OPTION_DD_WIDTH", "132"); + addKey("GAME_OPTION_DD_HEIGHT", "22"); dtaCnCNetClientIni.WriteIniFile(); } @@ -105,19 +105,19 @@ public override Patch Apply() { IniFile playerExtraOptionsPanelIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "PlayerExtraOptionsPanel.ini")); var addKey = (string section, string key, string value) => AddKeyWithLog(playerExtraOptionsPanelIni, section, key, value); - addKey("btnClose", "Location", "220,0"); - addKey("btnClose", "Size", "18,18"); - addKey("lblHeader", "Location", "12,6"); - addKey("chkBoxForceRandomSides", "Location", "12,28"); - addKey("chkBoxForceRandomColors", "Location", "12,50"); - addKey("chkBoxForceRandomTeams", "Location", "12,72"); - addKey("chkBoxForceRandomStarts", "Location", "12,94"); + addKey("btnClose", "Location", "220,0"); + addKey("btnClose", "Size", "18,18"); + addKey("lblHeader", "Location", "12,6"); + addKey("chkBoxForceRandomSides", "Location", "12,28"); + addKey("chkBoxForceRandomColors", "Location", "12,50"); + addKey("chkBoxForceRandomTeams", "Location", "12,72"); + addKey("chkBoxForceRandomStarts", "Location", "12,94"); addKey("chkBoxUseTeamStartMappings", "Location", "12,130"); - addKey("btnHelp", "Location", "160,130"); - addKey("lblPreset", "Location", "12,156"); - addKey("ddTeamStartMappingPreset", "Location", "65,154"); - addKey("ddTeamStartMappingPreset", "Size", "157,21"); - addKey("teamStartMappingsPanel", "Location", "12,189"); + addKey("btnHelp", "Location", "160,130"); + addKey("lblPreset", "Location", "12,156"); + addKey("ddTeamStartMappingPreset", "Location", "65,154"); + addKey("ddTeamStartMappingPreset", "Size", "157,21"); + addKey("teamStartMappingsPanel", "Location", "12,189"); playerExtraOptionsPanelIni.WriteIniFile(); } @@ -136,16 +136,16 @@ public override Patch Apply() List gameOptionsIniControlKeys = new() { "CheckBoxes", "DropDowns", "Labels" }; // Old configs - IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); + IniFile skirmishLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); IniFile multiplayerGameLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); - IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); + IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); // New configs - IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini")); - IniFile skirmishLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}_New.ini")); - IniFile multiplayerGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}_New.ini")); - IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); - IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); + IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini")); + IniFile skirmishLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}_New.ini")); + IniFile multiplayerGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}_New.ini")); + IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); + IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); // Add random color to the GameOptions.ini AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); @@ -158,10 +158,10 @@ public override Patch Apply() // Add inheritance //AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow.ini"); - AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); + AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); - AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); - AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); + AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); + AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); // Transfer old SkirmishLobby.ini->[ExtraControls] to new SkirmishLobby.ini->[$ExtraControls] if (skirmishLobbyIni_old.SectionExists($"{ExtraControls}")) @@ -207,9 +207,9 @@ public override Patch Apply() string itemType = itemName switch { "CheckBoxes" => "GameLobbyCheckBox", - "DropDowns" => "GameLobbyDropDown", - "Labels" => "XNALabel", - _ => throw new Exception($"Unknown type of elements {itemName}") + "DropDowns" => "GameLobbyDropDown", + "Labels" => "XNALabel", + _ => throw new Exception($"Unknown type of elements {itemName}") }; string[] items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split([","], StringSplitOptions.RemoveEmptyEntries); @@ -240,27 +240,27 @@ public override Patch Apply() skirmishLobbyIni_old.RemoveSection(section); }; - addControl("$CC-SK00", "btnLaunchGame", "GameLaunchButton"); - addControl("$CC-SK01", "MapPreviewBox", "MapPreviewBox"); + addControl("$CC-SK00", "btnLaunchGame", "GameLaunchButton"); + addControl("$CC-SK01", "MapPreviewBox", "MapPreviewBox"); addControl("$CC-SK02", "PlayerOptionsPanel", "XNAPanel"); - addControl("$CC-SK03", "ddGameMode", "XNAClientDropDown"); - addControl("$CC-SK04", "tbMapSearch", "XNASuggestionTextBox"); - addControl("$CC-SK05", "btnPickRandomMap", "XNAClientButton"); - addControl("$CC-SK06", "lblGameModeSelect", "XNALabel"); - addControl("$CC-SK07", "lbMapList", "XNAMultiColumnListBox"); - addControl("$CC-SK08", "lblMapSize", "XNALabel"); - addControl("$CC-SK09", "lblGameMode", "XNALabel"); - addControl("$CC-SK10", "lblMapAuthor", "XNALabel"); - addControl("$CC-SK11", "lblMapName", "XNALabel"); - addControl("$CC-SK12", "btnLeaveGame", "XNAClientButton"); - - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK13", "btnSaveLoadGameOptions:XNAClientButton"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "IdleTexture", "comboBoxArrow.png"); + addControl("$CC-SK03", "ddGameMode", "XNAClientDropDown"); + addControl("$CC-SK04", "tbMapSearch", "XNASuggestionTextBox"); + addControl("$CC-SK05", "btnPickRandomMap", "XNAClientButton"); + addControl("$CC-SK06", "lblGameModeSelect", "XNALabel"); + addControl("$CC-SK07", "lbMapList", "XNAMultiColumnListBox"); + addControl("$CC-SK08", "lblMapSize", "XNALabel"); + addControl("$CC-SK09", "lblGameMode", "XNALabel"); + addControl("$CC-SK10", "lblMapAuthor", "XNALabel"); + addControl("$CC-SK11", "lblMapName", "XNALabel"); + addControl("$CC-SK12", "btnLeaveGame", "XNAClientButton"); + + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK13", "btnSaveLoadGameOptions:XNAClientButton"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "IdleTexture", "comboBoxArrow.png"); AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "HoverTexture", "comboBoxArrow.png"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Width", "18"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Height", "21"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$X", "getRight(GameOptionsPanel) - getWidth($Self) - 1"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Y", "getY(GameOptionsPanel) + 1"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Width", "18"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Height", "21"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$X", "getRight(GameOptionsPanel) - getWidth($Self) - 1"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Y", "getY(GameOptionsPanel) + 1"); skirmishLobbyIni_old.GetSections().ForEach(x => TransferKeys(skirmishLobbyIni_old, x, gameLobbyBaseIni)); } @@ -294,7 +294,7 @@ public override Patch Apply() } // Find controls to exclude and include - List skirmishControls = new(); + List skirmishControls = new(); List multiplayerControls = new(); gameOptionsIniControlKeys.ForEach(x => skirmishControls.AddRange(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty).Split(','))); gameOptionsIniControlKeys.ForEach(x => multiplayerControls.AddRange(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty).Split(','))); @@ -302,12 +302,12 @@ public override Patch Apply() var addControls = multiplayerControls.Except(skirmishControls).ToList(); // Disable skirmish lobby only controls from GameOptions.ini - excludeControls.ForEach(x => + excludeControls.ForEach(x => AddKeyWithLog(multiplayerGameLobbyIni, x, "Visible", "false") .AddKeyWithLog(multiplayerGameLobbyIni, x, "Enabled", "false")); - + // Add multiplayer lobby only controls from GameOptions.ini - addControls.ForEach(control => + addControls.ForEach(control => AddKeyWithLog( multiplayerGameLobbyIni, "GameOptionsPanel", @@ -338,17 +338,17 @@ public override Patch Apply() multiplayerGameLobbyIni_old.RemoveSection(section); }; - addControl("$CC-MP01", "btnLockGame", "XNAClientButton"); - addControl("$CC-MP02", "lbChatMessages_Host", "ChatListBox"); + addControl("$CC-MP01", "btnLockGame", "XNAClientButton"); + addControl("$CC-MP02", "lbChatMessages_Host", "ChatListBox"); addControl("$CC-MP03", "lbChatMessages_Player", "ChatListBox"); - addControl("$CC-MP04", "tbChatInput_Host", "XNAChatTextBox"); - addControl("$CC-MP05", "tbChatInput_Player", "XNAChatTextBox"); - addControl("$CC-MP06", "chkAutoReady", "XNAClientCheckBox"); + addControl("$CC-MP04", "tbChatInput_Host", "XNAChatTextBox"); + addControl("$CC-MP05", "tbChatInput_Player", "XNAChatTextBox"); + addControl("$CC-MP06", "chkAutoReady", "XNAClientCheckBox"); AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP07", "lbChatMessages:ChatListBox"); AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP08", "tbChatInput:XNAChatTextBox"); TransferKeys(multiplayerGameLobbyIni, "lbChatMessages_Player", multiplayerGameLobbyIni, "lbChatMessages"); - TransferKeys(multiplayerGameLobbyIni, "tbChatInput_Player", multiplayerGameLobbyIni, "tbChatInput"); + TransferKeys(multiplayerGameLobbyIni, "tbChatInput_Player", multiplayerGameLobbyIni, "tbChatInput"); multiplayerGameLobbyIni_old.GetSections().ForEach(x => TransferKeys(multiplayerGameLobbyIni_old, x, multiplayerGameLobbyIni)); } @@ -356,10 +356,10 @@ public override Patch Apply() // Configure CnCNetGameLobby.ini AddKeyWithLog(cncnetGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP99", "btnChangeTunnel:XNAClientButton"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); // Remove empty keys foreach (var ini in new List() { gameLobbyBaseIni, multiplayerGameLobbyIni, skirmishLobbyIni, lanGameLobbyIni, cncnetGameLobbyIni }) @@ -393,7 +393,7 @@ public override Patch Apply() SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobbyIni_old.FileName)); - + gameOptionsIni.WriteIniFile(); gameLobbyBaseIni.WriteIniFile(); lanGameLobbyIni.WriteIniFile(); diff --git a/MigrationTool/Patch_v2_11_1.cs b/MigrationTool/Patch_v2_11_1.cs index 1d81f6e3c..014e9322d 100644 --- a/MigrationTool/Patch_v2_11_1.cs +++ b/MigrationTool/Patch_v2_11_1.cs @@ -4,7 +4,7 @@ namespace MigrationTool; internal class Patch_v2_11_1 : Patch { - public Patch_v2_11_1 (string clientPath) : base (clientPath) + public Patch_v2_11_1(string clientPath) : base(clientPath) { ClientVersion = Version.v2_11_1; } From 92f2fb6c9dcf89257fd76e33bebe7313c2a08692 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:10:45 +0800 Subject: [PATCH 55/81] Rename `exefile` to `resourceName` --- MigrationTool/Patch_v2_11_0.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index adee7e1df..ffc05e54e 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -402,10 +402,10 @@ public override Patch Apply() // Add new texture files var assembly = Assembly.GetExecutingAssembly(); - foreach (var exefile in assembly.GetManifestResourceNames()) - using (Stream resourceStream = assembly.GetManifestResourceStream(exefile)) + foreach (var resourceName in assembly.GetManifestResourceNames()) + using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) { - var filename = exefile.Replace($"{nameof(MigrationTool)}.Pictures.", string.Empty); + var filename = resourceName.Replace($"{nameof(MigrationTool)}.Pictures.", string.Empty); var filepath = SafePath.CombineFilePath(ResouresDir.FullName, filename); if (!File.Exists(filepath)) From 9e95a3e37362a9d89d2f7bc20d607dcccbca047b Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:12:25 +0800 Subject: [PATCH 56/81] Use FileMode.CreateNew since the file does not exist --- MigrationTool/Patch_v2_11_0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index ffc05e54e..d46e34c39 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -410,7 +410,7 @@ public override Patch Apply() if (!File.Exists(filepath)) { - using (FileStream fileStream = new FileStream(filepath, FileMode.Create)) + using (FileStream fileStream = new FileStream(filepath, FileMode.CreateNew)) { Logger.Log($"Copy {filename} to the {ResouresDir.FullName}"); resourceStream.CopyTo(fileStream); From 79ddb631b5022806032bf4cd39db05e13eec2eee Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:56:22 +0800 Subject: [PATCH 57/81] Move optionsWindowIni to a nested block --- MigrationTool/Patch_v2_11_0.cs | 64 ++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index d46e34c39..7bc825ba2 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -36,45 +36,47 @@ public override Patch Apply() } // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} - IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "OptionsWindow.ini")); - foreach (var section in optionsWindowIni.GetSections()) { - foreach (var key in optionsWindowIni.GetSectionKeys(section)) + IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "OptionsWindow.ini")); + foreach (var section in optionsWindowIni.GetSections()) { - var value = optionsWindowIni.GetStringValue(section, key, string.Empty); - - if (value.Contains(":CustomSettingFileCheckBox")) + foreach (var key in optionsWindowIni.GetSectionKeys(section)) { - optionsWindowIni.SetStringValue(section, key, value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox")); - continue; - } + var value = optionsWindowIni.GetStringValue(section, key, string.Empty); - if (value.Contains(":CustomSettingFileDropDown")) - { - optionsWindowIni.SetStringValue(section, key, value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown")); - continue; + if (value.Contains(":CustomSettingFileCheckBox")) + { + optionsWindowIni.SetStringValue(section, key, value.Replace(":CustomSettingFileCheckBox", ":FileSettingCheckBox")); + continue; + } + + if (value.Contains(":CustomSettingFileDropDown")) + { + optionsWindowIni.SetStringValue(section, key, value.Replace(":CustomSettingFileDropDown", ":FileSettingDropDown")); + continue; + } } } - } - // Add new sections into OptionsWindow.ini - { - var addKey = (string section, string key, string value) => AddKeyWithLog(optionsWindowIni, section, key, value); - addKey("lblPlayerName", "Location", "12,195"); - addKey("tbPlayerName", "Location", "113,193"); - addKey("lblNotice", "Location", "12,220"); - addKey("btnConfigureHotkeys", "Location", "12,290"); - addKey("chkDisablePrivateMessagePopup", "Location", "12,138"); - addKey("chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); - addKey("chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); - addKey("chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); - addKey("lblAllPrivateMessagesFrom", "Location", "276,138"); - addKey("ddAllowPrivateMessagesFrom", "Location", "470,137"); - addKey("gameListPanel", "Location", "0,200"); - addKey("btnForceUpdate", "Location", "407,213"); - addKey("btnForceUpdate", "Size", "133,23"); + // Add new sections into OptionsWindow.ini + { + var addKey = (string section, string key, string value) => AddKeyWithLog(optionsWindowIni, section, key, value); + addKey("lblPlayerName", "Location", "12,195"); + addKey("tbPlayerName", "Location", "113,193"); + addKey("lblNotice", "Location", "12,220"); + addKey("btnConfigureHotkeys", "Location", "12,290"); + addKey("chkDisablePrivateMessagePopup", "Location", "12,138"); + addKey("chkDisablePrivateMessagePopup", "Text", "Disable private message pop-ups"); + addKey("chkAllowGameInvitesFromFriendsOnly", "Location", "276,68"); + addKey("chkAllowGameInvitesFromFriendsOnly", "Text", "Only receive game invitations@from friends"); + addKey("lblAllPrivateMessagesFrom", "Location", "276,138"); + addKey("ddAllowPrivateMessagesFrom", "Location", "470,137"); + addKey("gameListPanel", "Location", "0,200"); + addKey("btnForceUpdate", "Location", "407,213"); + addKey("btnForceUpdate", "Size", "133,23"); + } + optionsWindowIni.WriteIniFile(); } - optionsWindowIni.WriteIniFile(); // Add DTACnCNetClient.ini { From de40f805e49e08056097ed948e5fc1c3c679e111 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 16 Jul 2025 16:58:59 +0800 Subject: [PATCH 58/81] Change return type of `Apply()` to void --- MigrationTool/Patch.cs | 3 +-- MigrationTool/Patch_Latest.cs | 3 +-- MigrationTool/Patch_v2_11_0.cs | 3 +-- MigrationTool/Patch_v2_11_1.cs | 5 ++--- MigrationTool/Patch_v2_11_2.cs | 3 +-- MigrationTool/Patch_v2_12_1.cs | 3 +-- MigrationTool/Patch_v2_12_6.cs | 3 +-- 7 files changed, 8 insertions(+), 15 deletions(-) diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 90d57aeba..4fc604fe3 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -30,10 +30,9 @@ public Patch(string clientPath) } } - public virtual Patch Apply() + public virtual void Apply() { Logger.Log($"Applying patch for client version {ClientVersion.ToString().Replace('_', '.')}..."); - return this; } public Patch AddKeyWithLog(IniFile src, string section, string key, string value) diff --git a/MigrationTool/Patch_Latest.cs b/MigrationTool/Patch_Latest.cs index 92f62fc28..9bd503dd9 100644 --- a/MigrationTool/Patch_Latest.cs +++ b/MigrationTool/Patch_Latest.cs @@ -11,13 +11,12 @@ public Patch_Latest(string clientPath) : base(clientPath) ClientVersion = Version.Latest; } - public override Patch Apply() + public override void Apply() { base.Apply(); // Write latest patch there - return this; } } diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 7bc825ba2..4f4455574 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -16,7 +16,7 @@ public Patch_v2_11_0(string clientPath) : base(clientPath) ClientVersion = Version.v2_11_0; } - public override Patch Apply() + public override void Apply() { base.Apply(); @@ -420,6 +420,5 @@ public override Patch Apply() } } - return this; } } diff --git a/MigrationTool/Patch_v2_11_1.cs b/MigrationTool/Patch_v2_11_1.cs index 014e9322d..1a0572aeb 100644 --- a/MigrationTool/Patch_v2_11_1.cs +++ b/MigrationTool/Patch_v2_11_1.cs @@ -9,7 +9,7 @@ public Patch_v2_11_1(string clientPath) : base(clientPath) ClientVersion = Version.v2_11_1; } - public override Patch Apply() + public override void Apply() { base.Apply(); @@ -28,7 +28,7 @@ public override Patch Apply() var presets = glb.GetSection("BtnSaveLoadGameOptions"); if (presets == null) - return this; + return; presets.SectionName = "btnSaveLoadGameOptions"; @@ -40,6 +40,5 @@ public override Patch Apply() glb.WriteIniFile(); - return this; } } diff --git a/MigrationTool/Patch_v2_11_2.cs b/MigrationTool/Patch_v2_11_2.cs index 49032aea3..5a2332b60 100644 --- a/MigrationTool/Patch_v2_11_2.cs +++ b/MigrationTool/Patch_v2_11_2.cs @@ -13,7 +13,7 @@ public Patch_v2_11_2(string clientPath) : base(clientPath) ClientVersion = Version.v2_11_2; } - public override Patch Apply() + public override void Apply() { base.Apply(); @@ -33,6 +33,5 @@ public override Patch Apply() AddKeyWithLog(clientDefsIni, "Settings", "ShowDevelopmentBuildWarnings", "true"); clientDefsIni.WriteIniFile(); - return this; } } diff --git a/MigrationTool/Patch_v2_12_1.cs b/MigrationTool/Patch_v2_12_1.cs index e5be64755..35dd7e76b 100644 --- a/MigrationTool/Patch_v2_12_1.cs +++ b/MigrationTool/Patch_v2_12_1.cs @@ -14,7 +14,7 @@ public Patch_v2_12_1(string clientPath) : base(clientPath) { ClientVersion = Version.v2_12_1; } - public override Patch Apply() + public override void Apply() { base.Apply(); @@ -23,6 +23,5 @@ public override Patch Apply() AddKeyWithLog(clientDefsIni, "Settings", "ClientGameType", Game.ToString()); clientDefsIni.WriteIniFile(); - return this; } } diff --git a/MigrationTool/Patch_v2_12_6.cs b/MigrationTool/Patch_v2_12_6.cs index ec72e3fd4..403ef20d6 100644 --- a/MigrationTool/Patch_v2_12_6.cs +++ b/MigrationTool/Patch_v2_12_6.cs @@ -11,7 +11,7 @@ public Patch_v2_12_6(string clientPath) : base(clientPath) ClientVersion = Version.v2_12_6; } - public override Patch Apply() + public override void Apply() { base.Apply(); @@ -27,7 +27,6 @@ public override Patch Apply() genericWindowIni.GetSection("GameCreationWindow_Advanced").RemoveKey("Size"); genericWindowIni.WriteIniFile(); - return this; } } From 674811dc2c7fd34c8cabe325daa1570a79a31c52 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 16 Jul 2025 13:02:06 +0300 Subject: [PATCH 59/81] Rework patch apply mechanism to use reflection --- MigrationTool/Patch_v2_12_6.cs | 4 ++-- .../{Patch_Latest.cs => Patch_vLatest.cs} | 5 ++--- MigrationTool/Program.cs | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 11 deletions(-) rename MigrationTool/{Patch_Latest.cs => Patch_vLatest.cs} (70%) diff --git a/MigrationTool/Patch_v2_12_6.cs b/MigrationTool/Patch_v2_12_6.cs index 403ef20d6..4d914c256 100644 --- a/MigrationTool/Patch_v2_12_6.cs +++ b/MigrationTool/Patch_v2_12_6.cs @@ -23,8 +23,8 @@ public override void Apply() // Remove GenericWindow.ini->{ GameCreationWindow; GameCreationWindow_Advanced }->Size IniFile genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); - genericWindowIni.GetSection("GameCreationWindow").RemoveKey("Size"); - genericWindowIni.GetSection("GameCreationWindow_Advanced").RemoveKey("Size"); + genericWindowIni.GetSection("GameCreationWindow")?.RemoveKey("Size"); + genericWindowIni.GetSection("GameCreationWindow_Advanced")?.RemoveKey("Size"); genericWindowIni.WriteIniFile(); } diff --git a/MigrationTool/Patch_Latest.cs b/MigrationTool/Patch_vLatest.cs similarity index 70% rename from MigrationTool/Patch_Latest.cs rename to MigrationTool/Patch_vLatest.cs index 9bd503dd9..9e3301f22 100644 --- a/MigrationTool/Patch_Latest.cs +++ b/MigrationTool/Patch_vLatest.cs @@ -4,9 +4,9 @@ namespace MigrationTool; -internal class Patch_Latest : Patch +internal class Patch_vLatest : Patch { - public Patch_Latest(string clientPath) : base(clientPath) + public Patch_vLatest(string clientPath) : base(clientPath) { ClientVersion = Version.Latest; } @@ -16,7 +16,6 @@ public override void Apply() base.Apply(); // Write latest patch there - } } diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index e307133c9..42e0902e9 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using Rampastring.Tools; @@ -74,15 +75,21 @@ private static void Main(string[] args) return; } + var assembly = Assembly.GetExecutingAssembly(); Patch? patch = null; try { - patch = new Patch_v2_11_0(arg); patch.Apply(); Console.WriteLine(""); - patch = new Patch_v2_11_1(arg); patch.Apply(); Console.WriteLine(""); - patch = new Patch_v2_11_2(arg); patch.Apply(); Console.WriteLine(""); - patch = new Patch_v2_12_1(arg); patch.Apply(); Console.WriteLine(""); - patch = new Patch_v2_12_6(arg); patch.Apply(); Console.WriteLine(""); - patch = new Patch_Latest(arg); patch.Apply(); + // https://stackoverflow.com/questions/16038819/how-to-find-all-direct-subclasses-of-a-class-with-net-reflection + assembly.GetTypes() + .Where(type => type.BaseType == typeof(Patch)) + .OrderBy(type => type.FullName) // Used to order patches by their names + .ToList() + .ForEach(type => + { + var patch = (Patch)Activator.CreateInstance(type, arg); + patch?.Apply(); + Console.WriteLine(""); + }); } catch (Exception ex) { From a4256e3cf1454116e53a7cf67cca49f78c79b83f Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 16 Jul 2025 14:06:28 +0300 Subject: [PATCH 60/81] Upate patch apply mechanism --- Directory.Build.props | 3 ++- MigrationTool/MigrationTool.csproj | 3 +++ MigrationTool/Program.cs | 23 ++++++++++++----------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2db800d50..0436fa41a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -52,7 +52,8 @@ - net8.0;net48 + net48;net8.0-windows + net8.0 AnyCPU diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index 0972f00c2..3860e543c 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -31,4 +31,7 @@ + + + diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 42e0902e9..db851ea58 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Rampastring.Tools; +using ClientCore.Extensions; namespace MigrationTool; @@ -47,6 +47,8 @@ private static void Main(string[] args) Logger.Log("Version: " + GitVersionInformation.AssemblySemVer); Logger.WriteToConsole = true; + args = new string[] { @"D:\_Downloads\MO" }; + // Check arguments switch (args.Length) { @@ -80,16 +82,15 @@ private static void Main(string[] args) try { // https://stackoverflow.com/questions/16038819/how-to-find-all-direct-subclasses-of-a-class-with-net-reflection - assembly.GetTypes() - .Where(type => type.BaseType == typeof(Patch)) - .OrderBy(type => type.FullName) // Used to order patches by their names - .ToList() - .ForEach(type => - { - var patch = (Patch)Activator.CreateInstance(type, arg); - patch?.Apply(); - Console.WriteLine(""); - }); + var patches = assembly.GetTypes().Where(type => type.BaseType == typeof(Patch)).ToList(); + var patchNames = Enum.GetValues(typeof(Version)); + foreach (var patchName in patchNames) + { + Type type = patches.Where(t => t.FullName.Contains(patchName.ToString())).First(); + patch = (Patch)Activator.CreateInstance(type, arg); + patch?.Apply(); + Console.WriteLine(""); + } } catch (Exception ex) { From 5f135f400c82733a4175e7eea1a79c458d2466db Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 16 Jul 2025 14:12:44 +0300 Subject: [PATCH 61/81] Refactoring --- MigrationTool/Patch.cs | 9 +++++---- .../{Patch_vLatest.cs => Patch_Latest.cs} | 4 ++-- MigrationTool/Program.cs | 18 +----------------- MigrationTool/Version.cs | 11 +++++++++++ 4 files changed, 19 insertions(+), 23 deletions(-) rename MigrationTool/{Patch_vLatest.cs => Patch_Latest.cs} (70%) create mode 100644 MigrationTool/Version.cs diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 4fc604fe3..031a3280b 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -3,12 +3,13 @@ using System.Collections.Generic; using Rampastring.Tools; +using ClientCore.Enums; namespace MigrationTool; internal abstract class Patch { public Version ClientVersion { get; protected set; } - public ClientGameType Game { get; protected set; } + public ClientType Game { get; protected set; } public DirectoryInfo ClientDir { get; protected set; } public DirectoryInfo ResouresDir { get; protected set; } @@ -18,15 +19,15 @@ public Patch(string clientPath) ResouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(clientPath, "Resources")); // Predict client type by guessing game engine files - Game = ClientGameType.TS; + Game = ClientType.TS; if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "Ares.dll")).Exists) { - Game = ClientGameType.Ares; + Game = ClientType.Ares; } else if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "gamemd-spawn.dll")).Exists) { - Game = ClientGameType.YR; + Game = ClientType.YR; } } diff --git a/MigrationTool/Patch_vLatest.cs b/MigrationTool/Patch_Latest.cs similarity index 70% rename from MigrationTool/Patch_vLatest.cs rename to MigrationTool/Patch_Latest.cs index 9e3301f22..d01b6dbe0 100644 --- a/MigrationTool/Patch_vLatest.cs +++ b/MigrationTool/Patch_Latest.cs @@ -4,9 +4,9 @@ namespace MigrationTool; -internal class Patch_vLatest : Patch +internal class Patch_Latest : Patch { - public Patch_vLatest(string clientPath) : base(clientPath) + public Patch_Latest(string clientPath) : base(clientPath) { ClientVersion = Version.Latest; } diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index db851ea58..52375485c 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -4,27 +4,11 @@ using System.Reflection; using Rampastring.Tools; +using ClientCore.Enums; using ClientCore.Extensions; namespace MigrationTool; -public enum Version -{ - v2_11_0, - v2_11_1, - v2_11_2, - v2_12_1, - v2_12_6, - Latest, -} - -public enum ClientGameType -{ - TS, - YR, - Ares, -} - internal sealed class Program { private const string errMsg = "Unknown arguments detected. Use -h argument to print help information."; diff --git a/MigrationTool/Version.cs b/MigrationTool/Version.cs new file mode 100644 index 000000000..2b1b7cdff --- /dev/null +++ b/MigrationTool/Version.cs @@ -0,0 +1,11 @@ +namespace MigrationTool; + +public enum Version +{ + v2_11_0, + v2_11_1, + v2_11_2, + v2_12_1, + v2_12_6, + Latest, +} From 53cc0f31ad6d9048836396d783e284576c26fb3e Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 16 Jul 2025 14:12:59 +0300 Subject: [PATCH 62/81] Remove debug lines --- MigrationTool/Program.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 52375485c..02450f3ac 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -31,8 +31,6 @@ private static void Main(string[] args) Logger.Log("Version: " + GitVersionInformation.AssemblySemVer); Logger.WriteToConsole = true; - args = new string[] { @"D:\_Downloads\MO" }; - // Check arguments switch (args.Length) { From 667622e5f36f2d57413f49d238f08ca653b1c5e2 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 16 Jul 2025 14:19:45 +0300 Subject: [PATCH 63/81] Fix compilation error --- MigrationTool/Patch_v2_11_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Patch_v2_11_1.cs b/MigrationTool/Patch_v2_11_1.cs index 1a0572aeb..4ea20f100 100644 --- a/MigrationTool/Patch_v2_11_1.cs +++ b/MigrationTool/Patch_v2_11_1.cs @@ -35,7 +35,7 @@ public override void Apply() foreach (var pair in glb.GetSection("SkirmishLobby").Keys) { if (pair.Value.Contains("BtnSaveLoadGameOptions")) - pair.Value.Replace("BtnSaveLoadGameOptions", "btnSaveLoadGameOptions"); + glb.SetStringValue("SkirmishLobby", pair.Key, pair.Value.Replace("BtnSaveLoadGameOptions", "btnSaveLoadGameOptions")); } glb.WriteIniFile(); From 128a8bf903ee5baab1d32370783b12aba2ea065d Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 16 Jul 2025 16:16:36 +0300 Subject: [PATCH 64/81] Rework patch class daughter search --- MigrationTool/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 02450f3ac..96feb4786 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -68,7 +68,7 @@ private static void Main(string[] args) var patchNames = Enum.GetValues(typeof(Version)); foreach (var patchName in patchNames) { - Type type = patches.Where(t => t.FullName.Contains(patchName.ToString())).First(); + Type type = patches.Where(t => t.FullName == "MigrationTool.Patch_" + patchName.ToString()).First(); patch = (Patch)Activator.CreateInstance(type, arg); patch?.Apply(); Console.WriteLine(""); From 16802912b245a0456c7d446c3fe1ea3f8860ce00 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 26 Sep 2025 22:56:48 +0300 Subject: [PATCH 65/81] Fix building --- DXClient.sln | 130 +++++++++++++++++++++--------------------- Directory.Build.props | 8 +-- 2 files changed, 66 insertions(+), 72 deletions(-) diff --git a/DXClient.sln b/DXClient.sln index 2aa833b16..92fcbab3b 100644 --- a/DXClient.sln +++ b/DXClient.sln @@ -27,7 +27,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecondStageUpdater", "Secon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientUpdater", "ClientUpdater\ClientUpdater.csproj", "{551D080B-5624-4793-AC31-69D77C62F6B1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrationTool", "MigrationTool\MigrationTool.csproj", "{6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrationTool", "MigrationTool\MigrationTool.csproj", "{5A4EB355-10FB-4705-98E7-FB4A972CB94C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -449,70 +449,70 @@ Global {551D080B-5624-4793-AC31-69D77C62F6B1}.WindowsXNARelease|x64.Build.0 = WindowsXNARelease|x64 {551D080B-5624-4793-AC31-69D77C62F6B1}.WindowsXNARelease|x86.ActiveCfg = WindowsXNARelease|x86 {551D080B-5624-4793-AC31-69D77C62F6B1}.WindowsXNARelease|x86.Build.0 = WindowsXNARelease|x86 - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|Any CPU.ActiveCfg = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|Any CPU.Build.0 = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|ARM64.ActiveCfg = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|ARM64.Build.0 = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x64.ActiveCfg = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x64.Build.0 = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x86.ActiveCfg = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLDebug|x86.Build.0 = UniversalGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|Any CPU.ActiveCfg = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|Any CPU.Build.0 = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|ARM64.ActiveCfg = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|ARM64.Build.0 = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x64.ActiveCfg = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x64.Build.0 = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x86.ActiveCfg = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.UniversalGLRelease|x86.Build.0 = UniversalGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|Any CPU.ActiveCfg = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|Any CPU.Build.0 = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|ARM64.ActiveCfg = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|ARM64.Build.0 = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x64.ActiveCfg = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x64.Build.0 = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x86.ActiveCfg = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXDebug|x86.Build.0 = WindowsDXDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|Any CPU.ActiveCfg = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|Any CPU.Build.0 = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|ARM64.ActiveCfg = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|ARM64.Build.0 = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x64.ActiveCfg = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x64.Build.0 = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x86.ActiveCfg = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsDXRelease|x86.Build.0 = WindowsDXRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|Any CPU.ActiveCfg = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|Any CPU.Build.0 = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|ARM64.ActiveCfg = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|ARM64.Build.0 = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x64.ActiveCfg = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x64.Build.0 = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x86.ActiveCfg = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLDebug|x86.Build.0 = WindowsGLDebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|Any CPU.ActiveCfg = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|Any CPU.Build.0 = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|ARM64.ActiveCfg = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|ARM64.Build.0 = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x64.ActiveCfg = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x64.Build.0 = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x86.ActiveCfg = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsGLRelease|x86.Build.0 = WindowsGLRelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|Any CPU.ActiveCfg = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|Any CPU.Build.0 = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|ARM64.ActiveCfg = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|ARM64.Build.0 = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x64.ActiveCfg = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x64.Build.0 = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x86.ActiveCfg = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNADebug|x86.Build.0 = WindowsXNADebug|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|Any CPU.ActiveCfg = WindowsXNARelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|Any CPU.Build.0 = WindowsXNARelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|ARM64.ActiveCfg = WindowsXNARelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|ARM64.Build.0 = WindowsXNARelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x64.ActiveCfg = WindowsXNARelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x64.Build.0 = WindowsXNARelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x86.ActiveCfg = WindowsXNARelease|Any CPU - {6A9DA4AF-EBF7-4D14-8A3D-F85E014280F9}.WindowsXNARelease|x86.Build.0 = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|Any CPU.ActiveCfg = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|Any CPU.Build.0 = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|ARM64.ActiveCfg = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|ARM64.Build.0 = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|x64.ActiveCfg = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|x64.Build.0 = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|x86.ActiveCfg = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLDebug|x86.Build.0 = UniversalGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|Any CPU.ActiveCfg = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|Any CPU.Build.0 = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|ARM64.ActiveCfg = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|ARM64.Build.0 = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|x64.ActiveCfg = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|x64.Build.0 = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|x86.ActiveCfg = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.UniversalGLRelease|x86.Build.0 = UniversalGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|Any CPU.ActiveCfg = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|Any CPU.Build.0 = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|ARM64.ActiveCfg = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|ARM64.Build.0 = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|x64.ActiveCfg = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|x64.Build.0 = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|x86.ActiveCfg = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXDebug|x86.Build.0 = WindowsDXDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|Any CPU.ActiveCfg = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|Any CPU.Build.0 = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|ARM64.ActiveCfg = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|ARM64.Build.0 = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|x64.ActiveCfg = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|x64.Build.0 = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|x86.ActiveCfg = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsDXRelease|x86.Build.0 = WindowsDXRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|Any CPU.ActiveCfg = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|Any CPU.Build.0 = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|ARM64.ActiveCfg = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|ARM64.Build.0 = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|x64.ActiveCfg = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|x64.Build.0 = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|x86.ActiveCfg = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLDebug|x86.Build.0 = WindowsGLDebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|Any CPU.ActiveCfg = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|Any CPU.Build.0 = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|ARM64.ActiveCfg = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|ARM64.Build.0 = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|x64.ActiveCfg = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|x64.Build.0 = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|x86.ActiveCfg = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsGLRelease|x86.Build.0 = WindowsGLRelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|Any CPU.ActiveCfg = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|Any CPU.Build.0 = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|ARM64.ActiveCfg = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|ARM64.Build.0 = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|x64.ActiveCfg = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|x64.Build.0 = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|x86.ActiveCfg = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNADebug|x86.Build.0 = WindowsXNADebug|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|Any CPU.ActiveCfg = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|Any CPU.Build.0 = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|ARM64.ActiveCfg = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|ARM64.Build.0 = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|x64.ActiveCfg = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|x64.Build.0 = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|x86.ActiveCfg = WindowsXNARelease|Any CPU + {5A4EB355-10FB-4705-98E7-FB4A972CB94C}.WindowsXNARelease|x86.Build.0 = WindowsXNARelease|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Directory.Build.props b/Directory.Build.props index 08dd79989..f92259a09 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -30,7 +30,7 @@ WindowsXNA - + net48;net8.0-windows net8.0 @@ -51,12 +51,6 @@ AnyCPU - - net48;net8.0-windows - net8.0 - AnyCPU - - From fd40066d8963e0a334805b4902a0028dbef4fda3 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Sat, 27 Sep 2025 17:14:18 +0300 Subject: [PATCH 66/81] Rework Directory* xml files --- Directory.Build.props | 6 +++++- Directory.Build.targets | 4 ++-- Directory.Packages.props | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f92259a09..febbf4d83 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -30,7 +30,11 @@ WindowsXNA - + net48;net8.0-windows net8.0 diff --git a/Directory.Build.targets b/Directory.Build.targets index 93dc3991d..fa085032e 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -27,14 +27,14 @@ + Properties="TargetFramework=$(TargetFramework);Platform=AnyCPU;RuntimeIdentifier=" /> diff --git a/Directory.Packages.props b/Directory.Packages.props index 1af940ae2..4735966d4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -34,7 +34,10 @@ - + From 8455a80803b24fcceeb375983a71b6152892c048 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 30 Sep 2025 13:06:57 +0300 Subject: [PATCH 67/81] Make compiler happy --- Directory.Build.props | 10 +++- Directory.Build.targets | 8 ++-- MigrationTool/MigrationTool.csproj | 8 ---- Scripts/build.ps1 | 75 ++++++++++++++++++++++++------ 4 files changed, 72 insertions(+), 29 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index febbf4d83..21a360140 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,8 +33,7 @@ + Or '$(MSBuildProjectName)' == 'DXMainClient'"> net48;net8.0-windows net8.0 @@ -54,6 +53,13 @@ net8.0;net48 AnyCPU + + + net8.0-windows + AnyCPU + enable + Exe + diff --git a/Directory.Build.targets b/Directory.Build.targets index fa085032e..49c4db864 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -24,17 +24,17 @@ Properties="TargetFramework=$(TargetFramework.Split('-')[0]);Platform=AnyCPU;RuntimeIdentifier=" /> - + - + + Properties="TargetFramework=$(TargetFramework);Platform=$(Platform);RuntimeIdentifier=" /> diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj index 3860e543c..c31d4053c 100644 --- a/MigrationTool/MigrationTool.csproj +++ b/MigrationTool/MigrationTool.csproj @@ -1,9 +1,4 @@  - - Exe - false - Enable - CnCNet.MigrationTool CnCNet Client Migration Tool @@ -28,9 +23,6 @@ - - - diff --git a/Scripts/build.ps1 b/Scripts/build.ps1 index 12a0ca0b2..affe5f478 100644 --- a/Scripts/build.ps1 +++ b/Scripts/build.ps1 @@ -32,7 +32,9 @@ build.ps1 Ares -IsDebug Build for ares game on debug mode. #> -param( + +param +( [Parameter()] [switch] $IsDebug, @@ -48,7 +50,8 @@ param( ) $Script:ConfigurationSuffix = 'Release' -if ($IsDebug) { +if ($IsDebug) +{ $Script:ConfigurationSuffix = 'Debug' } @@ -67,17 +70,21 @@ $Script:FrameworkBinariesFolderMap = @{ 'net8.0-windows' = 'BinariesNET8' } -if (!$NoClean -AND (Test-Path $Script:CompiledRoot)) { +if (!$NoClean -AND (Test-Path $Script:CompiledRoot)) +{ Remove-Item -Recurse -Force -LiteralPath $Script:CompiledRoot } -if ($null -EQ $IsWindows -AND 'Desktop' -EQ $PSEdition) { +if ($null -EQ $IsWindows -AND 'Desktop' -EQ $PSEdition) +{ $Script:IsWindows = $true } -function Script:Invoke-BuildProject { +function Script:Invoke-BuildProject +{ [CmdletBinding(DefaultParameterSetName = 'ByGame')] - param ( + param + ( [Parameter(Mandatory, ParameterSetName = 'Detail', Position = 0)] [string] $Engine, @@ -86,8 +93,10 @@ function Script:Invoke-BuildProject { $Framework ) - process { - if ($Engine) { + process + { + if ($Engine) + { $Output = Join-Path $CompiledRoot 'Resources' ($FrameworkBinariesFolderMap[$Framework]) ($EngineSubFolderMap[$Engine]) $Private:ArgumentList = [System.Collections.Generic.List[string]]::new(11) @@ -98,28 +107,35 @@ function Script:Invoke-BuildProject { $Private:ArgumentList.Add("--framework:$Framework") $Private:ArgumentList.Add("--output:$Output") $Private:ArgumentList.Add('-property:SatelliteResourceLanguages=en') - if ($Log) { + if ($Log) + { $Private:ArgumentList.Add('-verbosity:diagnostic') } - if ($NoMove) { + if ($NoMove) + { $Private:ArgumentList.Add('-property:NoMove=true') } # $Private:ArgumentList.Add("-property:AssemblyVersion=$AssemblySemVer") # $Private:ArgumentList.Add("-property:FileVersion=$AssemblySemFileVer") # $Private:ArgumentList.Add("-property:InformationalVersion=$InformationalVersion") - if ($Engine -eq 'WindowsXNA') { + if ($Engine -eq 'WindowsXNA') + { $Private:ArgumentList.Add('--arch=x86') } - & 'dotnet' $Private:ArgumentList - if ($LASTEXITCODE) { + echo '' + & 'dotnet' $Private:ArgumentList + if ($LASTEXITCODE) + { throw "Build failed for ${Engine}$Script:ConfigurationSuffix $Framework" } } - else { + else + { Invoke-BuildProject -Engine 'UniversalGL' -Framework 'net8.0' - if ($IsWindows) { + if ($IsWindows) + { @('WindowsDX', 'WindowsGL', 'WindowsXNA') | ForEach-Object { $Private:Engine = $PSItem @@ -134,4 +150,33 @@ function Script:Invoke-BuildProject { } } +# Build client binaries Script:Invoke-BuildProject + +# Build migration tool binaries +$Private:MTFramework='net8.0-windows' +$Private:MTCompiledPath = Join-Path $RepoRoot 'Compiled' +$Private:MTProjectPath = Join-Path $RepoRoot 'MigrationTool' 'MigrationTool.csproj' +$Private:MTOutput = Join-Path $MTCompiledPath 'Resources' $FrameworkBinariesFolderMap['net8.0-windows'] +$Private:MTArgumentList = [System.Collections.Generic.List[string]]::new(11) +$Private:MTArgumentList.Add('publish') +$Private:MTArgumentList.Add("$MTProjectPath") +$Private:MTArgumentList.Add('--graph') +$Private:MTArgumentList.Add("--framework:$MTFramework") +$Private:MTArgumentList.Add("--output:$MTOutput\MigrationTool") +$Private:MTArgumentList.Add('-property:SatelliteResourceLanguages=en') +if ($Log) +{ + $Private:ArgumentList.Add('-verbosity:diagnostic') +} +if ($NoMove) +{ + $Private:ArgumentList.Add('-property:NoMove=true') +} + +echo '' +& 'dotnet' $MTArgumentList +if ($LASTEXITCODE) +{ + throw "Build failed for Migration Tool" +} From 20bafff8f42b189ac4770792e848e260891b7358 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 1 Oct 2025 12:17:55 +0300 Subject: [PATCH 68/81] Add info about migration tool to the docs --- Docs/Migration.md | 5 +++++ MigrationTool/Program.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/Docs/Migration.md b/Docs/Migration.md index c2a745cf2..ee28a8679 100644 --- a/Docs/Migration.md +++ b/Docs/Migration.md @@ -1,6 +1,11 @@ Migrating from older versions ----------------------------- +> [!NOTE] +> CnCNet has a program called `Migration Tool` that implements all the instructions described down below notices to automate the migration process. +> To use this tool, download the latest nightly build and run `Resources/BinariesNET8/MigrationTool/MigrationTool.exe` with older client path as the first argument. +> Example: `MigrationTool.exe C:\TiberianSunClient` + This document lists all the breaking changes and how to address them. Each section corresponds to the migration steps that are required to upgrade to the selected version. If you're skipping multiple versions in the upgrade process - you have to apply all corresponding migration steps. ## 2.12.0 diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 96feb4786..1c61b35c7 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -81,6 +81,8 @@ private static void Main(string[] args) Logger.Log("Migration to the latest client version has been failed"); } + Console.WriteLine("Patching has been done."); + break; case 0: default: From d80163e07a0d7246828c3073482c8cbe63345f0e Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 22 Oct 2025 13:39:40 +0300 Subject: [PATCH 69/81] Rewording log message --- MigrationTool/Patch_v2_11_0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 4f4455574..0fbdc28a2 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -128,7 +128,7 @@ public override void Apply() // Rework skirmish/lan/cncnet lobbies ini's if (File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini"))) { - Logger.Log($"Update lobbies has been aborted, {GameLobbyBase}.ini already exists"); + Logger.Log($"Update lobbies has been skipped, {GameLobbyBase}.ini already exists"); } else { From d54ccdad31513da93a6f4742a50808438997578a Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 22 Oct 2025 13:40:11 +0300 Subject: [PATCH 70/81] Add `RemoveKeyWithLog` --- MigrationTool/Patch.cs | 15 +++++++++++++++ MigrationTool/Patch_v2_12_6.cs | 5 ++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 031a3280b..9e86161ce 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -52,6 +52,21 @@ public Patch AddKeyWithLog(IniFile src, string section, string key, string value return this; } + public Patch RemoveKeyWithLog(IniFile src, string section, string key) + { + if (!src.KeyExists(section, key)) + { + Logger.Log($"Update {src.FileName}: Skip removing [{section}]->{key}, reason: doesn't exist"); + } + else + { + Logger.Log($"Update {src.FileName}: Remove [{section}]->{key}={src.GetSection(section).Keys.First(kvp => kvp.Key == key).Value}"); + src.GetSection(section).RemoveKey(key); + } + + return this; + } + public void CalculatePositions(IniFile ini, string parent, string child) { int parentX, parentY, childX, childY; diff --git a/MigrationTool/Patch_v2_12_6.cs b/MigrationTool/Patch_v2_12_6.cs index 4d914c256..cb3eea99e 100644 --- a/MigrationTool/Patch_v2_12_6.cs +++ b/MigrationTool/Patch_v2_12_6.cs @@ -23,10 +23,9 @@ public override void Apply() // Remove GenericWindow.ini->{ GameCreationWindow; GameCreationWindow_Advanced }->Size IniFile genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); - genericWindowIni.GetSection("GameCreationWindow")?.RemoveKey("Size"); - genericWindowIni.GetSection("GameCreationWindow_Advanced")?.RemoveKey("Size"); + RemoveKeyWithLog(genericWindowIni, "GameCreationWindow", "Size"); + RemoveKeyWithLog(genericWindowIni, "GameCreationWindow_Advanced", "Size"); genericWindowIni.WriteIniFile(); - } } From 31844b493684e115cea5d7ea47b2080ab3a97929 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 22 Oct 2025 13:50:28 +0300 Subject: [PATCH 71/81] Add log message about renaming custom checkboxes and dropdowns in `OptionsWindow.ini` --- MigrationTool/Patch_v2_11_0.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 0fbdc28a2..074fa4599 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -37,6 +37,7 @@ public override void Apply() // Rename OptionsWindow.ini->[*]->{CustomSettingFileCheckBox -- > FileSettingCheckBox & CustomSettingFileDropDown --> FileSettingDropDown} { + Logger.Log("Renaming CustomSettingFileCheckBox to FileSettingCheckBox and CustomSettingFileDropDown to FileSettingDropDown"); IniFile optionsWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "OptionsWindow.ini")); foreach (var section in optionsWindowIni.GetSections()) { From 98ad9ebbf1897ea5e3fa6f07a3667a321e86875e Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 22 Oct 2025 14:04:11 +0300 Subject: [PATCH 72/81] Add backup notice in documentation --- Docs/Migration.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Docs/Migration.md b/Docs/Migration.md index ee28a8679..c0088edb9 100644 --- a/Docs/Migration.md +++ b/Docs/Migration.md @@ -3,8 +3,11 @@ Migrating from older versions > [!NOTE] > CnCNet has a program called `Migration Tool` that implements all the instructions described down below notices to automate the migration process. -> To use this tool, download the latest nightly build and run `Resources/BinariesNET8/MigrationTool/MigrationTool.exe` with older client path as the first argument. +> To use this tool, download the latest nightly build and run `Resources\BinariesNET8\MigrationTool\MigrationTool.exe` with older client path as the first argument. +> > Example: `MigrationTool.exe C:\TiberianSunClient` +> +> It is recommended that you back up the client configuration before running the migration tool. This document lists all the breaking changes and how to address them. Each section corresponds to the migration steps that are required to upgrade to the selected version. If you're skipping multiple versions in the upgrade process - you have to apply all corresponding migration steps. From 467d078fe4bb9957a3ed07e8c25fb6fee76b520e Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Wed, 26 Nov 2025 15:49:17 +0300 Subject: [PATCH 73/81] Add migration tool to the project list in slnx file --- DXClient.slnx | 1 + 1 file changed, 1 insertion(+) diff --git a/DXClient.slnx b/DXClient.slnx index 74f3a7861..09df3418b 100644 --- a/DXClient.slnx +++ b/DXClient.slnx @@ -32,6 +32,7 @@ + From 2e5f43be96ca6e644bc3f13af7ac48ff1b6ce899 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Tue, 2 Dec 2025 01:33:58 +0300 Subject: [PATCH 74/81] Add .NET 4.8 build --- Directory.Build.props | 3 ++- Scripts/build.ps1 | 60 ++++++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5c24c6252..cf91671f4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -56,7 +56,8 @@ - net8.0-windows + net8.0;net48 + win-x86 AnyCPU enable Exe diff --git a/Scripts/build.ps1 b/Scripts/build.ps1 index 0c47c811a..ed4afc16f 100644 --- a/Scripts/build.ps1 +++ b/Scripts/build.ps1 @@ -147,33 +147,41 @@ function Script:Invoke-BuildProject } } +function Script:Invoke-BuildMigrationTool +{ + ForEach ($Engine in 'net8.0', 'net48') + { + $Private:MTFramework = $Engine + $Private:MTCompiledPath = Join-Path $RepoRoot 'Compiled' + $Private:MTProjectPath = Join-Path $RepoRoot 'MigrationTool' 'MigrationTool.csproj' + $Private:MTOutput = Join-Path $MTCompiledPath 'Resources' $FrameworkBinariesFolderMap[$Engine] + $Private:MTArgumentList = [System.Collections.Generic.List[string]]::new(11) + $Private:MTArgumentList.Add('publish') + $Private:MTArgumentList.Add("$MTProjectPath") + $Private:MTArgumentList.Add('--graph') + $Private:MTArgumentList.Add("--framework:$MTFramework") + $Private:MTArgumentList.Add("--output:$MTOutput\MigrationTool") + $Private:MTArgumentList.Add('-property:SatelliteResourceLanguages=en') + if ($Log) + { + $Private:ArgumentList.Add('-verbosity:diagnostic') + } + if ($NoMove) + { + $Private:ArgumentList.Add('-property:NoMove=true') + } + + echo '' + & 'dotnet' $MTArgumentList + if ($LASTEXITCODE) + { + throw "Build failed for Migration Tool" + } + } +} + # Build client binaries Script:Invoke-BuildProject # Build migration tool binaries -$Private:MTFramework='net8.0-windows' -$Private:MTCompiledPath = Join-Path $RepoRoot 'Compiled' -$Private:MTProjectPath = Join-Path $RepoRoot 'MigrationTool' 'MigrationTool.csproj' -$Private:MTOutput = Join-Path $MTCompiledPath 'Resources' $FrameworkBinariesFolderMap['net8.0-windows'] -$Private:MTArgumentList = [System.Collections.Generic.List[string]]::new(11) -$Private:MTArgumentList.Add('publish') -$Private:MTArgumentList.Add("$MTProjectPath") -$Private:MTArgumentList.Add('--graph') -$Private:MTArgumentList.Add("--framework:$MTFramework") -$Private:MTArgumentList.Add("--output:$MTOutput\MigrationTool") -$Private:MTArgumentList.Add('-property:SatelliteResourceLanguages=en') -if ($Log) -{ - $Private:ArgumentList.Add('-verbosity:diagnostic') -} -if ($NoMove) -{ - $Private:ArgumentList.Add('-property:NoMove=true') -} - -echo '' -& 'dotnet' $MTArgumentList -if ($LASTEXITCODE) -{ - throw "Build failed for Migration Tool" -} +Script:Invoke-BuildMigrationTool From ab0f4d419beaae47dcaf0497bae8c87891d9e37d Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 12 Jan 2026 17:23:27 +0300 Subject: [PATCH 75/81] Update submodule --- Rampastring.XNAUI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rampastring.XNAUI b/Rampastring.XNAUI index 018c9b83f..c7b2b70d7 160000 --- a/Rampastring.XNAUI +++ b/Rampastring.XNAUI @@ -1 +1 @@ -Subproject commit 018c9b83f398b72fa005ca4dbb95c6304bab6360 +Subproject commit c7b2b70d7af987161346e7d4a446dea33da62219 From 3bdc03b4235923480df867f0b5ba86b193506b32 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 26 Jan 2026 15:04:35 +0300 Subject: [PATCH 76/81] Fix warning --- MigrationTool/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index 1c61b35c7..fa6458760 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -69,7 +69,7 @@ private static void Main(string[] args) foreach (var patchName in patchNames) { Type type = patches.Where(t => t.FullName == "MigrationTool.Patch_" + patchName.ToString()).First(); - patch = (Patch)Activator.CreateInstance(type, arg); + patch = (Patch?)Activator.CreateInstance(type, arg); patch?.Apply(); Console.WriteLine(""); } From c7393ceb070c5e4f4c1cd74c3ae32d6f05b9958b Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 26 Jan 2026 15:04:52 +0300 Subject: [PATCH 77/81] Refactoring names for const vars --- MigrationTool/Program.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs index fa6458760..eee72468f 100644 --- a/MigrationTool/Program.cs +++ b/MigrationTool/Program.cs @@ -11,8 +11,8 @@ namespace MigrationTool; internal sealed class Program { - private const string errMsg = "Unknown arguments detected. Use -h argument to print help information."; - private const string helpMsg = + private const string ERROR_MESSAGE = "Unknown arguments detected. Use -h argument to print help information."; + private const string HELP_MESSAGE = """ CnCNet Client Migration Tool. @@ -43,13 +43,13 @@ private static void Main(string[] args) or "/?" or "/h") { - Console.WriteLine(helpMsg); + Console.WriteLine(HELP_MESSAGE); return; } if (!SafePath.GetDirectory(arg).Exists) { - Console.WriteLine(errMsg); + Console.WriteLine(ERROR_MESSAGE); return; } @@ -86,7 +86,7 @@ private static void Main(string[] args) break; case 0: default: - Console.WriteLine(errMsg); + Console.WriteLine(ERROR_MESSAGE); break; } } From 133e08cc3aba37b7f9c8f576051ee11925866073 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 26 Jan 2026 16:26:47 +0300 Subject: [PATCH 78/81] Add more spacing --- MigrationTool/Patch.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs index 9e86161ce..25dabd9a0 100644 --- a/MigrationTool/Patch.cs +++ b/MigrationTool/Patch.cs @@ -45,7 +45,10 @@ public Patch AddKeyWithLog(IniFile src, string section, string key, string value else { Logger.Log($"Update {src.FileName}: Add [{section}]->{key}={value}"); - if (!src.SectionExists(section)) src.AddSection(section); + + if (!src.SectionExists(section)) + src.AddSection(section); + src.GetSection(section).AddKey(key, value); } From 7fc5508edb6dca62be8b1c3f50728c6deed3b64c Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Mon, 26 Jan 2026 16:28:29 +0300 Subject: [PATCH 79/81] Fix warnings --- MigrationTool/Patch_v2_11_0.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 074fa4599..52fc71481 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -406,20 +406,22 @@ public override void Apply() // Add new texture files var assembly = Assembly.GetExecutingAssembly(); foreach (var resourceName in assembly.GetManifestResourceNames()) - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) + { + using (Stream? resourceStream = assembly.GetManifestResourceStream(resourceName)) { var filename = resourceName.Replace($"{nameof(MigrationTool)}.Pictures.", string.Empty); var filepath = SafePath.CombineFilePath(ResouresDir.FullName, filename); if (!File.Exists(filepath)) { - using (FileStream fileStream = new FileStream(filepath, FileMode.CreateNew)) + using (FileStream fileStream = new(filepath, FileMode.CreateNew)) { Logger.Log($"Copy {filename} to the {ResouresDir.FullName}"); - resourceStream.CopyTo(fileStream); + resourceStream?.CopyTo(fileStream); } } } + } } } From f02cd771b363a60f8b4e58f71e1d943b9ae9ff74 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 6 Feb 2026 12:01:07 +0300 Subject: [PATCH 80/81] Refactore removing Rampastring.Tools.* near to the client*.exe --- MigrationTool/Patch_v2_11_0.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 52fc71481..9174e7d45 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -21,12 +21,21 @@ public override void Apply() base.Apply(); // Remove Rampastring.Tools from Resources directory (not recursive) - Logger.Log("Remove Resources\\Rampastring.Tools.* (* -- dll, pdb, xml)"); - SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.dll"); - SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.pdb"); - SafePath.DeleteFileIfExists(ResouresDir.FullName, "Rampastring.Tools.xml"); + List rmptFiles = [SafePath.CombineFilePath(ResouresDir.FullName, "Rampastring.Tools.dll"), + SafePath.CombineFilePath(ResouresDir.FullName, "Rampastring.Tools.pdb"), + SafePath.CombineFilePath(ResouresDir.FullName, "Rampastring.Tools.xml")]; + + foreach (var file in rmptFiles) + { + if (!File.Exists(file)) + continue; + + Logger.Log($"Remove {file}"); + SafePath.DeleteFileIfExists(file); + } // Add GenericWindow.ini->[GenericWindow]->DrawBorders=false + // and rename [ExtraControls] => [$ExtraControls] { var genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); AddKeyWithLog(genericWindowIni, "GenericWindow", "DrawBorders", "false"); From 963ceb98bda8f3f1f94aceba7655ffdc58e3bf93 Mon Sep 17 00:00:00 2001 From: MahBoiDeveloper Date: Fri, 6 Feb 2026 12:16:35 +0300 Subject: [PATCH 81/81] Rework patching game lobbies --- MigrationTool/Patch_v2_11_0.cs | 468 +++++++++++++++++---------------- 1 file changed, 240 insertions(+), 228 deletions(-) diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs index 9174e7d45..7d9456f31 100644 --- a/MigrationTool/Patch_v2_11_0.cs +++ b/MigrationTool/Patch_v2_11_0.cs @@ -152,264 +152,276 @@ public override void Apply() IniFile multiplayerGameLobbyIni_old = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); IniFile gameOptionsIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GameOptions.ini")); - // New configs - IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini")); - IniFile skirmishLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}_New.ini")); - IniFile multiplayerGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}_New.ini")); - IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); - IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); - - // Add random color to the GameOptions.ini - AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); - - // Delete old inheritance - if (skirmishLobbyIni_old.SectionExists("INISystem")) - skirmishLobbyIni_old.RemoveSection("INISystem"); - if (multiplayerGameLobbyIni_old.SectionExists("INISystem")) - multiplayerGameLobbyIni_old.RemoveSection("INISystem"); - - // Add inheritance - //AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow.ini"); - AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); - AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); - AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); - AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); - - // Transfer old SkirmishLobby.ini->[ExtraControls] to new SkirmishLobby.ini->[$ExtraControls] - if (skirmishLobbyIni_old.SectionExists($"{ExtraControls}")) + Logger.Log($"Unable to find {GameLobbyBase}.ini. Checking {SkirmishLobby}.ini, {MultiplayerGameLobby}.ini..."); + + if (!skirmishLobbyIni_old.SectionExists("GameOptionsPanel") && !multiplayerGameLobbyIni_old.SectionExists("GameOptionsPanel")) { - TransferKeys(skirmishLobbyIni_old, $"{ExtraControls}", skirmishLobbyIni, $"${ExtraControls}"); - skirmishLobbyIni_old.RemoveSection($"{ExtraControls}"); - - foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) + Logger.Log($"Generating {GameLobbyBase}.ini..."); + + // New configs + IniFile gameLobbyBaseIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini")); + IniFile skirmishLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}_New.ini")); + IniFile multiplayerGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}_New.ini")); + IniFile lanGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "LANGameLobby.ini")); + IniFile cncnetGameLobbyIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "CnCNetGameLobby.ini")); + + // Add random color to the GameOptions.ini + AddKeyWithLog(gameOptionsIni, "General", "RandomColor", "168,168,168"); + + // Delete old inheritance + if (skirmishLobbyIni_old.SectionExists("INISystem")) + skirmishLobbyIni_old.RemoveSection("INISystem"); + + if (multiplayerGameLobbyIni_old.SectionExists("INISystem")) + multiplayerGameLobbyIni_old.RemoveSection("INISystem"); + + // Add inheritance + //AddKeyWithLog(gameLobbyBaseIni, "INISystem", "BasedOn", "GenericWindow.ini"); + AddKeyWithLog(skirmishLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); + AddKeyWithLog(multiplayerGameLobbyIni, "INISystem", "BasedOn", $"{GameLobbyBase}.ini"); + AddKeyWithLog(lanGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); + AddKeyWithLog(cncnetGameLobbyIni, "INISystem", "BasedOn", $"{MultiplayerGameLobby}.ini"); + + // Transfer old SkirmishLobby.ini->[ExtraControls] to new SkirmishLobby.ini->[$ExtraControls] + if (skirmishLobbyIni_old.SectionExists($"{ExtraControls}")) { - var section = skirmishLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; - TransferKeys(skirmishLobbyIni_old, section, skirmishLobbyIni); - skirmishLobbyIni_old.RemoveSection(section); - } - } + TransferKeys(skirmishLobbyIni_old, $"{ExtraControls}", skirmishLobbyIni, $"${ExtraControls}"); + skirmishLobbyIni_old.RemoveSection($"{ExtraControls}"); - // Configure GameLobbyBase.ini - { - // Add [SkirmishLobby] - { - var addKey = (string key) => AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", key, gameOptionsIni.GetStringValue($"{SkirmishLobby}", key, string.Empty)); - addKey("PlayerOptionLocationX"); - addKey("PlayerOptionLocationY"); - addKey("PlayerOptionVerticalMargin"); - addKey("PlayerOptionHorizontalMargin"); - addKey("PlayerOptionCaptionLocationY"); - addKey("PlayerNameWidth"); - addKey("SideWidth"); - addKey("ColorWidth"); - addKey("StartWidth"); - addKey("TeamWidth"); + foreach (var key in skirmishLobbyIni.GetSectionKeys($"${ExtraControls}")) + { + var section = skirmishLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; + TransferKeys(skirmishLobbyIni_old, section, skirmishLobbyIni); + skirmishLobbyIni_old.RemoveSection(section); + } } - TransferKeys(skirmishLobbyIni_old, $"{SkirmishLobby}", gameLobbyBaseIni); - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK-GOP", "GameOptionsPanel:XNAPanel"); - skirmishLobbyIni_old.RemoveSection($"{SkirmishLobby}"); - - TransferKeys(skirmishLobbyIni_old, "GameOptionsPanel", gameLobbyBaseIni); - skirmishLobbyIni_old.RemoveSection("GameOptionsPanel"); - // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[GameOptionsPanel] - int outerIndex = 0; - foreach (var itemName in gameOptionsIniControlKeys) + // Configure GameLobbyBase.ini { - string itemType = itemName switch + // Add [SkirmishLobby] { - "CheckBoxes" => "GameLobbyCheckBox", - "DropDowns" => "GameLobbyDropDown", - "Labels" => "XNALabel", - _ => throw new Exception($"Unknown type of elements {itemName}") - }; - - string[] items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split([","], StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; i < items.Length; i++) - { - var item = items[i]; - AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", $"$CC_{i + outerIndex}", $"{item}:{itemType}"); - TransferKeys(gameOptionsIni, item, gameLobbyBaseIni); - CalculatePositions(gameLobbyBaseIni, "GameOptionsPanel", item); + var addKey = (string key) => AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", key, gameOptionsIni.GetStringValue($"{SkirmishLobby}", key, string.Empty)); + addKey("PlayerOptionLocationX"); + addKey("PlayerOptionLocationY"); + addKey("PlayerOptionVerticalMargin"); + addKey("PlayerOptionHorizontalMargin"); + addKey("PlayerOptionCaptionLocationY"); + addKey("PlayerNameWidth"); + addKey("SideWidth"); + addKey("ColorWidth"); + addKey("StartWidth"); + addKey("TeamWidth"); } + TransferKeys(skirmishLobbyIni_old, $"{SkirmishLobby}", gameLobbyBaseIni); + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK-GOP", "GameOptionsPanel:XNAPanel"); + skirmishLobbyIni_old.RemoveSection($"{SkirmishLobby}"); - outerIndex += items.Length; - } + TransferKeys(skirmishLobbyIni_old, "GameOptionsPanel", gameLobbyBaseIni); + skirmishLobbyIni_old.RemoveSection("GameOptionsPanel"); - // Add other elements in GameLobbyBase.ini->[SkirmishLobby] - { - var addControl = (string controlKey, string section, string controlType) => + // Transfer checkboxes, dropdowns, labels from GameOptions.ini to GameLobbyBase.ini->[GameOptionsPanel] + int outerIndex = 0; + foreach (var itemName in gameOptionsIniControlKeys) { - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", controlKey, $"{section}:{controlType}"); - try + string itemType = itemName switch { - TransferKeys(skirmishLobbyIni_old, section, gameLobbyBaseIni); - } - catch + "CheckBoxes" => "GameLobbyCheckBox", + "DropDowns" => "GameLobbyDropDown", + "Labels" => "XNALabel", + _ => throw new Exception($"Unknown type of elements {itemName}") + }; + + string[] items = gameOptionsIni.GetStringValue($"{SkirmishLobby}", itemName, string.Empty).Split([","], StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < items.Length; i++) { - gameLobbyBaseIni.AddSection(section); + var item = items[i]; + AddKeyWithLog(gameLobbyBaseIni, "GameOptionsPanel", $"$CC_{i + outerIndex}", $"{item}:{itemType}"); + TransferKeys(gameOptionsIni, item, gameLobbyBaseIni); + CalculatePositions(gameLobbyBaseIni, "GameOptionsPanel", item); } - skirmishLobbyIni_old.RemoveSection(section); - }; - - addControl("$CC-SK00", "btnLaunchGame", "GameLaunchButton"); - addControl("$CC-SK01", "MapPreviewBox", "MapPreviewBox"); - addControl("$CC-SK02", "PlayerOptionsPanel", "XNAPanel"); - addControl("$CC-SK03", "ddGameMode", "XNAClientDropDown"); - addControl("$CC-SK04", "tbMapSearch", "XNASuggestionTextBox"); - addControl("$CC-SK05", "btnPickRandomMap", "XNAClientButton"); - addControl("$CC-SK06", "lblGameModeSelect", "XNALabel"); - addControl("$CC-SK07", "lbMapList", "XNAMultiColumnListBox"); - addControl("$CC-SK08", "lblMapSize", "XNALabel"); - addControl("$CC-SK09", "lblGameMode", "XNALabel"); - addControl("$CC-SK10", "lblMapAuthor", "XNALabel"); - addControl("$CC-SK11", "lblMapName", "XNALabel"); - addControl("$CC-SK12", "btnLeaveGame", "XNAClientButton"); - - AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK13", "btnSaveLoadGameOptions:XNAClientButton"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "IdleTexture", "comboBoxArrow.png"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "HoverTexture", "comboBoxArrow.png"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Width", "18"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Height", "21"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$X", "getRight(GameOptionsPanel) - getWidth($Self) - 1"); - AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Y", "getY(GameOptionsPanel) + 1"); - - skirmishLobbyIni_old.GetSections().ForEach(x => TransferKeys(skirmishLobbyIni_old, x, gameLobbyBaseIni)); + + outerIndex += items.Length; + } + + // Add other elements in GameLobbyBase.ini->[SkirmishLobby] + { + var addControl = (string controlKey, string section, string controlType) => + { + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", controlKey, $"{section}:{controlType}"); + try + { + TransferKeys(skirmishLobbyIni_old, section, gameLobbyBaseIni); + } + catch + { + gameLobbyBaseIni.AddSection(section); + } + skirmishLobbyIni_old.RemoveSection(section); + }; + + addControl("$CC-SK00", "btnLaunchGame", "GameLaunchButton"); + addControl("$CC-SK01", "MapPreviewBox", "MapPreviewBox"); + addControl("$CC-SK02", "PlayerOptionsPanel", "XNAPanel"); + addControl("$CC-SK03", "ddGameMode", "XNAClientDropDown"); + addControl("$CC-SK04", "tbMapSearch", "XNASuggestionTextBox"); + addControl("$CC-SK05", "btnPickRandomMap", "XNAClientButton"); + addControl("$CC-SK06", "lblGameModeSelect", "XNALabel"); + addControl("$CC-SK07", "lbMapList", "XNAMultiColumnListBox"); + addControl("$CC-SK08", "lblMapSize", "XNALabel"); + addControl("$CC-SK09", "lblGameMode", "XNALabel"); + addControl("$CC-SK10", "lblMapAuthor", "XNALabel"); + addControl("$CC-SK11", "lblMapName", "XNALabel"); + addControl("$CC-SK12", "btnLeaveGame", "XNAClientButton"); + + AddKeyWithLog(gameLobbyBaseIni, $"{SkirmishLobby}", "$CC-SK13", "btnSaveLoadGameOptions:XNAClientButton"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "IdleTexture", "comboBoxArrow.png"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "HoverTexture", "comboBoxArrow.png"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Width", "18"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Height", "21"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$X", "getRight(GameOptionsPanel) - getWidth($Self) - 1"); + AddKeyWithLog(gameLobbyBaseIni, "btnSaveLoadGameOptions", "$Y", "getY(GameOptionsPanel) + 1"); + + skirmishLobbyIni_old.GetSections().ForEach(x => TransferKeys(skirmishLobbyIni_old, x, gameLobbyBaseIni)); + } } - } - // Transfer old MultiplayerGameLobby.ini->[ExtraControls] to new MultiplayerGameLobby.ini->[$ExtraControls] - if (multiplayerGameLobbyIni_old.SectionExists($"{ExtraControls}")) - { - TransferKeys(multiplayerGameLobbyIni_old, $"{ExtraControls}", multiplayerGameLobbyIni, $"${ExtraControls}"); - multiplayerGameLobbyIni_old.RemoveSection($"{ExtraControls}"); - foreach (var key in multiplayerGameLobbyIni.GetSectionKeys($"${ExtraControls}")) + // Transfer old MultiplayerGameLobby.ini->[ExtraControls] to new MultiplayerGameLobby.ini->[$ExtraControls] + if (multiplayerGameLobbyIni_old.SectionExists($"{ExtraControls}")) { - var value = multiplayerGameLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; - TransferKeys(multiplayerGameLobbyIni_old, value, multiplayerGameLobbyIni); - multiplayerGameLobbyIni_old.RemoveSection(value); + TransferKeys(multiplayerGameLobbyIni_old, $"{ExtraControls}", multiplayerGameLobbyIni, $"${ExtraControls}"); + multiplayerGameLobbyIni_old.RemoveSection($"{ExtraControls}"); + foreach (var key in multiplayerGameLobbyIni.GetSectionKeys($"${ExtraControls}")) + { + var value = multiplayerGameLobbyIni.GetStringValue($"${ExtraControls}", key, string.Empty).Split(':')[0]; + TransferKeys(multiplayerGameLobbyIni_old, value, multiplayerGameLobbyIni); + multiplayerGameLobbyIni_old.RemoveSection(value); + } } - } - - // Configure MultiplayerGameLobby.ini - { - AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$BaseSection", $"{SkirmishLobby}"); - // Add keys into [MultiplayerGameLobby] if values are changed with comparison to [SkirmishLobby] - foreach (var key in gameLobbyBaseIni.GetSectionKeys($"{SkirmishLobby}").Where(elem => !elem.StartsWith("$"))) + // Configure MultiplayerGameLobby.ini { - var valueSkirmish = gameLobbyBaseIni.GetStringValue($"{SkirmishLobby}", key, string.Empty); - var valueMultiplayer = gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", key, string.Empty); + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$BaseSection", $"{SkirmishLobby}"); - if (valueMultiplayer != valueSkirmish) - AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", key, valueMultiplayer); - } + // Add keys into [MultiplayerGameLobby] if values are changed with comparison to [SkirmishLobby] + foreach (var key in gameLobbyBaseIni.GetSectionKeys($"{SkirmishLobby}").Where(elem => !elem.StartsWith("$"))) + { + var valueSkirmish = gameLobbyBaseIni.GetStringValue($"{SkirmishLobby}", key, string.Empty); + var valueMultiplayer = gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", key, string.Empty); - // Find controls to exclude and include - List skirmishControls = new(); - List multiplayerControls = new(); - gameOptionsIniControlKeys.ForEach(x => skirmishControls.AddRange(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty).Split(','))); - gameOptionsIniControlKeys.ForEach(x => multiplayerControls.AddRange(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty).Split(','))); - var excludeControls = skirmishControls.Except(multiplayerControls).ToList(); - var addControls = multiplayerControls.Except(skirmishControls).ToList(); - - // Disable skirmish lobby only controls from GameOptions.ini - excludeControls.ForEach(x => - AddKeyWithLog(multiplayerGameLobbyIni, x, "Visible", "false") - .AddKeyWithLog(multiplayerGameLobbyIni, x, "Enabled", "false")); - - // Add multiplayer lobby only controls from GameOptions.ini - addControls.ForEach(control => - AddKeyWithLog( - multiplayerGameLobbyIni, - "GameOptionsPanel", - $"$CC-M{addControls.IndexOf(control)}", - control + ':' + control.Substring(0, 3) switch - { - "chk" => "GameLobbyCheckBox", - "cmb" => "GameLobbyDropDown", - "lbl" => "XNALabel", - _ => throw new Exception($"GameOptions.ini contains unknown type of contol with name {control}") - }) - .TransferKeys(gameOptionsIni, control, multiplayerGameLobbyIni) - .CalculatePositions(multiplayerGameLobbyIni, "GameOptionsPanel", control)); - - // Add other elements in MultiplayerGameLobby.ini->[MultiplayerGameLobby] - { - var addControl = (string controlKey, string section, string controlType) => + if (valueMultiplayer != valueSkirmish) + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", key, valueMultiplayer); + } + + // Find controls to exclude and include + List skirmishControls = new(); + List multiplayerControls = new(); + gameOptionsIniControlKeys.ForEach(x => skirmishControls.AddRange(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty).Split(','))); + gameOptionsIniControlKeys.ForEach(x => multiplayerControls.AddRange(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty).Split(','))); + var excludeControls = skirmishControls.Except(multiplayerControls).ToList(); + var addControls = multiplayerControls.Except(skirmishControls).ToList(); + + // Disable skirmish lobby only controls from GameOptions.ini + excludeControls.ForEach(x => + AddKeyWithLog(multiplayerGameLobbyIni, x, "Visible", "false") + .AddKeyWithLog(multiplayerGameLobbyIni, x, "Enabled", "false")); + + // Add multiplayer lobby only controls from GameOptions.ini + addControls.ForEach(control => + AddKeyWithLog( + multiplayerGameLobbyIni, + "GameOptionsPanel", + $"$CC-M{addControls.IndexOf(control)}", + control + ':' + control.Substring(0, 3) switch + { + "chk" => "GameLobbyCheckBox", + "cmb" => "GameLobbyDropDown", + "lbl" => "XNALabel", + _ => throw new Exception($"GameOptions.ini contains unknown type of contol with name {control}") + }) + .TransferKeys(gameOptionsIni, control, multiplayerGameLobbyIni) + .CalculatePositions(multiplayerGameLobbyIni, "GameOptionsPanel", control)); + + // Add other elements in MultiplayerGameLobby.ini->[MultiplayerGameLobby] { - AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", controlKey, $"{section}:{controlType}"); - try - { - TransferKeys(multiplayerGameLobbyIni_old, section, multiplayerGameLobbyIni); - } - catch + var addControl = (string controlKey, string section, string controlType) => { - multiplayerGameLobbyIni.AddSection(section); - } - multiplayerGameLobbyIni_old.RemoveSection(section); - }; - - addControl("$CC-MP01", "btnLockGame", "XNAClientButton"); - addControl("$CC-MP02", "lbChatMessages_Host", "ChatListBox"); - addControl("$CC-MP03", "lbChatMessages_Player", "ChatListBox"); - addControl("$CC-MP04", "tbChatInput_Host", "XNAChatTextBox"); - addControl("$CC-MP05", "tbChatInput_Player", "XNAChatTextBox"); - addControl("$CC-MP06", "chkAutoReady", "XNAClientCheckBox"); - - AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP07", "lbChatMessages:ChatListBox"); - AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP08", "tbChatInput:XNAChatTextBox"); - TransferKeys(multiplayerGameLobbyIni, "lbChatMessages_Player", multiplayerGameLobbyIni, "lbChatMessages"); - TransferKeys(multiplayerGameLobbyIni, "tbChatInput_Player", multiplayerGameLobbyIni, "tbChatInput"); - - multiplayerGameLobbyIni_old.GetSections().ForEach(x => TransferKeys(multiplayerGameLobbyIni_old, x, multiplayerGameLobbyIni)); + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", controlKey, $"{section}:{controlType}"); + try + { + TransferKeys(multiplayerGameLobbyIni_old, section, multiplayerGameLobbyIni); + } + catch + { + multiplayerGameLobbyIni.AddSection(section); + } + multiplayerGameLobbyIni_old.RemoveSection(section); + }; + + addControl("$CC-MP01", "btnLockGame", "XNAClientButton"); + addControl("$CC-MP02", "lbChatMessages_Host", "ChatListBox"); + addControl("$CC-MP03", "lbChatMessages_Player", "ChatListBox"); + addControl("$CC-MP04", "tbChatInput_Host", "XNAChatTextBox"); + addControl("$CC-MP05", "tbChatInput_Player", "XNAChatTextBox"); + addControl("$CC-MP06", "chkAutoReady", "XNAClientCheckBox"); + + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP07", "lbChatMessages:ChatListBox"); + AddKeyWithLog(multiplayerGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP08", "tbChatInput:XNAChatTextBox"); + TransferKeys(multiplayerGameLobbyIni, "lbChatMessages_Player", multiplayerGameLobbyIni, "lbChatMessages"); + TransferKeys(multiplayerGameLobbyIni, "tbChatInput_Player", multiplayerGameLobbyIni, "tbChatInput"); + + multiplayerGameLobbyIni_old.GetSections().ForEach(x => TransferKeys(multiplayerGameLobbyIni_old, x, multiplayerGameLobbyIni)); + } } - } - // Configure CnCNetGameLobby.ini - AddKeyWithLog(cncnetGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP99", "btnChangeTunnel:XNAClientButton"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); - AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); + // Configure CnCNetGameLobby.ini + AddKeyWithLog(cncnetGameLobbyIni, $"{MultiplayerGameLobby}", "$CC-MP99", "btnChangeTunnel:XNAClientButton"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Width", "133"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$X", "getX(btnLeaveGame) - getWidth($Self) - BUTTON_SPACING"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "$Y", "getY(btnLaunchGame)"); + AddKeyWithLog(cncnetGameLobbyIni, "btnChangeTunnel", "Text", "Change Tunnel"); - // Remove empty keys - foreach (var ini in new List() { gameLobbyBaseIni, multiplayerGameLobbyIni, skirmishLobbyIni, lanGameLobbyIni, cncnetGameLobbyIni }) - { - foreach (var section in ini.GetSections()) + // Remove empty keys + foreach (var ini in new List() { gameLobbyBaseIni, multiplayerGameLobbyIni, skirmishLobbyIni, lanGameLobbyIni, cncnetGameLobbyIni }) { - ini.GetSectionKeys(section) - .Where(key => string.IsNullOrWhiteSpace(ini.GetStringValue(section, key, string.Empty))) - .ToList() - .ForEach(key => ini.RemoveKey(section, key)); + foreach (var section in ini.GetSections()) + { + ini.GetSectionKeys(section) + .Where(key => string.IsNullOrWhiteSpace(ini.GetStringValue(section, key, string.Empty))) + .ToList() + .ForEach(key => ini.RemoveKey(section, key)); + } } - } - // Replace old configs with new one, delete placeholders, delete redundant sections - var sb = new StringBuilder(); - gameOptionsIniControlKeys - .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty)).Append(',')); - gameOptionsIniControlKeys - .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty)).Append(',')); - sb.ToString() - .Split(',') - .Distinct() - .Select(x => x = x.Trim()) - .Where(x => !string.IsNullOrEmpty(x)) - .ToList() - .ForEach(x => gameOptionsIni.RemoveSection(x)); - - gameOptionsIni.RemoveSection($"{SkirmishLobby}"); - gameOptionsIni.RemoveSection($"{MultiplayerGameLobby}"); - SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); - SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); - skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); - multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobbyIni_old.FileName)); - - gameOptionsIni.WriteIniFile(); - gameLobbyBaseIni.WriteIniFile(); - lanGameLobbyIni.WriteIniFile(); - cncnetGameLobbyIni.WriteIniFile(); + // Replace old configs with new one, delete placeholders, delete redundant sections + var sb = new StringBuilder(); + gameOptionsIniControlKeys + .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{SkirmishLobby}", x, string.Empty)).Append(',')); + gameOptionsIniControlKeys + .ForEach(x => sb.Append(gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", x, string.Empty)).Append(',')); + sb.ToString() + .Split(',') + .Distinct() + .Select(x => x = x.Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .ToList() + .ForEach(x => gameOptionsIni.RemoveSection(x)); + + gameOptionsIni.RemoveSection($"{SkirmishLobby}"); + gameOptionsIni.RemoveSection($"{MultiplayerGameLobby}"); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{SkirmishLobby}.ini")); + SafePath.DeleteFileIfExists(SafePath.CombineFilePath(ResouresDir.FullName, $"{MultiplayerGameLobby}.ini")); + skirmishLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, skirmishLobbyIni_old.FileName)); + multiplayerGameLobbyIni.WriteIniFile(SafePath.CombineFilePath(ResouresDir.FullName, multiplayerGameLobbyIni_old.FileName)); + + gameOptionsIni.WriteIniFile(); + gameLobbyBaseIni.WriteIniFile(); + lanGameLobbyIni.WriteIniFile(); + cncnetGameLobbyIni.WriteIniFile(); + } + else + { + Logger.Log($"GameOptionsPanel section found. Skip game lobby update"); + } } // Add new texture files