From a12ce6598bdaad1d4a8c66f6642400430046745e Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 1 Dec 2025 02:53:47 -0800 Subject: [PATCH 1/2] [Core] Recursively preprocess entities in MultiMsg Previously, MultiMsgEntity only uploaded the outer container but failed to upload internal entities (like images), causing empty MsgInfo and -400 errors. This fix ensures all internal entities are recursively preprocessed using the parent message context. --- .../Message/Entities/MultiMsgEntity.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Lagrange.Core/Message/Entities/MultiMsgEntity.cs b/Lagrange.Core/Message/Entities/MultiMsgEntity.cs index 44be011c..d7aca564 100644 --- a/Lagrange.Core/Message/Entities/MultiMsgEntity.cs +++ b/Lagrange.Core/Message/Entities/MultiMsgEntity.cs @@ -26,9 +26,34 @@ public async Task Preprocess(BotContext context, BotMessage message) { if (string.IsNullOrEmpty(ResId)) { + // Recursively preprocess internal messages + Console.WriteLine($"[MultiMsgEntity] Preprocessing {Messages.Count} messages for forward chain..."); + + foreach (var innerMsg in Messages) + { + foreach (var entity in innerMsg.Entities) + { + try + { + await entity.Preprocess(context, message); + + if (entity is ImageEntity img) + { + if (img.MsgInfo != null) + Console.WriteLine($"[MultiMsgEntity] Image uploaded successfully. Size: {img.ImageSize}"); + else + Console.WriteLine("[MultiMsgEntity] WARNING: Image MsgInfo is NULL after preprocess!"); + } + } + catch (Exception ex) + { + Console.WriteLine($"[MultiMsgEntity] Error preprocessing entity: {ex.Message}"); + } + } + } + var result = await context.EventContext.SendEvent(new LongMsgSendEventReq(message.Receiver, Messages)); ResId = result.ResId; - } } From 776107921ccb81a2a5119d38e83ab3eb73251064 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 1 Dec 2025 03:14:16 -0800 Subject: [PATCH 2/2] [Proto] Correctly handle negative values in VarInt encoding This PR fixes two critical issues related to negative integer handling in VarInt encoding: 1. Fixed `IndexOutOfRangeException` in `ProtoHelper.GetVarIntLength`. - Previously, `uint.CreateSaturating` converted negative values to 0, causing `BitOperations.LeadingZeroCount(0)` to return 32, which exceeded the `VarIntLengths32` array bounds. - Changed to `CreateTruncating` to preserve the bit pattern. 2. Fixed packet corruption in `ProtoWriter.EncodeVarInt`. - The check `if (value < 0x80)` was performing a signed comparison. Negative values (e.g., -1) were treated as single-byte values, breaking the Protobuf MSB rule and corrupting the stream. - Fixed by casting to `ulong` for unsigned comparison. Added regression tests to verify correctness for negative integer serialization. --- Lagrange.Proto.Test/CrashTest.cs | 45 ++++++++++++++++++++++++ Lagrange.Proto/Primitives/ProtoWriter.cs | 3 +- Lagrange.Proto/Utility/ProtoHelper.cs | 6 ++-- 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 Lagrange.Proto.Test/CrashTest.cs diff --git a/Lagrange.Proto.Test/CrashTest.cs b/Lagrange.Proto.Test/CrashTest.cs new file mode 100644 index 00000000..6b4c37f8 --- /dev/null +++ b/Lagrange.Proto.Test/CrashTest.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using Lagrange.Proto.Utility; + +namespace Lagrange.Proto.Test +{ + [TestFixture] + public class CrashTest + { + [Test] + public void TestNegativeInt32VarIntLength() + { + // This test is used to reproduce the IndexOutOfRangeException Bug + int value = -1; + + int length = ProtoHelper.GetVarIntLength(value); + + // Verify: For a 32-bit all-ones number (0xFFFF), VarInt encoding should be 5 bytes + Assert.That(length, Is.EqualTo(5)); + } + + [Test] + public void TestNegativeInt64VarIntLength() + { + long value = -1; + + // For 64-bit numbers with all 1s, VarInt encoding should be 10 bytes + int length = ProtoHelper.GetVarIntLength(value); + + Assert.That(length, Is.EqualTo(10)); + } + + [Test] + public void TestOtherNegativeValues() + { + // Test other negative values to ensure stability + int val1 = -100; + int len1 = ProtoHelper.GetVarIntLength(val1); + Assert.That(len1, Is.GreaterThan(0)); + + long val2 = long.MinValue; // 0x8000000000000000 + int len2 = ProtoHelper.GetVarIntLength(val2); + Assert.That(len2, Is.EqualTo(10)); + } + } +} \ No newline at end of file diff --git a/Lagrange.Proto/Primitives/ProtoWriter.cs b/Lagrange.Proto/Primitives/ProtoWriter.cs index 55b5da29..a59946b2 100644 --- a/Lagrange.Proto/Primitives/ProtoWriter.cs +++ b/Lagrange.Proto/Primitives/ProtoWriter.cs @@ -96,7 +96,8 @@ public void EncodeVarInt(T value) where T : unmanaged, INumber { if (_memory.Length - BytesPending >= 10) { - if (value < T.CreateTruncating(0x80)) + // For-1 (int), converting to ulong is 0xFF... FF, much greater than 0x80, will not mistakenly enter this branch. + if (ulong.CreateTruncating(value) < 0x80) { Unsafe.Add(ref MemoryMarshal.GetReference(_memory.Span), BytesPending++) = byte.CreateTruncating(value); return; diff --git a/Lagrange.Proto/Utility/ProtoHelper.cs b/Lagrange.Proto/Utility/ProtoHelper.cs index 01517c05..399db314 100644 --- a/Lagrange.Proto/Utility/ProtoHelper.cs +++ b/Lagrange.Proto/Utility/ProtoHelper.cs @@ -36,12 +36,12 @@ public static unsafe int GetVarIntLength(T value) where T : unmanaged, INumbe if (sizeof(T) <= 4) { - int leadingZeros = BitOperations.LeadingZeroCount(uint.CreateSaturating(value)); + int leadingZeros = BitOperations.LeadingZeroCount(uint.CreateTruncating(value)); return VarIntLengths32[leadingZeros]; } else { - int leadingZeros = BitOperations.LeadingZeroCount(ulong.CreateSaturating(value)); + int leadingZeros = BitOperations.LeadingZeroCount(ulong.CreateTruncating(value)); return VarIntLengths64[leadingZeros]; } } @@ -98,4 +98,4 @@ public static int CountBytes(ReadOnlySpan str) { return GetVarIntLength(str.Length) + str.Length; } -} \ No newline at end of file +}