Skip to content
Open
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
92 changes: 92 additions & 0 deletions AquaMai.Mods/GameSystem/MoreContentEncoding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.IO;
using System.IO.Compression;
using Net;
using AquaMai.Config.Attributes;
using HarmonyLib;

namespace AquaMai.Mods.GameSystem;

[ConfigSection(
name: "更多Content-Encoding格式",
en: """
Enables support for decompressing data encoded with formats other than deflate, such as gzip.
Useful when the server uses CDNs such as Cloudflare that do not support deflate.
""",
zh: """
开启后游戏将支持解压非deflate格式的数据包,比如Gzip;
用于服务器使用Cloudflare等不支持deflate格式的CDN;
""",
defaultOn: false)]

public class MoreContentEncoding
{
private const int BufferSize = 1024;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: BufferSize 固定为 1024,但读取目标 _buffer 是游戏内部字段,其长度未知。若 _buffer.Length < 1024CopyTo 中的 Read(buffer, 0, 1024) 会抛 ArgumentOutOfRangeException。建议按 _buffer.Length 读取(如 Math.Min(BufferSize, buffer.Length)),或直接使用 Math.Min 保证不超过缓冲区长度。

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AquaMai.Mods/GameSystem/MoreContentEncoding.cs, line 23:

<comment>`BufferSize` 固定为 1024,但读取目标 `_buffer` 是游戏内部字段,其长度未知。若 `_buffer.Length < 1024`,`CopyTo` 中的 `Read(buffer, 0, 1024)` 会抛 `ArgumentOutOfRangeException`。建议按 `_buffer.Length` 读取(如 `Math.Min(BufferSize, buffer.Length)`),或直接使用 `Math.Min` 保证不超过缓冲区长度。</comment>

<file context>
@@ -0,0 +1,92 @@
+
+public class MoreContentEncoding
+{
+    private const int BufferSize = 1024;  
+  
+    [HarmonyPrefix]  
</file context>


[HarmonyPrefix]
[HarmonyPatch(typeof(NetHttpClient), "Decompress")]
public static bool PreDecompress(NetHttpClient __instance)
{
var traverse = Traverse.Create(__instance);
var temporaryStream = traverse.Field<MemoryStream>("_temporaryStream").Value;
var memoryStream = traverse.Field<MemoryStream>("_memoryStream").Value;
var buffer = traverse.Field<byte[]>("_buffer").Value;
Comment on lines +23 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): CopyTo 使用固定的 BufferSize 而不是实际的缓冲区长度,如果底层缓冲区大小发生变化,就可能产生不匹配。

CopyTo 接受一个 buffer 参数,但在读取时总是使用 BufferSize 常量。如果 NetHttpClient 中的 _buffer 不是恰好 1024 字节,或者将来发生变化,就可能导致过度读取和运行时错误。考虑改为使用 buffer.Length,或者移除缓冲区参数并明确强制使用固定大小缓冲区的约定。

Original comment in English

issue (bug_risk): CopyTo uses a fixed BufferSize instead of the actual buffer length, which can cause mismatches if the underlying buffer size changes.

CopyTo takes a buffer argument but always uses the BufferSize constant when reading. If _buffer in NetHttpClient is not exactly 1024 bytes or changes in the future, this can lead to over-reads and runtime errors. Consider using buffer.Length instead, or remove the buffer parameter and enforce a fixed-size buffer contract explicitly.


memoryStream.SetLength(0L);
if (temporaryStream.Length == 0L)
{
return false;
}

var raw = temporaryStream.ToArray();

// - 0x1F 0x8B -> gzip
// - 0x78 ?? + valid zlib -> zlib (the stock format: zlib header + raw deflate + adler32)
// - otherwise -> treat as plaintext
if (raw.Length >= 2 && raw[0] == 0x1F && raw[1] == 0x8B)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: gzip 解压分支没有 try/catch,而 zlib 分支有。由于该 prefix 完全替换了 Decompress,损坏/被截断的 gzip 或碰巧以 0x1F 0x8B 开头的明文都会让 GZipStream 抛出未捕获的 InvalidDataException,传播出 prefix 后可能中断网络包处理。为 gzip 分支加上与 TryInflateZlib 一致的 try/catch,失败时回退到明文写入。

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AquaMai.Mods/GameSystem/MoreContentEncoding.cs, line 45:

<comment>gzip 解压分支没有 try/catch,而 zlib 分支有。由于该 prefix 完全替换了 `Decompress`,损坏/被截断的 gzip 或碰巧以 0x1F 0x8B 开头的明文都会让 `GZipStream` 抛出未捕获的 `InvalidDataException`,传播出 prefix 后可能中断网络包处理。为 gzip 分支加上与 `TryInflateZlib` 一致的 try/catch,失败时回退到明文写入。</comment>

<file context>
@@ -0,0 +1,92 @@
+        //  - 0x1F 0x8B            -> gzip
+        //  - 0x78 ?? + valid zlib -> zlib (the stock format: zlib header + raw deflate + adler32)
+        //  - otherwise            -> treat as plaintext
+        if (raw.Length >= 2 && raw[0] == 0x1F && raw[1] == 0x8B)  
+        {  
+            using var gz = new GZipStream(new MemoryStream(raw, writable: false), CompressionMode.Decompress);  
</file context>

{
using var gz = new GZipStream(new MemoryStream(raw, writable: false), CompressionMode.Decompress);
CopyTo(gz, memoryStream, buffer);
}
else if (raw.Length >= 6 && raw[0] == 0x78 && TryInflateZlib(raw, memoryStream, buffer))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: 使用非默认窗口大小的合法 zlib 响应不会被解压。不要只匹配 raw[0] == 0x78,应校验完整 zlib 头部(CM、CINFO 和 FCHECK)后再调用 TryInflateZlib

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AquaMai.Mods/GameSystem/MoreContentEncoding.cs, line 50:

<comment>使用非默认窗口大小的合法 zlib 响应不会被解压。不要只匹配 `raw[0] == 0x78`,应校验完整 zlib 头部(CM、CINFO 和 FCHECK)后再调用 `TryInflateZlib`。</comment>

<file context>
@@ -0,0 +1,92 @@
+            using var gz = new GZipStream(new MemoryStream(raw, writable: false), CompressionMode.Decompress);  
+            CopyTo(gz, memoryStream, buffer);  
+        }  
+        else if (raw.Length >= 6 && raw[0] == 0x78 && TryInflateZlib(raw, memoryStream, buffer))  
+        {  
+            // zlib 成功解压(
</file context>

{
// zlib 成功解压(
}
else
{
memoryStream.Write(raw, 0, raw.Length);
}

memoryStream.Seek(0L, SeekOrigin.Begin);
temporaryStream.Seek(0L, SeekOrigin.Begin);
temporaryStream.SetLength(0L);
return false;
}

private static bool TryInflateZlib(byte[] raw, MemoryStream output, byte[] buffer)
{
var startLength = output.Length;
try
{
// 跳过 2 字节的 zlib 头部,忽略末尾的 4 字节 Adler32 校验和。
using var input = new MemoryStream(raw, 2, raw.Length - 6, writable: false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: 该分支会接受 Adler32 校验失败的 zlib 响应,可能把损坏数据当成正常网络数据。解压后校验 zlib 尾部的 Adler32,校验失败时应返回失败并清理输出。

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AquaMai.Mods/GameSystem/MoreContentEncoding.cs, line 71:

<comment>该分支会接受 `Adler32` 校验失败的 zlib 响应,可能把损坏数据当成正常网络数据。解压后校验 zlib 尾部的 `Adler32`,校验失败时应返回失败并清理输出。</comment>

<file context>
@@ -0,0 +1,92 @@
+        try  
+        {  
+            // 跳过 2 字节的 zlib 头部,忽略末尾的 4 字节 Adler32 校验和。
+            using var input = new MemoryStream(raw, 2, raw.Length - 6, writable: false);  
+            using var deflate = new DeflateStream(input, CompressionMode.Decompress);  
+            CopyTo(deflate, output, buffer);  
</file context>

using var deflate = new DeflateStream(input, CompressionMode.Decompress);
CopyTo(deflate, output, buffer);
return true;
}
catch
{
output.SetLength(startLength);
return false;
}
}

private static void CopyTo(Stream from, Stream to, byte[] buffer)
{
while (true)
{
var count = from.Read(buffer, 0, BufferSize);
if (count <= 0) break;
to.Write(buffer, 0, count);
}
}
}
1 change: 1 addition & 0 deletions AquaMai/configSort.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
- Utils.ShowErrorLog
- GameSettings.ForceAsServer
- GameSystem.RemoveEncryption
- GameSystem.MoreContentEncoding

社区功能:
- GameSystem.VolumeSync
Expand Down