Release 1.10.7.2 - Autumn Update 2025 Hotfix 4

This commit is contained in:
Regalis11
2025-10-22 14:54:03 +03:00
parent 37ffc94551
commit 7e25111487
17 changed files with 315 additions and 83 deletions

View File

@@ -3815,7 +3815,10 @@ namespace Barotrauma.Networking
//let's adhere to those
foreach (Submarine sub in Submarine.Loaded.Take(5))
{
string subNameTruncated = sub.Info.Name.Length > 16 ? sub.Info.Name.Substring(0, 16) : sub.Info.Name;
string subNameTruncated =
sub.Info.Name.Length > MaxSubNameLengthInErrorMessages ?
sub.Info.Name.Substring(0, MaxSubNameLengthInErrorMessages) :
sub.Info.Name;
outMsg.WriteString(subNameTruncated);
}
break;

View File

@@ -26,11 +26,72 @@ namespace Barotrauma.Networking
public Option<DataSource> MetadataSource = Option.None;
// Cached normalized versions of server strings for efficient homoglyph comparison
private string? cachedNormalizedName;
private string? cachedNormalizedMessage;
private string? cachedNormalizedGameMode;
private string serverName = "";
private string serverMessage = "";
private Identifier gameMode = Identifier.Empty;
[Serialize("", IsPropertySaveable.Yes)]
public string ServerName { get; set; } = "";
public string ServerName
{
get { return serverName; }
set
{
serverName = value;
cachedNormalizedName = null; // Invalidate cache
}
}
[Serialize("", IsPropertySaveable.Yes)]
public string ServerMessage { get; set; } = "";
public string ServerMessage
{
get { return serverMessage; }
set
{
serverMessage = value;
cachedNormalizedMessage = null; // Invalidate cache
}
}
public string NormalizedServerName
{
get
{
if (cachedNormalizedName == null)
{
cachedNormalizedName = Homoglyphs.Normalize(ServerName);
}
return cachedNormalizedName;
}
}
public string NormalizedServerMessage
{
get
{
if (cachedNormalizedMessage == null)
{
cachedNormalizedMessage = Homoglyphs.Normalize(ServerMessage);
}
return cachedNormalizedMessage;
}
}
public string NormalizedGameMode
{
get
{
if (cachedNormalizedGameMode == null)
{
cachedNormalizedGameMode = Homoglyphs.Normalize(GameMode.Value);
}
return cachedNormalizedGameMode;
}
}
public int PlayerCount { get; set; }
@@ -43,8 +104,16 @@ namespace Barotrauma.Networking
public bool HasPassword { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier GameMode { get; set; }
public Identifier GameMode
{
get { return gameMode; }
set
{
gameMode = value;
cachedNormalizedGameMode = null; // Invalidate cache
}
}
[Serialize(SelectionMode.Manual, IsPropertySaveable.Yes)]
public SelectionMode ModeSelectionMode { get; set; }
@@ -541,14 +610,27 @@ namespace Barotrauma.Networking
return Array.Empty<ServerListContentPackageInfo>();
}
return contentPackageNames
.Zip(contentPackageHashes, (name, hash) => (name, hash))
.Zip(contentPackageIds, (t1, id) =>
new ServerListContentPackageInfo(
t1.name,
t1.hash,
ContentPackageId.Parse(id)))
.ToArray();
List<ServerListContentPackageInfo> contentPackageInfos = new List<ServerListContentPackageInfo>();
for (int i = 0; i < contentPackageNames.Count; i++)
{
string name = contentPackageNames[i];
string hash = contentPackageHashes[i];
string ugcId = contentPackageIds[i];
//according to Steam documentation, this is the maximum length of a workshop item title
//see k_cchPublishedDocumentTitleMax
if (name.Length > 128 + 1)
{
name = name.Substring(0, 128 + 1);
}
if (hash.Length > Md5Hash.MaxHashLength)
{
hash = hash.Substring(0, Md5Hash.MaxHashLength);
}
//note that we don't validate the UGC ID here, that should be handled in ContentPackageId.Parse
contentPackageInfos.Add(new ServerListContentPackageInfo(name, hash, ContentPackageId.Parse(ugcId)));
}
return contentPackageInfos.ToArray();
}
public static Option<ServerInfo> FromXElement(XElement element)

