From 0ebf0cad0958c5c98798eacdd19f04a3a4526d61 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 26 Apr 2026 21:51:36 +0200 Subject: [PATCH] =?UTF-8?q?fix(net):=20VectorUpdate=20parser=20was=20readi?= =?UTF-8?q?ng=20guid=20from=20opcode=20bytes=20=E2=80=94=20remote=20jumps?= =?UTF-8?q?=20invisible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User report: "in ACdream client, when other client is jumping, nothing happens at all". Diagnostic [VU.recv] revealed the parser was reading guid = 0x0000F74E (= the opcode itself) and velocity values in the billions: [VU.recv] guid=0x0000F74E vel=(8589944832.00,0.00,0.00) isLocal=False hasRemote=False WorldSession.ProcessDatagram passes the FULL reassembled body including the 4-byte opcode at offset 0 — every other parser in src/AcDream.Core.Net/Messages/ verifies the opcode word before reading payload (UpdateMotion.TryParse:77, UpdatePosition.TryParse, etc.). VectorUpdate.TryParse skipped that step and read every field shifted four bytes early, making the guid the opcode bytes and the velocities random floats from later in the buffer. With guid=0xF74E never matching any tracked entity, OnLiveVectorUpdated returned early and remote jumps rendered nothing. Fix: read + verify opcode at offset 0 in TryParse, then read guid at offset 4, velocity at 8/12/16, omega at 20/24/28, sequences at 32/34. Body length now 4 (opcode) + 32 (payload). Tests stay 1222 green. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.Core.Net/Messages/VectorUpdate.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/AcDream.Core.Net/Messages/VectorUpdate.cs b/src/AcDream.Core.Net/Messages/VectorUpdate.cs index f9dfcff..d695022 100644 --- a/src/AcDream.Core.Net/Messages/VectorUpdate.cs +++ b/src/AcDream.Core.Net/Messages/VectorUpdate.cs @@ -39,27 +39,38 @@ public static class VectorUpdate ushort VectorSequence); /// - /// Parse a 0xF74E body. Returns null if the buffer is truncated or - /// malformed (sequence-number mismatch is not checked here — the - /// session-level handler decides what to do). + /// Parse a 0xF74E body. must start with the + /// 4-byte opcode (matches the convention used by UpdateMotion / + /// UpdatePosition / etc.). Returns null on truncation or opcode + /// mismatch. /// public static Parsed? TryParse(ReadOnlySpan body) { - if (body.Length < 32) return null; + // K-fix16 (2026-04-26): body convention includes the opcode at + // offset 0 — every other parser in this folder verifies the + // opcode word before reading payload fields. The previous + // version of this method skipped that, reading guid from the + // opcode bytes (and shifting every subsequent field by 4), + // which is why VU.recv lines showed guid=0xF74E and gigantic + // garbage velocity values. + if (body.Length < 4 + 32) return null; try { - uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body[..4]); + uint opcode = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(0, 4)); + if (opcode != Opcode) return null; - float vx = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(4, 4))); - float vy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(8, 4))); - float vz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(12, 4))); + uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(4, 4)); - float ox = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(16, 4))); - float oy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(20, 4))); - float oz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(24, 4))); + float vx = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(8, 4))); + float vy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(12, 4))); + float vz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(16, 4))); - ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(28, 2)); - ushort vecSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(30, 2)); + float ox = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(20, 4))); + float oy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(24, 4))); + float oz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(28, 4))); + + ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(32, 2)); + ushort vecSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(34, 2)); return new Parsed(guid, new Vector3(vx, vy, vz), new Vector3(ox, oy, oz), instSeq, vecSeq); }