Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,6 @@ src/AdminPanel/wwwroot/content/js/app.js.map
.opencode/
# Keys which are generated when the server runs locally.
data-protection-keys/

# The archive of the network observation, when the server is started from the repository.
captures/
2 changes: 2 additions & 0 deletions src/Dapr/GameServer.Host/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using MUnique.OpenMU.Dapr.Common;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameServer;
using MUnique.OpenMU.GameServer.Host;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
Expand Down Expand Up @@ -43,6 +44,7 @@
.AddPeristenceProvider()
.AddPlugInManager(plugInConfigurations)
.AddIpResolver(args)
.AddNetworkObservation()
.AddHostedService<GameServerHostedServiceWrapper>()
.PublishManageableServer<IGameServer>();

Expand Down
56 changes: 56 additions & 0 deletions src/DataModel/Configuration/SystemConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,62 @@ public partial class SystemConfiguration
ResourceType = typeof(Resources))]
public string? TimeZoneId { get; set; }

/// <summary>
/// Gets or sets the number of data packets which are kept in memory per connection which
/// is watched in the network analyzer page of the admin panel.
/// </summary>
[Display(
Order = 7,
Name = nameof(Resources.SystemConfiguration_NetworkAnalyzerLiveBufferSize_Name),
Description = nameof(Resources.SystemConfiguration_NetworkAnalyzerLiveBufferSize_Description),
GroupName = nameof(Resources.SystemConfiguration_NetworkAnalyzer_Name),
ResourceType = typeof(Resources))]
public int NetworkAnalyzerLiveBufferSize { get; set; } = 5000;

/// <summary>
/// Gets or sets the path in which the traffic of the observed accounts is archived.
/// </summary>
[Display(
Order = 8,
Name = nameof(Resources.SystemConfiguration_NetworkObservationArchivePath_Name),
Description = nameof(Resources.SystemConfiguration_NetworkObservationArchivePath_Description),
GroupName = nameof(Resources.SystemConfiguration_NetworkAnalyzer_Name),
ResourceType = typeof(Resources))]
public string? NetworkObservationArchivePath { get; set; } = "captures";

/// <summary>
/// Gets or sets the maximum size of one file of an archived session, in megabytes.
/// </summary>
[Display(
Order = 9,
Name = nameof(Resources.SystemConfiguration_NetworkObservationMaxSessionSizeMb_Name),
Description = nameof(Resources.SystemConfiguration_NetworkObservationMaxSessionSizeMb_Description),
GroupName = nameof(Resources.SystemConfiguration_NetworkAnalyzer_Name),
ResourceType = typeof(Resources))]
public int NetworkObservationMaxSessionSizeMb { get; set; } = 50;

/// <summary>
/// Gets or sets the maximum size of the whole observation archive, in megabytes.
/// </summary>
[Display(
Order = 10,
Name = nameof(Resources.SystemConfiguration_NetworkObservationMaxTotalSizeMb_Name),
Description = nameof(Resources.SystemConfiguration_NetworkObservationMaxTotalSizeMb_Description),
GroupName = nameof(Resources.SystemConfiguration_NetworkAnalyzer_Name),
ResourceType = typeof(Resources))]
public int NetworkObservationMaxTotalSizeMb { get; set; } = 1000;

/// <summary>
/// Gets or sets the number of days after which an archived session is removed.
/// </summary>
[Display(
Order = 11,
Name = nameof(Resources.SystemConfiguration_NetworkObservationRetentionDays_Name),
Description = nameof(Resources.SystemConfiguration_NetworkObservationRetentionDays_Description),
GroupName = nameof(Resources.SystemConfiguration_NetworkAnalyzer_Name),
ResourceType = typeof(Resources))]
public int NetworkObservationRetentionDays { get; set; } = 30;