View File

@@ -27,6 +27,10 @@ namespace Barotrauma
private DateTime lastRefreshTime = DateTime.Now;
// Cache for spam-filtered servers to avoid re-checking on every filter change
private readonly HashSet<string> spamServerCache = new HashSet<string>();
private readonly Dictionary<string, string> serverInfoStringCache = new Dictionary<string, string>();
private GUIFrame menu;
private GUIListBox serverList;
@@ -1013,7 +1017,6 @@ namespace Barotrauma
return false;
}
#endif
if (SpamServerFilters.IsFiltered(serverInfo)) { return false; }
if (!string.IsNullOrEmpty(searchBox.Text) && !serverInfo.ServerName.Contains(searchBox.Text, StringComparison.OrdinalIgnoreCase)) { return false; }
@@ -1216,6 +1219,11 @@ namespace Barotrauma
PingUtils.QueryPingData();
// Clear spam server cache to allow re-checking servers (user might have changed filters)
spamServerCache.Clear();
// Also clear server info string cache when manually refreshing, just so we don't end up with broken data in any situation
serverInfoStringCache.Clear();
tabs[TabEnum.All].Clear();
serverList.ClearChildren();
serverPreview.Content.ClearChildren();
@@ -1328,26 +1336,21 @@ namespace Barotrauma
if (serverInfo.PlayerCount > serverInfo.MaxPlayers + 1) { return; }
if (serverInfo.PlayerCount < 0) { return; }
if (serverInfo.MaxPlayers <= 0) { return; }
if (!serverInfo.SelectedSub.IsNullOrEmpty())
{
if (serverInfo.SelectedSub.Length > SubmarineInfo.MaxNameLength) { return; }
}
//no way a legit server can have this many players
if (serverInfo.MaxPlayers > MaxAllowedPlayers) { return; }
int similarServerCount = 0;
string serverInfoStr = getServerInfoStr(serverInfo);
foreach (var serverElement in serverList.Content.Children)
// Check spam filter with caching to avoid re-checking on every filter change
string serverCacheKey = serverInfo.Endpoints.First().StringRepresentation;
if (spamServerCache.Contains(serverCacheKey)) { return; }
if (SpamServerFilters.IsFiltered(serverInfo))
{
if (!serverElement.Visible) { continue; }
if (serverElement.UserData is not ServerInfo otherServer || otherServer == serverInfo) { continue; }
if (ToolBox.LevenshteinDistance(serverInfoStr, getServerInfoStr(otherServer)) < serverInfoStr.Length * (1.0f - MinSimilarityPercentage))
{
similarServerCount++;
if (similarServerCount > MaxAllowedSimilarServers)
{
DebugConsole.Log($"Server {serverInfo.ServerName} seems to be almost identical to {otherServer.ServerName}. Hiding as a potential spam server.");
break;
}
}
spamServerCache.Add(serverCacheKey);
return;
}
if (similarServerCount > MaxAllowedSimilarServers) { return; }
static string getServerInfoStr(ServerInfo serverInfo)
{
@@ -1356,6 +1359,35 @@ namespace Barotrauma
return str;
}
string getCachedServerInfoStr(ServerInfo serverInfo)
{
string cacheKey = serverInfo.Endpoints.First().StringRepresentation;
if (!serverInfoStringCache.TryGetValue(cacheKey, out string cachedStr))
{
cachedStr = getServerInfoStr(serverInfo);
serverInfoStringCache[cacheKey] = cachedStr;
}
return cachedStr;
}
int similarServerCount = 0;
string serverInfoStr = getServerInfoStr(serverInfo);
foreach (var serverElement in serverList.Content.Children)
{
if (!serverElement.Visible) { continue; }
if (serverElement.UserData is not ServerInfo otherServer || otherServer == serverInfo) { continue; }
if (ToolBox.LevenshteinDistance(serverInfoStr, getCachedServerInfoStr(otherServer)) < serverInfoStr.Length * (1.0f - MinSimilarityPercentage))
{
similarServerCount++;
if (similarServerCount > MaxAllowedSimilarServers)
{
DebugConsole.Log($"Server {serverInfo.ServerName} seems to be almost identical to {otherServer.ServerName}. Hiding as a potential spam server.");
break;
}
}
}
if (similarServerCount > MaxAllowedSimilarServers) { return; }
RemoveMsgFromServerList(MsgUserData.RefreshingServerList);
RemoveMsgFromServerList(MsgUserData.NoServers);
var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) },

