diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b531c9164..1bb5bf0b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - name: Setup .NET Core SDK uses: actions/setup-dotnet@v4 with: - dotnet-version: '7.x.x' + dotnet-version: '8.x.x' - name: Build ${{matrix.Game}} run: ./BuildScripts/Build-${{matrix.Game}}.ps1 diff --git a/BuildScripts/Build-Ares.ps1 b/BuildScripts/Build-Ares.ps1 index 636105cdc..7cb6f0647 100755 --- a/BuildScripts/Build-Ares.ps1 +++ b/BuildScripts/Build-Ares.ps1 @@ -5,9 +5,14 @@ param($Configuration = "Release") . $PSScriptRoot\Common.ps1 -Build-Project $Configuration Ares UniversalGL net7.0 +$Game = "Ares" + +Build-Project $Configuration $Game UniversalGL net8.0 if ($IsWindows) { @('WindowsDX', 'WindowsGL', 'WindowsXNA') | ForEach-Object { - Build-Project $Configuration Ares $_ net7.0-windows + $Engine = $_ + @('net48', 'net8.0-windows') | ForEach-Object { + Build-Project $Configuration $Game $Engine $_ + } } } \ No newline at end of file diff --git a/BuildScripts/Build-TS.ps1 b/BuildScripts/Build-TS.ps1 index 27d36d6bf..caef8c2fa 100755 --- a/BuildScripts/Build-TS.ps1 +++ b/BuildScripts/Build-TS.ps1 @@ -5,9 +5,14 @@ param($Configuration = "Release") . $PSScriptRoot\Common.ps1 -Build-Project $Configuration TS UniversalGL net7.0 +$Game = "TS" + +Build-Project $Configuration $Game UniversalGL net8.0 if ($IsWindows) { @('WindowsDX', 'WindowsGL', 'WindowsXNA') | ForEach-Object { - Build-Project $Configuration TS $_ net7.0-windows + $Engine = $_ + @('net48', 'net8.0-windows') | ForEach-Object { + Build-Project $Configuration $Game $Engine $_ + } } } \ No newline at end of file diff --git a/BuildScripts/Build-YR.ps1 b/BuildScripts/Build-YR.ps1 index f987d7c3f..4dc264616 100755 --- a/BuildScripts/Build-YR.ps1 +++ b/BuildScripts/Build-YR.ps1 @@ -5,9 +5,14 @@ param($Configuration = "Release") . $PSScriptRoot\Common.ps1 -Build-Project $Configuration YR UniversalGL net7.0 +$Game = "YR" + +Build-Project $Configuration $Game UniversalGL net8.0 if ($IsWindows) { @('WindowsDX', 'WindowsGL', 'WindowsXNA') | ForEach-Object { - Build-Project $Configuration YR $_ net7.0-windows + $Engine = $_ + @('net48', 'net8.0-windows') | ForEach-Object { + Build-Project $Configuration $Game $Engine $_ + } } } \ No newline at end of file diff --git a/BuildScripts/Common.ps1 b/BuildScripts/Common.ps1 index 1396219bd..ce39431a3 100644 --- a/BuildScripts/Common.ps1 +++ b/BuildScripts/Common.ps1 @@ -4,21 +4,21 @@ $RepoRoot = Split-Path $PSScriptRoot -Parent $ProjectPath = Join-Path $RepoRoot DXMainClient DXMainClient.csproj $CompiledRoot = Join-Path $RepoRoot Compiled -$EngineMap = @{ +$EngineSubFolderMap = @{ 'UniversalGL' = 'UniversalGL' 'WindowsDX' = 'Windows' 'WindowsGL' = 'OpenGL' 'WindowsXNA' = 'XNA' } +$FrameworkBinariesFolderMap = @{ + 'net48' = 'Binaries' + 'net8.0' = 'BinariesNET8' + 'net8.0-windows' = 'BinariesNET8' +} function Build-Project($Configuration, $Game, $Engine, $Framework) { - $Output = Join-Path $CompiledRoot $Game $Output Resources Binaries ($EngineMap[$Engine]) - if ($Engine -EQ 'WindowsXNA') { - dotnet publish $ProjectPath --configuration=$Configuration -property:GAME=$Game -property:ENGINE=$Engine --framework=$Framework --output=$Output --arch=x86 - } - else { - dotnet publish $ProjectPath --configuration=$Configuration -property:GAME=$Game -property:ENGINE=$Engine --framework=$Framework --output=$Output - } + $Output = Join-Path $CompiledRoot $Game $Output Resources ($FrameworkBinariesFolderMap[$Framework]) ($EngineSubFolderMap[$Engine]) + dotnet publish $ProjectPath -c $Configuration -p:GAME=$Game -p:ENGINE=$Engine -f $Framework -o $Output -p:SatelliteResourceLanguages=en -p:AssemblyVersion=$AssemblySemVer -p:FileVersion=$AssemblySemFileVer -p:InformationalVersion=$InformationalVersion $($Engine -EQ 'WindowsXNA' ? '--arch=x86' : '') if ($LASTEXITCODE) { throw "Build failed for $Game $Engine $Framework $Configuration" } diff --git a/BuildScripts/README.md b/BuildScripts/README.md index 4e3cd0f4f..1de8b7161 100644 --- a/BuildScripts/README.md +++ b/BuildScripts/README.md @@ -30,10 +30,10 @@ TargetFramework configurations For each Engine configuration one or more TargetFrameworks will be build: UniversalGL: -* net7.0 +* net8.0 WindowsDX, WindowsGL & WindowsXNA: -* net7.0-windows +* net48 Overview of the Engine configurations differences: diff --git a/ClientCore/ClientConfiguration.cs b/ClientCore/ClientConfiguration.cs index 5f172d05e..98d24ee9c 100644 --- a/ClientCore/ClientConfiguration.cs +++ b/ClientCore/ClientConfiguration.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Collections.Generic; using ClientCore.I18N; using ClientCore.Extensions; @@ -283,7 +282,7 @@ private List ParseTranslationGameFiles() { // the syntax is GameFileX=path/to/source.file,path/to/destination.file[,checked] string value = clientDefinitionsIni.GetStringValue(TRANSLATIONS, $"GameFile{i}", string.Empty); - string[] parts = value.Split(',', StringSplitOptions.TrimEntries); + string[] parts = value.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); // fail explicitly if the syntax is wrong if (parts.Length is < 2 or > 3 @@ -382,15 +381,53 @@ public IEnumerable SupplementalMapFileExtensions public OSVersion GetOperatingSystemVersion() { +#if NETFRAMEWORK + // OperatingSystem.IsWindowsVersionAtLeast() is the preferred API but is not supported on earlier .NET versions + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + Version osVersion = Environment.OSVersion.Version; + + if (osVersion.Major <= 4) + return OSVersion.UNKNOWN; + + if (osVersion.Major == 5) + return OSVersion.WINXP; + + if (osVersion.Major == 6 && osVersion.Minor == 0) + return OSVersion.WINVISTA; + + if (osVersion.Major == 6 && osVersion.Minor <= 1) + return OSVersion.WIN7; + + return OSVersion.WIN810; + } + + if (ProgramConstants.ISMONO) + return OSVersion.UNIX; + + // http://mono.wikia.com/wiki/Detecting_the_execution_platform + int p = (int)Environment.OSVersion.Platform; + if (p == 4 || p == 6 || p == 128) + return OSVersion.UNIX; + + return OSVersion.UNKNOWN; +#else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (OperatingSystem.IsWindowsVersionAtLeast(6, 3)) + if (OperatingSystem.IsWindowsVersionAtLeast(6, 2)) return OSVersion.WIN810; - - return OSVersion.WIN7; + else if (OperatingSystem.IsWindowsVersionAtLeast(6, 1)) + return OSVersion.WIN7; + else if (OperatingSystem.IsWindowsVersionAtLeast(6, 0)) + return OSVersion.WINVISTA; + else if (OperatingSystem.IsWindowsVersionAtLeast(5, 0)) + return OSVersion.WINXP; + else + return OSVersion.UNKNOWN; } return OSVersion.UNIX; +#endif } } diff --git a/ClientCore/ClientCore.csproj b/ClientCore/ClientCore.csproj index 96160385b..c83d348b9 100644 --- a/ClientCore/ClientCore.csproj +++ b/ClientCore/ClientCore.csproj @@ -4,7 +4,7 @@ CnCNet Client Core Library CnCNet CnCNet Client - Copyright © CnCNet, Rampastring 2011-2022 + Copyright © CnCNet, Rampastring 2011-2024 CnCNet 2.0.0.3 2.0.0.3 @@ -50,9 +50,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + diff --git a/ClientCore/Extensions/StringExtensions.cs b/ClientCore/Extensions/StringExtensions.cs index 11a9d2368..a028c333c 100644 --- a/ClientCore/Extensions/StringExtensions.cs +++ b/ClientCore/Extensions/StringExtensions.cs @@ -40,10 +40,10 @@ public static string ToIniString(this string raw) throw new ArgumentException($"The string contains an illegal character sequence! ({ESCAPED_SEMICOLON})"); return raw - .Replace(ProgramConstants.INI_NEWLINE_PATTERN, ESCAPED_INI_NEWLINE_PATTERN, StringComparison.InvariantCulture) - .Replace(";", ESCAPED_SEMICOLON, StringComparison.InvariantCulture) - .Replace(Environment.NewLine, "\n", StringComparison.InvariantCulture) - .Replace("\n", ProgramConstants.INI_NEWLINE_PATTERN, StringComparison.InvariantCulture); + .Replace(ProgramConstants.INI_NEWLINE_PATTERN, ESCAPED_INI_NEWLINE_PATTERN) + .Replace(";", ESCAPED_SEMICOLON) + .Replace(Environment.NewLine, "\n") + .Replace("\n", ProgramConstants.INI_NEWLINE_PATTERN); } /// @@ -54,9 +54,9 @@ public static string ToIniString(this string raw) public static string FromIniString(this string iniString) { return iniString - .Replace(ESCAPED_INI_NEWLINE_PATTERN, ProgramConstants.INI_NEWLINE_PATTERN, StringComparison.InvariantCulture) - .Replace(ESCAPED_SEMICOLON, ";", StringComparison.InvariantCulture) - .Replace(ProgramConstants.INI_NEWLINE_PATTERN, Environment.NewLine, StringComparison.InvariantCulture); + .Replace(ESCAPED_INI_NEWLINE_PATTERN, ProgramConstants.INI_NEWLINE_PATTERN) + .Replace(ESCAPED_SEMICOLON, ";") + .Replace(ProgramConstants.INI_NEWLINE_PATTERN, Environment.NewLine); } /// diff --git a/ClientCore/I18N/Translation.cs b/ClientCore/I18N/Translation.cs index 09b8a26d7..d4884ceb3 100644 --- a/ClientCore/I18N/Translation.cs +++ b/ClientCore/I18N/Translation.cs @@ -88,7 +88,8 @@ public Translation(string localeCode) public Translation(IniFile ini, string localeCode) : this(localeCode) { - ArgumentNullException.ThrowIfNull(ini); + if (ini is null) + throw new ArgumentNullException(nameof(ini)); IniSection metadataSection = ini.GetSection(METADATA_SECTION); Name = metadataSection?.GetStringValue(nameof(Name), string.Empty); diff --git a/ClientCore/ProgramConstants.cs b/ClientCore/ProgramConstants.cs index feb207172..081d4f68c 100644 --- a/ClientCore/ProgramConstants.cs +++ b/ClientCore/ProgramConstants.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Reflection; #if WINFORMS @@ -20,11 +21,7 @@ public static class ProgramConstants public static readonly string StartupPath = SafePath.CombineDirectoryPath(new FileInfo(StartupExecutable).Directory.FullName); -#if DEBUG - public static readonly string GamePath = SafePath.CombineDirectoryPath(SafePath.GetDirectory(StartupPath).Parent.Parent.FullName); -#else - public static readonly string GamePath = SafePath.CombineDirectoryPath(SafePath.GetDirectory(StartupPath).Parent.Parent.Parent.FullName); -#endif + public static readonly string GamePath = SafePath.CombineDirectoryPath(GetGamePath(StartupPath)); public static string ClientUserFilesPath => SafePath.CombineDirectoryPath(GamePath, "Client"); @@ -62,6 +59,15 @@ public static class ProgramConstants public static readonly Encoding LAN_ENCODING = Encoding.UTF8; +#if NETFRAMEWORK + private static bool? isMono; + + /// + /// Gets a value whether or not the application is running under Mono. Uses lazy loading and caching. + /// + public static bool ISMONO => isMono ??= Type.GetType("Mono.Runtime") != null; +#endif + public static string GAME_VERSION = "Undefined"; private static string PlayerName = "No name"; @@ -115,11 +121,17 @@ public static string GetAILevelName(int aiLevel) /// /// Gets or sets the action to perform to notify the user of an error. /// - public static Action DisplayErrorAction { get; set; } = (title, error, exit) => + public static Action DisplayErrorAction { get; set; } = DefaultDisplayErrorAction; + + public static Action DefaultDisplayErrorAction = (title, error, exit) => { Logger.Log(FormattableString.Invariant($"{(title is null ? null : title + Environment.NewLine + Environment.NewLine)}{error}")); #if WINFORMS +#if NETFRAMEWORK MessageBox.Show(error, title, MessageBoxButtons.OK); +#else + TaskDialog.ShowDialog(new() { Caption = title, Heading = error }); +#endif #else ProcessLauncher.StartShellProcess(LogFileName); #endif @@ -127,5 +139,39 @@ public static string GetAILevelName(int aiLevel) if (exit) Environment.Exit(1); }; + + /// + /// This method finds the "Resources" directory by traversing the directory tree upwards from the startup path. + /// + /// + /// This method is needed by both ClientCore and DXMainClient. However, since it is usually called at the very beginning, + /// where DXMainClient could not refer to ClientCore, this method is copied to both projects. + /// Remember to keep and consistent if you have modified its source codes. + /// + private static string SearchResourcesDir(string startupPath) + { + DirectoryInfo currentDir = new(startupPath); + for (int i = 0; i < 3; i++) + { + // Determine if currentDir is the "Resources" folder + if (currentDir.Name.ToLowerInvariant() == "Resources".ToLowerInvariant()) + return currentDir.FullName; + + // Additional check. This makes developers to debug the client inside Visual Studio a little bit easier. + DirectoryInfo resourcesDir = currentDir.GetDirectories("Resources", SearchOption.TopDirectoryOnly).FirstOrDefault(); + if (resourcesDir is not null) + return resourcesDir.FullName; + + currentDir = currentDir.Parent; + } + + throw new Exception("Could not find Resources directory."); + } + + private static string GetGamePath(string startupPath) + { + string resourceDir = SearchResourcesDir(startupPath); + return new DirectoryInfo(resourceDir).Parent.FullName; + } } } \ No newline at end of file diff --git a/ClientGUI/ClientGUI.csproj b/ClientGUI/ClientGUI.csproj index 3e41f5b84..7349f8f22 100644 --- a/ClientGUI/ClientGUI.csproj +++ b/ClientGUI/ClientGUI.csproj @@ -4,7 +4,7 @@ CnCNet Client UI Library CnCNet CnCNet Client - Copyright © CnCNet, Rampastring 2011-2022 + Copyright © CnCNet, Rampastring 2011-2024 CnCNet 2.1.0.1 2.1.0.1 diff --git a/ClientGUI/GameProcessLogic.cs b/ClientGUI/GameProcessLogic.cs index d725da2f8..a8f2258af 100644 --- a/ClientGUI/GameProcessLogic.cs +++ b/ClientGUI/GameProcessLogic.cs @@ -79,6 +79,7 @@ public static void StartGameProcess(WindowManager windowManager) Logger.Log("Windowed mode is enabled - using QRes."); Process QResProcess = new Process(); QResProcess.StartInfo.FileName = ProgramConstants.QRES_EXECUTABLE; + QResProcess.StartInfo.UseShellExecute = false; if (!string.IsNullOrEmpty(extraCommandLine)) QResProcess.StartInfo.Arguments = "c=16 /R " + "\"" + SafePath.CombineFilePath(ProgramConstants.GamePath, gameExecutableName) + "\" " + additionalExecutableName + "-SPAWN " + extraCommandLine; @@ -119,6 +120,7 @@ public static void StartGameProcess(WindowManager windowManager) var gameProcess = new Process(); gameProcess.StartInfo.FileName = gameFileInfo.FullName; gameProcess.StartInfo.Arguments = arguments; + gameProcess.StartInfo.UseShellExecute = false; gameProcess.EnableRaisingEvents = true; gameProcess.Exited += Process_Exited; @@ -144,7 +146,7 @@ public static void StartGameProcess(WindowManager windowManager) if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) && Environment.ProcessorCount > 1 && SingleCoreAffinity) { - gameProcess.ProcessorAffinity = 2; + gameProcess.ProcessorAffinity = (IntPtr)2; } } diff --git a/DTAConfig/DTAConfig.csproj b/DTAConfig/DTAConfig.csproj index a401e6a18..3bda0d506 100644 --- a/DTAConfig/DTAConfig.csproj +++ b/DTAConfig/DTAConfig.csproj @@ -4,7 +4,7 @@ CnCNet Config Library CnCNet CnCNet Client - Copyright © CnCNet, Rampastring 2011-2022 + Copyright © CnCNet, Rampastring 2011-2024 CnCNet 2.2.0.0 2.2.0.0 @@ -14,7 +14,8 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/DXMainClient/DXGUI/Generic/ExtrasWindow.cs b/DXMainClient/DXGUI/Generic/ExtrasWindow.cs index 8a72062d2..6642f0fd0 100644 --- a/DXMainClient/DXGUI/Generic/ExtrasWindow.cs +++ b/DXMainClient/DXGUI/Generic/ExtrasWindow.cs @@ -73,6 +73,8 @@ private void BtnExMapEditor_LeftClick(object sender, EventArgs e) else mapEditorProcess.StartInfo.FileName = SafePath.CombineFilePath(ProgramConstants.GamePath, ClientConfiguration.Instance.UnixMapEditorExePath); + mapEditorProcess.StartInfo.UseShellExecute = false; + mapEditorProcess.Start(); Enabled = false; diff --git a/DXMainClient/DXGUI/Generic/MainMenu.cs b/DXMainClient/DXGUI/Generic/MainMenu.cs index f928ac78d..de441abf9 100644 --- a/DXMainClient/DXGUI/Generic/MainMenu.cs +++ b/DXMainClient/DXGUI/Generic/MainMenu.cs @@ -1069,6 +1069,8 @@ private void LaunchMapEditor() else mapEditorProcess.StartInfo.FileName = SafePath.CombineFilePath(ProgramConstants.GamePath, ClientConfiguration.Instance.UnixMapEditorExePath); + mapEditorProcess.StartInfo.UseShellExecute = false; + mapEditorProcess.Start(); } diff --git a/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs b/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs index f8fb4d7dd..28002e817 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs @@ -1621,7 +1621,7 @@ private void CopySupplementalMapFiles(IniFile mapIni) } // Write the supplemental map files to the INI (eventual spawnmap.ini) - mapIni.SetStringValue("Basic", "SupplementalFiles", string.Join(',', supplementalFileNames)); + mapIni.SetStringValue("Basic", "SupplementalFiles", string.Join(",", supplementalFileNames)); } /// diff --git a/DXMainClient/DXMainClient.csproj b/DXMainClient/DXMainClient.csproj index 5eded4e66..48c7f167e 100644 --- a/DXMainClient/DXMainClient.csproj +++ b/DXMainClient/DXMainClient.csproj @@ -2,11 +2,15 @@ WinExe Exe + + true + false + false CnCNet Client CnCNet Main Client Library CnCNet CnCNet Client - Copyright © CnCNet, Rampastring 2011-2022 + Copyright © CnCNet, Rampastring 2011-2024 CnCNet 2.8.0.0 2.8.0.0 @@ -20,7 +24,6 @@ clientxna app.manifest true - false @@ -31,17 +34,27 @@ + - + - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/DXMainClient/Domain/Multiplayer/Map.cs b/DXMainClient/Domain/Multiplayer/Map.cs index a78b6859f..f29e22e05 100644 --- a/DXMainClient/Domain/Multiplayer/Map.cs +++ b/DXMainClient/Domain/Multiplayer/Map.cs @@ -296,7 +296,7 @@ public bool SetInfoFromMpMapsINI(IniFile iniFile) EnforceMaxPlayers = section.GetBooleanValue("EnforceMaxPlayers", false); FileInfo mapFile = SafePath.GetFile(BaseFilePath); - PreviewPath = SafePath.CombineFilePath(SafePath.GetDirectory(mapFile.ToString()).Parent.ToString()[ProgramConstants.GamePath.Length..], FormattableString.Invariant($"{section.GetStringValue("PreviewImage", mapFile.Name)}.png")); + PreviewPath = SafePath.CombineFilePath(SafePath.GetDirectory(mapFile.FullName).Parent.FullName[ProgramConstants.GamePath.Length..], FormattableString.Invariant($"{section.GetStringValue("PreviewImage", mapFile.Name)}.png")); Briefing = section.GetStringValue("Briefing", string.Empty) .FromIniString() diff --git a/DXMainClient/Online/FileHashCalculator.cs b/DXMainClient/Online/FileHashCalculator.cs index c6386e13b..3150ef7eb 100644 --- a/DXMainClient/Online/FileHashCalculator.cs +++ b/DXMainClient/Online/FileHashCalculator.cs @@ -77,10 +77,13 @@ public void CalculateHashes(List gameModes) fh = new FileHashes { GameOptionsHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GamePath, ProgramConstants.BASE_RESOURCE_PATH, "GameOptions.ini")), - ClientDXHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GetBaseResourcePath(), "Binaries", "Windows", "clientdx.dll")), - ClientXNAHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GetBaseResourcePath(), "Binaries", "XNA", "clientxna.dll")), - ClientOGLHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GetBaseResourcePath(), "Binaries", "OpenGL", "clientogl.dll")), - ClientUGLHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GetBaseResourcePath(), "Binaries", "UniversalGL", "clientogl.dll")), + ClientDXHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GetBaseResourcePath(), "clientdx.exe")), + ClientXNAHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GetBaseResourcePath(), "clientxna.exe")), + ClientOGLHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GetBaseResourcePath(), "clientogl.exe")), + ClientDXNET8Hash = string.Empty, + ClientXNANET8Hash = string.Empty, + ClientOGLNET8Hash = string.Empty, + ClientUGLNET8Hash = string.Empty, GameExeHash = calculateGameExeHash ? Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GamePath, ClientConfiguration.Instance.GetGameExecutableName())) : string.Empty, LauncherExeHash = Utilities.CalculateSHA1ForFile(SafePath.CombineFilePath(ProgramConstants.GamePath, ClientConfiguration.Instance.GameLauncherExecutableName)), @@ -89,12 +92,32 @@ public void CalculateHashes(List gameModes) INIHashes = string.Empty }; + // .NET 8 hashes are optional + FileInfo fileDX8 = SafePath.GetFile(ProgramConstants.GetBaseResourcePath(), "BinariesNET8", "Windows", "clientdx.dll"); + if (fileDX8.Exists) + fh.ClientDXNET8Hash = Utilities.CalculateSHA1ForFile(fileDX8.FullName); + + FileInfo fileXNA8 = SafePath.GetFile(ProgramConstants.GetBaseResourcePath(), "BinariesNET8", "XNA", "clientxna.dll"); + if (fileXNA8.Exists) + fh.ClientXNANET8Hash = Utilities.CalculateSHA1ForFile(fileXNA8.FullName); + + FileInfo fileOGL8 = SafePath.GetFile(ProgramConstants.GetBaseResourcePath(), "BinariesNET8", "OpenGL", "clientogl.dll"); + if (fileOGL8.Exists) + fh.ClientOGLNET8Hash = Utilities.CalculateSHA1ForFile(fileOGL8.FullName); + + FileInfo fileUGL8 = SafePath.GetFile(ProgramConstants.GetBaseResourcePath(), "BinariesNET8", "UniversalGL", "clientogl.dll"); + if (fileUGL8.Exists) + fh.ClientUGLNET8Hash = Utilities.CalculateSHA1ForFile(fileUGL8.FullName); + Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + CONFIGNAME + ": " + fh.FHCConfigHash); Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\GameOptions.ini: " + fh.GameOptionsHash); - Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\Binaries\\Windows\\clientdx.dll: " + fh.ClientDXHash); - Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\Binaries\\XNA\\clientxna.dll: " + fh.ClientXNAHash); - Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\Binaries\\OpenGL\\clientogl.dll: " + fh.ClientOGLHash); - Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\Binaries\\UniversalGL\\clientogl.dll: " + fh.ClientUGLHash); + Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\clientdx.exe: " + fh.ClientDXHash); + Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\clientxna.exe: " + fh.ClientXNAHash); + Logger.Log("Hash for " + ProgramConstants.BASE_RESOURCE_PATH + "\\clientogl.exe: " + fh.ClientOGLHash); + Logger.Log("Hash for ClientDXNET8: " + fh.ClientDXNET8Hash); + Logger.Log("Hash for ClientXNANET8: " + fh.ClientXNANET8Hash); + Logger.Log("Hash for ClientOGLNET8: " + fh.ClientOGLNET8Hash); + Logger.Log("Hash for ClientUGLNET8: " + fh.ClientUGLNET8Hash); Logger.Log("Hash for " + ClientConfiguration.Instance.MPMapsIniPath + ": " + fh.MPMapsHash); if (calculateGameExeHash) @@ -153,7 +176,12 @@ public void CalculateHashes(List gameModes) { string sha1 = Utilities.CalculateSHA1ForFile(filePath); fh.INIHashes += sha1; - Logger.Log("Hash for " + Path.GetRelativePath(ProgramConstants.GamePath, filePath) + ": " + sha1); + + string fileRelativePath = filePath; + if (filePath.StartsWith(ProgramConstants.GamePath)) + fileRelativePath = fileRelativePath.Substring(ProgramConstants.GamePath.Length).TrimStart(Path.DirectorySeparatorChar); + + Logger.Log("Hash for " + fileRelativePath + ": " + sha1); } } } @@ -176,7 +204,10 @@ public string GetCompleteHash() str += fh.ClientDXHash; str += fh.ClientXNAHash; str += fh.ClientOGLHash; - str += fh.ClientUGLHash; + str += fh.ClientDXNET8Hash; + str += fh.ClientXNANET8Hash; + str += fh.ClientOGLNET8Hash; + str += fh.ClientUGLNET8Hash; str += fh.GameExeHash; str += fh.LauncherExeHash; str += fh.INIHashes; @@ -212,7 +243,10 @@ private record struct FileHashes( string ClientDXHash, string ClientXNAHash, string ClientOGLHash, - string ClientUGLHash, + string ClientDXNET8Hash, + string ClientXNANET8Hash, + string ClientOGLNET8Hash, + string ClientUGLNET8Hash, string INIHashes, string MPMapsHash, string GameExeHash, diff --git a/DXMainClient/PreStartup.cs b/DXMainClient/PreStartup.cs index 2e25c91bb..985a47b83 100644 --- a/DXMainClient/PreStartup.cs +++ b/DXMainClient/PreStartup.cs @@ -72,7 +72,13 @@ public static void Initialize(StartupParams parameters) CheckPermissions(); if (clientLogFile.Exists) - File.Move(clientLogFile.FullName, SafePath.GetFile(clientUserFilesDirectory.FullName, "client_previous.log").FullName, true); + { + // Copy client.log file as client_previous.log. Override client_previous.log if it exists. + FileInfo clientPrevLogFile = SafePath.GetFile(clientUserFilesDirectory.FullName, "client_previous.log"); + if (clientPrevLogFile.Exists) + File.Delete(clientPrevLogFile.FullName); + File.Move(clientLogFile.FullName, clientPrevLogFile.FullName); + } Logger.Initialize(clientUserFilesDirectory.FullName, clientLogFile.Name); Logger.WriteLogFile = true; @@ -188,10 +194,33 @@ public static void Initialize(StartupParams parameters) } #if WINFORMS +#if NET6_0_OR_GREATER + // .NET 6.0 brings a source generator ApplicationConfiguration which is not available in previous .NET versions + // https://medium.com/c-sharp-progarmming/whats-new-in-windows-forms-in-net-6-0-840c71856751 ApplicationConfiguration.Initialize(); +#else + global::System.Windows.Forms.Application.EnableVisualStyles(); + global::System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); +#endif +#endif + + Startup startup = new(); +#if DEBUG + startup.Execute(); +#else + try + { + startup.Execute(); + } + catch (Exception ex) + { + // ProgramConstants.DisplayErrorAction might have been overriden by XNA messagebox, which might be unable to display an error message. + // Fallback to MessageBox. + ProgramConstants.DisplayErrorAction = ProgramConstants.DefaultDisplayErrorAction; + HandleException(startup, ex); + } #endif - new Startup().Execute(); } public static void LogException(Exception ex, bool innerException = false) diff --git a/DXMainClient/Program.cs b/DXMainClient/Program.cs index b8bfe3079..3d50ba555 100644 --- a/DXMainClient/Program.cs +++ b/DXMainClient/Program.cs @@ -1,18 +1,19 @@ using System; using System.Collections.Generic; -#if !DEBUG using System.IO; +using System.Linq; using System.Reflection; +#if !NETFRAMEWORK using System.Runtime.Loader; #endif using System.Threading; + /* !! We cannot use references to other projects or non-framework assemblies in this class, assembly loading events not hooked up yet !! */ namespace DTAClient { static class Program { -#if !DEBUG static Program() { /* We have different binaries depending on build platform, but for simplicity @@ -20,30 +21,42 @@ static Program() * To avoid DLL hell, we load the binaries from different directories * depending on the build platform. */ - string startupPath = new FileInfo(Assembly.GetEntryAssembly().Location).Directory.Parent.Parent.FullName + Path.DirectorySeparatorChar; + DirectoryInfo currentDir = new FileInfo(Assembly.GetEntryAssembly().Location).Directory; + string startupPath = SearchResourcesDir(currentDir.FullName); - COMMON_LIBRARY_PATH = Path.Combine(startupPath, "Binaries") + Path.DirectorySeparatorChar; + string binariesFolderName = "Binaries"; +#if !NETFRAMEWORK + binariesFolderName = "BinariesNET8"; +#endif + + COMMON_LIBRARY_PATH = Path.Combine(startupPath, binariesFolderName) + Path.DirectorySeparatorChar; #if XNA - SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, "Binaries", "XNA") + Path.DirectorySeparatorChar; + SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, binariesFolderName, "XNA") + Path.DirectorySeparatorChar; #elif GL && ISWINDOWS - SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, "Binaries", "OpenGL") + Path.DirectorySeparatorChar; + SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, binariesFolderName, "OpenGL") + Path.DirectorySeparatorChar; #elif GL && !ISWINDOWS - SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, "Binaries", "UniversalGL") + Path.DirectorySeparatorChar; + SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, binariesFolderName, "UniversalGL") + Path.DirectorySeparatorChar; #elif DX - SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, "Binaries", "Windows") + Path.DirectorySeparatorChar; + SPECIFIC_LIBRARY_PATH = Path.Combine(startupPath, binariesFolderName, "Windows") + Path.DirectorySeparatorChar; #else Yuri has won #endif +#if !DEBUG +#if !NETFRAMEWORK // Set up DLL load paths as early as possible AssemblyLoadContext.Default.Resolving += DefaultAssemblyLoadContextOnResolving; +#else + // Set up DLL load paths as early as possible + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; +#endif +#endif } private static string COMMON_LIBRARY_PATH; private static string SPECIFIC_LIBRARY_PATH; -#endif /// /// The main entry point for the application. /// @@ -116,25 +129,71 @@ static void Main(string[] args) mutex.ReleaseMutex(); } } -#if !DEBUG - + +#if !NETFRAMEWORK private static Assembly DefaultAssemblyLoadContextOnResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) { if (assemblyName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) return null; + // the specific dll should be in priority than the common one + + var specificFileInfo = new FileInfo(Path.Combine(SPECIFIC_LIBRARY_PATH, FormattableString.Invariant($"{assemblyName.Name}.dll"))); + + if (specificFileInfo.Exists) + return assemblyLoadContext.LoadFromAssemblyPath(specificFileInfo.FullName); + var commonFileInfo = new FileInfo(Path.Combine(COMMON_LIBRARY_PATH, FormattableString.Invariant($"{assemblyName.Name}.dll"))); if (commonFileInfo.Exists) return assemblyLoadContext.LoadFromAssemblyPath(commonFileInfo.FullName); - var specificFileInfo = new FileInfo(Path.Combine(SPECIFIC_LIBRARY_PATH, FormattableString.Invariant($"{assemblyName.Name}.dll"))); + return null; + } +#else + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + string unresolvedAssemblyName = args.Name.Split(',').First(); + + if (unresolvedAssemblyName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) + return null; + + // the specific dll should be in priority than the common one + + var specificFileInfo = new FileInfo(FormattableString.Invariant($"{Path.Combine(SPECIFIC_LIBRARY_PATH, unresolvedAssemblyName)}.dll")); if (specificFileInfo.Exists) - return assemblyLoadContext.LoadFromAssemblyPath(specificFileInfo.FullName); + return Assembly.Load(AssemblyName.GetAssemblyName(specificFileInfo.FullName)); + + var commonFileInfo = new FileInfo(FormattableString.Invariant($"{Path.Combine(COMMON_LIBRARY_PATH, unresolvedAssemblyName)}.dll")); + + if (commonFileInfo.Exists) + return Assembly.Load(AssemblyName.GetAssemblyName(commonFileInfo.FullName)); return null; } #endif + + /// + private static string SearchResourcesDir(string startupPath) + { + DirectoryInfo currentDir = new(startupPath); + for (int i = 0; i < 3; i++) + { + // Determine if currentDir is the "Resources" folder + if (currentDir.Name.ToLowerInvariant() == "Resources".ToLowerInvariant()) + return currentDir.FullName; + + // Additional check. This makes developers to debug the client inside Visual Studio a little bit easier. + DirectoryInfo resourcesDir = currentDir.GetDirectories("Resources", SearchOption.TopDirectoryOnly).FirstOrDefault(); + if (resourcesDir is not null) + return resourcesDir.FullName; + + currentDir = currentDir.Parent; + } + + throw new Exception("Could not find Resources directory."); + } + } } \ No newline at end of file diff --git a/DXMainClient/Resources/DTA/SecondStageUpdater.deps.json b/DXMainClient/Resources/DTA/SecondStageUpdater.deps.json deleted file mode 100644 index e10d2f169..000000000 --- a/DXMainClient/Resources/DTA/SecondStageUpdater.deps.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v7.0", - "signature": "" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v7.0": { - "SecondStageUpdater/1.0.0": { - "dependencies": { - "Rampastring.Tools": "2.0.4", - "StyleCop.Analyzers": "1.2.0-beta.435" - }, - "runtime": { - "SecondStageUpdater.dll": {} - } - }, - "Rampastring.Tools/2.0.4": { - "runtime": { - "lib/net7.0/Rampastring.Tools.dll": { - "assemblyVersion": "2.0.4.0", - "fileVersion": "2.0.4.0" - } - } - }, - "StyleCop.Analyzers/1.2.0-beta.435": { - "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.435" - } - }, - "StyleCop.Analyzers.Unstable/1.2.0.435": {} - } - }, - "libraries": { - "SecondStageUpdater/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Rampastring.Tools/2.0.4": { - "type": "package", - "serviceable": true, - "sha512": "sha512-mxwKnnsAKi4vqdzzQbq7+eK8mupw/XBAGe1Tw/7sS6mALvRDMviabZ5gwfixLc0SsXvq6Mh6468Sz90NHCPjag==", - "path": "rampastring.tools/2.0.4", - "hashPath": "rampastring.tools.2.0.4.nupkg.sha512" - }, - "StyleCop.Analyzers/1.2.0-beta.435": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==", - "path": "stylecop.analyzers/1.2.0-beta.435", - "hashPath": "stylecop.analyzers.1.2.0-beta.435.nupkg.sha512" - }, - "StyleCop.Analyzers.Unstable/1.2.0.435": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==", - "path": "stylecop.analyzers.unstable/1.2.0.435", - "hashPath": "stylecop.analyzers.unstable.1.2.0.435.nupkg.sha512" - } - } -} \ No newline at end of file diff --git a/DXMainClient/Resources/DTA/SecondStageUpdater.dll b/DXMainClient/Resources/DTA/SecondStageUpdater.dll deleted file mode 100644 index 51ebb44a1..000000000 Binary files a/DXMainClient/Resources/DTA/SecondStageUpdater.dll and /dev/null differ diff --git a/DXMainClient/Resources/DTA/SecondStageUpdater.runtimeconfig.json b/DXMainClient/Resources/DTA/SecondStageUpdater.runtimeconfig.json deleted file mode 100644 index 398903e05..000000000 --- a/DXMainClient/Resources/DTA/SecondStageUpdater.runtimeconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net7.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "7.0.0" - }, - "configProperties": { - "System.Reflection.Metadata.MetadataUpdater.IsSupported": false - } - } -} \ No newline at end of file diff --git a/DXMainClient/Startup.cs b/DXMainClient/Startup.cs index c268ec00a..1b3b74495 100644 --- a/DXMainClient/Startup.cs +++ b/DXMainClient/Startup.cs @@ -49,7 +49,6 @@ public void Execute() Logger.Log("OSArchitecture: " + RuntimeInformation.OSArchitecture); Logger.Log("ProcessArchitecture: " + RuntimeInformation.ProcessArchitecture); Logger.Log("FrameworkDescription: " + RuntimeInformation.FrameworkDescription); - Logger.Log("RuntimeIdentifier: " + RuntimeInformation.RuntimeIdentifier); Logger.Log("Selected OS profile: " + MainClientConstants.OSId); Logger.Log("Current culture: " + CultureInfo.CurrentCulture); diff --git a/DXMainClient/app.manifest b/DXMainClient/app.manifest index 729c7ea4a..b6bf8dcfb 100644 --- a/DXMainClient/app.manifest +++ b/DXMainClient/app.manifest @@ -1,6 +1,6 @@  - + @@ -44,5 +44,33 @@ + + + + + + true/PM + PerMonitorV2, PerMonitor + + + + + + + + + + + diff --git a/Directory.Build.props b/Directory.Build.props index a5996d3a7..6dcd3113e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,6 @@ + 12.0 false false AnyCPU;x64;ARM64;x86 @@ -34,6 +35,27 @@ + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets index 606d5316e..99c3d06ac 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,5 +1,5 @@ - + $(MSBuildProgramFiles32)\dotnet\dotnet \ No newline at end of file diff --git a/Docs/Migration.md b/Docs/Migration.md index 4734fed6c..9d47b3fb9 100644 --- a/Docs/Migration.md +++ b/Docs/Migration.md @@ -9,7 +9,7 @@ Migrating from older versions - Second-stage updater no longer has hardcoded list of launcher executables to check for when restarting the client. It will now only check `ClientDefinitions.ini` for `LauncherExe` key, and it it fails to read and launch this the client will not automatically restart after updating. -- Updater DLL filename has been changed from `DTAUpdater.dll` to `ClientUpdater.dll` and second-stage updater from `clientupdt.dat` to `SecondStageUpdater.dll` and has been moved from base folder to `Resources\Updater`. +- Updater DLL filename has been changed from `DTAUpdater.dll` to `ClientUpdater.dll` and second-stage updater from `clientupdt.dat` to `SecondStageUpdater.dll` for .NET 8 / `SecondStageUpdater.exe` for .NET 4.8 and has been moved from base folder to `Resources\Updater`. - Second-stage updater is now maintained as a separate project. Download the latest release [here](https://github.com/CnCNet/cncnet-client-updater/releases) (select `SecondStageUpdater-.zip`), and extract its contents to the client's `Resources\Updater` folder. diff --git a/LICENSE.md b/LICENSE.md index ded950670..322bff8f1 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,5 @@ CnCNet Client -Copyright (C) 2022 CnCNet, Rampastring +Copyright (C) 2022-2024 CnCNet, Rampastring This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ along with this program. If not, see . This software includes Rampastring.Tools and Rampastring.XNAUI. Their license follows: -Copyright (c) 2022 Rami "Rampastring" Pasanen +Copyright (c) 2016-2023 Rami "Rampastring" Pasanen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -31,7 +31,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI The repository contains compiled MonoGame binaries to make the build process easier. MonoGame's license follows: -Microsoft Public License (Ms-PL) +Microsoft Public License (Ms-PL) MonoGame - Copyright © 2009-2018 The MonoGame Team All rights reserved. diff --git a/README.md b/README.md index fa1933f93..cd0eb0fae 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,94 @@ -# CnCNet Client # +# CnCNet Client The MonoGame / XNA CnCNet client, a platform for playing classic Command & Conquer games and their mods both online and offline. Supports setting up and launching both singleplayer and multiplayer games with [a CnCNet game spawner](https://github.com/CnCNet/ts-patches). Includes an IRC-based chat client with advanced features like private messaging, a friend list, a configurable game lobby, flexible and moddable UI graphics, and extras like game setting configuration and keeping track of match statistics. And much more! -Targets -------- +## Targets The primary targets of the client project are - - * [Dawn of the Tiberium Age](https://www.moddb.com/mods/the-dawn-of-the-tiberium-age) * [Twisted Insurrection](https://www.moddb.com/mods/twisted-insurrection) * [Mental Omega](https://www.moddb.com/mods/mental-omega) * [CnCNet Yuri's Revenge](https://cncnet.org/yuris-revenge) - However, there is no limitation in the client that would prevent incorporating it into other projects. Any game or mod project that utilizes the CnCNet spawner for Tiberian Sun and Red Alert 2 can be supported. Several other projects also use the client or an unofficial fork of it, including [Tiberian Sun Client](https://www.moddb.com/mods/tiberian-sun-client), [Project Phantom](https://www.moddb.com/mods/project-phantom), [YR Red-Resurrection](https://www.moddb.com/mods/yr-red-resurrection), [The Second Tiberium War](https://www.moddb.com/mods/the-second-tiberium-war) and [CnC: Final War](https://www.moddb.com/mods/cncfinalwar). -Development requirements ------------------------- +## Development requirements -The client has 4 builds: Windows DirectX11, Windows OpenGL, Windows XNA and Universal OpenGL. +The client has 2 variants: .NET 4.8 and .NET 8.0. +* Both variants have 3 builds: Windows DirectX11, Windows OpenGL and Windows XNA. +* .NET 8.0 in addition has a cross-platform Universal OpenGL build. * The DirectX11 and OpenGL builds rely on MonoGame. * The XNA build relies on Microsoft's XNA Framework 4.0 Refresh. -Building the solution for any platform requires Visual Studio 2022 17.3 or newer and/or the .NET SDK 7.0. A modern version of Visual Studio Code, MonoDevelop or Visual Studio for Mac could also work, but are not officially supported. -To debug WindowsXNA builds the .NET SDK 7.0 x86 is additionally required. +Building the solution for **any** platform requires Visual Studio 2022 17.8 or newer and/or the .NET SDK 8.0. A modern version of Visual Studio Code, MonoDevelop or Visual Studio for Mac could also work, but are not officially supported. +To debug WindowsXNA builds the .NET SDK 8.0 x86 is additionally required. When using the included build scripts PowerShell 7.2 or newer is required. -Compiling and debugging ------------------------ +## Compiling and debugging -* Compiling itself is simple: assuming you have the .NET 7.0 SDK installed, you can just open the solution with Visual Studio and compile it right away. +* Compiling itself is simple: assuming you have the .NET 8.0 SDK installed, you can just open the solution with Visual Studio and compile it right away. * When built as a debug build, the client executable expects to reside in the same directory with the target project's main game executable. Resources should exist in a "Resources" sub-directory in the same directory. The repository contains sample resources and post-build commands for copying them so that you can immediately run the client in debug mode by just hitting the Debug button in Visual Studio. -* When built in release mode, the client executable expects to reside in the "Resources" sub-directory itself. In target projects, the client libraries are named `clientdx.dll`, `clientogl.dll` and `clientxna.dll` respectively for each platform. +* When built in release mode, the client executables expect to reside in the `Resources` sub-directory itself for .NET 4.8, named `clientdx.exe`, `clientogl.exe` and `clientxna.exe`. For .NET 8.0, those executables will reside in `Resources\BinariesNET8\{Windows, OpenGL, UniversalGL, XNA}` folders, named `client{dx, ogl, ogl, xna}.dll`, respectively. Note that `client{dx, ogl, ogl, xna}.runtimeconfig.json` files are required for the corresponding dlls. * When built on an OS other than Windows, only the Universal OpenGL build is available. * The `BuildScripts` directory has automated build scripts that build the client for all platforms and copy the output files to a folder named `Compiled` in the project root. You can then copy the contents of this `Compiled` directory into the `Resources` sub-directory of any target project. -End-user usage --------------- +
+ Development workarounds + +* If you switch among different solution configurations in Visual Studio (e.g. switch to `TSUniversalGLRelease` from `AresWindowsDXDebug`), especially switching between .NET 4.8 and .NET 8.0 variants, it is recommended to restart Visual Studio after switching configurations to prevent unexpected error messages. If restarting Visual Studio do not work as intended, try deleting all `obj` folders in each project. Due to the same reason, it is advised to close Visual Studio when building the client using the scripts in `BuildScripts` folder. +* Some dependencies are stored in `References` folder instead of the official NuGet source. This folder is also useful if you are working on modifying a dependency and debugging in your local machine without publishing the modification to NuGet. However, if you have replaced the `.(s)nupkg` files of a package, without altering the package version, be sure to remove the corresponding package from `%USERPROFILE%\.nuget\packages` folder to purge the old version. +
+ +## End-user usage -* Windows: Windows 7 SP1 or higher is required. The DirectX11 build is preferred. The XNA build is intended for those whose GPU does not properly support DX11. +* Windows: Windows 7 SP1 or higher is required. The preferred build is DirectX11 (.NET 4.8), i.e., `clientdx.exe`. If your GPU does not support DX11, consider using the OpenGL or XNA build instead. Advanced users may experiment with the .NET 8 builds at their discretion. * Other OS: Use the Universal OpenGL build. -End-user requirements ---------------------- +## End-user requirements -Windows requirements: -* The [.NET 7.0 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/7.0/runtime) for your specific platform. -* Additionally the [.NET 7.0 Desktop Runtime x86](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-7.0.0-windows-x86-installer) used by the client launcher and the XNA build. +### Windows .NET 4.8 requirements: -The XNA build additionally requires: +* The [.NET Framework 4.8 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet-framework/thank-you/net48-web-installer) + +(Optional) The XNA build requires: * [Microsoft XNA Framework Redistributable 4.0 Refresh](https://www.microsoft.com/en-us/download/details.aspx?id=27598). +### Linux requirements: + +* The [.NET 8.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime?initial-os=linux) for your specific platform. + +### macOS requirements: + +* The [.NET 8.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime?initial-os=macos) for your specific platform. + +### Windows .NET 8.0 requirements: + +
+ Windows .NET 8.0 requirements + +* The [.NET 8.0 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime?initial-os=windows) for your specific platform. + +(Optional) The XNA build requires: +* [Microsoft XNA Framework Redistributable 4.0 Refresh](https://www.microsoft.com/en-us/download/details.aspx?id=27598). +* [.NET 8.0 Desktop Runtime x86](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.0-windows-x86-installer). + Windows 7 SP1 and Windows 8.x additionally require: * Microsoft Visual C++ 2015-2019 Redistributable [64-bit](https://aka.ms/vs/16/release/vc_redist.x64.exe) / [32-bit](https://aka.ms/vs/16/release/vc_redist.x86.exe). Windows 7 SP1 additionally requires: * KB3063858 [64-bit](https://www.microsoft.com/download/details.aspx?id=47442) / [32-bit](https://www.microsoft.com/download/details.aspx?id=47409). +
+## Client launcher -Other OS requirements: -* The [.NET 7.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/7.0/runtime) for your specific platform. - -Client launcher ---------------- - -This repository does not contain the client launcher (for example, `DTA.exe` in Dawn of the Tiberium Age) that selects which platform's client executable is most suitable for each user's system. For that, see [dta-mg-client-launcher](https://github.com/CnCNet/dta-mg-client-launcher). +This repository does not contain the client launcher (for example, `DTA.exe` in Dawn of the Tiberium Age) that selects which platform's client executable is most suitable for each user's system. +See [dta-mg-client-launcher](https://github.com/CnCNet/dta-mg-client-launcher). -Branches --------- +## Branches Currently there are only two major active branches. `develop` is where development happens, and while things should be fairly stable, occasionally there can also be bugs. If you want stability and reliability, the `master` branch is recommended. -Screenshots ------------ +## Screenshots ![Screenshot](cncnetchatlobby.png?raw=true "CnCNet IRC Chat Lobby") ![Screenshot](cncnetgamelobby.png?raw=true "CnCNet Game Lobby") diff --git a/References/CnCNet.ClientUpdater.1.0.15.nupkg b/References/CnCNet.ClientUpdater.1.0.15.nupkg deleted file mode 100644 index 90aa528b8..000000000 Binary files a/References/CnCNet.ClientUpdater.1.0.15.nupkg and /dev/null differ diff --git a/References/CnCNet.ClientUpdater.1.0.15.snupkg b/References/CnCNet.ClientUpdater.1.0.15.snupkg deleted file mode 100644 index 1192b4b18..000000000 Binary files a/References/CnCNet.ClientUpdater.1.0.15.snupkg and /dev/null differ diff --git a/References/CnCNet.ClientUpdater.1.0.16-local-cbc884c.nupkg b/References/CnCNet.ClientUpdater.1.0.16-local-cbc884c.nupkg new file mode 100644 index 000000000..d3fef7d10 Binary files /dev/null and b/References/CnCNet.ClientUpdater.1.0.16-local-cbc884c.nupkg differ diff --git a/References/CnCNet.ClientUpdater.1.0.16-local-cbc884c.snupkg b/References/CnCNet.ClientUpdater.1.0.16-local-cbc884c.snupkg new file mode 100644 index 000000000..f5d256066 Binary files /dev/null and b/References/CnCNet.ClientUpdater.1.0.16-local-cbc884c.snupkg differ diff --git a/References/Rampastring.Tools.2.0.6-local-397fda5.nupkg b/References/Rampastring.Tools.2.0.6-local-397fda5.nupkg new file mode 100644 index 000000000..0c49c1c80 Binary files /dev/null and b/References/Rampastring.Tools.2.0.6-local-397fda5.nupkg differ diff --git a/References/Rampastring.Tools.2.0.6-local-397fda5.snupkg b/References/Rampastring.Tools.2.0.6-local-397fda5.snupkg new file mode 100644 index 000000000..cc67d24a7 Binary files /dev/null and b/References/Rampastring.Tools.2.0.6-local-397fda5.snupkg differ diff --git a/References/Rampastring.XNAUI.UniversalGL.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.UniversalGL.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..eb58e90f8 Binary files /dev/null and b/References/Rampastring.XNAUI.UniversalGL.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.UniversalGL.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.UniversalGL.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..8062b56be Binary files /dev/null and b/References/Rampastring.XNAUI.UniversalGL.2.3.20-local-2c977ab.snupkg differ diff --git a/References/Rampastring.XNAUI.UniversalGL.Debug.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.UniversalGL.Debug.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..02d8e6046 Binary files /dev/null and b/References/Rampastring.XNAUI.UniversalGL.Debug.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.UniversalGL.Debug.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.UniversalGL.Debug.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..4c9240d36 Binary files /dev/null and b/References/Rampastring.XNAUI.UniversalGL.Debug.2.3.20-local-2c977ab.snupkg differ diff --git a/References/Rampastring.XNAUI.WindowsDX.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.WindowsDX.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..1a6bab8bd Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsDX.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.WindowsDX.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.WindowsDX.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..7ba8680ef Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsDX.2.3.20-local-2c977ab.snupkg differ diff --git a/References/Rampastring.XNAUI.WindowsDX.Debug.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.WindowsDX.Debug.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..bc17fc56a Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsDX.Debug.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.WindowsDX.Debug.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.WindowsDX.Debug.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..0c7cf5386 Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsDX.Debug.2.3.20-local-2c977ab.snupkg differ diff --git a/References/Rampastring.XNAUI.WindowsGL.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.WindowsGL.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..d7bce7c2a Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsGL.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.WindowsGL.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.WindowsGL.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..b4000efdc Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsGL.2.3.20-local-2c977ab.snupkg differ diff --git a/References/Rampastring.XNAUI.WindowsGL.Debug.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.WindowsGL.Debug.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..d67d0544a Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsGL.Debug.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.WindowsGL.Debug.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.WindowsGL.Debug.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..36a088f06 Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsGL.Debug.2.3.20-local-2c977ab.snupkg differ diff --git a/References/Rampastring.XNAUI.WindowsXNA.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.WindowsXNA.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..fa3e53c8a Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsXNA.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.WindowsXNA.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.WindowsXNA.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..194ff51f8 Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsXNA.2.3.20-local-2c977ab.snupkg differ diff --git a/References/Rampastring.XNAUI.WindowsXNA.Debug.2.3.20-local-2c977ab.nupkg b/References/Rampastring.XNAUI.WindowsXNA.Debug.2.3.20-local-2c977ab.nupkg new file mode 100644 index 000000000..6b244afcb Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsXNA.Debug.2.3.20-local-2c977ab.nupkg differ diff --git a/References/Rampastring.XNAUI.WindowsXNA.Debug.2.3.20-local-2c977ab.snupkg b/References/Rampastring.XNAUI.WindowsXNA.Debug.2.3.20-local-2c977ab.snupkg new file mode 100644 index 000000000..663b28e40 Binary files /dev/null and b/References/Rampastring.XNAUI.WindowsXNA.Debug.2.3.20-local-2c977ab.snupkg differ diff --git a/TranslationNotifierGenerator/TranslationNotifierGenerator.csproj b/TranslationNotifierGenerator/TranslationNotifierGenerator.csproj index e17bbe887..618073e6f 100644 --- a/TranslationNotifierGenerator/TranslationNotifierGenerator.csproj +++ b/TranslationNotifierGenerator/TranslationNotifierGenerator.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 10.0 + 12.0 diff --git a/build/AfterPublish.targets b/build/AfterPublish.targets index 7efa27848..2bd0fbb15 100644 --- a/build/AfterPublish.targets +++ b/build/AfterPublish.targets @@ -1,8 +1,18 @@ - - - - + + + + <_lib_x64 Include="$(OutputPath)\x64\*.*" /> + <_lib_x86 Include="$(OutputPath)\x86\*.*" /> + + + + + + + + + @@ -27,7 +37,11 @@ - + + + + + @@ -42,6 +56,8 @@ + + @@ -62,7 +78,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/WinForms.props b/build/WinForms.props index b4bf3f174..af6f0d66c 100644 --- a/build/WinForms.props +++ b/build/WinForms.props @@ -1,7 +1,10 @@ - - net7.0-windows - net7.0 - true + + net48;net8.0-windows + true + + + net8.0 + false \ No newline at end of file