diff --git a/UnityLauncher/App.xaml b/UnityLauncher/App.xaml index 9050ea5..57f7a18 100644 --- a/UnityLauncher/App.xaml +++ b/UnityLauncher/App.xaml @@ -1,8 +1,6 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/UnityLauncher/App.xaml.cs b/UnityLauncher/App.xaml.cs index bb129bf..99f987a 100644 --- a/UnityLauncher/App.xaml.cs +++ b/UnityLauncher/App.xaml.cs @@ -1,9 +1,19 @@ -namespace UnityLauncher +using UnityLauncher.Core; +using UnityLauncher.Core.Common; + +namespace UnityLauncher { /// /// Interaction logic for App.xaml /// public partial class App { + public App() + { + var mainWindow = new MainWindowView(); + var mainViewModel = new MainWindowModelView(mainWindow); + Behaviors.Init(mainViewModel); + mainWindow.Show(); + } } } diff --git a/UnityLauncher/Core/Behaviours.cs b/UnityLauncher/Core/Behaviors.cs similarity index 69% rename from UnityLauncher/Core/Behaviours.cs rename to UnityLauncher/Core/Behaviors.cs index 8da0e14..79178ca 100644 --- a/UnityLauncher/Core/Behaviours.cs +++ b/UnityLauncher/Core/Behaviors.cs @@ -6,30 +6,30 @@ namespace UnityLauncher.Core { - public static class Behaviours + public static class Behaviors { private static readonly Dictionary BehaviourLists = new Dictionary(); - private static readonly List WorkedBehavours = new List(); - private static bool isInited; + private static readonly List WorkedBehavours = new List(); + private static bool _isInited; - public static void Init(params IBaseObject[] predefinedBaseObjects) + public static void Init(params IBehavior[] predefinedBehaviors) { - if(isInited) return; - isInited = true; + if(_isInited) return; + _isInited = true; - if (predefinedBaseObjects != null) + if (predefinedBehaviors != null) { - WorkedBehavours.AddRange(predefinedBaseObjects); + WorkedBehavours.AddRange(predefinedBehaviors); } - var behavioursType = typeof(Behaviours).Assembly.GetTypes() - .Where(type => typeof(IBehaviour).IsAssignableFrom(type) && !type.IsAbstract && !type.IsInterface).ToArray(); + var behavioursType = typeof(Behaviors).Assembly.GetTypes() + .Where(type => typeof(IUIBehavior).IsAssignableFrom(type) && !type.IsAbstract && !type.IsInterface).ToArray(); foreach (var type in behavioursType) { try { - var instance = Activator.CreateInstance(type) as IBehaviour; + var instance = Activator.CreateInstance(type) as IUIBehavior; if (instance != null) { WorkedBehavours.Add(instance); @@ -42,7 +42,7 @@ public static void Init(params IBaseObject[] predefinedBaseObjects) } } - public static IList GetBehaviourList() where T: IBehaviour + public static IList GetBehaviourList() where T: IBehavior { IList tmp; if (!BehaviourLists.TryGetValue(typeof(T), out tmp) || !(tmp is List)) @@ -66,7 +66,7 @@ public static void SendMessage(T message) { foreach (var behavour in WorkedBehavours) { - var receiver = behavour as IMessageReceiver; + var receiver = behavour?.MessageReceiver as IMessageReceiver; receiver?.OnMessage(message); } } diff --git a/UnityLauncher/Core/Commands/Projects/EditorSelectionChangedMessage.cs b/UnityLauncher/Core/Commands/Projects/EditorSelectionChangedMessage.cs deleted file mode 100644 index 180a4f7..0000000 --- a/UnityLauncher/Core/Commands/Projects/EditorSelectionChangedMessage.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace UnityLauncher.Core.Commands.Projects -{ - public class EditorSelectionChangedMessage - { - public EditorInfo NewEditor { get; } - - public EditorSelectionChangedMessage(EditorInfo newEditor) - { - NewEditor = newEditor; - } - } -} \ No newline at end of file diff --git a/UnityLauncher/Core/Commands/Projects/Project.xaml b/UnityLauncher/Core/Commands/Projects/Project.xaml deleted file mode 100644 index 61552cc..0000000 --- a/UnityLauncher/Core/Commands/Projects/Project.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/UnityLauncher/Core/Commands/Projects/Project.xaml.cs b/UnityLauncher/Core/Commands/Projects/Project.xaml.cs deleted file mode 100644 index 017e8b4..0000000 --- a/UnityLauncher/Core/Commands/Projects/Project.xaml.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Windows; -using System.Windows.Forms.VisualStyles; - -namespace UnityLauncher.Core.Commands.Projects -{ - /// - /// Interaction logic for Project.xaml - /// - public partial class Project - { - public ProjectInfo Info { get; set; } - - public Visibility AdditionalVisibility { get; set; } - - public EditorInfo SelectEditorInfo { get; } - - public string AdditionalInfo => Info !=null && Info.Name != "---"? $"(Unity ver: {Info.Version}, author: {Info.Author})" : ""; - - public string VersionInfo => Info != null && Info.Version != "---" && !string.IsNullOrEmpty(SelectEditorInfo?.Version) - ? (SelectEditorInfo.Version != Info.Version?"Another version of editor selected!":"") : ""; - - public Thickness ProjectNameMargin => AdditionalVisibility == Visibility.Visible - ? new Thickness(0, 0, 0, 0) - : new Thickness(0, -2, 0, 0); - - - public Project(ProjectInfo info, bool showAdditional = false, EditorInfo editorInfo = null) - { - AdditionalVisibility = showAdditional? Visibility.Visible : Visibility.Collapsed; - Info = info; - SelectEditorInfo = editorInfo; - InitializeComponent(); - } - } -} diff --git a/UnityLauncher/Core/Commands/Projects/ProjectInfo.cs b/UnityLauncher/Core/Commands/Projects/ProjectInfo.cs deleted file mode 100644 index cf544b0..0000000 --- a/UnityLauncher/Core/Commands/Projects/ProjectInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace UnityLauncher.Core.Commands.Projects -{ - public class ProjectInfo - { - public int Idx { get; set; } - public string Path { get; set; } - public string Name { get; set; } - public string Version { get; set; } - public string Author { get; set; } - } -} \ No newline at end of file diff --git a/UnityLauncher/Core/Commands/Projects/ProjectSelect.xaml b/UnityLauncher/Core/Commands/Projects/ProjectSelect.xaml deleted file mode 100644 index fb051f6..0000000 --- a/UnityLauncher/Core/Commands/Projects/ProjectSelect.xaml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/UnityLauncher/Core/Commands/Projects/ProjectSelect.xaml.cs b/UnityLauncher/Core/Commands/Projects/ProjectSelect.xaml.cs deleted file mode 100644 index 8856ef7..0000000 --- a/UnityLauncher/Core/Commands/Projects/ProjectSelect.xaml.cs +++ /dev/null @@ -1,306 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using Microsoft.Win32; -using UnityLauncher.Interfaces; -using ComboBox = System.Windows.Controls.ComboBox; -using TextBox = System.Windows.Controls.TextBox; -using Microsoft.WindowsAPICodePack.Dialogs; -using MessageBox = System.Windows.MessageBox; - -namespace UnityLauncher.Core.Commands.Projects -{ - /// - /// Interaction logic for ProjectSelect.xaml - /// - public partial class ProjectSelect : ICommandBehaviour, IMessageReceiver - { - private const string IncorrectFolderWarningMessage = "Folder \"{0}\" not empty and not contain \"Assets\" subfolder. Do you want select another folder?"; - - private static readonly Project NotSelectedPopup = new Project(new ProjectInfo - { - Author = "---", - Idx = -1, - Name = "---", - Path = "---", - Version = "---" - }); - - private static readonly Project NotSelectedFull = new Project(new ProjectInfo - { - Author = "---", - Idx = -1, - Name = "---", - Path = "---", - Version = "---" - }, true); - - private bool isFilled; - private bool handUpdate; - private ComboBox comboBox; - private TextBox textBox; - private StackPanel stackPanel; - private Project selectedProject; - - public ProjectSelect() - { - InitializeComponent(); - } - - public FrameworkElement GetControl() - { - Fill(); - return this; - } - - private ProjectInfo GetProjectInfo(string path, int index) - { - if (!string.IsNullOrEmpty(path)) - { - if (Directory.Exists(path)) - { - var name = Path.GetFileName(path); - var author = "unknown"; - var version = "unknown"; - - if (File.Exists(path + "\\ProjectSettings\\ProjectVersion.txt")) - { - foreach (var line in File.ReadLines(path + "\\ProjectSettings\\ProjectVersion.txt")) - { - if(string.IsNullOrEmpty(line)) continue; - if (line.Trim().StartsWith("m_EditorVersion")) - { - var tmp = line.Replace("m_EditorVersion", "").Replace(":", "").Trim(); - if (!string.IsNullOrEmpty(tmp)) - { - version = tmp; - } - } - } - } - - - if (File.Exists(path + "\\ProjectSettings\\ProjectSettings.asset")) - { - foreach (var line in File.ReadLines(path + "\\ProjectSettings\\ProjectSettings.asset")) - { - if (string.IsNullOrEmpty(line)) continue; - if (line.Trim().StartsWith("organizationId")) - { - var tmp = line.Replace("organizationId", "").Replace(":", "").Trim(); - if (!string.IsNullOrEmpty(tmp)) - { - author = tmp; - } - } - } - } - - var result = new ProjectInfo - { - Idx = index, - Path = path, - Name = name, - Author = author, - Version = version - }; - return result; - } - } - return null; - } - - private Project[] GetProjects() - { - var tmp = new List(); - - var registry = Registry.CurrentUser.OpenSubKey("Software\\Unity Technologies\\Unity Editor 5.x"); - if (registry != null) - { - var names = registry.GetValueNames().Where(s => s.StartsWith("RecentlyUsedProjectPaths-") && registry.GetValueKind(s) == RegistryValueKind.Binary); - foreach (var name in names) - { - try - { - var path = System.Text.Encoding.UTF8.GetString((byte[])registry.GetValue(name)) - .TrimEnd('\0'); - var idxStr = name.Remove(0, "RecentlyUsedProjectPaths-".Length); - var numLen = idxStr.IndexOf("_", StringComparison.Ordinal); - if (numLen >= 0) - { - idxStr = idxStr.Substring(0, numLen); - } - int idx; - if (!Int32.TryParse(idxStr, out idx)) - { - idx = 0; - } - var proj = GetProjectInfo(path, idx); - if (proj != null) - { - tmp.Add(new Project(proj)); - } - } - catch - { - // - } - } - } - - return tmp.OrderBy(project => project.Info.Idx).ToArray(); - } - - private void Fill() - { - if(isFilled) return; - isFilled = true; - - comboBox = FindName("RecentlyProjects") as ComboBox; - if(comboBox == null) return; - - comboBox.Items.Add(NotSelectedPopup); - handUpdate = true; - comboBox.SelectedIndex = 0; - handUpdate = false; - - var projects = GetProjects(); - foreach (var project in projects) - { - comboBox.Items.Add(project); - } - - stackPanel = FindName("InfoPanel") as StackPanel; - - selectedProject = NotSelectedPopup; - - textBox = FindName("PathView") as TextBox; - - UpdateSelection(); - } - - public string GetCommandLineArguments() - { - if (!IsValid) return ""; - if (!Directory.Exists(selectedProject.Info.Path + "\\Assets")) - { - Directory.CreateDirectory(selectedProject.Info.Path + "\\Assets"); - } - return $"-projectPath \"{selectedProject.Info.Path}\""; - } - - public bool IsValid => selectedProject?.Info != null && selectedProject.Info.Path != "---" && Directory.Exists(selectedProject.Info.Path) && - (Directory.GetFiles(selectedProject.Info.Path).Length == 0 && Directory.GetDirectories(selectedProject.Info.Path).Length == 0 - || Directory.Exists(selectedProject.Info.Path + "\\Assets")); - - public string SettingsStoreKey => "Projects"; - - private void RecentlyProjects_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if(handUpdate) return; - selectedProject = comboBox.Items.GetItemAt(comboBox.SelectedIndex) as Project; - UpdateSelection(); - } - - private void UpdateSelection() - { - stackPanel.Children.Clear(); - - if (selectedProject != null) - { - var idx = -1; - for (int i = 0; i <= comboBox.Items.Count - 1; i++) - { - var project = comboBox.Items[i] as Project; - if (project != null && project.Info.Path == selectedProject.Info.Path) - { - idx = i; - break; - } - } - handUpdate = true; - comboBox.SelectedIndex = idx >= 0 ? idx : 0; - handUpdate = false; - stackPanel.Children.Add(new Project(selectedProject.Info, true, MainWindow.Instance.SelectedEditor)); - textBox.Text = selectedProject.Info.Path != "---" ? selectedProject.Info.Path : ""; - } - else - { - handUpdate = true; - comboBox.SelectedIndex = 0; - handUpdate = false; - stackPanel.Children.Add(NotSelectedFull); - textBox.Text = ""; - } - } - - private void Button_Click(object sender, RoutedEventArgs e) - { - - using (var dlg = new CommonOpenFileDialog - { - Title = "Select project folder", - IsFolderPicker = true, - EnsureFileExists = true, - EnsurePathExists = true, - EnsureReadOnly = false, - EnsureValidNames = true, - Multiselect = false, - ShowPlacesList = true, - NavigateToShortcut = true, - AddToMostRecentlyUsedList = false, - AllowNonFileSystemItems = false - }) - { - if (selectedProject != null && selectedProject.Info.Path != "---") - { - dlg.InitialDirectory = selectedProject.Info.Path.Replace("/", "\\"); - dlg.DefaultDirectory = selectedProject.Info.Path.Replace("/", "\\"); - } - else - { - dlg.InitialDirectory = @"C:\"; - dlg.DefaultDirectory = @"C:\"; - } - - dlg.FolderChanging += (o, args) => - { - ((CommonOpenFileDialog) o).InitialDirectory = args.Folder; - ((CommonOpenFileDialog)o).DefaultDirectory = args.Folder; - }; - - var dialogResult = dlg.ShowDialog(MainWindow.Instance); - - while (dialogResult == CommonFileDialogResult.Ok) - { - var folder = dlg.FileName.Replace("\\", "/"); - if (Directory.GetFiles(folder).Length == 0 && Directory.GetDirectories(folder).Length == 0 || Directory.Exists(folder + "\\Assets")) - { - var projInfo = GetProjectInfo(folder, -1); - selectedProject = new Project(projInfo); - UpdateSelection(); - break; - } - var messageResult = MessageBox.Show(MainWindow.Instance, string.Format(IncorrectFolderWarningMessage, folder), "Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning); - if (messageResult == MessageBoxResult.Yes) - { - dialogResult = dlg.ShowDialog(MainWindow.Instance); - } - else - { - break; - } - } - } - } - - public void OnMessage(EditorSelectionChangedMessage message) - { - UpdateSelection(); - } - - } -} diff --git a/UnityLauncher/Core/Commands/RenderMode/RenderMode.xaml b/UnityLauncher/Core/Commands/RenderMode/RenderMode.xaml deleted file mode 100644 index f2ee1b9..0000000 --- a/UnityLauncher/Core/Commands/RenderMode/RenderMode.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/UnityLauncher/Core/Commands/RenderMode/RenderMode.xaml.cs b/UnityLauncher/Core/Commands/RenderMode/RenderMode.xaml.cs deleted file mode 100644 index b845179..0000000 --- a/UnityLauncher/Core/Commands/RenderMode/RenderMode.xaml.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Windows.Controls; - -namespace UnityLauncher.Core.Commands.RenderMode -{ - /// - /// Interaction logic for RenderMode.xaml - /// - public partial class RenderMode - { - public string Value { get; } - public string Command { get; } - - public RenderMode(string value, string command) - { - Value = value; - Command = command; - InitializeComponent(); - } - } -} diff --git a/UnityLauncher/Core/Commands/RenderMode/RenderModeSelect.xaml b/UnityLauncher/Core/Commands/RenderMode/RenderModeSelect.xaml deleted file mode 100644 index d0200c9..0000000 --- a/UnityLauncher/Core/Commands/RenderMode/RenderModeSelect.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/UnityLauncher/Core/Commands/RenderMode/RenderModeSelect.xaml.cs b/UnityLauncher/Core/Commands/RenderMode/RenderModeSelect.xaml.cs deleted file mode 100644 index 5dfc1d7..0000000 --- a/UnityLauncher/Core/Commands/RenderMode/RenderModeSelect.xaml.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using UnityLauncher.Interfaces; - -namespace UnityLauncher.Core.Commands.RenderMode -{ - /// - /// Interaction logic for RenderModeSelect.xaml - /// - public partial class RenderModeSelect : ICommandBehaviour - { - private RenderMode selected; - - public RenderModeSelect() - { - InitializeComponent(); - FillComboBox(); - } - - private void FillComboBox() - { - var comboBox = FindName("ComboBox") as ComboBox; - - if (comboBox != null) - { - comboBox.Items.Add(selected = new RenderMode("---", "")); - comboBox.Items.Add(new RenderMode("DirectX 9", "-force-d3d9")); - comboBox.Items.Add(new RenderMode("DirectX 11", "-force-d3d11")); - comboBox.Items.Add(new RenderMode("OpenGL (last availible)", "-force-glcore")); - comboBox.Items.Add(new RenderMode("OpenGL 3.2", "-force-glcore32")); - comboBox.Items.Add(new RenderMode("OpenGL 3.3", "-force-glcore33")); - comboBox.Items.Add(new RenderMode("OpenGL 4.0", "-force-glcore40")); - comboBox.Items.Add(new RenderMode("OpenGL 4.1", "-force-glcore41")); - comboBox.Items.Add(new RenderMode("OpenGL 4.2", "-force-glcore42")); - comboBox.Items.Add(new RenderMode("OpenGL 4.3", "-force-glcore43")); - comboBox.Items.Add(new RenderMode("OpenGL 4.4", "-force-glcore44")); - comboBox.Items.Add(new RenderMode("OpenGL 4.5", "-force-glcore45")); - comboBox.Items.Add(new RenderMode("OpenGL ES (last availible)", "-force-gles")); - comboBox.Items.Add(new RenderMode("OpenGL ES 3.0", "-force-gles30")); - comboBox.Items.Add(new RenderMode("OpenGL ES 3.1", "-force-gles31")); - comboBox.Items.Add(new RenderMode("OpenGL ES 3.2", "-force-gles32")); - - var lastSelected = Settings.GetSetting(this, "LastSelected"); - - var idx = 0; - - for (int i = 1; i <= ComboBox.Items.Count - 1; i++) - { - if (((RenderMode) ComboBox.Items[i]).Value == lastSelected) - { - idx = i; - } - } - - if (idx == 0) - { - Settings.RemoveSetting(this, "LastSelected"); - } - - comboBox.SelectedIndex = idx; - } - } - - public FrameworkElement GetControl() - { - return this; - } - - - public bool IsValid => true; - public string SettingsStoreKey => "RenderMode"; - - public string GetCommandLineArguments() - { - return selected == null?"":selected.Command; - } - - private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - selected = ((ComboBox)sender).Items[((ComboBox)sender).SelectedIndex] as RenderMode; - if (selected == null || selected.Value == "---") - { - Settings.RemoveSetting(this, "LastSelected"); - } - else - { - Settings.SaveSetting(this, "LastSelected", selected.Value); - } - } - } -} diff --git a/UnityLauncher/Core/Common/BaseClasses/BaseBehaviorModelView.cs b/UnityLauncher/Core/Common/BaseClasses/BaseBehaviorModelView.cs new file mode 100644 index 0000000..040c61b --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/BaseBehaviorModelView.cs @@ -0,0 +1,21 @@ +using System.Windows; +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core +{ + public abstract class BaseBehaviorModelView : BaseModelView, IUIBehavior where TModel : BaseModel, new() where TView : FrameworkElement, new() + { + protected TView View { get; } + + protected BaseBehaviorModelView() + { + View = new TView(); + Init(View); + } + + public FrameworkElement GetControl() + { + return UiElement; + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/BaseModel.cs b/UnityLauncher/Core/Common/BaseClasses/BaseModel.cs new file mode 100644 index 0000000..be07dd3 --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/BaseModel.cs @@ -0,0 +1,16 @@ +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core +{ + public abstract class BaseModel + { + protected IContext Context { get; private set; } + + public virtual void Init(IContext context) + { + Context = context; + } + + protected BaseModel() { } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/BaseModelView.cs b/UnityLauncher/Core/Common/BaseClasses/BaseModelView.cs new file mode 100644 index 0000000..cfa73a9 --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/BaseModelView.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Windows; +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core +{ + public abstract class BaseModelView : Notifier, IBehavior + { + protected static readonly Dictionary ViewToModelView = new Dictionary(); + + public BaseModelView This => this; + + public static BaseModelView GetModelView(FrameworkElement uiElement) + { + if (uiElement == null) return null; + BaseModelView result; + ViewToModelView.TryGetValue(uiElement, out result); + return result; + } + + public abstract FrameworkElement UiElement { get; protected set; } + public abstract string ContextKey { get; } + public abstract IMessageReceiver MessageReceiver { get; } + } + + public abstract class BaseModelView : BaseModelView where T: BaseModel, new() + { + public T Model { get; private set; } + + public sealed override FrameworkElement UiElement { get; protected set; } + + protected void Init(FrameworkElement uiElement) + { + UiElement = uiElement; + if (uiElement != null) + { + ViewToModelView[uiElement] = this; + uiElement.DataContext = this; + } + Model = new T(); + Model.Init(this); + } + + protected BaseModelView(FrameworkElement uiElement) + { + Init(uiElement); + } + + protected BaseModelView() { } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/Commands/Command.cs b/UnityLauncher/Core/Common/BaseClasses/Commands/Command.cs new file mode 100644 index 0000000..0b3526a --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/Commands/Command.cs @@ -0,0 +1,86 @@ +using System; +using System.Windows.Input; + +namespace UnityLauncher.Core +{ + public class Command : ICommand + { + public event EventHandler CanExecuteChanged; + public event CancelCommandEventHandler Executing; + public event CommandEventHandler Executed; + protected Action action; + protected Action parameterizedAction; + private bool canExecute; + + public Command(Action action, bool canExecute = true) + { + this.action = action; + this.canExecute = canExecute; + } + + + public Command(Action parameterizedAction, bool canExecute = true) + { + this.parameterizedAction = parameterizedAction; + this.canExecute = canExecute; + } + + public virtual void DoExecute(object param) + { + CancelCommandEventArgs args = new CancelCommandEventArgs() { Parameter = param, Cancel = false }; + InvokeExecuting(args); + + if (args.Cancel) + return; + + InvokeAction(param); + InvokeExecuted(new CommandEventArgs() { Parameter = param }); + } + + protected void InvokeAction(object param) + { + Action theAction = action; + Action theParameterizedAction = parameterizedAction; + if (theAction != null) + theAction(); + else + { + theParameterizedAction?.Invoke(param); + } + } + + protected void InvokeExecuted(CommandEventArgs args) + { + Executed?.Invoke(this, args); + } + + protected void InvokeExecuting(CancelCommandEventArgs args) + { + Executing?.Invoke(this, args); + } + + public bool CanExecute + { + get => canExecute; + set + { + if (canExecute != value) + { + canExecute = value; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + } + + bool ICommand.CanExecute(object parameter) + { + return canExecute; + } + + void ICommand.Execute(object parameter) + { + DoExecute(parameter); + + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/Enableable/EnableableModel.cs b/UnityLauncher/Core/Common/BaseClasses/Enableable/EnableableModel.cs new file mode 100644 index 0000000..ae4cf31 --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/Enableable/EnableableModel.cs @@ -0,0 +1,31 @@ +using System; +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core +{ + public abstract class EnableableModel : BaseModel + { + public event Action OnEnableChange; + + private bool isEnabled; + public bool IsEnabled + { + get => isEnabled; + set + { + if (isEnabled != value) + { + isEnabled = value; + OnEnableChange?.Invoke(); + Settings.Instance.SaveSetting(Context, "IsEnabled", value); + } + } + } + + public override void Init(IContext context) + { + base.Init(context); + isEnabled = Settings.Instance.GetSetting(Context, "IsEnabled"); + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/Enableable/EnableableModelView.cs b/UnityLauncher/Core/Common/BaseClasses/Enableable/EnableableModelView.cs new file mode 100644 index 0000000..659cfea --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/Enableable/EnableableModelView.cs @@ -0,0 +1,19 @@ +using System.Windows; + +namespace UnityLauncher.Core +{ + public abstract class EnableableModelView : BaseBehaviorModelView where TModel : EnableableModel, new() where TView : FrameworkElement, new() + { + public bool IsEnabled + { + get => Model.IsEnabled; + set => Model.IsEnabled = value; + } + + protected EnableableModelView() + { + Model.OnEnableChange += () => NotifyPropertyChanged("IsEnabled"); + NotifyPropertyChanged("IsEnabled"); + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CancelCommandEventArgs.cs b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CancelCommandEventArgs.cs new file mode 100644 index 0000000..5bbcd2a --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CancelCommandEventArgs.cs @@ -0,0 +1,7 @@ +namespace UnityLauncher.Core +{ + public class CancelCommandEventArgs : CommandEventArgs + { + public bool Cancel { get; set; } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CancelCommandEventHandler.cs b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CancelCommandEventHandler.cs new file mode 100644 index 0000000..91b1878 --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CancelCommandEventHandler.cs @@ -0,0 +1,4 @@ +namespace UnityLauncher.Core +{ + public delegate void CancelCommandEventHandler(object sender, CancelCommandEventArgs args); +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CommandEventArgs.cs b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CommandEventArgs.cs new file mode 100644 index 0000000..8d676d8 --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CommandEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace UnityLauncher.Core +{ + public class CommandEventArgs : EventArgs + { + public object Parameter { get; set; } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CommandEventHandler.cs b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CommandEventHandler.cs new file mode 100644 index 0000000..7792079 --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/EventHandlers/CommandEventHandler.cs @@ -0,0 +1,4 @@ +namespace UnityLauncher.Core +{ + public delegate void CommandEventHandler(object sender, CommandEventArgs args); +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/Infos/EditorInfo.cs b/UnityLauncher/Core/Common/BaseClasses/Infos/EditorInfo.cs new file mode 100644 index 0000000..416a3fe --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/Infos/EditorInfo.cs @@ -0,0 +1,70 @@ +using System; +using System.Windows; +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core +{ + public class EditorInfo : Notifier, IContext + { + private string path; + public string Path + { + get => path; + private set + { + if (path != value) + { + path = value; + NotifyPropertyChanged("Path"); + } + } + } + + private string version; + public string Version + { + get => version; + set + { + if (version != value) + { + version = value; + NotifyPropertyChanged("Version"); + } + } + } + + private bool? isX64; + + public bool? IsX64 + { + get => isX64; + set + { + if (isX64 != value) + { + isX64 = value; + NotifyPropertyChanged("IsX64"); + } + } + } + + public string ContextKey => "EditorInfo"; + + public EditorInfo(string path) + { + Path = path; + ThrededJob.RunJob(this, () => + { + var newVersion = BinnaryHelper.GetUnityVersion(path); + var newIs64 = BinnaryHelper.IsX64(path); + Application.Current.Dispatcher.BeginInvoke((Action) ((ver, is64) => + { + Version = ver; + IsX64 = is64; + }), + newVersion, newIs64); + }); + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/BaseClasses/Notifier.cs b/UnityLauncher/Core/Common/BaseClasses/Notifier.cs new file mode 100644 index 0000000..d975b39 --- /dev/null +++ b/UnityLauncher/Core/Common/BaseClasses/Notifier.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; + +namespace UnityLauncher.Core +{ + public abstract class Notifier : INotifyPropertyChanged + { + public object parent; + + public object Parent + { + get => parent; + set + { + if (value != parent) + { + parent = value; + NotifyPropertyChanged("Parent"); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + + protected void NotifyPropertyChanged(string propertyName) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/MainWindow/MainWindowModel.cs b/UnityLauncher/Core/Common/MainWindow/MainWindowModel.cs new file mode 100644 index 0000000..b3ed7ca --- /dev/null +++ b/UnityLauncher/Core/Common/MainWindow/MainWindowModel.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Windows; +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core.Common +{ + public class MainWindowModel : BaseModel, IMessageReceiver + { + private struct PathWithVer + { + public string path; + public int first; + public int second; + public int third; + public long fourth; + } + + public event Action OnSelectedEditorChanged; + + private readonly List launchCommandSources = new List(); + public readonly ObservableCollection availibleEditors = new ObservableCollection(); + + private EditorInfo selectedEditorInfo; + public EditorInfo SelectedEditorInfo + { + get => selectedEditorInfo; + set + { + if (Equals(selectedEditorInfo, value)) return; + selectedEditorInfo = value; + OnSelectedEditorChanged?.Invoke(); + Behaviors.SendMessage(new SelectedEditorChanged{ selectEditorInfo = value }); + Settings.Instance.SaveSetting(Context, "LastSelectedEditor", selectedEditorInfo?.Path); + } + } + + public MainWindowModel() + { + } + + public override void Init(IContext context) + { + base.Init(context); + LoadPathes(); + Behaviors.SendMessage(new SelectedEditorChanged { selectEditorInfo = selectedEditorInfo }); + } + + private void LoadPathes() + { + var searchPathes = Settings.Instance.GetSetting>(Context, "SearchPathes"); + if (searchPathes == null) + { + searchPathes = Settings.DefaultUnityDirectoryPathes.Distinct().ToList(); + Settings.Instance.SaveSetting(Context, "SearchPathes", searchPathes); + } + var searchMasks = Settings.Instance.GetSetting>(Context, "SearchMasks"); + if (searchMasks == null) + { + searchMasks = Settings.DefaultUnityDirectoryMasks.Distinct().ToList(); + Settings.Instance.SaveSetting(Context, "SearchMasks", searchMasks); + } + + FillEditors(searchPathes.ToArray(), searchMasks.ToArray()); + } + + private void FillEditors(string[] pathes, string[] masks) + { + ThrededJob.RunJob(Context, () => + { + if (Application.Current != null) + { + List tmp = new List(); + + string lastPath =""; + + Application.Current.Dispatcher.BeginInvoke((Action) (() => + { + lastPath = SelectedEditorInfo != null? SelectedEditorInfo.Path : Settings.Instance.GetSetting(Context, "LastSelectedEditor"); + availibleEditors.Clear(); + })); + + if(pathes == null || masks == null) return; + foreach (var path in pathes) + { + foreach (var mask in masks) + { + var subPathes = Directory.GetDirectories(path, mask) + .Where(s => File.Exists(s + "\\Editor\\Unity.exe")); + tmp.AddRange(subPathes); + } + } + var locs = tmp.Distinct().Select(s => + { + var verInfo = FileVersionInfo.GetVersionInfo(s + "\\Editor\\Unity.exe"); + var pathVer = new PathWithVer + { + path = s, + first = verInfo.FileMajorPart, + second = verInfo.FileMinorPart, + third = verInfo.FileBuildPart + }; + var lastPartStr = + verInfo.FileVersion.Replace($"{pathVer.first}.{pathVer.second}.{pathVer.third}.", ""); + Int64.TryParse(lastPartStr, out pathVer.fourth); + return pathVer; + }) + .OrderBy(ver => ver.first) + .ThenBy(ver => ver.second) + .ThenBy(ver => ver.third) + .ThenBy(ver => ver.fourth) + .Select(ver => ver.path).ToArray(); + + Application.Current.Dispatcher.BeginInvoke((Action)(locations => + { + foreach (var loc in locations) + { + EditorInfo editorInfo = new EditorInfo(loc); + availibleEditors.Add(editorInfo); + if (loc == lastPath && selectedEditorInfo == null) + { + if (lastPath == loc) + { + SelectedEditorInfo = editorInfo; + } + } + } + }), new object[]{locs}); + + } + }); + } + + public void OnMessage(EditorLocationsChanged message) + { + FillEditors(message.pathes, message.masks); + } + + public void LaunchUnity() + { + if (SelectedEditorInfo == null) return; + if (File.Exists(SelectedEditorInfo.Path + "\\Editor\\Unity.exe")) + { + var path = SelectedEditorInfo.Path + "\\Editor\\Unity.exe"; + var commands = GetCommands(); + Process.Start(path, commands); + MainWindowView.Instance.Close(); + } + } + + private string GetCommands() + { + var commands = launchCommandSources + .Select(source => source?.CommandLineValue) + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray(); + return commands.Length == 0 ? "" : commands.Aggregate((s1, s2) => s1 + " " + s2); + } + + public void AddLaunchCommandSource(ILaunchCommandSource source) + { + if (source != null && launchCommandSources.IndexOf(source) < 0) + { + launchCommandSources.Add(source); + } + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/MainWindow/MainWindowModelView.cs b/UnityLauncher/Core/Common/MainWindow/MainWindowModelView.cs new file mode 100644 index 0000000..6822fed --- /dev/null +++ b/UnityLauncher/Core/Common/MainWindow/MainWindowModelView.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core.Common +{ + public class MainWindowModelView : + BaseModelView + { + public override string ContextKey => "Default"; + public override IMessageReceiver MessageReceiver => Model; + + private readonly Command launchCommand; + public ICommand LaunchCommand => launchCommand; + + public MainWindowModelView(FrameworkElement uiElement) : base(uiElement) + { + Model.OnSelectedEditorChanged += UpdateEditorProperties; + launchCommand = new Command(Model.LaunchUnity); + launchCommand.CanExecute = Model.SelectedEditorInfo != null; + } + + private void UpdateEditorProperties() + { + NotifyPropertyChanged("SelectedEditorIndex"); + launchCommand.CanExecute = Model.SelectedEditorInfo != null; + } + + public ObservableCollection AvailibleEditors => Model.availibleEditors; + + public int SelectedEditorIndex + { + get + { + var idx = -1; + var currentInfo = Model.SelectedEditorInfo; + if (currentInfo != null) + { + var findedInfo = AvailibleEditors.FirstOrDefault(info => info.Path == currentInfo.Path); + if (findedInfo != null) + { + idx = AvailibleEditors.IndexOf(findedInfo); + } + } + return idx; + } + set + { + var idx = value; + if (idx < 0 || idx >= AvailibleEditors.Count) + { + Model.SelectedEditorInfo = null; + } + else + { + Model.SelectedEditorInfo = AvailibleEditors[idx]; + } + launchCommand.CanExecute = Model.SelectedEditorInfo != null; + } + } + + public List launchElements; + public List LaunchElements + { + get + { + if (launchElements == null) + { + launchElements = new List(); + var launchBehaviors = Behaviors.GetBehaviourList(); + if (launchBehaviors != null) + { + foreach (var beh in launchBehaviors) + { + var ui = beh?.GetControl(); + if (ui != null) + { + launchElements.Add(ui); + } + var launchCommandSource = (beh as ILaunchCommandBehavior)?.LaunchCommandSource; + Model.AddLaunchCommandSource(launchCommandSource); + } + } + } + return launchElements; + } + } + + public List optionElements; + public List OptionElements + { + get + { + if (optionElements == null) + { + optionElements = new List(); + var optionBehaviors = Behaviors.GetBehaviourList(); + if (optionBehaviors != null) + { + foreach (var beh in optionBehaviors) + { + var ui = beh?.GetControl(); + if (ui != null) + { + optionElements.Add(ui); + } + } + } + } + return optionElements; + } + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Common/MainWindow/MainWindowView.xaml b/UnityLauncher/Core/Common/MainWindow/MainWindowView.xaml new file mode 100644 index 0000000..a10bab5 --- /dev/null +++ b/UnityLauncher/Core/Common/MainWindow/MainWindowView.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -35,10 +35,10 @@ - - - - + + + + @@ -47,9 +47,9 @@ - - - + + + @@ -57,9 +57,9 @@ - - - + + + @@ -67,8 +67,8 @@ - - + + diff --git a/UnityLauncher/Core/Options/Locations/EditorLocationsView.xaml.cs b/UnityLauncher/Core/Options/Locations/EditorLocationsView.xaml.cs new file mode 100644 index 0000000..f760211 --- /dev/null +++ b/UnityLauncher/Core/Options/Locations/EditorLocationsView.xaml.cs @@ -0,0 +1,10 @@ +namespace UnityLauncher.Core.Options +{ + public partial class EditorLocationsView + { + public EditorLocationsView() + { + InitializeComponent(); + } + } +} diff --git a/UnityLauncher/Core/Settings.cs b/UnityLauncher/Core/Settings.cs deleted file mode 100644 index a4d86e6..0000000 --- a/UnityLauncher/Core/Settings.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Newtonsoft.Json; -using UnityLauncher.Interfaces; - -namespace UnityLauncher.Core -{ - public static class Settings - { - private const string ConfigFileName = "config.json"; - private static bool _isInited; - private static string _configFilePath; - private static Dictionary> _settingValues; - - private static void LoadValues() - { - if (_settingValues == null) - { - _settingValues = new Dictionary>(); - } - _settingValues.Clear(); - if (File.Exists(_configFilePath)) - { - var str = File.ReadAllText(_configFilePath); - JsonConvert.PopulateObject(str, _settingValues); - } - } - - private static void SaveValues() - { - if (_settingValues == null) - { - _settingValues = new Dictionary>(); - } - File.WriteAllText(_configFilePath, JsonConvert.SerializeObject(_settingValues, Formatting.Indented)); - } - - - public static void Init() - { - if(_isInited) return; - _isInited = true; - _configFilePath = $"{AppDomain.CurrentDomain.BaseDirectory}{ConfigFileName}"; - } - - public static T GetSetting(IBaseObject behavour, string key) - { - if (behavour != null) - { - var subKey = string.IsNullOrEmpty(behavour.SettingsStoreKey)?"Default": behavour.SettingsStoreKey; - LoadValues(); - Dictionary subVals; - if (_settingValues.TryGetValue(subKey, out subVals)) - { - string valStr; - if (subVals.TryGetValue(key, out valStr)) - { - T val = JsonConvert.DeserializeObject(valStr); - return val; - } - } - } - return default(T); - } - - public static void SaveSetting(IBaseObject behavour, string key, T value) - { - if (behavour != null) - { - var subKey = string.IsNullOrEmpty(behavour.SettingsStoreKey) ? "Default" : behavour.SettingsStoreKey; - LoadValues(); - Dictionary subVals; - if (!_settingValues.TryGetValue(subKey, out subVals)) - { - subVals = new Dictionary(); - _settingValues[subKey] = subVals; - } - subVals[key] = JsonConvert.SerializeObject(value); - SaveValues(); - } - } - - public static void RemoveSetting(IBaseObject behavour, string key) - { - var subKey = string.IsNullOrEmpty(behavour.SettingsStoreKey) ? "Default" : behavour.SettingsStoreKey; - LoadValues(); - Dictionary subVals; - if (_settingValues.TryGetValue(subKey, out subVals)) - { - if (subVals.Remove(key)) - { - SaveValues(); - } - } - } - } -} \ No newline at end of file diff --git a/UnityLauncher/Core/Tools/BinnaryHelper.cs b/UnityLauncher/Core/Tools/BinnaryHelper.cs new file mode 100644 index 0000000..b614400 --- /dev/null +++ b/UnityLauncher/Core/Tools/BinnaryHelper.cs @@ -0,0 +1,155 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace UnityLauncher.Core +{ + public static class BinnaryHelper + { + private enum MachineType : ushort + { + Unknown = 0x0, + Am33 = 0x1d3, + Amd64 = 0x8664, + Arm = 0x1c0, + Ebc = 0xebc, + I386 = 0x14c, + Ia64 = 0x200, + M32R = 0x9041, + Mips16 = 0x266, + Mipsfpu = 0x366, + Mipsfpu16 = 0x466, + Powerpc = 0x1f0, + Powerpcfp = 0x1f1, + R4000 = 0x166, + Sh3 = 0x1a2, + Sh3Dsp = 0x1a3, + Sh4 = 0x1a6, + Sh5 = 0x1a8, + Thumb = 0x1c2, + Wcemipsv2 = 0x169, + } + + + [DllImport("version.dll", EntryPoint = "GetFileVersionInfo", SetLastError = true)] + private static extern bool GetFileVersionInfo(string filename, int handle, int len, IntPtr buffer); + [DllImport("version.dll", EntryPoint = "GetFileVersionInfoSize", SetLastError = true)] + private static extern int GetFileVersionInfoSize(string filename, ref int handle); + [DllImport("version.dll", EntryPoint = "VerQueryValue", SetLastError = true)] + private static extern bool VerQueryValue(IntPtr buffer, string subblock, ref IntPtr blockbuffer, ref int len); + + public static string GetUnityVersion(string directoryPath) + { + if (string.IsNullOrEmpty(directoryPath) || directoryPath == "---") return ""; + + var exePath = directoryPath + "\\Editor\\Unity.exe"; + using (FileHelper.LockForFileOperation(exePath)) + { + if (!File.Exists(exePath)) return ""; + int handle = 0; + int length = GetFileVersionInfoSize(exePath, ref handle); + string ver = "0.0.0"; + if (length > 0) + { + IntPtr buffer = Marshal.AllocHGlobal(length); + try + { + if (GetFileVersionInfo(exePath, handle, length, buffer)) + { + IntPtr codePageBuffer = IntPtr.Zero; + int codePageLen = 0; + if (VerQueryValue(buffer, "\\VarFileInfo\\Translation", ref codePageBuffer, ref codePageLen)) + { + byte[] codePageArray = new byte[codePageLen]; + Marshal.Copy(codePageBuffer, codePageArray, 0, codePageLen); + for (int i = 0; i <= codePageLen / 4 - 1; i++) + { + var key = $"\\StringFileInfo\\{codePageArray[i * 4 + 1]:X2}{codePageArray[i * 4]:X2}{codePageArray[i * 4 + 3]:X2}{codePageArray[i * 4 + 2]:X2}\\Unity Version"; + IntPtr unityVersionBuffer = IntPtr.Zero; + int unityVersionLen = 0; + if (VerQueryValue(buffer, key, ref unityVersionBuffer, ref unityVersionLen)) + { + byte[] unityVersionArray = new byte[unityVersionLen]; + Marshal.Copy(unityVersionBuffer, unityVersionArray, 0, unityVersionLen); + var verCandidate = Encoding.UTF8.GetString(unityVersionArray); + if (!string.IsNullOrEmpty(verCandidate)) + { + var idx = verCandidate.IndexOf("_", StringComparison.Ordinal); + if (idx >= 0) + { + verCandidate = verCandidate.Substring(0, idx).Trim(); + if (!string.IsNullOrEmpty(verCandidate)) + { + ver = verCandidate; + break; + } + } + } + } + } + } + } + } + catch + { + // + } + Marshal.FreeHGlobal(buffer); + return ver; + } + } + return ""; + } + + private static MachineType GetBinnaryMachineType(string filePath) + { + // See http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + // Offset to PE header is always at 0x3C. + // The PE header starts with "PE\0\0" = 0x50 0x45 0x00 0x00, + // followed by a 2-byte machine type field (see the document above for the enum). + + MachineType machineType = MachineType.Unknown; + using (FileHelper.LockForFileOperation(filePath)) + { + if (File.Exists(filePath)) + { + FileStream fs = null; + BinaryReader br = null; + try + { + fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); + br = new BinaryReader(fs); + + fs.Seek(0x3c, SeekOrigin.Begin); + Int32 peOffset = br.ReadInt32(); + fs.Seek(peOffset, SeekOrigin.Begin); + UInt32 peHead = br.ReadUInt32(); + + if (peHead != 0x00004550) // "PE\0\0", little-endian + throw new Exception("Can't find PE header"); + + machineType = (MachineType)br.ReadUInt16(); + } + catch + { + // + } + finally + { + br?.Close(); + fs?.Close(); + } + } + } + return machineType; + } + + public static bool IsX64(string directoryPath) + { + var exePath = directoryPath + "\\Editor\\Unity.exe"; + MachineType machineType = GetBinnaryMachineType(exePath); + return machineType == MachineType.Amd64 || machineType == MachineType.Ia64; + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Tools/FileHelper.cs b/UnityLauncher/Core/Tools/FileHelper.cs new file mode 100644 index 0000000..a3148ce --- /dev/null +++ b/UnityLauncher/Core/Tools/FileHelper.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace UnityLauncher.Core +{ + public static class FileHelper + { + private class LockerImpl : IDisposable + { + private object locker; + + public LockerImpl(object locker) + { + this.locker = locker; + + if (locker != null) + { + Monitor.Enter(locker); + } + } + + public void Dispose() + { + if (locker != null) + { + Monitor.Exit(locker); + } + } + } + + private static readonly Dictionary Lockers = new Dictionary(); + + private static object GetLocker(string path) + { + if (string.IsNullOrEmpty(path)) return null; + object locker; + if (!Lockers.TryGetValue(path, out locker) || locker == null) + { + locker = new object(); + Lockers[path] = locker; + } + return locker; + } + + public static IDisposable LockForFileOperation(string filePath) + { + return new LockerImpl(GetLocker(filePath)); + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Tools/StringComparerConverter.cs b/UnityLauncher/Core/Tools/StringComparerConverter.cs new file mode 100644 index 0000000..38ed6c6 --- /dev/null +++ b/UnityLauncher/Core/Tools/StringComparerConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace UnityLauncher.Core +{ + public class StringComparerConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values == null) return false; + if (values.Length < 2) return false; + if (values[0] is string && values[1] is string) + { + return (string) values[0] == (string) values[1]; + } + return false; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + return new object[0]; + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Tools/ThrededJob.cs b/UnityLauncher/Core/Tools/ThrededJob.cs new file mode 100644 index 0000000..7d6e370 --- /dev/null +++ b/UnityLauncher/Core/Tools/ThrededJob.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using UnityLauncher.Interfaces; + +namespace UnityLauncher.Core +{ + public class ThrededJob + { + private static readonly Dictionary Jobs = new Dictionary(); + + private readonly Thread thread; + private readonly string key; + + private static ThrededJob GetJob(IContext target) + { + var context = target?.ContextKey; + if (string.IsNullOrEmpty(context)) return null; + ThrededJob job; + lock (DictLockObject) + { + if (!Jobs.TryGetValue(context, out job) || job == null) + { + job = new ThrededJob(context); + Jobs[context] = job; + } + } + return job; + } + + private readonly object queueLockObject = new object(); + private static readonly object DictLockObject = new object(); + private readonly Queue queuedActions = new Queue(); + + private ThrededJob(string key) + { + thread = new Thread(Start); + this.key = key; + } + + private bool isRunning; + + public void Run() + { + lock (queueLockObject) + { + if(isRunning) return; + isRunning = true; + } + thread.Start(); + } + + private void Start() + { + bool run; + do + { + Action act = null; + lock (queueLockObject) + { + if (queuedActions.Count > 0) + { + act = queuedActions.Dequeue(); + run = true; + } + else + { + run = false; + } + } + act?.Invoke(); + } while (run); + lock (queueLockObject) + { + isRunning = false; + } + lock (DictLockObject) + { + Jobs.Remove(key); + } + } + + public static void RunJob(IContext target, Action action) + { + var job = GetJob(target); + if (job != null && action != null) + { + lock (job.queueLockObject) + { + job.queuedActions.Enqueue(action); + if (!job.isRunning) + { + job.Start(); + } + } + } + } + } +} \ No newline at end of file diff --git a/UnityLauncher/Core/Versions/MachineType.cs b/UnityLauncher/Core/Versions/MachineType.cs deleted file mode 100644 index 563d6b1..0000000 --- a/UnityLauncher/Core/Versions/MachineType.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace UnityLauncher.Core.Versions -{ - public enum MachineType : ushort - { - Unknown = 0x0, - Am33 = 0x1d3, - Amd64 = 0x8664, - Arm = 0x1c0, - Ebc = 0xebc, - I386 = 0x14c, - Ia64 = 0x200, - M32R = 0x9041, - Mips16 = 0x266, - Mipsfpu = 0x366, - Mipsfpu16 = 0x466, - Powerpc = 0x1f0, - Powerpcfp = 0x1f1, - R4000 = 0x166, - Sh3 = 0x1a2, - Sh3Dsp = 0x1a3, - Sh4 = 0x1a6, - Sh5 = 0x1a8, - Thumb = 0x1c2, - Wcemipsv2 = 0x169, - } -} \ No newline at end of file diff --git a/UnityLauncher/Core/Versions/Version.cs b/UnityLauncher/Core/Versions/Version.cs deleted file mode 100644 index 8cccdcf..0000000 --- a/UnityLauncher/Core/Versions/Version.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; - -namespace UnityLauncher.Core.Versions -{ - public static class Version - { - [DllImport("version.dll", EntryPoint = "GetFileVersionInfo", SetLastError = true)] - private static extern bool GetFileVersionInfo(string filename, int handle, int len, IntPtr buffer); - [DllImport("version.dll", EntryPoint = "GetFileVersionInfoSize", SetLastError = true)] - private static extern int GetFileVersionInfoSize(string filename, ref int handle); - [DllImport("version.dll", EntryPoint = "VerQueryValue", SetLastError = true)] - private static extern bool VerQueryValue(IntPtr buffer, string subblock, ref IntPtr blockbuffer, ref int len); - - public static string GetUnityVersion(string directoryPath) - { - if (string.IsNullOrEmpty(directoryPath) || directoryPath == "---") return ""; - - var exePath = directoryPath + "\\Editor\\Unity.exe"; - - if (!File.Exists(exePath)) return ""; - - int handle = 0; - int length = GetFileVersionInfoSize(exePath, ref handle); - string ver = "0.0.0"; - if (length > 0) - { - IntPtr buffer = Marshal.AllocHGlobal(length); - try - { - if (GetFileVersionInfo(exePath, handle, length, buffer)) - { - IntPtr codePageBuffer = IntPtr.Zero; - int codePageLen = 0; - if (VerQueryValue(buffer, "\\VarFileInfo\\Translation", ref codePageBuffer, ref codePageLen)) - { - byte[] codePageArray = new byte[codePageLen]; - Marshal.Copy(codePageBuffer, codePageArray, 0, codePageLen); - for (int i = 0; i <= codePageLen / 4 - 1; i++) - { - var key = $"\\StringFileInfo\\{codePageArray[i * 4 + 1]:X2}{codePageArray[i * 4]:X2}{codePageArray[i * 4 + 3]:X2}{codePageArray[i * 4 + 2]:X2}\\Unity Version"; - IntPtr unityVersionBuffer = IntPtr.Zero; - int unityVersionLen = 0; - if (VerQueryValue(buffer, key, ref unityVersionBuffer, ref unityVersionLen)) - { - byte[] unityVersionArray = new byte[unityVersionLen]; - Marshal.Copy(unityVersionBuffer, unityVersionArray, 0, unityVersionLen); - var verCandidate = Encoding.UTF8.GetString(unityVersionArray); - if (!string.IsNullOrEmpty(verCandidate)) - { - var idx = verCandidate.IndexOf("_", StringComparison.Ordinal); - if (idx >= 0) - { - verCandidate = verCandidate.Substring(0, idx).Trim(); - if (!string.IsNullOrEmpty(verCandidate)) - { - ver = verCandidate; - break; - } - } - } - } - } - } - } - } - catch - { - // - } - Marshal.FreeHGlobal(buffer); - } - - return ver; - } - - public static MachineType GetBinnaryMachineType(string filePath) - { - // See http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx - // Offset to PE header is always at 0x3C. - // The PE header starts with "PE\0\0" = 0x50 0x45 0x00 0x00, - // followed by a 2-byte machine type field (see the document above for the enum). - // - FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); - BinaryReader br = new BinaryReader(fs); - fs.Seek(0x3c, SeekOrigin.Begin); - Int32 peOffset = br.ReadInt32(); - fs.Seek(peOffset, SeekOrigin.Begin); - UInt32 peHead = br.ReadUInt32(); - - if (peHead != 0x00004550) // "PE\0\0", little-endian - throw new Exception("Can't find PE header"); - - MachineType machineType = (MachineType)br.ReadUInt16(); - br.Close(); - fs.Close(); - return machineType; - } - - public static bool IsX64(string directoryPath) - { - var exePath = directoryPath + "\\Editor\\Unity.exe"; - - if (!File.Exists(exePath)) return false; - - var machineType = GetBinnaryMachineType(exePath); - - if (machineType == MachineType.Amd64 || machineType == MachineType.Ia64) - { - return true; - } - return false; - } - } -} \ No newline at end of file diff --git a/UnityLauncher/MainWindow.xaml b/UnityLauncher/MainWindow.xaml deleted file mode 100644 index 40a8914..0000000 --- a/UnityLauncher/MainWindow.xaml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -