Merge remote-tracking branch 'upstream/master' into develop

This commit is contained in:
EvilFactory
2024-12-11 10:44:53 -03:00
257 changed files with 4793 additions and 1653 deletions
@@ -519,10 +519,12 @@ namespace Barotrauma.Networking
{
string errorMsg = "Error while reading a message from server. ";
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
AppendExceptionInfo(ref errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
AppendExceptionInfo(ref errorMsg, out Entity causingEntity, e);
string targetSite = e.TargetSite?.ToString() ?? "unknown";
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + targetSite, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError(errorMsg, contentPackage: causingEntity?.ContentPackage);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", targetSite)))
{
DisplayInLoadingScreens = true
};
@@ -664,7 +666,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string errorMsg = "Error while reading an ingame update message from server.";
AppendExceptionInfo(ref errorMsg, e);
AppendExceptionInfo(ref errorMsg, out Entity causingEntity, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw;
}
@@ -988,13 +990,15 @@ namespace Barotrauma.Networking
{
if (Level.Loaded.EqualityCheckValues[stage] != levelEqualityCheckValues[stage])
{
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" +
" (client value " + stage + ": " + Level.Loaded.EqualityCheckValues[stage].ToString("X") +
", server value " + stage + ": " + levelEqualityCheckValues[stage].ToString("X") +
", level value count: " + levelEqualityCheckValues.Count +
", seed: " + Level.Loaded.Seed +
", sub: " + (Submarine.MainSub == null ? "null" : (Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")")) +
", mirrored: " + Level.Loaded.Mirrored + "). Round init status: " + roundInitStatus + "." + campaignErrorInfo;
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server, " +
$"(client value {stage}:{Level.Loaded.EqualityCheckValues[stage].ToString("X")}, " +
$"server value {stage}: {levelEqualityCheckValues[stage].ToString("X")}, " +
$"level value count: {levelEqualityCheckValues.Count}, " +
$"seed: {Level.Loaded.Seed}, " +
$"missions: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"sub: {(Submarine.MainSub == null ? "null" : (Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation))}, " +
$"mirrored: {Level.Loaded.Mirrored}). Round init status: {roundInitStatus}." +
campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
@@ -1472,6 +1476,7 @@ namespace Barotrauma.Networking
{
string levelSeed = inc.ReadString();
float levelDifficulty = inc.ReadSingle();
Identifier biomeId = inc.ReadIdentifier();
string subName = inc.ReadString();
string subHash = inc.ReadString();
string shuttleName = inc.ReadString();
@@ -1559,7 +1564,7 @@ namespace Barotrauma.Networking
var selectedEnemySub = hasEnemySub && GameMain.NetLobbyScreen.SelectedEnemySub is { } enemySub ? Option.Some(enemySub) : Option.None;
GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, selectedEnemySub, gameMode, missionPrefabs: selectedMissions);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, levelGenerationParams: null, forceBiome: ServerSettings.Biome);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, levelGenerationParams: null, forceBiome: biomeId);
}
else
{
@@ -2077,16 +2082,15 @@ namespace Barotrauma.Networking
UInt16 updateID = inc.ReadUInt16();
UInt16 settingsLen = inc.ReadUInt16();
byte[] settingsData = inc.ReadBytes(settingsLen);
bool isInitialUpdate = inc.ReadBoolean();
DebugConsole.Log($"Received {(isInitialUpdate ? "initial" : string.Empty)} lobby update ID: {updateID}, last ID: {GameMain.NetLobbyScreen.LastUpdateID}.");
if (isInitialUpdate)
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("Received initial lobby update, ID: " + updateID + ", last ID: " + GameMain.NetLobbyScreen.LastUpdateID, Color.Gray);
}
{
ReadInitialUpdate(inc);
initialUpdateReceived = true;
}
@@ -2365,7 +2369,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
throw new Exception(
$"Exception thrown while reading segment {segment.Identifier} at position {segment.Pointer}." +
$"Exception thrown while reading a message of the type \"{segment.Identifier}\" at position {segment.Pointer}." +
(prevSegments.Any() ? $" Previous segments: {string.Join(", ", prevSegments)}" : ""),
ex);
});
@@ -3767,16 +3771,24 @@ namespace Barotrauma.Networking
eventErrorWritten = true;
}
private static void AppendExceptionInfo(ref string errorMsg, Exception e)
private static void AppendExceptionInfo(ref string errorMsg, out Entity causingEntity, Exception e)
{
if (!errorMsg.EndsWith("\n")) { errorMsg += "\n"; }
Exception innerMostException = e.GetInnermost();
causingEntity = GetCausingEntity(e);
if (causingEntity != null)
{
errorMsg += "Entity: " + causingEntity + "\n";
}
errorMsg += e.Message + "\n";
var innermostException = e.GetInnermost();
if (innermostException != e)
if (innerMostException != e)
{
// If available, only append the stacktrace of the innermost exception,
// because that's the most important one to fix
errorMsg += "Inner exception: " + innermostException.Message + "\n" + innermostException.StackTrace.CleanupStackTrace();
errorMsg += "Inner exception: " + innerMostException.Message + "\n" + innerMostException.StackTrace.CleanupStackTrace();
}
else
{
@@ -3784,6 +3796,24 @@ namespace Barotrauma.Networking
}
}
/// <summary>
/// Checks if the exception or any of its inner exceptions are EntityEventExceptions, and returns the entity that caused the innermost EntityEventException.
/// </summary>
private static Entity GetCausingEntity(Exception e)
{
Entity causingEntity = null;
Exception currentException = e;
while (currentException != null)
{
if (currentException is EntityEventException entityEventException)
{
causingEntity = entityEventException.Entity;
}
currentException = currentException.InnerException;
}
return causingEntity;
}
#if DEBUG
public void ForceTimeOut()
{
@@ -154,13 +154,25 @@ namespace Barotrauma.Networking
//16 = entity ID, 8 = msg length
if (msg.BitPosition + 16 + 8 > msg.LengthBits)
{
string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
UInt16 potentialEntityId = Entity.NullEntityID;
try
{
potentialEntityId = msg.ReadUInt16();
}
catch
{
//failed to read the ID, do nothing (we would've just used it for the error message)
}
Entity targetEntity = Entity.FindEntityByID(potentialEntityId);
string errorMsg = $"Error while reading a message from the server (entity: {targetEntity?.ToString() ?? "unknown"}).";
errorMsg += $" Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
errorMsg += "\nPrevious entities:";
for (int j = tempEntityList.Count - 1; j >= 0; j--)
{
errorMsg += "\n" + (tempEntityList[j] == null ? "NULL" : tempEntityList[j].ToString());
}
DebugConsole.ThrowError(errorMsg);
DebugConsole.ThrowError(errorMsg, contentPackage: targetEntity?.ContentPackage);
return false;
}
@@ -172,7 +184,7 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
Microsoft.Xna.Framework.Color.Orange);
Color.Orange);
}
tempEntityList.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
@@ -187,7 +199,7 @@ namespace Barotrauma.Networking
//skip the event if we've already received it or if the entity isn't found
if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
{
if (thisEventID != (UInt16) (lastReceivedID + 1))
if (thisEventID != (UInt16)(lastReceivedID + 1))
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
@@ -195,7 +207,7 @@ namespace Barotrauma.Networking
"Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")",
NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1))
? GUIStyle.Red
: Microsoft.Xna.Framework.Color.Yellow);
: Color.Yellow);
}
}
else if (entity == null)
@@ -215,12 +227,18 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")",
Microsoft.Xna.Framework.Color.Green);
Color.Green);
}
lastReceivedID++;
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
try
{
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
}
catch (Exception exception)
{
throw new EntityEventException("Failed to read event." , entity as Entity, exception);
}
if (msg.BitPosition != msgPosition + msgLength * 8)
{
var prevEntity = tempEntityList.Count >= 2 ? tempEntityList[tempEntityList.Count - 2] : null;
@@ -231,7 +249,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
throw new EntityEventException(errorMsg, entity as Entity);
}
}
}
@@ -74,7 +74,16 @@ sealed class SteamConnectSocket : P2PSocket
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
var connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
ConnectionManager connectionManager;
try
{
connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay<ConnectionManager>(endpoint.SteamId.Value);
}
catch (ArgumentException e)
{
DebugConsole.ThrowError("Failed to connect via SteamP2P. Are you logged in to Steam, is the same Steam account already connected to the server?", e);
return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket));
}
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
@@ -202,6 +202,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Capture device has been disconnected. You can select another available device in the settings.");
Disconnected = true;
TryRefreshDevice();
break;
}
}
@@ -401,5 +402,65 @@ namespace Barotrauma.Networking
captureThread = null;
if (captureDevice != IntPtr.Zero) { Alc.CaptureCloseDevice(captureDevice); }
}
public static void TryRefreshDevice()
{
DebugConsole.NewMessage("Refreshing audio capture device");
List<string> deviceList = Alc.GetStringList(IntPtr.Zero, Alc.CaptureDeviceSpecifier).ToList();
int alcError = Alc.GetError(IntPtr.Zero);
if (alcError != Alc.NoError)
{
DebugConsole.ThrowError("Failed to list available audio input devices: " + alcError.ToString());
return;
}
if (deviceList.Any())
{
string device;
if (deviceList.Find(n => n.Equals(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, StringComparison.OrdinalIgnoreCase))
is string availablePreviousDevice)
{
DebugConsole.NewMessage($" Previous device choice available: {availablePreviousDevice}");
device = availablePreviousDevice;
}
else
{
device = Alc.GetString(IntPtr.Zero, Alc.CaptureDefaultDeviceSpecifier);
DebugConsole.NewMessage($" Reverting to default device: {device}");
}
if (string.IsNullOrEmpty(device))
{
device = deviceList[0];
DebugConsole.NewMessage($" No default device found, resorting to first available device: {device}");
}
// Save the new device choice and generate a new voice capture instance with it
var currentConfig = GameSettings.CurrentConfig;
currentConfig.Audio.VoiceCaptureDevice = device;
GameSettings.SetCurrentConfig(currentConfig);
if (Instance is VoipCapture currentCaptureInstance)
{
currentCaptureInstance.Dispose();
}
Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice);
}
// Didn't end up with any capture device, so let's disable voice capture for now
if (Instance == null)
{
DebugConsole.NewMessage($" No devices found, disabling");
var currentConfig = GameSettings.CurrentConfig;
currentConfig.Audio.VoiceSetting = VoiceMode.Disabled;
GameSettings.SetCurrentConfig(currentConfig);
}
if (GUI.SettingsMenuOpen)
{
SettingsMenu.Instance?.CreateAudioAndVCTab(true);
}
}
}
}