using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Input; using ClassicAssist.Launcher.Properties; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Trinet.Core.IO.Ntfs; using Application = System.Windows.Application; using OpenFileDialog = Microsoft.Win32.OpenFileDialog; namespace ClassicAssist.Launcher { public class MainViewModel : BaseViewModel { private const string CONFIG_FILENAME = "Launcher.json"; private readonly ShardManager _manager; private ICommand _checkforUpdateCommand; private ObservableCollection _clientPaths = new ObservableCollection(); private ICommand _closingCommand; private ObservableCollection _dataPaths = new ObservableCollection(); private ICommand _optionsCommand; private ICommand _selectClientPathCommand; private ICommand _selectDataPathCommand; private string _selectedClientPath; private string _selectedDataPath; private ShardEntry _selectedShard; private ObservableCollection _shardEntries = new ObservableCollection(); private ICommand _showShardsWindowCommand; private ICommand _startCommand; public MainViewModel() { RemoveAlternateDataStreams( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location ) ); _manager = ShardManager.GetInstance(); _manager.Shards.CollectionChanged += ( sender, args ) => { ShardEntries = _manager.Shards; }; ShardEntries = _manager.Shards; string fullPath = Path.Combine( Environment.CurrentDirectory, CONFIG_FILENAME ); if ( !File.Exists( fullPath ) ) { return; } using ( JsonTextReader jtr = new JsonTextReader( new StreamReader( fullPath ) ) ) { JObject config = (JObject) JToken.ReadFrom( jtr ); if ( config["ClientPaths"] != null ) { foreach ( JToken token in config["ClientPaths"] ) { string path = token.ToObject(); if ( File.Exists( path ) ) { ClientPaths.Add( path ); } } } if ( config["SelectedClientPath"] != null ) { string path = config["SelectedClientPath"].ToObject(); SelectedClientPath = File.Exists( path ) ? path : ClientPaths.FirstOrDefault(); } if ( config["DataPaths"] != null ) { foreach ( JToken token in config["DataPaths"] ) { string path = token.ToObject(); if ( Directory.Exists( path ) ) { DataPaths.Add( path ); } } } if ( config["SelectedDataPath"] != null ) { string path = config["SelectedDataPath"].ToObject(); SelectedDataPath = Directory.Exists( path ) ? path : DataPaths.FirstOrDefault(); } if ( config["Shards"] != null ) { foreach ( JToken token in config["Shards"] ) { ShardEntry shard = new ShardEntry { Name = token["Name"]?.ToObject() ?? "Unknown", Address = token["Address"]?.ToObject() ?? "localhost", Port = token["Port"]?.ToObject() ?? 2593, HasStatusProtocol = token["HasStatusProtocol"]?.ToObject() ?? true, Encryption = token["Encryption"]?.ToObject() ?? false }; ShardEntries.Add( shard ); } } if ( config["SelectedShard"] != null ) { ShardEntry match = _manager.Shards.FirstOrDefault( s => s.Name == config["SelectedShard"].ToObject() ); if ( match != null ) { SelectedShard = match; } } if ( config["Plugins"] != null ) { foreach ( JToken token in config["Plugins"] ) { string pluginPath = token.ToObject(); Plugins.Add( new PluginEntry { Name = Path.GetFileName( pluginPath ), FullPath = pluginPath } ); } } ReadClassicOptions( config ); } } public ICommand CheckForUpdateCommand => _checkforUpdateCommand ?? ( _checkforUpdateCommand = new RelayCommand( CheckForUpdate, UpdaterExists ) ); public ClassicOptions ClassicOptions { get; set; } = new ClassicOptions(); public ObservableCollection ClientPaths { get => _clientPaths; set => SetProperty( ref _clientPaths, value ); } public ICommand ClosingCommand => _closingCommand ?? ( _closingCommand = new RelayCommand( Closing, o => true ) ); public ObservableCollection DataPaths { get => _dataPaths; set => SetProperty( ref _dataPaths, value ); } 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 SelectDataPathCommand => _selectDataPathCommand ?? ( _selectDataPathCommand = new RelayCommand( SelectDataPath ) ); public string SelectedClientPath { get => _selectedClientPath; set => SetProperty( ref _selectedClientPath, value ); } public string SelectedDataPath { get => _selectedDataPath; set => SetProperty( ref _selectedDataPath, value ); } public ShardEntry SelectedShard { get => _selectedShard; set => SetProperty( ref _selectedShard, value ); } public ObservableCollection ShardEntries { get => _shardEntries; set => SetProperty( ref _shardEntries, value ); } 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 ) ) ); private static void RemoveAlternateDataStreams( string path ) { string manifestFile = Path.Combine( path, "MANIFEST.json" ); if ( !File.Exists( manifestFile ) ) { return; } string json = File.ReadAllText( manifestFile ); IEnumerable manifestEntries = JsonConvert.DeserializeObject>( json ); foreach ( ManifestEntry manifestEntry in manifestEntries ) { string filePath = Path.Combine( path, manifestEntry.Name ); if ( !File.Exists( filePath ) ) { continue; } FileInfo file = new FileInfo( filePath ); if ( file.AlternateDataStreamExists( "Zone.Identifier" ) ) { file.DeleteAlternateDataStream( "Zone.Identifier" ); } } } private void ReadClassicOptions( JObject config ) { PropertyInfo[] properties = typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); foreach ( PropertyInfo property in properties ) { string propName = property.Name; Type propType = property.PropertyType; object defaultValue = null; ClassicOptionAttribute attr = property.GetCustomAttribute(); if ( attr?.DefaultValue != null ) { defaultValue = attr.DefaultValue; } if ( defaultValue == null ) { defaultValue = Activator.CreateInstance( propType ); } object val = config[propName]?.ToObject( propType ) ?? defaultValue; property.SetValue( ClassicOptions, val ); } } private void ShowOptionsWindow( object arg ) { OptionsViewModel vm = new OptionsViewModel( Plugins, ClassicOptions ); OptionsWindow window = new OptionsWindow { DataContext = vm }; window.ShowDialog(); if ( vm.DialogResult != DialogResult.OK ) { return; } Plugins.Clear(); Plugins.AddRange( vm.Plugins ); ClassicOptions = vm.ClassicOptions; } private static bool UpdaterExists( object arg ) { return File.Exists( Path.Combine( Environment.CurrentDirectory, "ClassicAssist.Updater.exe" ) ); } private static void CheckForUpdate( object obj ) { string updaterPath = Path.Combine( Environment.CurrentDirectory, "ClassicAssist.Updater.exe" ); if ( File.Exists( updaterPath ) ) { Process.Start( updaterPath, $"--pid {Process.GetCurrentProcess().Id}" ); } } private void SelectClientPath( object obj ) { OpenFileDialog ofd = new OpenFileDialog { CheckFileExists = true, Multiselect = false, Filter = "ClassicUO.exe|ClassicUO.exe", Title = Resources.Select_a_client }; bool? result = ofd.ShowDialog(); if ( !result.HasValue || !result.Value ) { return; } if ( !ClientPaths.Contains( ofd.FileName ) ) { ClientPaths.Add( ofd.FileName ); } SelectedClientPath = ofd.FileName; } private void SelectDataPath( object obj ) { FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog { Description = Resources.Select_your_Ultima_Online_directory, ShowNewFolderButton = false }; DialogResult result = folderBrowserDialog.ShowDialog(); if ( result != DialogResult.OK ) { return; } if ( !DataPaths.Contains( folderBrowserDialog.SelectedPath ) ) { DataPaths.Add( folderBrowserDialog.SelectedPath ); } SelectedDataPath = folderBrowserDialog.SelectedPath; } /* * Command line parameter documentation... * https://github.com/andreakarasho/ClassicUO/wiki/Distribuite-ClassicUO * https://github.com/andreakarasho/ClassicUO/wiki/Launch-Arguments */ private async Task Start( object obj ) { IPAddress ip = await Utility.ResolveAddress( SelectedShard.Address ); if ( ip == null ) { MessageBox.Show( Resources.Unable_to_resolve_shard_hostname_ ); return; } StringBuilder args = new StringBuilder(); List pluginList = new List { Path.Combine( Environment.CurrentDirectory, "ClassicAssist.dll" ) }; foreach ( PluginEntry plugin in Plugins ) { pluginList.Add( plugin.FullPath ); } string plugins = string.Join( ",", pluginList.ToArray() ); args.Append( $"-plugins \"{plugins}\" " ); args.Append( $"-ip \"{ip}\" -port \"{SelectedShard.Port}\" " ); args.Append( $"-uopath \"{SelectedDataPath}\" " ); args.Append( $"-encryption {( SelectedShard.Encryption ? 1 : 0 )} " ); args.Append( SelectedShard.ShardType > 0 ? $"-shard {SelectedShard.ShardType} " : "-shard 0 " ); BuildClassicOptions( args ); ProcessStartInfo psi = new ProcessStartInfo { WorkingDirectory = Path.GetDirectoryName( SelectedClientPath ) ?? throw new InvalidOperationException(), FileName = SelectedClientPath, Arguments = args.ToString(), UseShellExecute = true }; Process p = Process.Start( psi ); if ( p != null && !p.HasExited ) { Application.Current.Shutdown( 0 ); } } private void BuildClassicOptions( StringBuilder args ) { PropertyInfo[] properties = typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); foreach ( PropertyInfo property in properties ) { ClassicOptionAttribute attr = property.GetCustomAttribute(); object val = property.GetValue( ClassicOptions ); if ( attr == null ) { continue; } bool skip = val is bool b && b == false && !attr.IncludeIfFalse; bool canInclude = true; if ( !string.IsNullOrEmpty( attr.CanIncludeProperty ) ) { PropertyInfo canIncludeProperty = typeof( ClassicOptions ).GetProperty( attr.CanIncludeProperty ); if ( canIncludeProperty != null ) { canInclude = (bool) canIncludeProperty.GetValue( ClassicOptions ); } } if ( !skip && canInclude ) { args.Append( $"{attr.Argument} {val} " ); } } } private void ShowShardsWindow( object obj ) { ShardsWindow window = new ShardsWindow(); window.ShowDialog(); if ( !( window.DataContext is ShardsViewModel vm ) || vm.DialogResult != DialogResult.OK || vm.SelectedShard == null ) { return; } SelectedShard = vm.SelectedShard; } private void Closing( object obj ) { JObject config = new JObject(); JArray clientPathArray = new JArray(); foreach ( string clientPath in ClientPaths ) { clientPathArray.Add( clientPath ); } config.Add( "ClientPaths", clientPathArray ); config.Add( "SelectedClientPath", SelectedClientPath ?? string.Empty ); JArray dataPathArray = new JArray(); foreach ( string dataPath in DataPaths ) { dataPathArray.Add( dataPath ); } config.Add( "DataPaths", dataPathArray ); config.Add( "SelectedDataPath", SelectedDataPath ?? string.Empty ); config.Add( "SelectedShard", SelectedShard.Name ); IEnumerable shards = _manager.Shards.Where( s => !s.IsPreset ); JArray shardArray = new JArray(); foreach ( ShardEntry shard in shards ) { JObject shardObj = new JObject { { "Name", shard.Name }, { "Address", shard.Address }, { "Port", shard.Port }, { "HasStatusProtocol", shard.HasStatusProtocol }, { "Encryption", shard.Encryption } }; shardArray.Add( shardObj ); } config.Add( "Shards", shardArray ); JArray pluginsArray = new JArray(); foreach ( PluginEntry plugin in Plugins ) { pluginsArray.Add( plugin.FullPath ); } config.Add( "Plugins", pluginsArray ); WriteClassicOptions( config ); using ( JsonTextWriter jtw = new JsonTextWriter( new StreamWriter( Path.Combine( Environment.CurrentDirectory, CONFIG_FILENAME ) ) ) ) { jtw.Formatting = Formatting.Indented; config.WriteTo( jtw ); } } private void WriteClassicOptions( JObject config ) { PropertyInfo[] properties = typeof( ClassicOptions ).GetProperties( BindingFlags.Public | BindingFlags.Instance ); foreach ( PropertyInfo property in properties ) { string propName = property.Name; object val = property.GetValue( ClassicOptions ); if ( val != null ) { config.Add( propName, val.ToString() ); } } } } }