View File

@@ -338,10 +338,7 @@ namespace Barotrauma
private GUIImage previewImage;
private GUILayoutGroup previewImageButtonHolder;
private const int submarineNameLimit = 30;
private GUITextBlock submarineNameCharacterCount;
private const int submarineDescriptionLimit = 500;
private GUITextBlock submarineDescriptionCharacterCount;
private Mode mode;
@@ -2304,20 +2301,19 @@ namespace Barotrauma
};
nameBox.OnTextChanged += (textBox, text) =>
{
if (text.Length > submarineNameLimit)
if (text.Length > SubmarineInfo.MaxNameLength)
{
nameBox.Text = text.Substring(0, submarineNameLimit);
nameBox.Text = text.Substring(0, SubmarineInfo.MaxNameLength);
nameBox.Flash(GUIStyle.Red);
return true;
}
submarineNameCharacterCount.Text = text.Length + " / " + submarineNameLimit;
submarineNameCharacterCount.Text = text.Length + " / " + SubmarineInfo.MaxNameLength;
return true;
};
nameBox.Text = MainSub?.Info.Name ?? "";
submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + submarineNameLimit;
submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + SubmarineInfo.MaxNameLength;
var descriptionHeaderGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.03f), leftColumn.RectTransform), isHorizontal: true);
@@ -2333,9 +2329,9 @@ namespace Barotrauma
descriptionBox.OnTextChanged += (textBox, text) =>
{
if (text.Length > submarineDescriptionLimit)
if (text.Length > SubmarineInfo.MaxDescriptionLength)
{
descriptionBox.Text = text.Substring(0, submarineDescriptionLimit);
descriptionBox.Text = text.Substring(0, SubmarineInfo.MaxDescriptionLength);
descriptionBox.Flash(GUIStyle.Red);
return true;
}
@@ -3439,7 +3435,7 @@ namespace Barotrauma
enemySubmarineSettingsContainer.Recalculate();
descriptionBox.Text = MainSub == null ? "" : MainSub.Info.Description.Value;
submarineDescriptionCharacterCount.Text = descriptionBox.Text.Length + " / " + submarineDescriptionLimit;
submarineDescriptionCharacterCount.Text = descriptionBox.Text.Length + " / " + SubmarineInfo.MaxDescriptionLength;
subTypeDropdown.SelectItem(MainSub.Info.Type);
@@ -5045,7 +5041,7 @@ namespace Barotrauma
textBox.UserData = text;
}
submarineDescriptionCharacterCount.Text = text.Length + " / " + submarineDescriptionLimit;
submarineDescriptionCharacterCount.Text = text.Length + " / " + SubmarineInfo.MaxDescriptionLength;
}
private bool SelectPrefab(GUIComponent component, object obj)

View File

