diff --git a/DXClient.slnx b/DXClient.slnx index ddb3e967b..9883879b5 100644 --- a/DXClient.slnx +++ b/DXClient.slnx @@ -42,6 +42,7 @@ + diff --git a/Directory.Build.props b/Directory.Build.props index b6afc78bc..ccc983a43 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -58,6 +58,14 @@ AnyCPU + + net8.0;net48 + win-x86 + AnyCPU + enable + Exe + + diff --git a/Directory.Build.targets b/Directory.Build.targets index f43472c3e..49c4db864 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 e82f72da9..67134e906 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -24,11 +24,14 @@ - + - - + + diff --git a/Docs/Migration-INI.md b/Docs/Migration-INI.md index 72101e4b0..ebba33f10 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**. @@ -806,6 +806,8 @@ Location=470,137 Location=0,200 [btnForceUpdate] +Location=407,213 +Size=133,23 ``` 2. **OPTIONAL** Add sections: diff --git a/Docs/Migration.md b/Docs/Migration.md index 8c539a884..3b668df2a 100644 --- a/Docs/Migration.md +++ b/Docs/Migration.md @@ -1,6 +1,14 @@ 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` +> +> 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. > [!NOTE] diff --git a/MigrationTool/MigrationTool.csproj b/MigrationTool/MigrationTool.csproj new file mode 100644 index 000000000..c31d4053c --- /dev/null +++ b/MigrationTool/MigrationTool.csproj @@ -0,0 +1,29 @@ + + + CnCNet.MigrationTool + CnCNet Client Migration Tool + CnCNet.MigrationTool + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MigrationTool/Patch.cs b/MigrationTool/Patch.cs new file mode 100644 index 000000000..25dabd9a0 --- /dev/null +++ b/MigrationTool/Patch.cs @@ -0,0 +1,156 @@ +using System.IO; +using System.Linq; +using System.Collections.Generic; + +using Rampastring.Tools; +using ClientCore.Enums; +namespace MigrationTool; + +internal abstract class Patch +{ + public Version ClientVersion { get; protected set; } + public ClientType Game { get; protected set; } + public DirectoryInfo ClientDir { get; protected set; } + public DirectoryInfo ResouresDir { get; protected set; } + + public Patch(string clientPath) + { + ClientDir = SafePath.GetDirectory(clientPath); + ResouresDir = SafePath.GetDirectory(SafePath.CombineFilePath(clientPath, "Resources")); + + // Predict client type by guessing game engine files + Game = ClientType.TS; + + if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "Ares.dll")).Exists) + { + Game = ClientType.Ares; + } + else if (SafePath.GetFile(SafePath.CombineFilePath(ClientDir.FullName, "gamemd-spawn.dll")).Exists) + { + Game = ClientType.YR; + } + } + + public virtual void Apply() + { + Logger.Log($"Applying patch for client version {ClientVersion.ToString().Replace('_', '.')}..."); + } + + public Patch AddKeyWithLog(IniFile src, string section, string key, string value) + { + if (src.KeyExists(section, key)) + { + Logger.Log($"Update {src.FileName}: Skip adding [{section}]->{key}, reason: already exist"); + } + else + { + Logger.Log($"Update {src.FileName}: Add [{section}]->{key}={value}"); + + if (!src.SectionExists(section)) + src.AddSection(section); + + src.GetSection(section).AddKey(key, 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; + 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; + + srcIni.GetSectionKeys(srcSection) + .ForEach(key => AddKeyWithLog(desIni, desSection, key, srcIni.GetStringValue(srcSection, key, string.Empty))); + + return this; + } + + 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..d01b6dbe0 --- /dev/null +++ b/MigrationTool/Patch_Latest.cs @@ -0,0 +1,21 @@ +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 void Apply() + { + base.Apply(); + + // Write latest patch there + } +} + diff --git a/MigrationTool/Patch_v2_11_0.cs b/MigrationTool/Patch_v2_11_0.cs new file mode 100644 index 000000000..7d9456f31 --- /dev/null +++ b/MigrationTool/Patch_v2_11_0.cs @@ -0,0 +1,448 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +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 void Apply() + { + base.Apply(); + + // Remove Rampastring.Tools from Resources directory (not recursive) + 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"); + if (genericWindowIni.SectionExists("ExtraControls")) + genericWindowIni.GetSection("ExtraControls").SectionName = "$ExtraControls"; + genericWindowIni.WriteIniFile(); + } + + // 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()) + { + foreach (var key in optionsWindowIni.GetSectionKeys(section)) + { + var value = optionsWindowIni.GetStringValue(section, key, string.Empty); + + 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"); + } + optionsWindowIni.WriteIniFile(); + } + + // Add DTACnCNetClient.ini + { + 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("GAME_OPTION_COLUMN_SPACING", "160"); + addKey("GAME_OPTION_ROW_SPACING", "6"); + addKey("GAME_OPTION_DD_WIDTH", "132"); + addKey("GAME_OPTION_DD_HEIGHT", "22"); + dtaCnCNetClientIni.WriteIniFile(); + } + + // Add 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(); + } + + string GameLobbyBase = nameof(GameLobbyBase); + + // Rework skirmish/lan/cncnet lobbies ini's + if (File.Exists(SafePath.CombineFilePath(ResouresDir.FullName, $"{GameLobbyBase}.ini"))) + { + Logger.Log($"Update lobbies has been skipped, {GameLobbyBase}.ini already exists"); + } + else + { + string MultiplayerGameLobby = nameof(MultiplayerGameLobby); + string SkirmishLobby = nameof(SkirmishLobby); + string ExtraControls = nameof(ExtraControls); + List gameOptionsIniControlKeys = new() { "CheckBoxes", "DropDowns", "Labels" }; + + // Old configs + 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")); + + Logger.Log($"Unable to find {GameLobbyBase}.ini. Checking {SkirmishLobby}.ini, {MultiplayerGameLobby}.ini..."); + + if (!skirmishLobbyIni_old.SectionExists("GameOptionsPanel") && !multiplayerGameLobbyIni_old.SectionExists("GameOptionsPanel")) + { + 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}")) + { + TransferKeys(skirmishLobbyIni_old, $"{ExtraControls}", skirmishLobbyIni, $"${ExtraControls}"); + skirmishLobbyIni_old.RemoveSection($"{ExtraControls}"); + + 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 + { + // 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"); + } + 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) + { + string itemType = itemName switch + { + "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); + } + + 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}")) + { + 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("$"))) + { + var valueSkirmish = gameLobbyBaseIni.GetStringValue($"{SkirmishLobby}", key, string.Empty); + var valueMultiplayer = gameOptionsIni.GetStringValue($"{MultiplayerGameLobby}", key, string.Empty); + + 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] + { + var addControl = (string controlKey, string section, string controlType) => + { + 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"); + + // 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 + .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 + var assembly = Assembly.GetExecutingAssembly(); + foreach (var resourceName in assembly.GetManifestResourceNames()) + { + 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(filepath, FileMode.CreateNew)) + { + Logger.Log($"Copy {filename} to the {ResouresDir.FullName}"); + resourceStream?.CopyTo(fileStream); + } + } + } + } + + } +} diff --git a/MigrationTool/Patch_v2_11_1.cs b/MigrationTool/Patch_v2_11_1.cs new file mode 100644 index 000000000..4ea20f100 --- /dev/null +++ b/MigrationTool/Patch_v2_11_1.cs @@ -0,0 +1,44 @@ +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 void 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(); + + // 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; + + presets.SectionName = "btnSaveLoadGameOptions"; + + foreach (var pair in glb.GetSection("SkirmishLobby").Keys) + { + if (pair.Value.Contains("BtnSaveLoadGameOptions")) + glb.SetStringValue("SkirmishLobby", pair.Key, pair.Value.Replace("BtnSaveLoadGameOptions", "btnSaveLoadGameOptions")); + } + + glb.WriteIniFile(); + + } +} diff --git a/MigrationTool/Patch_v2_11_2.cs b/MigrationTool/Patch_v2_11_2.cs new file mode 100644 index 000000000..5a2332b60 --- /dev/null +++ b/MigrationTool/Patch_v2_11_2.cs @@ -0,0 +1,37 @@ +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 void 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" }; + Logger.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(); + + } +} diff --git a/MigrationTool/Patch_v2_12_1.cs b/MigrationTool/Patch_v2_12_1.cs new file mode 100644 index 000000000..35dd7e76b --- /dev/null +++ b/MigrationTool/Patch_v2_12_1.cs @@ -0,0 +1,27 @@ +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 void 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(); + + } +} diff --git a/MigrationTool/Patch_v2_12_6.cs b/MigrationTool/Patch_v2_12_6.cs new file mode 100644 index 000000000..cb3eea99e --- /dev/null +++ b/MigrationTool/Patch_v2_12_6.cs @@ -0,0 +1,31 @@ +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 void 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(); + + // Remove GenericWindow.ini->{ GameCreationWindow; GameCreationWindow_Advanced }->Size + IniFile genericWindowIni = new IniFile(SafePath.CombineFilePath(ResouresDir.FullName, "GenericWindow.ini")); + RemoveKeyWithLog(genericWindowIni, "GameCreationWindow", "Size"); + RemoveKeyWithLog(genericWindowIni, "GameCreationWindow_Advanced", "Size"); + genericWindowIni.WriteIniFile(); + } +} + diff --git a/MigrationTool/Pictures/favActive.png b/MigrationTool/Pictures/favActive.png new file mode 100644 index 000000000..545be40b4 Binary files /dev/null and b/MigrationTool/Pictures/favActive.png differ diff --git a/MigrationTool/Pictures/favActive_c.png b/MigrationTool/Pictures/favActive_c.png new file mode 100644 index 000000000..23080d576 Binary files /dev/null and b/MigrationTool/Pictures/favActive_c.png differ diff --git a/MigrationTool/Pictures/favInactive.png b/MigrationTool/Pictures/favInactive.png new file mode 100644 index 000000000..f841e9e50 Binary files /dev/null and b/MigrationTool/Pictures/favInactive.png differ diff --git a/MigrationTool/Pictures/noMapPreview.png b/MigrationTool/Pictures/noMapPreview.png new file mode 100644 index 000000000..3b0579569 Binary files /dev/null and b/MigrationTool/Pictures/noMapPreview.png differ diff --git a/MigrationTool/Pictures/sortAlphaActive.png b/MigrationTool/Pictures/sortAlphaActive.png new file mode 100644 index 000000000..9243fd4e0 Binary files /dev/null and b/MigrationTool/Pictures/sortAlphaActive.png differ diff --git a/MigrationTool/Pictures/sortAlphaAsc.png b/MigrationTool/Pictures/sortAlphaAsc.png new file mode 100644 index 000000000..9243fd4e0 Binary files /dev/null and b/MigrationTool/Pictures/sortAlphaAsc.png differ diff --git a/MigrationTool/Pictures/sortAlphaDesc.png b/MigrationTool/Pictures/sortAlphaDesc.png new file mode 100644 index 000000000..ac1389e46 Binary files /dev/null and b/MigrationTool/Pictures/sortAlphaDesc.png differ diff --git a/MigrationTool/Pictures/sortAlphaInactive.png b/MigrationTool/Pictures/sortAlphaInactive.png new file mode 100644 index 000000000..49b928d97 Binary files /dev/null and b/MigrationTool/Pictures/sortAlphaInactive.png differ diff --git a/MigrationTool/Pictures/sortAlphaNone.png b/MigrationTool/Pictures/sortAlphaNone.png new file mode 100644 index 000000000..49b928d97 Binary files /dev/null and b/MigrationTool/Pictures/sortAlphaNone.png differ diff --git a/MigrationTool/Pictures/statusAI.png b/MigrationTool/Pictures/statusAI.png new file mode 100644 index 000000000..2b8a9399d Binary files /dev/null and b/MigrationTool/Pictures/statusAI.png differ diff --git a/MigrationTool/Pictures/statusClear.png b/MigrationTool/Pictures/statusClear.png new file mode 100644 index 000000000..2ef08a0ba Binary files /dev/null and b/MigrationTool/Pictures/statusClear.png differ diff --git a/MigrationTool/Pictures/statusEmpty.png b/MigrationTool/Pictures/statusEmpty.png new file mode 100644 index 000000000..707f0689c Binary files /dev/null and b/MigrationTool/Pictures/statusEmpty.png differ diff --git a/MigrationTool/Pictures/statusError.png b/MigrationTool/Pictures/statusError.png new file mode 100644 index 000000000..eb4695908 Binary files /dev/null and b/MigrationTool/Pictures/statusError.png differ diff --git a/MigrationTool/Pictures/statusInProgress.png b/MigrationTool/Pictures/statusInProgress.png new file mode 100644 index 000000000..bc0b8be2d Binary files /dev/null and b/MigrationTool/Pictures/statusInProgress.png differ diff --git a/MigrationTool/Pictures/statusOk.png b/MigrationTool/Pictures/statusOk.png new file mode 100644 index 000000000..d1c20d01f Binary files /dev/null and b/MigrationTool/Pictures/statusOk.png differ diff --git a/MigrationTool/Pictures/statusUnavailable.png b/MigrationTool/Pictures/statusUnavailable.png new file mode 100644 index 000000000..aea07d0bb Binary files /dev/null and b/MigrationTool/Pictures/statusUnavailable.png differ diff --git a/MigrationTool/Pictures/statusWarning.png b/MigrationTool/Pictures/statusWarning.png new file mode 100644 index 000000000..bf046b98e Binary files /dev/null and b/MigrationTool/Pictures/statusWarning.png differ diff --git a/MigrationTool/Program.cs b/MigrationTool/Program.cs new file mode 100644 index 000000000..eee72468f --- /dev/null +++ b/MigrationTool/Program.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; + +using Rampastring.Tools; +using ClientCore.Enums; +using ClientCore.Extensions; + +namespace MigrationTool; + +internal sealed class Program +{ + private const string ERROR_MESSAGE = "Unknown arguments detected. Use -h argument to print help information."; + private const string HELP_MESSAGE = + """ + CnCNet Client Migration Tool. + + Execute this file with path to the unmigrated client directory as first argument. + """; + + 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); + Logger.WriteToConsole = true; + + // Check arguments + switch (args.Length) + { + case 1: + string arg = args[0].Trim(); + + if (arg is "-h" + or "--help" + or "-?" + or "/?" + or "/h") + { + Console.WriteLine(HELP_MESSAGE); + return; + } + + if (!SafePath.GetDirectory(arg).Exists) + { + Console.WriteLine(ERROR_MESSAGE); + return; + } + + if (!SafePath.GetFile(SafePath.CombineFilePath(arg, "Resources", "ClientDefinitions.ini")).Exists) + { + Logger.Log("Unable to find Resources/ClientDefinitions.ini. Migration aborted."); + return; + } + + var assembly = Assembly.GetExecutingAssembly(); + Patch? patch = null; + try + { + // https://stackoverflow.com/questions/16038819/how-to-find-all-direct-subclasses-of-a-class-with-net-reflection + 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 == "MigrationTool.Patch_" + patchName.ToString()).First(); + patch = (Patch?)Activator.CreateInstance(type, arg); + patch?.Apply(); + Console.WriteLine(""); + } + } + catch (Exception ex) + { + Logger.Log(""); + 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"); + } + + Console.WriteLine("Patching has been done."); + + break; + case 0: + default: + Console.WriteLine(ERROR_MESSAGE); + break; + } + } +} 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, +} diff --git a/Scripts/build.ps1 b/Scripts/build.ps1 index 440d8da8a..ed4afc16f 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,10 +107,12 @@ 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") @@ -117,9 +128,11 @@ function Script:Invoke-BuildProject { throw "Build failed for ${Engine}$Script:ConfigurationSuffix $Framework (exit code $LASTEXITCODE)" } } - else { + else + { Invoke-BuildProject -Engine 'UniversalGL' -Framework 'net8.0' - if ($IsWindows) { + if ($IsWindows) + { @('WindowsDX', 'WindowsGL', 'WindowsXNA') | ForEach-Object { $Private:Engine = $PSItem @@ -134,4 +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 +Script:Invoke-BuildMigrationTool