From e072f77fcf88e7d5e0195f2bd29f1c2bb7f2a6d3 Mon Sep 17 00:00:00 2001 From: Reetus Date: Tue, 10 Mar 2026 10:39:13 +0700 Subject: [PATCH 1/2] Add command-line shard selection and Windows Jump List support Allows launching specific shards directly using a new `--shard` command-line argument. The launcher now tracks the last played date for shards and populates the Windows Taskbar Jump List with recently played entries for quick access. This change also updates several dependencies and adds license headers to modified files. --- ClassicAssist.Launcher/App.xaml.cs | 25 +++- .../ClassicAssist.Launcher.csproj | 7 +- ClassicAssist.Launcher/CommandLineOptions.cs | 10 ++ ClassicAssist.Launcher/MainViewModel.cs | 140 ++++++++++++------ ClassicAssist.Launcher/ShardEntry.cs | 24 ++- ClassicAssist.Launcher/Utility.cs | 2 +- 6 files changed, 156 insertions(+), 52 deletions(-) create mode 100644 ClassicAssist.Launcher/CommandLineOptions.cs diff --git a/ClassicAssist.Launcher/App.xaml.cs b/ClassicAssist.Launcher/App.xaml.cs index 27f18381..becb563d 100644 --- a/ClassicAssist.Launcher/App.xaml.cs +++ b/ClassicAssist.Launcher/App.xaml.cs @@ -1,5 +1,20 @@ -using System.Threading; +#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.Threading; using System.Windows; +using CommandLine; using Exceptionless; namespace ClassicAssist.Launcher @@ -9,11 +24,15 @@ namespace ClassicAssist.Launcher /// public partial class App : Application { + public static CommandLineOptions CurrentOptions { get; set; } + protected override void OnStartup( StartupEventArgs e ) { - ExceptionlessClient.Default.Configuration.DefaultData.Add( "Locale", - Thread.CurrentThread.CurrentUICulture.Name ); + ExceptionlessClient.Default.Configuration.DefaultData.Add( "Locale", Thread.CurrentThread.CurrentUICulture.Name ); ExceptionlessClient.Default.Startup( "T8v0i7nL90cVRc4sr2pgo5hviThMPRF3OtQ0bK60" ); + + Parser.Default.ParseArguments( e.Args ).WithParsed( o => CurrentOptions = o ); + base.OnStartup( e ); } } diff --git a/ClassicAssist.Launcher/ClassicAssist.Launcher.csproj b/ClassicAssist.Launcher/ClassicAssist.Launcher.csproj index 57f93354..1b3e7cec 100644 --- a/ClassicAssist.Launcher/ClassicAssist.Launcher.csproj +++ b/ClassicAssist.Launcher/ClassicAssist.Launcher.csproj @@ -22,10 +22,11 @@ pdbonly - - + + + - + diff --git a/ClassicAssist.Launcher/CommandLineOptions.cs b/ClassicAssist.Launcher/CommandLineOptions.cs new file mode 100644 index 00000000..f2d77289 --- /dev/null +++ b/ClassicAssist.Launcher/CommandLineOptions.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace ClassicAssist.Launcher +{ + public class CommandLineOptions + { + [Option( "shard", Required = false )] + public string Shard { get; set; } + } +} \ No newline at end of file diff --git a/ClassicAssist.Launcher/MainViewModel.cs b/ClassicAssist.Launcher/MainViewModel.cs index 5d86ba10..39cf47a9 100644 --- a/ClassicAssist.Launcher/MainViewModel.cs +++ b/ClassicAssist.Launcher/MainViewModel.cs @@ -1,4 +1,18 @@ -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.Collections.Generic; using System.Collections.ObjectModel; using System.Configuration; @@ -12,8 +26,10 @@ using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Input; +using System.Windows.Shell; using ClassicAssist.Launcher.Properties; using ClassicAssist.Shared.Misc; +using ClassicAssist.Shared.Resources; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Trinet.Core.IO.Ntfs; @@ -116,7 +132,8 @@ public MainViewModel() HasStatusProtocol = token["HasStatusProtocol"]?.ToObject() ?? true, Encryption = token["Encryption"]?.ToObject() ?? false, Website = token["Website"]?.ToObject(), - IsPreset = true + IsPreset = true, + LastPlayed = token["LastPlayed"]?.ToObject() ?? default }; ShardManager.Shards.AddSorted( shard, new ShardEntryComparer() ); @@ -134,7 +151,8 @@ public MainViewModel() Port = token["Port"]?.ToObject() ?? 2593, Website = token["Website"]?.ToObject(), HasStatusProtocol = token["HasStatusProtocol"]?.ToObject() ?? true, - Encryption = token["Encryption"]?.ToObject() ?? false + Encryption = token["Encryption"]?.ToObject() ?? false, + LastPlayed = token["LastPlayed"]?.ToObject() ?? default }; ShardManager.Shards.AddSorted( shard, new ShardEntryComparer() ); @@ -145,10 +163,7 @@ public MainViewModel() { foreach ( JToken token in config["DeletedPresets"] ) { - ShardEntry shard = new ShardEntry - { - Name = token["Name"]?.ToObject() ?? "Unknown", IsPreset = true - }; + ShardEntry shard = new ShardEntry { Name = token["Name"]?.ToObject() ?? "Unknown", IsPreset = true }; ShardEntry preset = ShardManager.Shards.FirstOrDefault( e => e.Equals( shard ) ); @@ -161,8 +176,7 @@ public MainViewModel() if ( config["SelectedShard"] != null ) { - ShardEntry match = ShardManager.Shards.FirstOrDefault( - s => s.Name == config["SelectedShard"]?.ToObject() ); + ShardEntry match = ShardManager.Shards.FirstOrDefault( s => s.Name == config["SelectedShard"]?.ToObject() ); if ( match != null ) { @@ -185,11 +199,28 @@ public MainViewModel() { CheckPresets().ConfigureAwait( false ); } + + if ( !string.IsNullOrEmpty( App.CurrentOptions.Shard ) ) + { + ShardEntry shard = ShardManager.VisibleShards.FirstOrDefault( e => e.Name == App.CurrentOptions.Shard ); + + if ( shard == null ) + { + MessageBox.Show( @"Shard not found", Strings.Error ); + return; + } + + if ( shard != null ) + { + SelectedShard = shard; + } + + StartCommand.Execute( null ); + } } } - public ICommand CheckForUpdateCommand => - _checkforUpdateCommand ?? ( _checkforUpdateCommand = new RelayCommand( CheckForUpdate, UpdaterExists ) ); + public ICommand CheckForUpdateCommand => _checkforUpdateCommand ?? ( _checkforUpdateCommand = new RelayCommand( CheckForUpdate, UpdaterExists ) ); public ClassicOptions ClassicOptions { get; set; } = new ClassicOptions(); @@ -199,8 +230,7 @@ public ObservableCollection ClientPaths set => SetProperty( ref _clientPaths, value ); } - public ICommand ClosingCommand => - _closingCommand ?? ( _closingCommand = new RelayCommand( Closing, o => true ) ); + public ICommand ClosingCommand => _closingCommand ?? ( _closingCommand = new RelayCommand( Closing, o => true ) ); public ObservableCollection DataPaths { @@ -208,16 +238,13 @@ public ObservableCollection DataPaths set => SetProperty( ref _dataPaths, value ); } - public ICommand OptionsCommand => - _optionsCommand ?? ( _optionsCommand = new RelayCommand( ShowOptionsWindow, o => true ) ); + public ICommand OptionsCommand => _optionsCommand ?? ( _optionsCommand = new RelayCommand( ShowOptionsWindow, o => true ) ); public List Plugins { get; set; } = new List(); - public ICommand SelectClientPathCommand => - _selectClientPathCommand ?? ( _selectClientPathCommand = new RelayCommand( SelectClientPath ) ); + public ICommand SelectClientPathCommand => _selectClientPathCommand ?? ( _selectClientPathCommand = new RelayCommand( SelectClientPath ) ); - public ICommand SelectDataPathCommand => - _selectDataPathCommand ?? ( _selectDataPathCommand = new RelayCommand( SelectDataPath ) ); + public ICommand SelectDataPathCommand => _selectDataPathCommand ?? ( _selectDataPathCommand = new RelayCommand( SelectDataPath ) ); public string SelectedClientPath { @@ -243,12 +270,10 @@ public ShardEntry SelectedShard public string ShardsHash { get; set; } = string.Empty; - public ICommand ShowShardsWindowCommand => - _showShardsWindowCommand ?? ( _showShardsWindowCommand = new RelayCommand( ShowShardsWindow, o => true ) ); + public ICommand ShowShardsWindowCommand => _showShardsWindowCommand ?? ( _showShardsWindowCommand = new RelayCommand( ShowShardsWindow, o => true ) ); public ICommand StartCommand => - _startCommand ?? ( _startCommand = new RelayCommandAsync( Start, - o => !string.IsNullOrEmpty( SelectedClientPath ) && !string.IsNullOrEmpty( SelectedDataPath ) ) ); + _startCommand ?? ( _startCommand = new RelayCommandAsync( Start, o => !string.IsNullOrEmpty( SelectedClientPath ) && !string.IsNullOrEmpty( SelectedDataPath ) ) ); private async Task CheckPresets() { @@ -336,8 +361,7 @@ private static void RemoveAlternateDataStreams( string path ) string json = File.ReadAllText( manifestFile ); - IEnumerable manifestEntries = - JsonConvert.DeserializeObject>( json ); + IEnumerable manifestEntries = JsonConvert.DeserializeObject>( json ); if ( manifestEntries == null ) { @@ -364,8 +388,7 @@ private static void RemoveAlternateDataStreams( string path ) private void ReadClassicOptions( JObject config ) { - PropertyInfo[] properties = - typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); + PropertyInfo[] properties = typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); foreach ( PropertyInfo property in properties ) { @@ -451,10 +474,7 @@ private void SelectClientPath( object obj ) private void SelectDataPath( object obj ) { - FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog - { - Description = Resources.Select_your_Ultima_Online_directory, ShowNewFolderButton = false - }; + FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog { Description = Resources.Select_your_Ultima_Online_directory, ShowNewFolderButton = false }; DialogResult result = folderBrowserDialog.ShowDialog(); if ( result != DialogResult.OK ) @@ -507,8 +527,7 @@ private async Task Start( object obj ) ProcessStartInfo psi = new ProcessStartInfo { - WorkingDirectory = - Path.GetDirectoryName( SelectedClientPath ) ?? throw new InvalidOperationException(), + WorkingDirectory = Path.GetDirectoryName( SelectedClientPath ) ?? throw new InvalidOperationException(), FileName = SelectedClientPath, Arguments = args.ToString(), UseShellExecute = true @@ -516,16 +535,52 @@ private async Task Start( object obj ) Process p = Process.Start( psi ); + SelectedShard.LastPlayed = DateTime.Now; + + UpdateJumpList( SelectedShard.Name ); + if ( p != null && !p.HasExited ) { Application.Current.Shutdown( 0 ); } } + private void UpdateJumpList( string shardName ) + { + ProcessModule processModule = Process.GetCurrentProcess().MainModule; + + if ( processModule == null ) + { + return; + } + + string fileName = processModule.FileName; + string directory = Path.GetDirectoryName( fileName ); + + JumpList jumpList = new JumpList { ShowRecentCategory = true }; + + IOrderedEnumerable playedShards = ShardManager.VisibleShards.Where( e => e.LastPlayed != default ).OrderBy( e => e.LastPlayed ); + + foreach ( ShardEntry shard in playedShards ) + { + jumpList.JumpItems.Add( new JumpTask + { + Title = shard.Name, + Arguments = $"--shard \"{shard.Name}\"", + ApplicationPath = fileName, + IconResourcePath = fileName, + WorkingDirectory = directory, + CustomCategory = "Shards" + } ); + } + + JumpList.SetJumpList( Application.Current, jumpList ); + jumpList.Apply(); + } + private void BuildClassicOptions( StringBuilder args ) { - PropertyInfo[] properties = - typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); + PropertyInfo[] properties = typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); foreach ( PropertyInfo property in properties ) { @@ -537,7 +592,7 @@ private void BuildClassicOptions( StringBuilder args ) continue; } - bool skip = val is bool b && b == false && !attr.IncludeIfFalse; + bool skip = val is bool b && !b && !attr.IncludeIfFalse; bool canInclude = true; if ( !string.IsNullOrEmpty( attr.CanIncludeProperty ) ) @@ -562,8 +617,7 @@ private void ShowShardsWindow( object obj ) ShardsWindow window = new ShardsWindow(); window.ShowDialog(); - if ( !( window.DataContext is ShardsViewModel vm ) || vm.DialogResult != DialogResult.OK || - vm.SelectedShard == null ) + if ( !( window.DataContext is ShardsViewModel vm ) || vm.DialogResult != DialogResult.OK || vm.SelectedShard == null ) { return; } @@ -657,7 +711,8 @@ private void Closing( object obj ) { "Port", shard.Port }, { "HasStatusProtocol", shard.HasStatusProtocol }, { "Website", shard.Website }, - { "Encryption", shard.Encryption } + { "Encryption", shard.Encryption }, + { "LastPlayed", shard.LastPlayed } }; presetsArray.Add( shardObj ); @@ -668,9 +723,7 @@ private void Closing( object obj ) WriteClassicOptions( config ); - using ( JsonTextWriter jtw = - new JsonTextWriter( - new StreamWriter( Path.Combine( Environment.CurrentDirectory, CONFIG_FILENAME ) ) ) ) + using ( JsonTextWriter jtw = new JsonTextWriter( new StreamWriter( Path.Combine( Environment.CurrentDirectory, CONFIG_FILENAME ) ) ) ) { jtw.Formatting = Formatting.Indented; config.WriteTo( jtw ); @@ -679,8 +732,7 @@ private void Closing( object obj ) private void WriteClassicOptions( JObject config ) { - PropertyInfo[] properties = - typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); + PropertyInfo[] properties = typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); foreach ( PropertyInfo property in properties ) { diff --git a/ClassicAssist.Launcher/ShardEntry.cs b/ClassicAssist.Launcher/ShardEntry.cs index 7cab4f85..c86292aa 100644 --- a/ClassicAssist.Launcher/ShardEntry.cs +++ b/ClassicAssist.Launcher/ShardEntry.cs @@ -1,4 +1,18 @@ -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.ComponentModel; using System.Runtime.CompilerServices; using System.Windows.Input; @@ -13,6 +27,7 @@ public class ShardEntry : INotifyPropertyChanged, IEquatable private string _address; private bool _deleted; private bool _encryption; + private DateTime _lastPlayed; private string _name; private string _ping; private int _port; @@ -48,6 +63,13 @@ public bool Encryption [JsonIgnore] public bool IsPreset { get; set; } + [JsonProperty( "last_played" )] + public DateTime LastPlayed + { + get => _lastPlayed; + set => SetProperty( ref _lastPlayed, value ); + } + [JsonProperty( "name" )] public string Name { diff --git a/ClassicAssist.Launcher/Utility.cs b/ClassicAssist.Launcher/Utility.cs index 66d141b9..f7486bfe 100644 --- a/ClassicAssist.Launcher/Utility.cs +++ b/ClassicAssist.Launcher/Utility.cs @@ -1,4 +1,4 @@ -#region License +#region License // Copyright (C) 2020 Reetus // From 4a1448663db15a0dc445d176f64d893626d82c9f Mon Sep 17 00:00:00 2001 From: Reetus Date: Tue, 10 Mar 2026 21:08:02 +0700 Subject: [PATCH 2/2] Verify packet length for filtered incoming packets Centralizes packet length validation logic into a reusable method and extends verification to incoming packets within the filtering pipeline. Includes a new hex dump utility to provide better diagnostic information when invalid packet lengths are encountered. --- ClassicAssist/Engine.cs | 154 +++++++++++++----- ClassicAssist/Misc/Utility.cs | 89 +++++++++- .../UO/Network/IncomingPacketFilters.cs | 29 +++- 3 files changed, 221 insertions(+), 51 deletions(-) diff --git a/ClassicAssist/Engine.cs b/ClassicAssist/Engine.cs index e17797be..9f77308b 100644 --- a/ClassicAssist/Engine.cs +++ b/ClassicAssist/Engine.cs @@ -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 ); @@ -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 ); @@ -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(); @@ -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; diff --git a/ClassicAssist/Misc/Utility.cs b/ClassicAssist/Misc/Utility.cs index b14e832c..afbc26cf 100644 --- a/ClassicAssist/Misc/Utility.cs +++ b/ClassicAssist/Misc/Utility.cs @@ -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 { @@ -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 ); + + 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() ); + } } } \ No newline at end of file diff --git a/ClassicAssist/UO/Network/IncomingPacketFilters.cs b/ClassicAssist/UO/Network/IncomingPacketFilters.cs index 08bb9894..fa31ba5f 100644 --- a/ClassicAssist/UO/Network/IncomingPacketFilters.cs +++ b/ClassicAssist/UO/Network/IncomingPacketFilters.cs @@ -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; @@ -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 { @@ -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; } @@ -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 ); @@ -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; } } @@ -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; } } } \ No newline at end of file