Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 113 additions & 41 deletions ClassicAssist/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -742,27 +742,9 @@ public static void SendPacketToServer( byte[] packet, int length )

PacketWaitEntries?.CheckWait( packet, PacketDirection.Outgoing, true );

if ( _getPacketLength != null )
if (!VerifyPacketLengthCorrect( packet ))
{
int expectedLength = _getPacketLength( packet[0] );

if ( expectedLength == -1 )
{
expectedLength = ( packet[1] << 8 ) | packet[2];
}

if ( length != expectedLength )
{
SentrySdk.CaptureMessage( $"Invalid packet length: {length} != {expectedLength}", scope =>
{
scope.SetExtra( "Packet", packet );
scope.SetExtra( "Length", length );
scope.SetExtra( "Direction", PacketDirection.Outgoing );
scope.SetExtra( "Expected Length", expectedLength );
} );

return;
}
return;
}

( byte[] data, int dataLength ) = Utility.CopyBuffer( packet, length );
Expand Down Expand Up @@ -791,30 +773,12 @@ public static void SendPacketToClient( byte[] packet, int length, bool delay = t

PacketWaitEntries?.CheckWait( packet, PacketDirection.Incoming, true );

if ( _getPacketLength != null )
if ( !VerifyPacketLengthCorrect( packet ) || length != packet.Length)
{
int expectedLength = _getPacketLength( packet[0] );

if ( expectedLength == -1 )
{
expectedLength = ( packet[1] << 8 ) | packet[2];
}

if ( length != expectedLength )
{
SentrySdk.CaptureMessage( $"Invalid packet length: {length} != {expectedLength}", scope =>
{
scope.SetExtra( "Packet", packet );
scope.SetExtra( "Length", length );
scope.SetExtra( "Direction", PacketDirection.Incoming );
scope.SetExtra( "Expected Length", expectedLength );
} );

return;
}
return;
}

( byte[] data, int dataLength ) = Utility.CopyBuffer( packet, length );
(byte[] data, int dataLength) = Utility.CopyBuffer( packet, length );

_sendToClient?.Invoke( ref data, ref dataLength );

Expand All @@ -827,6 +791,78 @@ public static void SendPacketToClient( byte[] packet, int length, bool delay = t
}
}

public static void HexDump( byte[] bytes, string title = null, int bytesPerLine = 16 )
{
if ( bytes == null || bytes.Length == 0 )
{
return;
}

int bytesLength = bytes.Length;

Console.WriteLine( !string.IsNullOrEmpty( title ) ? $"{title}, Packet: {bytes[0]:X}, Size: {bytes.Length}" : $"Packet: {bytes[0]:X}, Size: {bytes.Length}" );

char[] HexChars = "0123456789ABCDEF".ToCharArray();

const int firstHexColumn = 8 // 8 characters for the address
+ 3; // 3 spaces

int firstCharColumn = firstHexColumn + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space
+ ( bytesPerLine - 1 ) / 8 // - 1 extra space every 8 characters from the 9th
+ 2; // 2 spaces

int lineLength = firstCharColumn + bytesPerLine // - characters to show the ascii value
+ Environment.NewLine.Length; // Carriage return and line feed (should normally be 2)

char[] line = ( new string( ' ', lineLength - Environment.NewLine.Length ) + Environment.NewLine ).ToCharArray();
int expectedLines = ( bytesLength + bytesPerLine - 1 ) / bytesPerLine;
StringBuilder result = new StringBuilder( expectedLines * lineLength );

for ( int i = 0; i < bytesLength; i += bytesPerLine )
{
line[0] = HexChars[( i >> 28 ) & 0xF];
line[1] = HexChars[( i >> 24 ) & 0xF];
line[2] = HexChars[( i >> 20 ) & 0xF];
line[3] = HexChars[( i >> 16 ) & 0xF];
line[4] = HexChars[( i >> 12 ) & 0xF];
line[5] = HexChars[( i >> 8 ) & 0xF];
line[6] = HexChars[( i >> 4 ) & 0xF];
line[7] = HexChars[( i >> 0 ) & 0xF];

int hexColumn = firstHexColumn;
int charColumn = firstCharColumn;

for ( int j = 0; j < bytesPerLine; j++ )
{
if ( j > 0 && ( j & 7 ) == 0 )
{
hexColumn++;
}

if ( i + j >= bytesLength )
{
line[hexColumn] = ' ';
line[hexColumn + 1] = ' ';
line[charColumn] = ' ';
}
else
{
byte b = bytes[i + j];
line[hexColumn] = HexChars[( b >> 4 ) & 0xF];
line[hexColumn + 1] = HexChars[b & 0xF];
line[charColumn] = b < 32 ? '.' : (char) b;
}

hexColumn += 3;
charColumn++;
}

result.Append( line );
}

Console.WriteLine( result.ToString() );
}

