feat:支持解压Gzip Content-Encoding格式的数据包 - #142
Conversation
Reviewer's Guide添加一个可选的模组配置,通过挂钩 NetHttpClient.Decompress 来支持 gzip 和更健壮的 zlib/deflate 处理,在无法解压时回退为纯文本。 针对 gzip/zlib/纯文本处理的 NetHttpClient.Decompress 补丁序列图sequenceDiagram
participant NetHttpClient
participant MoreContentEncoding
participant GZipStream
participant DeflateStream
NetHttpClient->>MoreContentEncoding: PreDecompress(__instance)
MoreContentEncoding->>MoreContentEncoding: Traverse.Field _temporaryStream/_memoryStream/_buffer
MoreContentEncoding->>MoreContentEncoding: memoryStream.SetLength(0)
alt [temporaryStream.Length == 0]
MoreContentEncoding-->>NetHttpClient: return false
else [raw starts with gzip header]
MoreContentEncoding->>GZipStream: new GZipStream(MemoryStream(raw), Decompress)
MoreContentEncoding->>MoreContentEncoding: CopyTo(gz, memoryStream, buffer)
else [raw starts with zlib header and TryInflateZlib succeeds]
MoreContentEncoding->>DeflateStream: new DeflateStream(MemoryStream(raw,2,...), Decompress)
MoreContentEncoding->>MoreContentEncoding: CopyTo(deflate, memoryStream, buffer)
else [no compression detected]
MoreContentEncoding->>MoreContentEncoding: memoryStream.Write(raw)
end
MoreContentEncoding->>MoreContentEncoding: memoryStream.Seek(0)
MoreContentEncoding->>MoreContentEncoding: temporaryStream.SetLength(0)
MoreContentEncoding-->>NetHttpClient: return false
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 来:
Getting HelpOriginal review guide in EnglishReviewer's GuideAdds an optional mod configuration that hooks NetHttpClient.Decompress to support gzip and robust zlib/deflate handling, falling back to plaintext when decompression is not possible. Sequence diagram for patched NetHttpClient.Decompress with gzip/zlib/plaintext handlingsequenceDiagram
participant NetHttpClient
participant MoreContentEncoding
participant GZipStream
participant DeflateStream
NetHttpClient->>MoreContentEncoding: PreDecompress(__instance)
MoreContentEncoding->>MoreContentEncoding: Traverse.Field _temporaryStream/_memoryStream/_buffer
MoreContentEncoding->>MoreContentEncoding: memoryStream.SetLength(0)
alt [temporaryStream.Length == 0]
MoreContentEncoding-->>NetHttpClient: return false
else [raw starts with gzip header]
MoreContentEncoding->>GZipStream: new GZipStream(MemoryStream(raw), Decompress)
MoreContentEncoding->>MoreContentEncoding: CopyTo(gz, memoryStream, buffer)
else [raw starts with zlib header and TryInflateZlib succeeds]
MoreContentEncoding->>DeflateStream: new DeflateStream(MemoryStream(raw,2,...), Decompress)
MoreContentEncoding->>MoreContentEncoding: CopyTo(deflate, memoryStream, buffer)
else [no compression detected]
MoreContentEncoding->>MoreContentEncoding: memoryStream.Write(raw)
end
MoreContentEncoding->>MoreContentEncoding: memoryStream.Seek(0)
MoreContentEncoding->>MoreContentEncoding: temporaryStream.SetLength(0)
MoreContentEncoding-->>NetHttpClient: return false
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Pull request overview
This PR adds a new GameSystem mod to extend the game client’s HTTP payload decompression beyond the stock zlib/deflate path, enabling compatibility with servers/CDNs that deliver gzip-encoded responses (e.g., Cloudflare setups that don’t support deflate).
Changes:
- Introduces
GameSystem.MoreContentEncoding, a Harmony prefix patch overNetHttpClient.Decompressthat detects gzip vs zlib vs plaintext. - Registers the new config section key in
configSort.yamlso it appears in the intended ordering.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| AquaMai/configSort.yaml | Adds GameSystem.MoreContentEncoding to config key ordering. |
| AquaMai.Mods/GameSystem/MoreContentEncoding.cs | New decompression patch supporting gzip + zlib + plaintext fallback. |
Suppressed comments (2)
AquaMai.Mods/GameSystem/MoreContentEncoding.cs:53
- The comment looks unfinished and contains an unmatched "(" which reads like a typo and may confuse future readers.
else if (raw.Length >= 6 && raw[0] == 0x78 && TryInflateZlib(raw, memoryStream, buffer))
{
// zlib 成功解压(
}
AquaMai.Mods/GameSystem/MoreContentEncoding.cs:89
- CopyTo reads with a hard-coded BufferSize (1024) but uses the instance-provided
buffer. If_bufferis ever smaller than 1024,Stream.Readwill throw becausecountexceedsbuffer.Length. It’s safer to cap the read size bybuffer.Length.
{
var count = from.Read(buffer, 0, BufferSize);
if (count <= 0) break;
to.Write(buffer, 0, count);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (raw.Length >= 2 && raw[0] == 0x1F && raw[1] == 0x8B) | ||
| { | ||
| using var gz = new GZipStream(new MemoryStream(raw, writable: false), CompressionMode.Decompress); | ||
| CopyTo(gz, memoryStream, buffer); | ||
| } |
There was a problem hiding this comment.
Hey - 我发现了一个问题,并给出了一些总体反馈:
- 在
CopyTo中,你在Read调用里使用了BufferSize常量,但传入的缓冲区长度可能不同;建议要么移除BufferSize并始终使用buffer.Length,要么验证buffer.Length == BufferSize,以避免越界或低效的读取。 - Harmony 的前缀假设私有字段
_temporaryStream、_memoryStream和_buffer总是非空;添加空值检查或回退逻辑会让这个补丁在面对 NetHttpClient 内部实现的未来变更时更加健壮。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- 在 `CopyTo` 中,你在 `Read` 调用里使用了 `BufferSize` 常量,但传入的缓冲区长度可能不同;建议要么移除 `BufferSize` 并始终使用 `buffer.Length`,要么验证 `buffer.Length == BufferSize`,以避免越界或低效的读取。
- Harmony 的前缀假设私有字段 `_temporaryStream`、`_memoryStream` 和 `_buffer` 总是非空;添加空值检查或回退逻辑会让这个补丁在面对 NetHttpClient 内部实现的未来变更时更加健壮。
## Individual Comments
### Comment 1
<location path="AquaMai.Mods/GameSystem/MoreContentEncoding.cs" line_range="23-32" />
<code_context>
+ private const int BufferSize = 1024;
</code_context>
<issue_to_address>
**issue (bug_risk):** CopyTo 使用固定的 BufferSize 而不是实际的缓冲区长度,如果底层缓冲区大小发生变化,就可能产生不匹配。
`CopyTo` 接受一个 `buffer` 参数,但在读取时总是使用 `BufferSize` 常量。如果 NetHttpClient 中的 `_buffer` 不是恰好 1024 字节,或者将来发生变化,就可能导致过度读取和运行时错误。考虑改为使用 `buffer.Length`,或者移除缓冲区参数并明确强制使用固定大小缓冲区的约定。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- In CopyTo you use the BufferSize constant for the Read call but pass in a buffer whose length may differ; consider either removing BufferSize and always using buffer.Length or validating that buffer.Length == BufferSize to avoid overruns or inefficient reads.
- The Harmony prefix assumes the private fields _temporaryStream, _memoryStream, and _buffer are always non-null; adding null checks or fallback behavior would make the patch more robust against future changes in NetHttpClient’s internals.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In CopyTo you use the BufferSize constant for the Read call but pass in a buffer whose length may differ; consider either removing BufferSize and always using buffer.Length or validating that buffer.Length == BufferSize to avoid overruns or inefficient reads.
- The Harmony prefix assumes the private fields _temporaryStream, _memoryStream, and _buffer are always non-null; adding null checks or fallback behavior would make the patch more robust against future changes in NetHttpClient’s internals.
## Individual Comments
### Comment 1
<location path="AquaMai.Mods/GameSystem/MoreContentEncoding.cs" line_range="23-32" />
<code_context>
+ private const int BufferSize = 1024;
</code_context>
<issue_to_address>
**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.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| private const int BufferSize = 1024; | ||
|
|
||
| [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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
4 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="AquaMai.Mods/GameSystem/MoreContentEncoding.cs">
<violation number="1" location="AquaMai.Mods/GameSystem/MoreContentEncoding.cs:23">
P2: `BufferSize` 固定为 1024,但读取目标 `_buffer` 是游戏内部字段,其长度未知。若 `_buffer.Length < 1024`,`CopyTo` 中的 `Read(buffer, 0, 1024)` 会抛 `ArgumentOutOfRangeException`。建议按 `_buffer.Length` 读取(如 `Math.Min(BufferSize, buffer.Length)`),或直接使用 `Math.Min` 保证不超过缓冲区长度。</violation>
<violation number="2" location="AquaMai.Mods/GameSystem/MoreContentEncoding.cs:45">
P2: gzip 解压分支没有 try/catch,而 zlib 分支有。由于该 prefix 完全替换了 `Decompress`,损坏/被截断的 gzip 或碰巧以 0x1F 0x8B 开头的明文都会让 `GZipStream` 抛出未捕获的 `InvalidDataException`,传播出 prefix 后可能中断网络包处理。为 gzip 分支加上与 `TryInflateZlib` 一致的 try/catch,失败时回退到明文写入。</violation>
<violation number="3" location="AquaMai.Mods/GameSystem/MoreContentEncoding.cs:50">
P2: 使用非默认窗口大小的合法 zlib 响应不会被解压。不要只匹配 `raw[0] == 0x78`,应校验完整 zlib 头部(CM、CINFO 和 FCHECK)后再调用 `TryInflateZlib`。</violation>
<violation number="4" location="AquaMai.Mods/GameSystem/MoreContentEncoding.cs:71">
P2: 该分支会接受 `Adler32` 校验失败的 zlib 响应,可能把损坏数据当成正常网络数据。解压后校验 zlib 尾部的 `Adler32`,校验失败时应返回失败并清理输出。</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| try | ||
| { | ||
| // 跳过 2 字节的 zlib 头部,忽略末尾的 4 字节 Adler32 校验和。 | ||
| using var input = new MemoryStream(raw, 2, raw.Length - 6, writable: false); |
There was a problem hiding this comment.
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 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)) |
There was a problem hiding this comment.
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>
|
|
||
| public class MoreContentEncoding | ||
| { | ||
| private const int BufferSize = 1024; |
There was a problem hiding this comment.
P2: BufferSize 固定为 1024,但读取目标 _buffer 是游戏内部字段,其长度未知。若 _buffer.Length < 1024,CopyTo 中的 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>
| // - 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) |
There was a problem hiding this comment.
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>
开启后游戏将支持解压非deflate格式的数据包,比如Gzip;
用于服务器使用Cloudflare等不支持deflate格式的CDN;
Summary by Sourcery
添加一个可选的游戏系统设置,在解压网络响应时支持额外的 HTTP
content-encoding格式。新功能:
MoreContentEncoding游戏系统模块,将解压支持从仅deflate扩展到包括gzip和zlib编码的负载,并在必要时回退到纯文本。增强:
MoreContentEncoding配置选项,以便可以在设置中启用或禁用扩展的content-encoding支持。Original summary in English
Summary by Sourcery
Add an optional game system setting to support additional HTTP content-encoding formats when decompressing network responses.
New Features:
Enhancements: