diff --git a/ClientCore/ClientConfiguration.cs b/ClientCore/ClientConfiguration.cs
index 25b8069ad..d80fb6a05 100644
--- a/ClientCore/ClientConfiguration.cs
+++ b/ClientCore/ClientConfiguration.cs
@@ -20,12 +20,14 @@ public class ClientConfiguration
private const string CLIENT_SETTINGS = "DTACnCNetClient.ini";
private const string GAME_OPTIONS = "GameOptions.ini";
private const string CLIENT_DEFS = "ClientDefinitions.ini";
+ private const string NETWORK_DEFS = "NetworkDefinitions.ini";
private static ClientConfiguration _instance;
private IniFile gameOptions_ini;
private IniFile DTACnCNetClient_ini;
private IniFile clientDefinitionsIni;
+ private IniFile networkDefinitionsIni;
protected ClientConfiguration()
{
@@ -36,7 +38,7 @@ protected ClientConfiguration()
FileInfo clientDefinitionsFile = SafePath.GetFile(baseResourceDirectory.FullName, CLIENT_DEFS);
- if (clientDefinitionsFile is null)
+ if (!(clientDefinitionsFile?.Exists ?? false))
throw new FileNotFoundException($"Couldn't find {CLIENT_DEFS} at {baseResourceDirectory}. Please verify that you're running the client from the correct directory.");
clientDefinitionsIni = new IniFile(clientDefinitionsFile.FullName);
@@ -44,6 +46,8 @@ protected ClientConfiguration()
DTACnCNetClient_ini = new IniFile(SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), CLIENT_SETTINGS));
gameOptions_ini = new IniFile(SafePath.CombineFilePath(baseResourceDirectory.FullName, GAME_OPTIONS));
+
+ networkDefinitionsIni = new IniFile(SafePath.CombineFilePath(ProgramConstants.GetResourcePath(), NETWORK_DEFS));
}
///
@@ -99,7 +103,7 @@ public void RefreshSettings()
public string AltUIBackgroundColor => DTACnCNetClient_ini.GetStringValue(GENERAL, "AltUIBackgroundColor", "196,196,196");
- public string WindowBorderColor => DTACnCNetClient_ini.GetStringValue(GENERAL, "WindowBorderColor", "128,128,128");
+ public string WindowBorderColor => DTACnCNetClient_ini.GetStringValue(GENERAL, "WindowBorderColor", "128,128,128");
public string PanelBorderColor => DTACnCNetClient_ini.GetStringValue(GENERAL, "PanelBorderColor", "255,255,255");
@@ -385,6 +389,37 @@ public IEnumerable SupplementalMapFileExtensions
#endregion
+ #region Network definitions
+
+ public string CnCNetTunnelListURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetTunnelListURL", "http://cncnet.org/master-list");
+
+ public string CnCNetPlayerCountURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetPlayerCountURL", "http://api.cncnet.org/status");
+
+ public string CnCNetMapDBDownloadURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetMapDBDownloadURL", "http://mapdb.cncnet.org");
+
+ public string CnCNetMapDBUploadURL => networkDefinitionsIni.GetStringValue(SETTINGS, "CnCNetMapDBUploadURL", "http://mapdb.cncnet.org/upload");
+
+ public bool DisableDiscordIntegration => networkDefinitionsIni.GetBooleanValue(SETTINGS, "DisableDiscordIntegration", false);
+
+ public List IRCServers => GetIRCServers();
+
+ #endregion
+
+ public List GetIRCServers()
+ {
+ List servers = [];
+
+ IniSection serversSection = networkDefinitionsIni.GetSection("IRCServers");
+ if (serversSection != null)
+ foreach ((_, string value) in serversSection.Keys)
+ if (!string.IsNullOrWhiteSpace(value))
+ servers.Add(value);
+
+ return servers;
+ }
+
+ public bool DiscordIntegrationGloballyDisabled => string.IsNullOrWhiteSpace(DiscordAppId) || DisableDiscordIntegration;
+
public OSVersion GetOperatingSystemVersion()
{
#if NETFRAMEWORK
diff --git a/DTAConfig/OptionPanels/CnCNetOptionsPanel.cs b/DTAConfig/OptionPanels/CnCNetOptionsPanel.cs
index 1ba5e8756..13ce2142f 100644
--- a/DTAConfig/OptionPanels/CnCNetOptionsPanel.cs
+++ b/DTAConfig/OptionPanels/CnCNetOptionsPanel.cs
@@ -140,7 +140,7 @@ private void InitOptions()
chkConnectOnStartup.Bottom + 12, 0, 0);
chkDiscordIntegration.Text = "Show detailed game info in Discord status".L10N("Client:DTAConfig:DiscordStatus");
- if (String.IsNullOrEmpty(ClientConfiguration.Instance.DiscordAppId))
+ if (ClientConfiguration.Instance.DiscordIntegrationGloballyDisabled)
{
chkDiscordIntegration.AllowChecking = false;
chkDiscordIntegration.Checked = false;
@@ -317,7 +317,7 @@ public override void Load()
chkSkipLoginWindow.Checked = IniSettings.SkipConnectDialog;
chkPersistentMode.Checked = IniSettings.PersistentMode;
- chkDiscordIntegration.Checked = !String.IsNullOrEmpty(ClientConfiguration.Instance.DiscordAppId)
+ chkDiscordIntegration.Checked = !ClientConfiguration.Instance.DiscordIntegrationGloballyDisabled
&& IniSettings.DiscordIntegration;
chkAllowGameInvitesFromFriendsOnly.Checked = IniSettings.AllowGameInvitesFromFriendsOnly;
@@ -352,7 +352,7 @@ public override bool Save()
IniSettings.SkipConnectDialog.Value = chkSkipLoginWindow.Checked;
IniSettings.PersistentMode.Value = chkPersistentMode.Checked;
- if (!String.IsNullOrEmpty(ClientConfiguration.Instance.DiscordAppId))
+ if (!ClientConfiguration.Instance.DiscordIntegrationGloballyDisabled)
{
IniSettings.DiscordIntegration.Value = chkDiscordIntegration.Checked;
}
diff --git a/DXMainClient/DXGUI/Generic/MainMenu.cs b/DXMainClient/DXGUI/Generic/MainMenu.cs
index 795c55a66..8c0749a38 100644
--- a/DXMainClient/DXGUI/Generic/MainMenu.cs
+++ b/DXMainClient/DXGUI/Generic/MainMenu.cs
@@ -410,7 +410,7 @@ private void SettingsSaved(object sender, EventArgs e)
if (!connectionManager.IsConnected)
ProgramConstants.PLAYERNAME = UserINISettings.Instance.PlayerName;
- if (UserINISettings.Instance.DiscordIntegration)
+ if (UserINISettings.Instance.DiscordIntegration && !ClientConfiguration.Instance.DiscordIntegrationGloballyDisabled)
discordHandler.Connect();
else
discordHandler.Disconnect();
diff --git a/DXMainClient/Domain/DiscordHandler.cs b/DXMainClient/Domain/DiscordHandler.cs
index b87e2bbfa..7fc292a93 100644
--- a/DXMainClient/Domain/DiscordHandler.cs
+++ b/DXMainClient/Domain/DiscordHandler.cs
@@ -48,7 +48,7 @@ public RichPresence CurrentPresence
///
public DiscordHandler()
{
- if (!UserINISettings.Instance.DiscordIntegration || string.IsNullOrEmpty(ClientConfiguration.Instance.DiscordAppId))
+ if (!UserINISettings.Instance.DiscordIntegration || ClientConfiguration.Instance.DiscordIntegrationGloballyDisabled)
return;
InitializeClient();
diff --git a/DXMainClient/Domain/MainClientConstants.cs b/DXMainClient/Domain/MainClientConstants.cs
index b35e5346c..f5f05ec76 100644
--- a/DXMainClient/Domain/MainClientConstants.cs
+++ b/DXMainClient/Domain/MainClientConstants.cs
@@ -12,7 +12,7 @@ namespace DTAClient.Domain
{
public static class MainClientConstants
{
- public const string CNCNET_TUNNEL_LIST_URL = "http://cncnet.org/master-list";
+ public static string CNCNET_TUNNEL_LIST_URL = "http://cncnet.org/master-list";
public static string GAME_NAME_LONG = "CnCNet Client";
public static string GAME_NAME_SHORT = "CnCNet";
@@ -92,6 +92,8 @@ public static void Initialize()
CREDITS_URL = clientConfiguration.CreditsURL;
+ CNCNET_TUNNEL_LIST_URL = clientConfiguration.CnCNetTunnelListURL;
+
USE_ISOMETRIC_CELLS = clientConfiguration.UseIsometricCells;
TDRA_WAYPOINT_COEFFICIENT = clientConfiguration.WaypointCoefficient;
MAP_CELL_SIZE_X = clientConfiguration.MapCellSizeX;
diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetPlayerCountTask.cs b/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetPlayerCountTask.cs
index 0bf357b90..b417dd1d9 100644
--- a/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetPlayerCountTask.cs
+++ b/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetPlayerCountTask.cs
@@ -50,10 +50,15 @@ private static int GetCnCNetPlayerCount()
{
try
{
+ // Don't fetch the player count if it is explicitly disabled
+ // For example, the official CnCNet server might be unavailable/unstable in a country with Internet censorship,
+ // which causes lags in the splash screen. In the worst case, say if packets are dropped, it waits until timeouts --- 30 seconds
+ if (string.IsNullOrWhiteSpace(ClientConfiguration.Instance.CnCNetPlayerCountURL))
+ return -1;
+
WebClient client = new WebClient();
+ Stream data = client.OpenRead(ClientConfiguration.Instance.CnCNetPlayerCountURL);
- Stream data = client.OpenRead("http://api.cncnet.org/status");
-
string info = string.Empty;
using (StreamReader reader = new StreamReader(data))
diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/MapSharer.cs b/DXMainClient/Domain/Multiplayer/CnCNet/MapSharer.cs
index 7dfa4e8a2..99fb6905d 100644
--- a/DXMainClient/Domain/Multiplayer/CnCNet/MapSharer.cs
+++ b/DXMainClient/Domain/Multiplayer/CnCNet/MapSharer.cs
@@ -36,8 +36,6 @@ public static class MapSharer
private static readonly object locker = new object();
- private const string MAPDB_URL = "http://mapdb.cncnet.org/upload";
-
///
/// Adds a map into the CnCNet map upload queue.
///
@@ -78,8 +76,14 @@ private static void Upload(object mapAndGame)
Logger.Log("MapSharer: Starting upload of " + map.BaseFilePath);
- bool success = false;
- string message = MapUpload(MAPDB_URL, map, myGameId, out success);
+ if (string.IsNullOrWhiteSpace(ClientConfiguration.Instance.CnCNetMapDBUploadURL))
+ {
+ Logger.Log("MapSharer: Upload URL is not configured.");
+ MapUploadFailed?.Invoke(null, new MapEventArgs(map));
+ return;
+ }
+
+ string message = MapUpload(ClientConfiguration.Instance.CnCNetMapDBUploadURL, map, myGameId, out bool success);
if (success)
{
@@ -380,12 +384,22 @@ private static string DownloadMain(string sha1, string myGame, string mapName, o
using (TWebClient webClient = new TWebClient())
{
+ // TODO enable proxy support for some users
webClient.Proxy = null;
+ if (string.IsNullOrWhiteSpace(ClientConfiguration.Instance.CnCNetMapDBDownloadURL))
+ {
+ success = false;
+ Logger.Log("MapSharer: Download URL is not configured.");
+ return null;
+ }
+
+ string url = string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}.zip", ClientConfiguration.Instance.CnCNetMapDBDownloadURL, myGame, sha1);
+
try
{
- Logger.Log("MapSharer: Downloading URL: " + "http://mapdb.cncnet.org/" + myGame + "/" + sha1 + ".zip");
- webClient.DownloadFile("http://mapdb.cncnet.org/" + myGame + "/" + sha1 + ".zip", destinationFile.FullName);
+ Logger.Log($"MapSharer: Downloading URL: {url}");
+ webClient.DownloadFile(url, destinationFile.FullName);
}
catch (Exception ex)
{
@@ -449,6 +463,7 @@ class TWebClient : WebClient
public TWebClient()
{
+ // TODO enable proxy support for some users
this.Proxy = null;
}
diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs
index 929a193b3..d569e017b 100644
--- a/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs
+++ b/DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs
@@ -143,48 +143,78 @@ private Task PingCurrentTunnelAsync(bool checkTunnelList = false)
});
}
- ///
- /// Downloads and parses the list of CnCNet tunnels.
- ///
- /// A list of tunnel servers.
- private List RefreshTunnels()
- {
- FileInfo tunnelCacheFile = SafePath.GetFile(ProgramConstants.ClientUserFilesPath, "tunnel_cache");
-
- List returnValue = new List();
+ private bool OnlineTunnelDataAvailable => !string.IsNullOrWhiteSpace(MainClientConstants.CNCNET_TUNNEL_LIST_URL);
+ private bool OfflineTunnelDataAvailable => SafePath.GetFile(ProgramConstants.ClientUserFilesPath, "tunnel_cache").Exists;
+ private byte[] GetRawTunnelDataOnline()
+ {
WebClient client = new WebClient();
+ return client.DownloadData(MainClientConstants.CNCNET_TUNNEL_LIST_URL);
+ }
- byte[] data;
+ private byte[] GetRawTunnelDataOffline()
+ {
+ FileInfo tunnelCacheFile = SafePath.GetFile(ProgramConstants.ClientUserFilesPath, "tunnel_cache");
+ return File.ReadAllBytes(tunnelCacheFile.FullName);
+ }
+ private byte[] GetRawTunnelData(int retryCount = 2)
+ {
Logger.Log("Fetching tunnel server info.");
- try
- {
- data = client.DownloadData(MainClientConstants.CNCNET_TUNNEL_LIST_URL);
- }
- catch (Exception ex)
+ if (OnlineTunnelDataAvailable)
{
- Logger.Log("Error when downloading tunnel server info: " + ex.ToString());
- Logger.Log("Retrying.");
- try
+ for (int i = 0; i < retryCount; i++)
{
- data = client.DownloadData(MainClientConstants.CNCNET_TUNNEL_LIST_URL);
- }
- catch
- {
- if (!tunnelCacheFile.Exists)
+ try
{
- Logger.Log("Tunnel cache file doesn't exist!");
- return returnValue;
+ byte[] data = GetRawTunnelDataOnline();
+ return data;
}
- else
+ catch (Exception ex)
{
- Logger.Log("Fetching tunnel server list failed. Using cached tunnel data.");
- data = File.ReadAllBytes(tunnelCacheFile.FullName);
+ Logger.Log("Error when downloading tunnel server info: " + ex.Message);
+ if (i < retryCount - 1)
+ Logger.Log("Retrying.");
+ else
+ Logger.Log("Fetching tunnel server list failed.");
}
}
}
+ else
+ {
+ // Don't fetch the latest tunnel list if it is explicitly disabled
+ // For example, the official CnCNet server might be unavailable/unstable in a country with Internet censorship,
+ // where players might either establish a substitute server or manually distribute the tunnel cache file
+ Logger.Log("Fetching tunnel server list online is disabled.");
+ }
+
+ if (OfflineTunnelDataAvailable)
+ {
+ Logger.Log("Using cached tunnel data.");
+ byte[] data = GetRawTunnelDataOffline();
+ return data;
+ }
+ else
+ Logger.Log("Tunnel cache file doesn't exist!");
+
+ return null;
+ }
+
+
+ ///
+ /// Downloads and parses the list of CnCNet tunnels.
+ ///
+ /// A list of tunnel servers.
+ private List RefreshTunnels()
+ {
+ List returnValue = new List();
+
+ FileInfo tunnelCacheFile = SafePath.GetFile(ProgramConstants.ClientUserFilesPath, "tunnel_cache");
+
+ byte[] data = GetRawTunnelData();
+ if (data is null)
+ return returnValue;
string convertedData = Encoding.Default.GetString(data);
@@ -234,6 +264,7 @@ private List RefreshTunnels()
}
}
+ Logger.Log($"Successfully refreshed tunnel cache with {returnValue.Count} servers.");
return returnValue;
}
diff --git a/DXMainClient/Online/Connection.cs b/DXMainClient/Online/Connection.cs
index 3eed41359..15ad88087 100644
--- a/DXMainClient/Online/Connection.cs
+++ b/DXMainClient/Online/Connection.cs
@@ -31,28 +31,32 @@ public Connection(IConnectionManager connectionManager)
IConnectionManager connectionManager;
+ private static IList _servers = null;
///
/// The list of CnCNet / GameSurge IRC servers to connect to.
///
- private static readonly IList Servers = new List
+ private static IList Servers
{
- new Server("Burstfire.UK.EU.GameSurge.net", "GameSurge London, UK", new int[3] { 6667, 6668, 7000 }),
- new Server("ColoCrossing.IL.US.GameSurge.net", "GameSurge Chicago, IL", new int[5] { 6660, 6666, 6667, 6668, 6669 }),
- new Server("Gameservers.NJ.US.GameSurge.net", "GameSurge Newark, NJ", new int[7] { 6665, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("Krypt.CA.US.GameSurge.net", "GameSurge Santa Ana, CA", new int[4] { 6666, 6667, 6668, 6669 }),
- new Server("NuclearFallout.WA.US.GameSurge.net", "GameSurge Seattle, WA", new int[2] { 6667, 5960 }),
- new Server("Portlane.SE.EU.GameSurge.net", "GameSurge Stockholm, Sweden", new int[5] { 6660, 6666, 6667, 6668, 6669 }),
- new Server("Prothid.NY.US.GameSurge.Net", "GameSurge NYC, NY", new int[7] { 5960, 6660, 6666, 6667, 6668, 6669, 6697 }),
- new Server("TAL.DE.EU.GameSurge.net", "GameSurge Wuppertal, Germany", new int[5] { 6660, 6666, 6667, 6668, 6669 }),
- new Server("208.167.237.120", "GameSurge IP 208.167.237.120", new int[7] { 6660, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("192.223.27.109", "GameSurge IP 192.223.27.109", new int[7] { 6660, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("108.174.48.100", "GameSurge IP 108.174.48.100", new int[7] { 6660, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("208.146.35.105", "GameSurge IP 208.146.35.105", new int[7] { 6660, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("195.8.250.180", "GameSurge IP 195.8.250.180", new int[7] { 6660, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("91.217.189.76", "GameSurge IP 91.217.189.76", new int[7] { 6660, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("195.68.206.250", "GameSurge IP 195.68.206.250", new int[7] { 6660, 6666, 6667, 6668, 6669, 7000, 8080 }),
- new Server("irc.gamesurge.net", "GameSurge", new int[1] { 6667 }),
- }.AsReadOnly();
+ get
+ {
+ if (_servers is not null)
+ return _servers;
+
+ IEnumerable serversList;
+ if (ClientConfiguration.Instance.IRCServers.Count > 0)
+ serversList = ClientConfiguration.Instance.IRCServers;
+ else
+ {
+ // fallback to the hardcoded servers list
+ serversList = [
+ "irc.gamesurge.net|GameSurge|6667,6660,6666,6668,6669",
+ ];
+ }
+
+ _servers = serversList.Select(Server.Deserialize).ToList();
+ return _servers;
+ }
+ }
bool _isConnected = false;
public bool IsConnected
diff --git a/DXMainClient/Online/Server.cs b/DXMainClient/Online/Server.cs
index e603b8135..96cc3c51b 100644
--- a/DXMainClient/Online/Server.cs
+++ b/DXMainClient/Online/Server.cs
@@ -1,4 +1,6 @@
-namespace DTAClient.Online
+using System;
+
+namespace DTAClient.Online
{
///
/// A struct containing information on an IRC server.
@@ -15,5 +17,21 @@ public Server(string host, string name, int[] ports)
public string Host;
public string Name;
public int[] Ports;
+
+ public string Serialize() => FormattableString.Invariant($"{Host}|{Name}|{string.Join(",", Ports)}");
+
+ public static Server Deserialize(string serialized)
+ {
+ string[] parts = serialized.Split('|');
+ string host = parts[0];
+ string name = parts[1];
+ string[] portStrings = parts[2].Split(',');
+ int[] ports = new int[portStrings.Length];
+
+ for (int i = 0; i < portStrings.Length; i++)
+ ports[i] = int.Parse(portStrings[i]);
+
+ return new Server(host, name, ports);
+ }
}
}
diff --git a/DXMainClient/Resources/DTA/NetworkDefinitions.ini b/DXMainClient/Resources/DTA/NetworkDefinitions.ini
new file mode 100644
index 000000000..7d4490937
--- /dev/null
+++ b/DXMainClient/Resources/DTA/NetworkDefinitions.ini
@@ -0,0 +1,9 @@
+[Settings]
+CnCNetTunnelListURL=http://cncnet.org/master-list
+CnCNetPlayerCountURL=http://api.cncnet.org/status
+CnCNetMapDBDownloadURL=http://mapdb.cncnet.org
+CnCNetMapDBUploadURL=http://mapdb.cncnet.org/upload
+DisableDiscordIntegration=False
+
+[IRCServers]
+1=irc.gamesurge.net|GameSurge|6667,6660,6666,6668,6669
\ No newline at end of file