@@ -1,10 +1,12 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
@@ -20,8 +22,10 @@ namespace Barotrauma
Invalid,
NameEquals,
NameContains,
NameMatchesRegex,
MessageEquals,
MessageContains,
MessageMatchesRegex,
PlayerCountLarger,
PlayerCountExact,
MaxPlayersLarger,
@@ -29,20 +33,21 @@ namespace Barotrauma
GameModeEquals,
PlayStyleEquals,
Endpoint,
LanguageEquals
LanguageEquals,
LobbyId
}
internal readonly record struct SpamFilter(ImmutableHashSet<(SpamServerFilterType Type, string Value)> Filters)
internal readonly record struct SpamFilter(ImmutableHashSet<(SpamServerFilterType Type, string Value, string NormalizedValue)> Filters)
{
public bool IsFiltered(ServerInfo info)
{
if (Filters.IsEmpty) { return false; }
foreach (var (type, value) in Filters)
foreach (var (type, value, normalizedValue) in Filters)
{
try
{
if (!IsFiltered(info, type, value)) { return false; }
if (!IsFiltered(info, type, value, normalizedValue)) { return false; }
}
catch (Exception e)
{
@@ -53,57 +58,83 @@ namespace Barotrauma
return true;
}
private static bool IsFiltered(ServerInfo info, SpamServerFilterType type, string value)
private static bool IsFiltered(ServerInfo info, SpamServerFilterType type, string value, string normalizedValue)
{
if (info == null) { return true; }
string desc = info.ServerMessage,
name = info.ServerName;
int.TryParse(value, out int parsedInt);
return type switch
{
SpamServerFilterType.NameEquals => CompareEquals(name, value),
SpamServerFilterType.NameContains => CompareContains(name, value),
SpamServerFilterType.NameEquals => CompareEquals(info.NormalizedServerName, normalizedValue),
SpamServerFilterType.NameContains => CompareContains(info.NormalizedServerName, normalizedValue),
SpamServerFilterType.NameMatchesRegex => CompareRegex(info.NormalizedServerName, value),
SpamServerFilterType.MessageEquals => CompareEquals(desc, value),
SpamServerFilterType.MessageContains => CompareContains(desc, value),
SpamServerFilterType.MessageEquals => CompareEquals(info.NormalizedServerMessage, normalizedValue),
SpamServerFilterType.MessageContains => CompareContains(info.NormalizedServerMessage, normalizedValue),
SpamServerFilterType.MessageMatchesRegex => CompareRegex(info.NormalizedServerMessage, value),
SpamServerFilterType.Endpoint =>
info.Endpoints != null &&
info.Endpoints.First().StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase),
SpamServerFilterType.LobbyId =>
info.MetadataSource.TryUnwrap(out var dataSource) &&
dataSource is SteamP2PServerProvider.DataSource steamDataSource &&
ulong.TryParse(value, out ulong lobbyIdToFilter) &&
steamDataSource.Lobby.Id == lobbyIdToFilter,
SpamServerFilterType.PlayerCountLarger => info.PlayerCount > parsedInt,
SpamServerFilterType.PlayerCountExact => info.PlayerCount == parsedInt,
SpamServerFilterType.MaxPlayersLarger => info.MaxPlayers > parsedInt,
SpamServerFilterType.MaxPlayersExact => info.MaxPlayers == parsedInt,
SpamServerFilterType.GameModeEquals => info.GameMode == value,
SpamServerFilterType.GameModeEquals => CompareEquals(info.NormalizedGameMode, normalizedValue),
SpamServerFilterType.PlayStyleEquals => info.PlayStyle.ToIdentifier() == value,
SpamServerFilterType.LanguageEquals => info.Language.Value == value,
_ => false
};
static bool CompareEquals(string a, string b)
static bool CompareEquals(string? normalizedA, string? normalizedB)
{
if (a == null || b == null)
if (normalizedA == null || normalizedB == null)
{
return a == b;
return normalizedA == normalizedB;
}
return a.Equals(b, StringComparison.OrdinalIgnoreCase) || Homoglyphs.Compare(a, b);
// Both strings are already normalized, just do case-insensitive comparison
return normalizedA.Equals(normalizedB, StringComparison.OrdinalIgnoreCase);
}
static bool CompareContains(string a, string b)
static bool CompareContains(string? normalizedA, string? normalizedB)
{
if (a == null || b == null)
if (normalizedA == null || normalizedB == null)
{
return a == b;
return normalizedA == normalizedB;
}
return a.Contains(b, StringComparison.OrdinalIgnoreCase);
// Both strings are already normalized, just do case-insensitive contains
return normalizedA.Contains(normalizedB, StringComparison.OrdinalIgnoreCase);
}
static bool CompareRegex(string? a, string? pattern)
{
if (a == null || pattern == null)
{
return a == pattern;
}
// Use cached compiled regex for performance
if (SpamServerFilters.GetCachedRegex(pattern) is Regex regex)
{
return regex.IsMatch(a);
}
else
{
DebugConsole.ThrowError($"Regex pattern somehow not found in cache: \"{pattern}\"");
}
return false;
}
}
@@ -111,7 +142,7 @@ namespace Barotrauma
{
var element = new XElement("Filter");
foreach (var (type, value) in Filters)
foreach (var (type, value, _) in Filters)
{
element.Add(new XAttribute(type.ToString().ToLowerInvariant(), value));
}
@@ -121,7 +152,7 @@ namespace Barotrauma
public static bool TryParse(XElement element, out SpamFilter filter)
{
var builder = ImmutableHashSet.CreateBuilder<(SpamServerFilterType Type, string Value)>();
var builder = ImmutableHashSet.CreateBuilder<(SpamServerFilterType Type, string Value, string NormalizedValue)>();
foreach (var attribute in element.Attributes())
{
if (!Enum.TryParse(attribute.Name.ToString(), ignoreCase: true, out SpamServerFilterType e))
@@ -130,7 +161,25 @@ namespace Barotrauma
continue;
}
if (e is SpamServerFilterType.Invalid) { continue; }
builder.Add((e, attribute.Value));
string value = attribute.Value;
// Compile regex patterns during loading (for validation and performance)
if (e is SpamServerFilterType.NameMatchesRegex or SpamServerFilterType.MessageMatchesRegex)
{
// Skip invalid regex filters (will throw error to the log though)
if (!SpamServerFilters.TryCompileAndCacheRegex(value)) { continue; }
}
// Only normalize values for filter types that actually use homoglyph comparison
string normalizedValue = e is SpamServerFilterType.NameEquals
or SpamServerFilterType.NameContains
or SpamServerFilterType.MessageEquals
or SpamServerFilterType.MessageContains
or SpamServerFilterType.GameModeEquals
? Homoglyphs.Normalize(value)
: value;
builder.Add((e, value, normalizedValue));
}
if (builder.Any())
@@ -207,6 +256,37 @@ namespace Barotrauma
public static Option<SpamServerFilter> LocalSpamFilter;
public static Option<SpamServerFilter> GlobalSpamFilter;
private static readonly Dictionary<string, Regex> CompiledRegexCache = new Dictionary<string, Regex>();
/// <summary>
/// Attempts to compile a regex pattern and cache it. Returns false if the pattern is invalid.
/// Compilation validates the regex is correct, avoiding crashes at runtime, and subsequent use will be more performant.
/// </summary>
internal static bool TryCompileAndCacheRegex(string pattern)
{
if (CompiledRegexCache.ContainsKey(pattern)) { return true; }
try
{
var regex = new Regex(pattern, RegexOptions.Compiled);
CompiledRegexCache[pattern] = regex;
return true;
}
catch (Exception e)
{
DebugConsole.ThrowError($"Invalid regex pattern in spam filter: \"{pattern}\"", e);
return false;
}
}
/// <summary>
/// Attempts to get a cached compiled regex, returns null if not found.
/// </summary>
internal static Regex? GetCachedRegex(string pattern)
{
return CompiledRegexCache.GetValueOrDefault(pattern);
}
public const string LocalFilterComment = @"
This file contains a list of filters that can be used to hide servers from the server list.
You can add filters by right-clicking a server in the server list and selecting ""Hide server"" or by reporting the server and choosing ""Report and hide server"".
@@ -214,28 +294,34 @@ The filters are saved in this file, which you can edit manually if you want to.
The available filter types are:
- NameEquals: The server name must equal the specified value. Homoglyphs are also checked.
- NameContains: The server name must contain the specified value.
- NameContains: The server name must contain the specified value. Homoglyphs are also checked.
- NameMatchesRegex: The server name must match the specified regular expression pattern. Use inline options like (?i) for case-insensitive matching.
- MessageEquals: The server description must equal the specified value. Homoglyphs are also checked.
- MessageContains: The server description must contain the specified value.
- MessageContains: The server description must contain the specified value. Homoglyphs are also checked.
- MessageMatchesRegex: The server description must match the specified regular expression pattern. Use inline options like (?i) for case-insensitive matching.
- PlayerCountLarger: The player count must be larger than the specified value.
- PlayerCountExact: The player count must match the specified value exactly.
- MaxPlayersLarger: The max player count must be larger than the specified value.
- MaxPlayersExact: The max player count must match the specified value exactly.
- GameModeEquals: The game mode identifier must match the specified value exactly.
- GameModeEquals: The game mode identifier must match the specified value exactly. Homoglyphs are also checked.
- PlayStyleEquals: The play style must match the specified value exactly.
- Endpoint: The server endpoint, which is a Steam ID or an IP address, must match the specified value exactly. Steam ID is in the format of STEAM_X:Y:Z.
- LanguageEquals: The server language must match the specified value exactly.
- LobbyId: The Steam lobby ID must match the specified value exactly. This is the most efficient way to filter Steam P2P lobbies, when we have already identified harmful ones.
The filter values are case-insensitive and adding multiple conditions on one filter will require all of them to be met.
Homoglyph comparison is used for NameEquals and MessageEquals filters, which means that it checks whether the words look the same, meaning you can't abuse identical-looking but different symbols to work around the filter. For example ""lmaobox"" and ""lmаobox"" (with a cyrillic a) are considered equal.
Homoglyph comparison is used for name, message, and game mode filters, which means that it checks whether the words look the same, meaning you can't abuse identical-looking but different symbols to work around the filter. For example ""lmaobox"" and ""lmаobox"" (with a cyrillic a) are considered equal, and ""dіscord.gg"" (with a cyrillic i) will be caught by a ""discord.gg"" contains filter.
Examples:
<Filters>
<Filter namecontains=""discord.gg"" />
<Filter messagecontains=""discord.gg"" />
<Filter nameequals=""get good get lmaobox"" maxplayersexact=""999"" />
<Filter lobbyid=""109775241070418378"" />
<Filter namematchesregex=""(?i)(buy|sell|trade).*cheap"" />
<Filter messagematchesregex=""(?i)join.*discord\.(gg|com)"" />
</Filters>
These will hide all servers that have a discord.gg link in their name or description and servers with the name ""get good get lmaobox"" that have 999 max players.
These will hide all servers that have a discord.gg link in their name or description, servers with the name ""get good get lmaobox"" that have 999 max players, the specific lobby with ID 109775241070418378, servers with names matching the pattern for buying/selling/trading (case-insensitive), and servers with messages containing discord links (case-insensitive)..
";
static SpamServerFilters()
{
@@ -279,7 +365,7 @@ These will hide all servers that have a discord.gg link in their name or descrip
if (!LocalSpamFilter.TryUnwrap(out var localFilter)) { return; }
if (localFilter.IsFiltered(info)) { return; }
var filters = localFilter.Filters.Add(new SpamFilter(ImmutableHashSet.Create((NameExact: SpamServerFilterType.NameEquals, info.ServerName))));
var filters = localFilter.Filters.Add(new SpamFilter(ImmutableHashSet.Create((SpamServerFilterType.NameEquals, info.ServerName, info.NormalizedServerName))));
var newFilter = new SpamServerFilter(filters);
newFilter.Save(SpamServerFilter.SavePath);
LocalSpamFilter = Option.Some(newFilter);

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.10.7.1</Version>
<Version>1.10.7.2</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.10.7.1</Version>
<Version>1.10.7.2</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.10.7.1</Version>
<Version>1.10.7.2</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.10.7.1</Version>
<Version>1.10.7.2</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.10.7.1</Version>
<Version>1.10.7.2</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -1071,7 +1071,7 @@ namespace Barotrauma.Networking
for (int i = 0; i < Math.Min(subCount, 5); i++)
{
string subName = inc.ReadString();
if (subName == null || subName.Length > 16)
if (subName == null || subName.Length > MaxSubNameLengthInErrorMessages)
{
malformedData = true;
}
@@ -1092,7 +1092,7 @@ namespace Barotrauma.Networking
}
else if (entity is Item item)
{
errorStr = errorStrNoName = $"Missing item {item.Name}, sub: {item.Submarine?.Info?.Name ?? "none"} (event id {eventID}, entity id {entityID}).";
errorStr = errorStrNoName = $"Missing item {item.Name} ({item.Prefab.Identifier}), sub: {item.Submarine?.Info?.Name ?? "none"} (event id {eventID}, entity id {entityID}).";
}
else
{
@@ -1100,7 +1100,8 @@ namespace Barotrauma.Networking
}
if (GameStarted)
{
var serverSubNames = Submarine.Loaded.Select(s => s.Info.Name);
var serverSubNames = Submarine.Loaded.Select(s =>
s.Info.Name.Length > MaxSubNameLengthInErrorMessages ? s.Info.Name.Substring(0, MaxSubNameLengthInErrorMessages) : s.Info.Name);
if (subCount != Submarine.Loaded.Count || !subNames.SequenceEqual(serverSubNames))
{
string subErrorStr = $" Loaded submarines don't match (client: {string.Join(", ", subNames)}, server: {string.Join(", ", serverSubNames)}).";

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.10.7.1</Version>
<Version>1.10.7.2</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -63,6 +63,9 @@ namespace Barotrauma
public HashSet<string> RequiredContentPackages = new HashSet<string>();
public const int MaxNameLength = 30;
public const int MaxDescriptionLength = 500;
public string Name
{
get;

View File

@@ -181,6 +181,8 @@ namespace Barotrauma.Networking
abstract partial class NetworkMember
{
protected const int MaxSubNameLengthInErrorMessages = 16;
public UInt16 LastClientListUpdateID
{
get;

View File

@@ -1874,5 +1874,24 @@ namespace Barotrauma
}
return true;
}
/// <summary>
/// Normalizes a string by replacing all homoglyph characters with their ASCII equivalents.
/// This allows for efficient homoglyph-aware string comparisons using standard string operations.
/// </summary>
public static string Normalize(string input)
{
if (string.IsNullOrEmpty(input)) { return input; }
var normalized = new char[input.Length];
for (int i = 0; i < input.Length; i++)
{
uint charCode = (uint)input[i];
uint[] glyphGroup = homoglyphs.Find(g => g.Contains(charCode));
// The first element in each homoglyph group is the canonical ASCII character
normalized[i] = glyphGroup != null ? (char)glyphGroup[0] : input[i];
}
return new string(normalized);
}
}
}

View File

@@ -11,7 +11,9 @@ namespace Barotrauma
{
public class Md5Hash
{
public static readonly Md5Hash Blank = new Md5Hash(new string('0', 32));
public const int MaxHashLength = 32;
public static readonly Md5Hash Blank = new Md5Hash(new string('0', MaxHashLength));
private static string RemoveWhitespace(string s)
{

View File

@@ -1,4 +1,10 @@
-------------------------------------------------------------------------------------------------------------------------------------------------
v1.10.7.2
-------------------------------------------------------------------------------------------------------------------------------------------------
- Improvements to the spam server filtering.
-------------------------------------------------------------------------------------------------------------------------------------------------
v1.10.7.1
-------------------------------------------------------------------------------------------------------------------------------------------------