diff --git a/ClassicAssist.Launcher/App.xaml.cs b/ClassicAssist.Launcher/App.xaml.cs
index 27f18381..41384819 100644
--- a/ClassicAssist.Launcher/App.xaml.cs
+++ b/ClassicAssist.Launcher/App.xaml.cs
@@ -1,5 +1,25 @@
-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; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#endregion
+
+using System.Threading;
using System.Windows;
+using CommandLine;
using Exceptionless;
namespace ClassicAssist.Launcher
@@ -9,11 +29,15 @@ namespace ClassicAssist.Launcher
///
public partial class App : Application
{
+ public static CommandLineOptions CurrentOptions { get; set; } = new CommandLineOptions();
+
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..8a9a0d3c 100644
--- a/ClassicAssist.Launcher/MainViewModel.cs
+++ b/ClassicAssist.Launcher/MainViewModel.cs
@@ -1,4 +1,23 @@
-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; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#endregion
+
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
@@ -12,8 +31,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 +137,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 +156,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 +168,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 +181,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 +204,25 @@ 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( Resources.Shard_not_found, Strings.Error );
+ return;
+ }
+
+ 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 +232,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 +240,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 +272,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 +363,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 +390,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 )
{
@@ -426,13 +451,7 @@ private static void CheckForUpdate( object obj )
private void SelectClientPath( object obj )
{
- OpenFileDialog ofd = new OpenFileDialog
- {
- CheckFileExists = true,
- Multiselect = false,
- Filter = "ClassicUO.exe|ClassicUO.exe",
- Title = Resources.Select_a_client
- };
+ OpenFileDialog ofd = new OpenFileDialog { CheckFileExists = true, Multiselect = false, Filter = "ClassicUO.exe|ClassicUO.exe", Title = Resources.Select_a_client };
bool? result = ofd.ShowDialog();
@@ -451,10 +470,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 )
@@ -487,8 +503,7 @@ private async Task Start( object obj )
StringBuilder args = new StringBuilder();
- List pluginList =
- new List { Path.Combine( Environment.CurrentDirectory, "ClassicAssist.dll" ) };
+ List pluginList = new List { Path.Combine( Environment.CurrentDirectory, "ClassicAssist.dll" ) };
foreach ( PluginEntry plugin in Plugins )
{
@@ -507,8 +522,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 +530,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 +587,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 +612,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 +706,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 +718,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 +727,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/Properties/Resources.Designer.cs b/ClassicAssist.Launcher/Properties/Resources.Designer.cs
index f510c3fd..2d8d0613 100644
--- a/ClassicAssist.Launcher/Properties/Resources.Designer.cs
+++ b/ClassicAssist.Launcher/Properties/Resources.Designer.cs
@@ -19,7 +19,7 @@ namespace ClassicAssist.Launcher.Properties {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
@@ -330,6 +330,15 @@ public static string Shard {
}
}
+ ///
+ /// Looks up a localized string similar to Shard not found.
+ ///
+ public static string Shard_not_found {
+ get {
+ return ResourceManager.GetString("Shard not found", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Shards.
///
diff --git a/ClassicAssist.Launcher/Properties/Resources.resx b/ClassicAssist.Launcher/Properties/Resources.resx
index 29be225e..63308b42 100644
--- a/ClassicAssist.Launcher/Properties/Resources.resx
+++ b/ClassicAssist.Launcher/Properties/Resources.resx
@@ -222,4 +222,7 @@
Moving item {0} / {1}
+
+ Shard not found
+
\ No newline at end of file
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
//