Skip to content

Commit

Permalink
DeflateMiddleware: Add deflate compression to game responses (#487)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvyden authored May 13, 2024
2 parents ac5e529 + f367be8 commit f51821a
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 10 deletions.
4 changes: 2 additions & 2 deletions Refresh.Common/Refresh.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bunkum" Version="4.5.2" />
<PackageReference Include="Bunkum.Protocols.Http" Version="4.5.2" />
<PackageReference Include="Bunkum" Version="4.5.3" />
<PackageReference Include="Bunkum.Protocols.Http" Version="4.5.3" />
</ItemGroup>

</Project>
7 changes: 6 additions & 1 deletion Refresh.GameServer/Configuration/GameServerConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Refresh.GameServer.Configuration;
[SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
public class GameServerConfig : Config
{
public override int CurrentConfigVersion => 13;
public override int CurrentConfigVersion => 14;
public override int Version { get; set; } = 0;

protected override void Migrate(int oldVer, dynamic oldConfig) {}
Expand All @@ -27,6 +27,11 @@ protected override void Migrate(int oldVer, dynamic oldConfig) {}
public bool MaintenanceMode { get; set; } = false;
public bool RequireGameLoginToRegister { get; set; } = false;
public bool TrackRequestStatistics { get; set; } = false;
/// <summary>
/// Whether to use deflate compression for responses.
/// If this is disabled, large enough responses will cause LBP to overflow its read buffer and eventually corrupt its own memory to the point of crashing.
/// </summary>
public bool UseDeflateCompression { get; set; } = true;
public string WebExternalUrl { get; set; } = "https://refresh.example.com";
/// <summary>
/// The base URL that LBP3 uses to grab config files like `network_settings.nws`.
Expand Down
61 changes: 61 additions & 0 deletions Refresh.GameServer/Middlewares/DeflateMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Bunkum.Core.Database;
using Bunkum.Core.Endpoints.Middlewares;
using Bunkum.Listener.Request;
using Org.BouncyCastle.Utilities.Zlib;
using Refresh.GameServer.Configuration;
using Refresh.GameServer.Endpoints;

namespace Refresh.GameServer.Middlewares;

/// <summary>
/// Adds deflate encoding to LBP game server responses if they reach a certain size.
/// The game will eventually corrupt its own memory if we do not do this,
/// since non-deflate requests only get a very small request buffer, and if you send too big of a request,
/// it overruns that buffer and starts corrupting random heap memory.
/// Requests encoded with deflate get a much larger read buffer, so they avoid this problem until much larger buffer sizes.
/// </summary>
public class DeflateMiddleware(GameServerConfig config) : IMiddleware
{
/// <summary>
/// After this many bytes, start deflating request bodies
/// </summary>
private const int DeflateCutoff = 1024;

public void HandleRequest(ListenerContext context, Lazy<IDatabaseContext> database, Action next)
{
next();

if (!config.UseDeflateCompression)
return;

// If this isn't a game request or it's a resource request, don't deflate, since resource requests will be corrupted if deflated
if (!context.Uri.AbsolutePath.StartsWith(GameEndpointAttribute.BaseRoute) || context.Uri.AbsolutePath.StartsWith($"{GameEndpointAttribute.BaseRoute}r/"))
return;

string? encodings = context.RequestHeaders.Get("Accept-Encoding");
// If the accepted encodings aren't specified, or they don't contain deflate, or we don't need to use deflate on the data, do nothing.
if (encodings == null || !encodings.Contains("deflate") || context.ResponseStream.Length <= DeflateCutoff)
return;

// Update the headers marking that we are sending encoded data
context.ResponseHeaders["X-Original-Content-Length"] = context.ResponseStream.Length.ToString();
context.ResponseHeaders["Vary"] = "Accept-Encoding";
context.ResponseHeaders["Content-Encoding"] = "deflate";

// Create a copy of our uncompressed data
byte[] uncompressed = context.ResponseStream.ToArray();

// Reset the response stream position and length to 0, so we can start writing to it again
context.ResponseStream.Position = 0;
context.ResponseStream.SetLength(0);

// Compress our source data into the response stream
using ZOutputStreamLeaveOpen zlibStream = new(context.ResponseStream, 6);
zlibStream.Write(uncompressed);
zlibStream.Finish();
zlibStream.Close();

// Seek back to the start
context.ResponseStream.Position = 0;
}
}
2 changes: 1 addition & 1 deletion Refresh.GameServer/Middlewares/DigestMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private void SetDigestResponse(ListenerContext context)

public void HandleRequest(ListenerContext context, Lazy<IDatabaseContext> database, Action next)
{
//If this isnt an LBP endpoint, dont do digest
//If this isn't an LBP endpoint, dont do digest
if (!context.Uri.AbsolutePath.StartsWith(GameEndpointAttribute.BaseRoute))
{
next();
Expand Down
12 changes: 6 additions & 6 deletions Refresh.GameServer/Refresh.GameServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@
</ItemGroup>

<ItemGroup Condition="'$(Configuration)'!='DebugLocalBunkum'">
<PackageReference Include="Bunkum" Version="4.5.2" />
<PackageReference Include="Bunkum.RealmDatabase" Version="4.5.2" />
<PackageReference Include="Bunkum.AutoDiscover" Version="4.5.2" />
<PackageReference Include="Bunkum.HealthChecks" Version="4.5.2" />
<PackageReference Include="Bunkum.HealthChecks.RealmDatabase" Version="4.5.2" />
<PackageReference Include="Bunkum.Protocols.Http" Version="4.5.2" />
<PackageReference Include="Bunkum" Version="4.5.3" />
<PackageReference Include="Bunkum.RealmDatabase" Version="4.5.3" />
<PackageReference Include="Bunkum.AutoDiscover" Version="4.5.3" />
<PackageReference Include="Bunkum.HealthChecks" Version="4.5.3" />
<PackageReference Include="Bunkum.HealthChecks.RealmDatabase" Version="4.5.3" />
<PackageReference Include="Bunkum.Protocols.Http" Version="4.5.3" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions Refresh.GameServer/RefreshGameServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ protected override void SetupMiddlewares()
{
this.Server.AddMiddleware<LegacyAdapterMiddleware>();
this.Server.AddMiddleware<WebsiteMiddleware>();
this.Server.AddMiddleware(new DeflateMiddleware(this._config!));
this.Server.AddMiddleware<DigestMiddleware>();
this.Server.AddMiddleware<CrossOriginMiddleware>();
this.Server.AddMiddleware<PspVersionMiddleware>();
Expand Down

0 comments on commit f51821a

Please sign in to comment.