Fix inventory desync for <1.17 clients on 1.17+ servers#1243
Open
JobsonMarinho wants to merge 1 commit intoViaVersion:masterfrom
Open
Fix inventory desync for <1.17 clients on 1.17+ servers#1243JobsonMarinho wants to merge 1 commit intoViaVersion:masterfrom
JobsonMarinho wants to merge 1 commit intoViaVersion:masterfrom
Conversation
|
Can you record your test? |
Author
2026-04-07.23-31-56.mp4Hi @toidicakhia I’ve recorded the test and attached the video. However, due to confidentiality agreements with the company I work for, I’m not allowed to show my full screen. For that reason, the recording is slightly cropped to focus only on the relevant part. Please let me know if that works for you or if you’d like me to adjust anything. |
kennytv
reviewed
Apr 8, 2026
...com/viaversion/viabackwards/protocol/v1_17to1_16_4/rewriter/BlockItemPacketRewriter1_17.java
Outdated
Show resolved
Hide resolved
kennytv
reviewed
Apr 8, 2026
...n/src/main/java/com/viaversion/viabackwards/protocol/v1_17_1to1_17/Protocol1_17_1To1_17.java
Outdated
Show resolved
Hide resolved
This addresses the known issue where <1.17 clients experience inventory desyncs on certain inventory click actions when connected to 1.17+ servers. The root cause is threefold: 1. Carried item not forwarded from CONTAINER_SET_CONTENT (1.17.1->1.17) In 1.17.1+, CONTAINER_SET_CONTENT includes a carried/cursor item field that doesn't exist in <1.17 formats. The handler stripped this field and stored it internally, but never forwarded it to the client. This caused the cursor to remain desynced after cancelled clicks. Fix: Send the carried item as a separate CONTAINER_SET_SLOT packet (windowId=-1, slot=-1) so the client's cursor state is properly synced. 2. Empty changedSlots array in CONTAINER_CLICK translation (1.17->1.16.4) The serverbound CONTAINER_CLICK was translated with an empty changedSlots array. The server uses this to detect client-server desync and send corrections. With an empty array, the server skipped sending corrections for cancelled InventoryClickEvents. Fix: For PICKUP mode (mode 0), include the clicked slot with a null predicted value so the server detects the desync and sends a SET_SLOT correction. For non-PICKUP modes (shift-click, swap, throw, drag, etc.) which affect multiple unpredictable slots, force a state ID mismatch to trigger a full CONTAINER_SET_CONTENT resync.
520eb91 to
c18612b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the known issue where <1.17 clients on 1.17+ servers experience inventory desyncs on certain inventory click actions, most notably when the server cancels an
InventoryClickEvent.The problem
When a <1.16.5 client clicks an item in an inventory and the server cancels the click (e.g. a plugin GUI), the clicked item visually disappears from the slot. Clicking another item makes the first one reappear, but the newly clicked item disappears — creating a persistent desync loop. This does not affect 1.17+ clients.
Root cause analysis
The desync is caused by three separate issues in the 1.17→1.16.4 and 1.17.1→1.17 protocol translation:
1. Missing
CONTAINER_SET_CONTENTitem remapping (1.17→1.16.4)BlockItemPacketRewriter1_17had no handler registered forCONTAINER_SET_CONTENT. The packet passed through without item ID remapping, causing items to show up with wrong IDs (or as air) on <1.17 clients when the server sent inventory resyncs.Fix: Register a
replaceClientboundhandler that reads the item array, applieshandleItemToClientto each item, and writes it back.2. Carried item stripped but never forwarded (1.17.1→1.17)
In 1.17.1+,
CONTAINER_SET_CONTENTincludes a carried item (cursor) field. The 1.17.1→1.17 handler correctly strips this field (since 1.17.0 doesn't have it) and stores it inPlayerLastCursorItem, but never sends it to the client. This means the <1.17 client's cursor is never updated when the server resyncs the inventory — causing ghost items on the cursor after cancelled clicks, which led to item duplication on subsequent clicks.Fix: After reading the carried item, create a
CONTAINER_SET_SLOTpacket (windowId=-1, slot=-1) containing the carried item and schedule it viascheduleSend. This packet goes through the 1.17→1.16.4 handler for proper item remapping before reaching the client.3. Empty
changedSlotsarray inCONTAINER_CLICKtranslation (1.17→1.16.4)The serverbound
CONTAINER_CLICKwas translated withchangedSlots = [](empty array). In 1.17+, the server uses this array to compare the client's predicted inventory state against the actual state and sends corrections for any mismatches. With an empty array, the server assumed no slots were modified and skipped sending corrections entirely — even for cancelled events.This was the primary cause of the "item disappears" bug: the client applied its click prediction locally (removing the item from the slot), but the server never sent a correction because it didn't know the client had changed anything.
Fix (PICKUP mode, mode 0): Include the clicked slot in the
changedSlotsarray with anullpredicted value (item picked up to cursor). When the event is cancelled, the server detects the mismatch (client says empty, server has the item) and sends aCONTAINER_SET_SLOTcorrection.Fix (non-PICKUP modes — shift-click, swap, throw, drag, etc.): These modes affect multiple slots that are difficult to predict without replicating vanilla logic. Instead, force a state ID mismatch by invalidating the stored state ID in
InventoryStateIds. This causes the server to detect a global desync and send a fullCONTAINER_SET_CONTENTresync, which correctly restores all affected slots.Testing
Tested on a AdvancedSlimePaper/ASP (Paper fork) 1.21.11 server with a 1.8.9 client (via ViaVersion + ViaBackwards + ViaRewind):
InventoryClickEvent→ item no longer disappears ✅InventoryClickEvent→ item no longer desyncs ✅Files changed
Protocol1_17_1To1_17.java— Forward carried item asCONTAINER_SET_SLOTinCONTAINER_SET_CONTENThandlerBlockItemPacketRewriter1_17.java— AddCONTAINER_SET_CONTENTitem remapping, populatechangedSlotsarray, and force state ID mismatch for complex click modes