public static void SendPacketToClient( PacketWriter packet )
{
byte[] data = packet.ToArray();
Expand Down Expand Up @@ -872,6 +908,42 @@ public static void SendPacketToServer( BasePacket basePacket )
SendPacketToServer( data, data.Length );
}

public static bool VerifyPacketLengthCorrect( byte[] packet, [CallerMemberName] string callerMemberName = null )
{
int length = packet.Length;
int expectedLength = length;

if ( _getPacketLength == null )
{
return true;
}

expectedLength = _getPacketLength( packet[0] );

if ( expectedLength == -1 )
{
expectedLength = ( packet[1] << 8 ) | packet[2];
}

if ( packet.Length != expectedLength )
{
string message = $"Invalid packet length: {length} != {expectedLength}";

Utility.FormatBuffer( packet, message );

SentrySdk.CaptureMessage( $"Invalid packet length: {length} != {expectedLength}", scope =>
{
scope.SetExtra( "Packet", packet );
scope.SetExtra( "Length", length );
scope.SetExtra( "Direction", PacketDirection.Incoming );
scope.SetExtra( "Expected Length", expectedLength );
scope.SetExtra( "Callsite", callerMemberName );
} );
}

return packet.Length == expectedLength;
}

public static bool Move( Direction direction, bool run )
{
return _requestMove?.Invoke( (int) direction, run ) ?? false;
Expand Down
89 changes: 88 additions & 1 deletion ClassicAssist/Misc/Utility.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
using System;
#region License

// Copyright (C) 2026 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY

#endregion

using System;
using System.Linq;
using System.Text;

namespace ClassicAssist.Misc
{
Expand Down Expand Up @@ -29,5 +44,77 @@ public static (byte[] data, int length) CopyBuffer( byte[] source, int sourceLen

return ( data, dataLength );
}

public static void FormatBuffer( byte[] bytes, string title = null, int bytesPerLine = 16 )
{
if ( bytes == null || bytes.Length == 0 )
{
return;
}

int bytesLength = bytes.Length;

Console.WriteLine( !string.IsNullOrEmpty( title ) ? $"{title}, Packet: {bytes[0]:X}, Size: {bytes.Length}" : $"Packet: {bytes[0]:X}, Size: {bytes.Length}" );

char[] HexChars = "0123456789ABCDEF".ToCharArray();

const int firstHexColumn = 8 // 8 characters for the address
+ 3; // 3 spaces

int firstCharColumn = firstHexColumn + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space
+ ( bytesPerLine - 1 ) / 8 // - 1 extra space every 8 characters from the 9th
+ 2; // 2 spaces

int lineLength = firstCharColumn + bytesPerLine // - characters to show the ascii value
+ Environment.NewLine.Length; // Carriage return and line feed (should normally be 2)

char[] line = ( new string( ' ', lineLength - Environment.NewLine.Length ) + Environment.NewLine ).ToCharArray();
int expectedLines = ( bytesLength + bytesPerLine - 1 ) / bytesPerLine;
StringBuilder result = new StringBuilder( expectedLines * lineLength );
Comment on lines +48 to +73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject non-positive bytesPerLine.

expectedLines = ( bytesLength + bytesPerLine - 1 ) / bytesPerLine will throw when bytesPerLine <= 0, so this new public helper can fail before it prints anything. Guard the argument up-front.

Suggested fix
 public static void FormatBuffer( byte[] bytes, string title = null, int bytesPerLine = 16 )
 {
+    if ( bytesPerLine <= 0 )
+    {
+        throw new ArgumentOutOfRangeException( nameof( bytesPerLine ) );
+    }
+
     if ( bytes == null || bytes.Length == 0 )
     {
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ClassicAssist/Misc/Utility.cs` around lines 48 - 73, The FormatBuffer method
can divide by zero when bytesPerLine <= 0; validate and reject non-positive
bytesPerLine at the start of FormatBuffer (e.g., in the method body of
FormatBuffer) by throwing an ArgumentOutOfRangeException or defaulting to a sane
positive value; ensure the check runs before computing expectedLines =
(bytesLength + bytesPerLine - 1) / bytesPerLine and before any calculations that
use bytesPerLine so the method never attempts a division by zero.


for ( int i = 0; i < bytesLength; i += bytesPerLine )
{
line[0] = HexChars[( i >> 28 ) & 0xF];
line[1] = HexChars[( i >> 24 ) & 0xF];
line[2] = HexChars[( i >> 20 ) & 0xF];
line[3] = HexChars[( i >> 16 ) & 0xF];
line[4] = HexChars[( i >> 12 ) & 0xF];
line[5] = HexChars[( i >> 8 ) & 0xF];
line[6] = HexChars[( i >> 4 ) & 0xF];
line[7] = HexChars[( i >> 0 ) & 0xF];

int hexColumn = firstHexColumn;
int charColumn = firstCharColumn;

for ( int j = 0; j < bytesPerLine; j++ )
{
if ( j > 0 && ( j & 7 ) == 0 )
{
hexColumn++;
}

if ( i + j >= bytesLength )
{
line[hexColumn] = ' ';
line[hexColumn + 1] = ' ';
line[charColumn] = ' ';
}
else
{
byte b = bytes[i + j];
line[hexColumn] = HexChars[( b >> 4 ) & 0xF];
line[hexColumn + 1] = HexChars[b & 0xF];
line[charColumn] = b < 32 ? '.' : (char) b;
}

hexColumn += 3;
charColumn++;
}

result.Append( line );
}

Console.WriteLine( result.ToString() );
}
}
}
29 changes: 20 additions & 9 deletions ClassicAssist/UO/Network/IncomingPacketFilters.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Assistant;
using ClassicAssist.Data;
using ClassicAssist.Data.Filters;
Expand All @@ -10,6 +6,12 @@
using ClassicAssist.UO.Network.PacketFilter;
using ClassicAssist.UO.Network.Packets;
using ClassicAssist.UO.Objects;
using Sentry;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClassicAssist.Misc;

namespace ClassicAssist.UO.Network
{
Expand Down Expand Up @@ -71,13 +73,16 @@ private static bool OnProperties( ref byte[] packet, ref int length )

length = packet.Length - span.Length + newArgumentsBytes.Length;

packet[19] = (byte) ( newArgumentsBytes.Length << 8 );
packet[19] = (byte) ( newArgumentsBytes.Length >> 8 );
packet[20] = (byte) newArgumentsBytes.Length;

Array.Resize( ref packet, length );
Array.Copy( newArgumentsBytes, 0, packet, 21, newArgumentsBytes.Length );
Array.Copy( remainingPacket.ToArray(), 0, packet, 21 + newArgumentsBytes.Length, remainingPacket.Length );

packet[1] = (byte) ( length >> 8 );
packet[2] = (byte) length;

return false;
}

Expand Down Expand Up @@ -168,6 +173,9 @@ private static bool OnASCIIMessage( ref byte[] packet, ref int length )
Array.Resize( ref nameOverrideBytes, 30 );
Array.Copy( nameOverrideBytes, 0, packet, 14, 30 );
Array.Copy( nameOverrideBytes, 0, packet, 44, length - 44 );

packet[1] = (byte) ( length >> 8 );
packet[2] = (byte) length;
}

bool block = RepeatedMessagesFilter.CheckMessage( journalEntry );
Expand Down Expand Up @@ -411,7 +419,7 @@ private static bool OnLocalizedMessage( ref byte[] packet, ref int length )
Array.Resize( ref packet, length );
Array.Copy( nameOverrideBytes, 0, packet, 18, 30 );
Array.Copy( unicodeBytes, 0, packet, 47, unicodeBytes.Length );
packet[1] = (byte) ( length << 8 );
packet[1] = (byte) ( length >> 8 );
packet[2] = (byte) length;
}
}
Expand Down Expand Up @@ -489,13 +497,16 @@ public static bool CheckPacket( ref byte[] data, ref int length )
{
bool result = dynamicFilterEntry.CheckPacket( ref data, ref length, PacketDirection.Incoming );

if ( result )
if ( !result )
{
return true;
continue;
}

filtered = true;
break;
}

return filtered;
return !Engine.VerifyPacketLengthCorrect( data ) || filtered;
}
}
}
Loading