/// <inheritdoc />
public override string ToString()
{
Expand Down
7 changes: 7 additions & 0 deletions src/DataModel/Entities/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ public class Account
/// </summary>
public bool IsBot { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the network traffic of this account is
/// observed. The game server then archives the traffic of each of its sessions, so that
/// it can be analyzed later - for example when the account is suspected of cheating.
/// </summary>
public bool IsNetworkObservationActive { get; set; }

/// <summary>
/// Gets or sets the characters.
/// </summary>
Expand Down
99 changes: 99 additions & 0 deletions src/DataModel/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions src/DataModel/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,37 @@ or behind a firewall) and you want to use it within your computer or private net
<data name="SocketAbbreviation" xml:space="preserve">
<value>S</value>
</data>
<data name="SystemConfiguration_NetworkAnalyzer_Name" xml:space="preserve">
<value>Network Analyzer</value>
</data>
<data name="SystemConfiguration_NetworkAnalyzerLiveBufferSize_Name" xml:space="preserve">
<value>Live buffer size</value>
</data>
<data name="SystemConfiguration_NetworkAnalyzerLiveBufferSize_Description" xml:space="preserve">
<value>The number of data packets which are kept in memory per connection which is watched in the network analyzer page. When more packets arrive, the oldest ones are dropped.</value>
</data>
<data name="SystemConfiguration_NetworkObservationArchivePath_Name" xml:space="preserve">
<value>Observation archive path</value>
</data>
<data name="SystemConfiguration_NetworkObservationArchivePath_Description" xml:space="preserve">
<value>The path in which the traffic of the observed accounts is archived. A relative path is resolved against the directory of the application. It must not point into a folder which is served by the web server - an archived session contains the login packet of the player in plain text.</value>
</data>
<data name="SystemConfiguration_NetworkObservationMaxSessionSizeMb_Name" xml:space="preserve">
<value>Maximum session size (MB)</value>
</data>
<data name="SystemConfiguration_NetworkObservationMaxSessionSizeMb_Description" xml:space="preserve">
<value>The maximum size of one file of an archived session, in megabytes. A session which grows bigger is continued in another file.</value>
</data>
<data name="SystemConfiguration_NetworkObservationMaxTotalSizeMb_Name" xml:space="preserve">
<value>Maximum archive size (MB)</value>
</data>
<data name="SystemConfiguration_NetworkObservationMaxTotalSizeMb_Description" xml:space="preserve">
<value>The maximum size of the whole observation archive, in megabytes. When it's exceeded, the oldest sessions are removed. A value of 0 means that the size is unlimited.</value>
</data>
<data name="SystemConfiguration_NetworkObservationRetentionDays_Name" xml:space="preserve">
<value>Archive retention (days)</value>
</data>
<data name="SystemConfiguration_NetworkObservationRetentionDays_Description" xml:space="preserve">
<value>The number of days after which an archived session is removed. A value of 0 means that the sessions are kept forever.</value>
</data>
</root>
17 changes: 17 additions & 0 deletions src/GameLogic/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ public Player(IGameContext gameContext)
/// </summary>
public event AsyncEventHandler<Player>? PlayerEnteredWorld;

/// <summary>
/// Occurs when the player has been logged in, so that its <see cref="Account"/> is known.
/// </summary>
public event AsyncEventHandler<Player>? PlayerLoggedIn;

/// <summary>
/// Occurs when the player left the world with his selected character.
/// </summary>
Expand Down Expand Up @@ -581,6 +586,18 @@ public IPetCommandManager? PetCommandManager
/// </summary>
protected virtual bool IsPlayerStoreOpeningAfterEnterSupported => true;

/// <summary>
/// Sets the account of the player after a successful login and notifies the subscribers
/// of <see cref="PlayerLoggedIn"/>.
/// </summary>
/// <param name="account">The account of the player.</param>
/// <returns>The async task.</returns>
public async ValueTask SetAccountAsync(Account account)
{
this.Account = account;
await this.PlayerLoggedIn.SafeInvokeAsync(this).ConfigureAwait(false);
}

/// <summary>
/// Sets the selected character.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/PlayerActions/LoginAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ private async ValueTask HandleAlreadyConnectedAsync(Player player, string userna

private async ValueTask FinishLoginAsync(Player player, string username, Account account)
{
player.Account = account;
await player.SetAccountAsync(account).ConfigureAwait(false);
player.Logger.LogDebug("Login successful, username: [{Username}].", username);

if (player.IsTemplatePlayer)
Expand Down
12 changes: 11 additions & 1 deletion src/GameServer/GameServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace MUnique.OpenMU.GameServer;
using MUnique.OpenMU.GameServer.RemoteView;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Network.Analyzer;
using MUnique.OpenMU.Network.Analyzer.Archive;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
using Nito.AsyncEx;
Expand All @@ -35,6 +36,8 @@ public sealed class GameServer : IGameServer, IDisposable, IAsyncDisposable, IGa

private readonly ICollection<IGameServerListener> _listeners = new List<IGameServerListener>();

private readonly NetworkObservationHandler? _observationHandler;

private ServerState _serverState;

/// <summary>
Expand All @@ -49,6 +52,8 @@ public sealed class GameServer : IGameServer, IDisposable, IAsyncDisposable, IGa
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="plugInManager">The plug in manager.</param>
/// <param name="changeMediator"> The change mediatior.</param>
/// <param name="packetArchive">The archive for the traffic of observed accounts. It's only
/// available when the network observation is configured.</param>
public GameServer(
GameServerDefinition gameServerDefinition,
IGuildServer guildServer,
Expand All @@ -58,12 +63,16 @@ public GameServer(
IFriendServer friendServer,
ILoggerFactory loggerFactory,
PlugInManager plugInManager,
IConfigurationChangeMediator changeMediator)
IConfigurationChangeMediator changeMediator,
IPacketArchive? packetArchive = null)
{
this.Id = gameServerDefinition.ServerID;
this.Description = gameServerDefinition.Description;
this.ConfigurationId = gameServerDefinition.GetId();
this._logger = loggerFactory.CreateLogger<GameServer>();
this._observationHandler = packetArchive is null
? null
: new NetworkObservationHandler(packetArchive, this.Id, this.Description, loggerFactory.CreateLogger<NetworkObservationHandler>());
try
{
var gameConfiguration = gameServerDefinition.GameConfiguration ?? throw Error.NotInitializedProperty(gameServerDefinition, nameof(gameServerDefinition.GameConfiguration));
Expand Down Expand Up @@ -489,6 +498,7 @@ private async ValueTask RemovePlayerFromGuildAsync(Player player, bool unregiste
private async ValueTask OnPlayerConnectedAsync(PlayerConnectedEventArgs e)
{
var player = e.ConntectedPlayer;
this._observationHandler?.Watch(player);
await this._gameContext.AddPlayerAsync(player).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IShowLoginWindowPlugIn>(p => p.ShowLoginWindowAsync()).ConfigureAwait(false);
await player.PlayerState.TryAdvanceToAsync(PlayerState.LoginScreen).ConfigureAwait(false);
Expand Down
Loading
Loading