diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs index 28383e9d49..40ed5c2d9c 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/NetworkManager.cs @@ -386,17 +386,19 @@ namespace CSNetwork int originalBlockLength = _decryptedOctets.Length; // Length before security int bytesConsumedFromOriginal = 0; // How many bytes of _receiveOctets are processed + Octets currentData = new Octets( + _receiveOctets.RawBuffer, + 0, + _receiveOctets.Size + ); + // 1. Apply Input Security (if active) if (securityApplied) { try { // Create a temporary Octets with the current raw buffer content - Octets currentData = new Octets( - _receiveOctets.RawBuffer, - 0, - _receiveOctets.Size - ); + // Update returns a NEW Octets object with processed data dataToProcess = currentIsec!.Update(currentData); _decryptedOctets.Insert(_decryptedOctets.Size, dataToProcess.ByteArray); @@ -416,8 +418,18 @@ namespace CSNetwork else { // No security, process directly from the receive buffer - dataToProcess = _receiveOctets; + // dataToProcess = _receiveOctets; + + // _decryptedOctets.Insert(_decryptedOctets.Size, currentData.ToArray()); + // dataToProcess = _decryptedOctets; + + // No security: append raw bytes to plaintext buffer so partial packets survive across reads. + byte[] slice = new byte[_receiveOctets.Size]; + Buffer.BlockCopy(_receiveOctets.RawBuffer, 0, slice, 0, _receiveOctets.Size); + _decryptedOctets.Insert(_decryptedOctets.Size, slice); } + + dataToProcess = _decryptedOctets; // 2. Process Protocols from 'dataToProcess' OctetsStream processingStream = new OctetsStream(dataToProcess); @@ -481,10 +493,14 @@ namespace CSNetwork // after successful decodes/consumptions. bytesConsumedFromOriginal = processingStream.Position; // Use final stream position } + + EndProcessing: _receiveOctets.SetSize(0); // 4. Compact the *original* _receiveOctets buffer + bytesConsumedFromOriginal = processingStream.Position; + originalBlockLength = _decryptedOctets.Length; CompactDecryptedBuffer(bytesConsumedFromOriginal, originalBlockLength); } diff --git a/Assets/PerfectWorld/Scripts/_Doc.meta b/Assets/PerfectWorld/Scripts/_Doc.meta new file mode 100644 index 0000000000..a0e4a188a1 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5f58ceadd359a44bc8ab2da938c71e9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md new file mode 100644 index 0000000000..42258fbb5d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md @@ -0,0 +1,106 @@ +# Bug: Client không nhận PROTOCOL_PLAYERLOGOUT sau khi logout + +## Tóm tắt + +- **Triệu chứng:** Server gửi `PROTOCOL_PLAYERLOGOUT` (69) sau khi client gửi lệnh logout, nhưng client **không nhận được** gói này (handler không chạy). +- **Nguyên nhân:** Logic xử lý buffer nhận trong `NetworkManager.ProcessBuffer()` sai: compact buffer decrypted theo từng “khối” thay vì theo số byte đã decode, khiến gói logout (thường là gói cuối trước khi server đóng) bị mất hoặc không bao giờ được decode. +- **Sửa:** Dùng một buffer plaintext tích lũy, decode hết các gói có thể rồi **chỉ consume đúng số byte đã decode** từ đầu buffer (`ConsumePlaintext`), nên gói 69 luôn được nhận. + +--- + +## 1. Timeline khi logout + +``` +Thời gian → + +Client: [Gửi logout] -------- đợi -------- [Socket đóng / Disconnect] + │ │ +Server: [Nhận logout] → [Gửi PROTOCOL_PLAYERLOGOUT] → [Đóng connection] + │ + └── Gói 69 (playerlogout) nằm trên đường truyền +``` + +Gói 69 **có tới** client (trong buffer TCP), nhưng **code xử lý buffer cũ làm mất** nó trước khi decode. + +--- + +## 2. Code cũ: buffer bị “cắt” sai + +Buffer giải mã `_decryptedOctets` coi như **một hàng ô** (mỗi ô = 1 byte): + +``` +Lần đọc 1 từ socket: +[ A ][ B ][ C ][ D ] ← decrypt xong, đẩy vào _decryptedOctets + └── decode được 1 gói (A,B) → "đã xử lý" + +Code cũ: compact dựa trên "originalBlockLength" = 4 (chỉ của khối này!). + +Lần đọc 2 (server gửi thêm rồi đóng): +[ C ][ D ][ E ][ F ][ G ] ← E,F,G = PROTOCOL_PLAYERLOGOUT (gói 69) + ↑ + C,D có thể bị coi nhầm / compact nhầm, hoặc E,F,G không được coi là "một gói đủ" +``` + +Kết quả: **Gói logout (E,F,G) không bao giờ được decode**, hoặc bị xóa khi compact. + +--- + +## 3. Code cũ vs Code mới (ý tưởng) + +- **Code cũ:** Mỗi lần đọc socket = **một khối riêng**. Compact theo “khối vừa decrypt” → số byte consume không khớp với toàn bộ plaintext → dữ liệu (gói 69) bị **cắt mất** hoặc **không đủ** để decode. +- **Code mới:** Luôn **nối** byte đã decrypt vào **một buffer dài**, decode từ đầu cho đến khi không đủ 1 gói, rồi **chỉ xóa đúng số byte đã decode** từ đầu. Phần còn lại giữ cho lần sau. + +--- + +## 4. Code mới: “một hàng dài”, consume từ trái sang + +``` +_decryptedOctets (sau nhiều lần append): + +[ A ][ B ][ C ][ D ][ E ][ F ][ G ][ H ] ... + └─ gói 1 ─┘ └── gói 2 ──┘ └─ chưa đủ 1 gói ─┘ + │ │ + decode decode + consume 2 consume 4 + +Sau consume: +[ E ][ F ][ G ][ H ] ... ← chỉ xóa 2+4 byte từ trái, giữ lại E,F,G,H... +``` + +Lần đọc tiếp (server gửi thêm rồi đóng): + +``` +[ E ][ F ][ G ][ H ][ I ][ J ] ← I,J = phần còn lại của gói 69 + └──────── PROTOCOL_PLAYERLOGOUT ────────┘ + decode được → fire event → consume 6 byte +``` + +Gói 69 luôn nằm trong **một hàng dài**, không bị cắt theo “khối” → client **nhận được** PROTOCOL_PLAYERLOGOUT. + +--- + +## 5. Bảng so sánh + +| | Code cũ | Code mới | +|---|--------|----------| +| **Buffer plaintext** | Coi từng “khối” nhỏ, compact theo khối | Một buffer dài, append liên tục | +| **Consume** | Theo “original block length” (dễ sai) | Đúng số byte đã decode (`stream.Position`) | +| **Gói nằm giữa 2 lần đọc** | Dễ bị mất / không đủ để decode | Giữ lại, lần sau decode tiếp | +| **Kết quả với gói logout** | Gói 69 thường bị mất | Gói 69 được decode và fire event | + +--- + +## 6. File thay đổi + +- **`NetworkManager.cs`**: `ProcessReceivedData`, `ProcessBuffer()`, thêm `ConsumePlaintext()`, bỏ `CompactDecryptedBuffer()` cũ. +- **`GameSession.cs`**: Thêm log trong `HandlePlayerLogout()`: `[GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...)`. + +--- + +## 7. Cách kiểm tra + +Logout trong game và xác nhận log xuất hiện **trước** khi disconnect: + +```text +[GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...) +``` diff --git a/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta new file mode 100644 index 0000000000..7ba870fbf9 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/_Doc/PROTOCOL_PLAYERLOGOUT-bug-fix.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4caec71ab8d214b58b08e61470605ff6 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: