diff --git a/UnityLauncherPro/App.config b/UnityLauncherPro/App.config index 9ca2fdf..9c04471 100644 --- a/UnityLauncherPro/App.config +++ b/UnityLauncherPro/App.config @@ -1,12 +1,15 @@ - + + + + - -
+ +
- + @@ -16,14 +19,6 @@ 650 - - - - C:\Program Files\ - - - True @@ -49,10 +44,10 @@ False - + - + True @@ -67,7 +62,7 @@ False - + False @@ -100,14 +95,47 @@ 0 - + - + False + + False + + + + + + + + C:\Program Files\ + + + + + InitializeProject.cs + + + False + + + 50000 + + + + + + + + False + + + 40 + - \ No newline at end of file + diff --git a/UnityLauncherPro/App.xaml b/UnityLauncherPro/App.xaml index 7c014f7..bb3ce34 100644 --- a/UnityLauncherPro/App.xaml +++ b/UnityLauncherPro/App.xaml @@ -1,13 +1,538 @@  - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UnityLauncherPro/Converters/ReleaseDateConverter.cs b/UnityLauncherPro/Converters/ReleaseDateConverter.cs new file mode 100644 index 0000000..e6a5be4 --- /dev/null +++ b/UnityLauncherPro/Converters/ReleaseDateConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace UnityLauncherPro.Converters +{ + [ValueConversion(typeof(DateTime), typeof(String))] + public class ReleaseDateConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) return null; + DateTime date = (DateTime)value; + + // get first part of string until space character (updates only contain mm/dd/yyyy) + string dateStrTrimmed = MainWindow.currentDateFormat; + if (dateStrTrimmed.IndexOf(' ') > -1) dateStrTrimmed = dateStrTrimmed.Split(' ')[0]; + + return MainWindow.useHumanFriendlyDateFormat ? Tools.GetElapsedTime(date) : date.ToString(dateStrTrimmed); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + // not used ? + return DateTime.ParseExact((string)value, MainWindow.currentDateFormat, culture); + } + + } +} diff --git a/UnityLauncherPro/Data/BuildReport.cs b/UnityLauncherPro/Data/BuildReport.cs new file mode 100644 index 0000000..d167924 --- /dev/null +++ b/UnityLauncherPro/Data/BuildReport.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace UnityLauncherPro +{ + public class BuildReport + { + public long ElapsedTimeMS { set; get; } + public List Stats { set; get; } // overal per category sizes + public List Items { set; get; } // report rows + } +} diff --git a/UnityLauncherPro/Data/BuildReportItem.cs b/UnityLauncherPro/Data/BuildReportItem.cs index fe0d7e3..bc78afe 100644 --- a/UnityLauncherPro/Data/BuildReportItem.cs +++ b/UnityLauncherPro/Data/BuildReportItem.cs @@ -3,6 +3,7 @@ public class BuildReportItem { // TODO use real values, so can sort and convert kb/mb + public string Category { set; get; } // for category list public string Size { set; get; } public string Percentage { set; get; } public string Path { set; get; } diff --git a/UnityLauncherPro/Data/Project.cs b/UnityLauncherPro/Data/Project.cs index 7d813e0..53c18a3 100644 --- a/UnityLauncherPro/Data/Project.cs +++ b/UnityLauncherPro/Data/Project.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Globalization; using System.Windows.Data; @@ -12,9 +11,9 @@ public class Project : IValueConverter public string Path { set; get; } public DateTime? Modified { set; get; } public string Arguments { set; get; } - public string GITBranch { set; get; } + public string GITBranch { set; get; } // TODO rename to Branch //public string TargetPlatform { set; get; } - public string TargetPlatform { set; get; } + public string TargetPlatform { set; get; } // TODO rename to Platform public string[] TargetPlatforms { set; get; } public bool folderExists { set; get; } diff --git a/UnityLauncherPro/Data/UnityInstallation.cs b/UnityLauncherPro/Data/UnityInstallation.cs index 7b6a379..3647bf6 100644 --- a/UnityLauncherPro/Data/UnityInstallation.cs +++ b/UnityLauncherPro/Data/UnityInstallation.cs @@ -6,6 +6,7 @@ namespace UnityLauncherPro public class UnityInstallation : IValueConverter { public string Version { set; get; } + public long VersionCode { set; get; } // version as number, cached for sorting public string Path { set; get; } // exe path public DateTime? Installed { set; get; } @@ -15,6 +16,8 @@ public class UnityInstallation : IValueConverter public bool IsPreferred { set; get; } + public string ReleaseType { set; get; } // Alpha, Beta, LTS.. TODO could be enum + // https://stackoverflow.com/a/5551986/5452781 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { diff --git a/UnityLauncherPro/GetProjects.cs b/UnityLauncherPro/GetProjects.cs index 9f6f519..836d509 100644 --- a/UnityLauncherPro/GetProjects.cs +++ b/UnityLauncherPro/GetProjects.cs @@ -1,6 +1,7 @@ using Microsoft.Win32; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Text; @@ -14,8 +15,7 @@ public static class GetProjects // convert target platform name into valid buildtarget platform name, NOTE this depends on unity version, now only 2019 and later are supported public static Dictionary remapPlatformNames = new Dictionary { { "StandaloneWindows64", "Win64" }, { "StandaloneWindows", "Win" }, { "Android", "Android" }, { "WebGL", "WebGL" } }; - // TODO separate scan and folders - public static List Scan(bool getGitBranch = false, bool getArguments = false, bool showMissingFolders = false, bool showTargetPlatform = false) + public static List Scan(bool getGitBranch = false, bool getPlasticBranch = false, bool getArguments = false, bool showMissingFolders = false, bool showTargetPlatform = false, StringCollection AllProjectPaths = null) { List projectsFound = new List(); @@ -40,7 +40,6 @@ public static List Scan(bool getGitBranch = false, bool getArguments = { if (valueName.IndexOf("RecentlyUsedProjectPaths-") == 0) { - bool folderExists = false; string projectPath = ""; // check if binary or not var valueKind = key.GetValueKind(valueName); @@ -54,105 +53,167 @@ public static List Scan(bool getGitBranch = false, bool getArguments = projectPath = (string)key.GetValue(valueName); } - // first check if whole folder exists, if not, skip - folderExists = Directory.Exists(projectPath); - if (showMissingFolders == false && folderExists == false) - { - //Console.WriteLine("Recent project directory not found, skipping: " + projectPath); - continue; - } + var p = GetProjectInfo(projectPath, getGitBranch, getPlasticBranch, getArguments, showMissingFolders, showTargetPlatform); + //Console.WriteLine(projectPath+" "+p.TargetPlatform); - string projectName = ""; + // if want to hide project and folder path for screenshot + //p.Title = "Example Project "; + //p.Path = "C:/Projects/ExamplePath/MyProj"; - // get project name from full path - if (projectPath.IndexOf(Path.DirectorySeparatorChar) > -1) - { - projectName = projectPath.Substring(projectPath.LastIndexOf(Path.DirectorySeparatorChar) + 1); - } - else if (projectPath.IndexOf(Path.AltDirectorySeparatorChar) > -1) + if (p != null) { - projectName = projectPath.Substring(projectPath.LastIndexOf(Path.AltDirectorySeparatorChar) + 1); - } - else // no path separator found - { - projectName = projectPath; - } - - //Console.WriteLine("valueName="+ valueName+" , projectName =" + projectName); + projectsFound.Add(p); - // get last modified date from folder - DateTime? lastUpdated = folderExists ? Tools.GetLastModifiedTime(projectPath) : null; + // add found projects to history also (gets added only if its not already there) + Tools.AddProjectToHistory(p.Path); + } + } // valid key + } // each key + } // for each registry root - // get project version - string projectVersion = folderExists ? Tools.GetProjectVersion(projectPath) : null; + // NOTE those 40 projects should be added to custom list, otherwise they will disappear (since last item is not yet added to our list, until its launched once, so need to launch many projects, to start collecting history..) + // but then we would have to loop here again..? or add in the loop above..if doesnt exists on list, and the remove extra items from the end - // get custom launch arguments, only if column in enabled - string customArgs = ""; - if (getArguments == true) + // scan info for custom folders (if not already on the list) + if (AllProjectPaths != null) + { + // iterate custom full projects history + foreach (var projectPath in AllProjectPaths) + { + // check if registry list contains this path already, then skip it + bool found = false; + for (int i = 0, len = projectsFound.Count; i < len; i++) + { + if (projectsFound[i].Path == projectPath) { - customArgs = folderExists ? Tools.ReadCustomProjectData(projectPath, MainWindow.launcherArgumentsFile) : null; + found = true; + break; } + } - // get git branchinfo, only if column in enabled - string gitBranch = ""; - if (getGitBranch == true) - { - gitBranch = folderExists ? Tools.ReadGitBranchInfo(projectPath) : null; - } + // if not found from registry, add to recent projects list + if (found == false) + { + var p = GetProjectInfo(projectPath, getGitBranch, getPlasticBranch, getArguments, showMissingFolders, showTargetPlatform); + if (p != null) projectsFound.Add(p); + } + } + } - string targetPlatform = ""; - if (showTargetPlatform == true) - { - targetPlatform = folderExists ? Tools.GetTargetPlatform(projectPath) : null; - } + // sometimes projects are in wrong order, seems to be related to messing up your unity registry, the keys are received in created order (so if you had removed/modified them manually, it might return wrong order instead of 0 - 40) + // thats why need to sort projects list by modified date + // sort by modified date, projects without modified date are put to last, NOTE: this would remove projects without modified date (if they become last items, before trimming list on next step) + projectsFound.Sort((x, y) => + { + if (x.Modified == null && y.Modified == null) return 0; // cannot be -1, https://stackoverflow.com/a/42821992/5452781 + if (x.Modified == null) return 1; + if (y.Modified == null) return -1; + return y.Modified.Value.CompareTo(x.Modified.Value); + //return x.Modified.Value.CompareTo(y.Modified.Value); // BUG this breaks something, so that last item platform is wrong (for project that is missing from disk) ? + }); + + // trim list to max amount + if (projectsFound.Count > MainWindow.maxProjectCount) + { + //Console.WriteLine("Trimming projects list to " + MainWindow.maxProjectCount + " projects"); + projectsFound.RemoveRange(MainWindow.maxProjectCount, projectsFound.Count - MainWindow.maxProjectCount); + } - var p = new Project(); + return projectsFound; + } // Scan() - switch (MainWindow.projectNameSetting) - { - case 0: - p.Title = Tools.ReadCustomProjectData(projectPath, MainWindow.projectNameFile); - break; - case 1: - p.Title = Tools.ReadProjectName(projectPath); - break; - default: - p.Title = projectName; - break; - } + static Project GetProjectInfo(string projectPath, bool getGitBranch = false, bool getPlasticBranch = false, bool getArguments = false, bool showMissingFolders = false, bool showTargetPlatform = false) + { + bool folderExists = Directory.Exists(projectPath); - // if no custom data or no product name found - if (string.IsNullOrEmpty(p.Title)) p.Title = projectName; + // if displaying missing folders are disabled, and folder doesnt exists, skip this project + if (showMissingFolders == false && folderExists == false) return null; + string projectName = ""; - p.Version = projectVersion; - p.Path = projectPath; - p.Modified = lastUpdated; - p.Arguments = customArgs; - p.GITBranch = gitBranch; - //Console.WriteLine("targetPlatform " + targetPlatform + " projectPath:" + projectPath); - p.TargetPlatform = targetPlatform; + // get project name from full path + if (projectPath.IndexOf(Path.DirectorySeparatorChar) > -1) + { + projectName = projectPath.Substring(projectPath.LastIndexOf(Path.DirectorySeparatorChar) + 1); + } + else if (projectPath.IndexOf(Path.AltDirectorySeparatorChar) > -1) + { + projectName = projectPath.Substring(projectPath.LastIndexOf(Path.AltDirectorySeparatorChar) + 1); + } + else // no path separator found + { + projectName = projectPath; + } - // bubblegum(TM) solution, fill available platforms for this unity version, for this project - p.TargetPlatforms = Tools.GetPlatformsForUnityVersion(projectVersion); + //Console.WriteLine("valueName="+ valueName+" , projectName =" + projectName); - p.folderExists = folderExists; + // get last modified date from folder + DateTime? lastUpdated = folderExists ? Tools.GetLastModifiedTime(projectPath) : null; - // if want to hide project and folder path for screenshot - //p.Title = "Example Project "; - //p.Path = "C:/Projects/ExamplePath/MyProj"; + // get project version + string projectVersion = folderExists ? Tools.GetProjectVersion(projectPath) : null; - projectsFound.Add(p); - } // valid key - } // each key - } // for each registry root + // get custom launch arguments, only if column in enabled + string customArgs = ""; + if (getArguments == true) + { + customArgs = folderExists ? Tools.ReadCustomProjectData(projectPath, MainWindow.launcherArgumentsFile) : null; + } - // NOTE sometimes projects are in wrong order, seems to be related to messing up your unity registry, the keys are received in created order (so if you had removed/modified them manually, it might return wrong order instead of 0 - 40) + // get git branchinfo, only if column in enabled + string gitBranch = ""; + if (getGitBranch == true) + { + gitBranch = folderExists ? Tools.ReadGitBranchInfo(projectPath) : null; + // check for plastic, if enabled + if (getPlasticBranch == true && gitBranch == null) + { + gitBranch = folderExists ? Tools.ReadPlasticBranchInfo(projectPath) : null; + } + } - return projectsFound; - } // Scan() + string targetPlatform = null; + if (showTargetPlatform == true) + { + targetPlatform = folderExists ? Tools.GetTargetPlatform(projectPath) : null; + //if (projectPath.Contains("Shader")) Console.WriteLine(projectPath + " targetPlatform=" + targetPlatform); + } + var p = new Project(); + switch (MainWindow.projectNameSetting) + { + case 0: + p.Title = Tools.ReadCustomProjectData(projectPath, MainWindow.projectNameFile); + break; + case 1: + p.Title = Tools.ReadProjectName(projectPath); + break; + default: + p.Title = projectName; + break; + } + + // if no custom data or no product name found + if (string.IsNullOrEmpty(p.Title)) p.Title = projectName; + + p.Version = projectVersion; + p.Path = projectPath; + p.Modified = lastUpdated; + p.Arguments = customArgs; + p.GITBranch = gitBranch; + //Console.WriteLine("targetPlatform " + targetPlatform + " projectPath:" + projectPath); + p.TargetPlatform = targetPlatform; + + // bubblegum(TM) solution, fill available platforms for this unity version, for this project + p.TargetPlatforms = Tools.GetPlatformsForUnityVersion(projectVersion); + p.folderExists = folderExists; + return p; + } + + // removes project from unity registry recent projects, OR if its our custom list, remove from there public static bool RemoveRecentProject(string projectPathToRemove) { + bool result = false; + var hklm = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); // check each version path @@ -192,13 +253,30 @@ public static bool RemoveRecentProject(string projectPathToRemove) { Console.WriteLine("Deleted registery item: " + valueName + " projectPath=" + projectPath); key.DeleteValue(valueName); - return true; + //return true; + result = true; + // TODO should break both loops + //break; + goto jumpCustomHistory; } } // valid key } // each key } // for each registry root - return false; + + jumpCustomHistory: + // check if its in our custom list, NOTE should only do if custom list is enabled + if (Properties.Settings.Default.projectPaths.Contains(projectPathToRemove)) + { + Properties.Settings.Default.projectPaths.Remove(projectPathToRemove); + Properties.Settings.Default.Save(); + Console.WriteLine("Deleted recent history item: " + projectPathToRemove + " from custom list"); + //return true; + result = true; + } + + + return result; } // RemoveRecentProject() } // class diff --git a/UnityLauncherPro/GetUnityInstallations.cs b/UnityLauncherPro/GetUnityInstallations.cs index dbfab05..cbae708 100644 --- a/UnityLauncherPro/GetUnityInstallations.cs +++ b/UnityLauncherPro/GetUnityInstallations.cs @@ -13,15 +13,15 @@ public static class GetUnityInstallations // returns unity installations - public static UnityInstallation[] Scan() + public static List Scan() { - // convert settings list to string array + // get list from settings var rootFolders = Properties.Settings.Default.rootFolders; // unityversion, exe_path List results = new List(); - // iterate all root folders + // iterate all folders under root folders foreach (string rootFolder in rootFolders) { // if folder exists @@ -59,6 +59,7 @@ public static UnityInstallation[] Scan() DateTime? installDate = Tools.GetLastModifiedTime(dataFolder); UnityInstallation unity = new UnityInstallation(); unity.Version = version; + unity.VersionCode = Tools.VersionAsLong(version); // cached version code unity.Path = exePath; unity.Installed = installDate; unity.IsPreferred = (version == MainWindow.preferredVersion); @@ -83,11 +84,31 @@ public static UnityInstallation[] Scan() } // all root folders // sort by version - results.Sort((s1, s2) => Tools.VersionAsInt(s2.Version).CompareTo(Tools.VersionAsInt(s1.Version))); + results.Sort((s1, s2) => s2.VersionCode.CompareTo(s1.VersionCode)); - return results.ToArray(); + return results; } // scan() + public static bool HasUnityInstallations(string path) + { + var directories = Directory.GetDirectories(path); + + // loop folders inside root + for (int i = 0, length = directories.Length; i < length; i++) + { + var editorFolder = Path.Combine(directories[i], "Editor"); + if (Directory.Exists(editorFolder) == false) continue; + + var editorExe = Path.Combine(editorFolder, "Unity.exe"); + if (File.Exists(editorExe) == false) continue; + + // have atleast 1 installation + return true; + } + + return false; + } + // scans unity installation folder for installed platforms static string[] GetPlatforms(string dataFolder) { diff --git a/UnityLauncherPro/GetUnityUpdates.cs b/UnityLauncherPro/GetUnityUpdates.cs index 64c1335..e5a9b89 100644 --- a/UnityLauncherPro/GetUnityUpdates.cs +++ b/UnityLauncherPro/GetUnityUpdates.cs @@ -44,26 +44,39 @@ public static async Task Scan() return result; } - public static Updates[] Parse(string items)// object sender, DownloadStringCompletedEventArgs e) + public static Updates[] Parse(string items) { isDownloadingUnityList = false; //SetStatus("Downloading list of Unity versions ... done"); var receivedList = items.Split(new[] { Environment.NewLine }, StringSplitOptions.None); if (receivedList == null && receivedList.Length < 1) return null; Array.Reverse(receivedList); - var updates = new List(); + var releases = new Dictionary(); // parse into data + string prevVersion = null; for (int i = 0, len = receivedList.Length; i < len; i++) { var row = receivedList[i].Split(','); var versionTemp = row[6].Trim('"'); - var u = new Updates(); - u.ReleaseDate = DateTime.ParseExact(row[3], "MM/dd/yyyy", CultureInfo.InvariantCulture); //DateTime ? lastUpdated = Tools.GetLastModifiedTime(csprojFile); - u.Version = versionTemp; - updates.Add(u); + + if (versionTemp.Length < 1) continue; + if (prevVersion == versionTemp) continue; + + if (releases.ContainsKey(versionTemp) == false) + { + var u = new Updates(); + u.ReleaseDate = DateTime.ParseExact(row[3], "MM/dd/yyyy", CultureInfo.InvariantCulture); + u.Version = versionTemp; + releases.Add(versionTemp, u); + } + + prevVersion = versionTemp; } - return updates.ToArray(); + // convert to array + var results = new Updates[releases.Count]; + releases.Values.CopyTo(results, 0); + return results; } } diff --git a/UnityLauncherPro/MainWindow.xaml b/UnityLauncherPro/MainWindow.xaml index c8feb0c..48e5cb1 100644 --- a/UnityLauncherPro/MainWindow.xaml +++ b/UnityLauncherPro/MainWindow.xaml @@ -7,466 +7,13 @@ xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:converters="clr-namespace:UnityLauncherPro.Converters" x:Class="UnityLauncherPro.MainWindow" mc:Ignorable="d" - Title="UnityLauncherPro" Height="650" Width="880" WindowStartupLocation="CenterScreen" Background="{DynamicResource ThemeDarkestBackground}" MinWidth="600" MinHeight="650" AllowsTransparency="True" WindowStyle="None" Margin="0" KeyDown="OnWindowKeyDown" Closing="Window_Closing" SizeChanged="Window_SizeChanged" Icon="Images/icon.ico" SourceInitialized="Window_SourceInitialized"> + Title="UnityLauncherPro" Height="650" Width="880" WindowStartupLocation="CenterScreen" Background="{DynamicResource ThemeDarkestBackground}" MinWidth="780" MinHeight="650" AllowsTransparency="True" WindowStyle="None" Margin="0" KeyDown="OnWindowKeyDown" Closing="Window_Closing" SizeChanged="Window_SizeChanged" Icon="Images/icon.ico" SourceInitialized="Window_SourceInitialized" MouseDown="Window_MouseDown"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - @@ -476,7 +23,13 @@ - + - + @@ -517,10 +70,10 @@ - - diff --git a/UnityLauncherPro/NewProject.xaml.cs b/UnityLauncherPro/NewProject.xaml.cs index b5f4bfa..c6a9d11 100644 --- a/UnityLauncherPro/NewProject.xaml.cs +++ b/UnityLauncherPro/NewProject.xaml.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -19,26 +20,52 @@ public partial class NewProject : Window int previousSelectedTemplateIndex = -1; int previousSelectedModuleIndex = -1; - public NewProject(string unityVersion, string suggestedName, string targetFolder) + static string targetFolder = null; + + public NewProject(string unityVersion, string suggestedName, string targetFolder, bool nameIsLocked = false) { isInitializing = true; InitializeComponent(); + NewProject.targetFolder = targetFolder; + // get version newVersion = unityVersion; newName = suggestedName; + txtNewProjectName.IsEnabled = !nameIsLocked; + txtNewProjectName.Text = newName; lblNewProjectFolder.Content = targetFolder; - // fill available versions, TODO could show which modules are installed - if (gridAvailableVersions.ItemsSource == null) gridAvailableVersions.ItemsSource = MainWindow.unityInstallationsSource; + // fill available versions + if (gridAvailableVersions.ItemsSource == null) + { + // get release type info (not done in mainwindow yet, to avoid doing extra stuff) + for (int i = 0, len = MainWindow.unityInstallationsSource.Count; i < len; i++) + { + var ver = MainWindow.unityInstallationsSource[i].Version; + if (Tools.IsLTS(ver)) + { + MainWindow.unityInstallationsSource[i].ReleaseType = "LTS"; + } + else if (Tools.IsAlpha(ver)) + { + MainWindow.unityInstallationsSource[i].ReleaseType = "Alpha"; + } + else if (Tools.IsBeta(ver)) + { + MainWindow.unityInstallationsSource[i].ReleaseType = "Beta"; + } + } + gridAvailableVersions.ItemsSource = MainWindow.unityInstallationsSource; + } // we have that version installed if (MainWindow.unityInstalledVersions.ContainsKey(unityVersion) == true) { // find this unity version, TODO theres probably easier way than looping all - for (int i = 0; i < MainWindow.unityInstallationsSource.Length; i++) + for (int i = 0; i < MainWindow.unityInstallationsSource.Count; i++) { if (MainWindow.unityInstallationsSource[i].Version == newVersion) { @@ -81,27 +108,49 @@ void UpdateModulesDropdown(string version) // get modules and stick into combobox, NOTE we already have this info from GetProjects.Scan, so could access it platformsForThisUnity = Tools.GetPlatformsForUnityVersion(version); cmbNewProjectPlatform.ItemsSource = platformsForThisUnity; - //System.Console.WriteLine(Tools.GetPlatformsForUnityVersion(version).Length); var lastUsedPlatform = Properties.Settings.Default.newProjectPlatform; for (int i = 0; i < platformsForThisUnity.Length; i++) { // set default platform (win64) if never used this setting before - if ((lastUsedPlatform == null && platformsForThisUnity[i].ToLower() == "win64") || platformsForThisUnity[i] == lastUsedPlatform) + if ((string.IsNullOrEmpty(lastUsedPlatform) && platformsForThisUnity[i].ToLower() == "win64") || platformsForThisUnity[i] == lastUsedPlatform) { cmbNewProjectPlatform.SelectedIndex = i; break; } } - //lblTemplateTitleAndCount.Content = "Templates: (" + (cmbNewProjectTemplate.Items.Count - 1) + ")"; - } - + // if nothing found, use win64 + if (cmbNewProjectPlatform.SelectedIndex == -1) + { + //cmbNewProjectPlatform.SelectedIndex = cmbNewProjectPlatform.Items.Count > 1 ? 1 : 0; + for (int i = 0; i < platformsForThisUnity.Length; i++) + { + if (platformsForThisUnity[i].ToLower() == "win64") + { + cmbNewProjectPlatform.SelectedIndex = i; + break; + } + } + // if still nothing, use first + if (cmbNewProjectPlatform.SelectedIndex == -1) cmbNewProjectPlatform.SelectedIndex = 0; + //lblTemplateTitleAndCount.Content = "Templates: (" + (cmbNewProjectTemplate.Items.Count - 1) + ")"; + } + } private void BtnCreateNewProject_Click(object sender, RoutedEventArgs e) { + // check if projectname already exists (only if should be automatically created name) + var targetPath = Path.Combine(targetFolder, txtNewProjectName.Text); + if (txtNewProjectName.IsEnabled == true && Directory.Exists(targetPath) == true) + { + System.Console.WriteLine("Project already exists"); + return; + } + + templateZipPath = ((KeyValuePair)cmbNewProjectTemplate.SelectedValue).Value; selectedPlatform = cmbNewProjectPlatform.SelectedValue.ToString(); UpdateSelectedVersion(); @@ -181,8 +230,28 @@ void UpdateSelectedVersion() } } - private void TxtNewProjectName_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) + private void TxtNewProjectName_TextChanged(object sender, TextChangedEventArgs e) { + if (isInitializing == true) return; + + // validate new projectname that it doesnt exists already + var targetPath = Path.Combine(targetFolder, txtNewProjectName.Text); + if (Directory.Exists(targetPath) == true) + { + System.Console.WriteLine("Project already exists"); + txtNewProjectName.BorderBrush = Brushes.Red; // not visible if focused + txtNewProjectName.ToolTip = "Project folder already exists"; + btnCreateNewProject.IsEnabled = false; + } + else + { + txtNewProjectName.BorderBrush = null; + btnCreateNewProject.IsEnabled = true; + txtNewProjectName.ToolTip = ""; + } + + //System.Console.WriteLine("newProjectName: " + txtNewProjectName.Text); + newProjectName = txtNewProjectName.Text; } @@ -214,7 +283,8 @@ private void GridAvailableVersions_SelectionChanged(object sender, SelectionChan // new row selected, generate new project name for this version var k = gridAvailableVersions.SelectedItem as UnityInstallation; newVersion = k.Version; - GenerateNewName(); + // no new name, if field is locked (because its folder name then) + if (txtNewProjectName.IsEnabled == true) GenerateNewName(); // update templates list for selected unity version UpdateTemplatesDropDown(k.Path); @@ -225,7 +295,7 @@ private void GridAvailableVersions_Loaded(object sender, RoutedEventArgs e) { // set initial default row color DataGridRow row = (DataGridRow)gridAvailableVersions.ItemContainerGenerator.ContainerFromIndex(gridAvailableVersions.SelectedIndex); - // if now unitys available + // if no unitys available if (row == null) return; //row.Background = Brushes.Green; row.Foreground = Brushes.White; @@ -242,5 +312,16 @@ private void CmbNewProjectPlatform_DropDownOpened(object sender, System.EventArg { previousSelectedModuleIndex = cmbNewProjectPlatform.SelectedIndex; } + + private void gridAvailableVersions_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) + { + // check that we clicked actually on a row + var src = VisualTreeHelper.GetParent((DependencyObject)e.OriginalSource); + var srcType = src.GetType(); + if (srcType == typeof(ContentPresenter)) + { + BtnCreateNewProject_Click(null, null); + } + } } } diff --git a/UnityLauncherPro/Properties/Resources.Designer.cs b/UnityLauncherPro/Properties/Resources.Designer.cs index c468495..ff3b0d0 100644 --- a/UnityLauncherPro/Properties/Resources.Designer.cs +++ b/UnityLauncherPro/Properties/Resources.Designer.cs @@ -8,10 +8,10 @@ // //------------------------------------------------------------------------------ -namespace UnityLauncherPro.Properties -{ - - +namespace UnityLauncherPro.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,51 +19,43 @@ namespace UnityLauncherPro.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", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UnityLauncherPro.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/UnityLauncherPro/Properties/Settings.Designer.cs b/UnityLauncherPro/Properties/Settings.Designer.cs index 145b9cc..bb8dca2 100644 --- a/UnityLauncherPro/Properties/Settings.Designer.cs +++ b/UnityLauncherPro/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace UnityLauncherPro.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.3.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -25,7 +25,7 @@ public static Settings Default { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("600")] + [global::System.Configuration.DefaultSettingValueAttribute("880")] public int windowWidth { get { return ((int)(this["windowWidth"])); @@ -47,20 +47,6 @@ public int windowHeight { } } - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("\r\n\r\n C:\\Program Files\\\r\n")] - public global::System.Collections.Specialized.StringCollection rootFolders { - get { - return ((global::System.Collections.Specialized.StringCollection)(this["rootFolders"])); - } - set { - this["rootFolders"] = value; - } - } - [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("True")] @@ -158,7 +144,7 @@ public bool showProjectsMissingFolder { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] + [global::System.Configuration.DefaultSettingValueAttribute("True")] public bool AllowSingleInstanceOnly { get { return ((bool)(this["AllowSingleInstanceOnly"])); @@ -206,7 +192,7 @@ public bool askNameForQuickProject { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] + [global::System.Configuration.DefaultSettingValueAttribute("True")] public bool enableProjectRename { get { return ((bool)(this["enableProjectRename"])); @@ -230,7 +216,7 @@ public bool streamerMode { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] + [global::System.Configuration.DefaultSettingValueAttribute("True")] public bool showTargetPlatform { get { return ((bool)(this["showTargetPlatform"])); @@ -278,7 +264,7 @@ public string themeFile { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] + [global::System.Configuration.DefaultSettingValueAttribute("True")] public bool enablePlatformSelection { get { return ((bool)(this["enablePlatformSelection"])); @@ -326,7 +312,7 @@ public string customDateFormat { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] + [global::System.Configuration.DefaultSettingValueAttribute("True")] public bool useHumandFriendlyLastModified { get { return ((bool)(this["useHumandFriendlyLastModified"])); @@ -408,7 +394,7 @@ public string templatePackagesFolder { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] + [global::System.Configuration.DefaultSettingValueAttribute("win64")] public string newProjectPlatform { get { return ((string)(this["newProjectPlatform"])); @@ -429,5 +415,116 @@ public bool searchProjectPathAlso { this["searchProjectPathAlso"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool checkPlasticBranch { + get { + return ((bool)(this["checkPlasticBranch"])); + } + set { + this["checkPlasticBranch"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string shortcutBatchFileFolder { + get { + return ((string)(this["shortcutBatchFileFolder"])); + } + set { + this["shortcutBatchFileFolder"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("\r\n\r\n C:\\Program Files\\\r\n")] + public global::System.Collections.Specialized.StringCollection rootFolders { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["rootFolders"])); + } + set { + this["rootFolders"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("InitializeProject.cs")] + public string customInitFile { + get { + return ((string)(this["customInitFile"])); + } + set { + this["customInitFile"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool useInitScript { + get { + return ((bool)(this["useInitScript"])); + } + set { + this["useInitScript"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("50000")] + public int webglPort { + get { + return ((int)(this["webglPort"])); + } + set { + this["webglPort"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("\r\n")] + public global::System.Collections.Specialized.StringCollection projectPaths { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["projectPaths"])); + } + set { + this["projectPaths"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool override40ProjectCount { + get { + return ((bool)(this["override40ProjectCount"])); + } + set { + this["override40ProjectCount"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("50")] + public int maxProjectCount { + get { + return ((int)(this["maxProjectCount"])); + } + set { + this["maxProjectCount"] = value; + } + } } } diff --git a/UnityLauncherPro/Properties/Settings.settings b/UnityLauncherPro/Properties/Settings.settings index 0ca3bb1..069a46a 100644 --- a/UnityLauncherPro/Properties/Settings.settings +++ b/UnityLauncherPro/Properties/Settings.settings @@ -3,17 +3,11 @@ - 600 + 880 650 - - <?xml version="1.0" encoding="utf-16"?> -<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> - <string>C:\Program Files\</string> -</ArrayOfString> - True @@ -39,7 +33,7 @@ False - False + True @@ -51,13 +45,13 @@ True - False + True False - False + True @@ -69,7 +63,7 @@ theme.ini - False + True False @@ -81,7 +75,7 @@ dd/MM/yyyy HH:mm:ss - False + True @@ -102,10 +96,41 @@ - + win64 False + + False + + + + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <string>C:\Program Files\</string> +</ArrayOfString> + + + InitializeProject.cs + + + False + + + 50000 + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" /> + + + True + + + 50 + \ No newline at end of file diff --git a/UnityLauncherPro/Resources/Colors.xaml b/UnityLauncherPro/Resources/Colors.xaml index 2b41a71..d6d27b1 100644 --- a/UnityLauncherPro/Resources/Colors.xaml +++ b/UnityLauncherPro/Resources/Colors.xaml @@ -32,7 +32,8 @@ - + + diff --git a/UnityLauncherPro/ThemeEditor.xaml b/UnityLauncherPro/ThemeEditor.xaml index d1291a2..956250e 100644 --- a/UnityLauncherPro/ThemeEditor.xaml +++ b/UnityLauncherPro/ThemeEditor.xaml @@ -18,191 +18,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/UnityLauncherPro/Tools.cs b/UnityLauncherPro/Tools.cs index 73968c7..10c7324 100644 --- a/UnityLauncherPro/Tools.cs +++ b/UnityLauncherPro/Tools.cs @@ -1,16 +1,18 @@ -using Microsoft.Win32; +using Microsoft.Win32; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; +using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Media; using UnityLauncherPro.Helpers; namespace UnityLauncherPro @@ -30,6 +32,9 @@ public static class Tools [DllImport("user32")] private static extern bool ShowWindow(IntPtr handle, int nCmdShow); + // reference to already running webgl server processes and ports + static Dictionary webglServerProcesses = new Dictionary(); + // returns last modified date for file (or null if cannot get it) public static DateTime? GetLastModifiedTime(string path) { @@ -123,12 +128,7 @@ public static string GetProjectVersion(string path) if (vertemp.IndexOf(".") > -1) version = vertemp; } - // if still nothing, take a quess based on yaml year info, lets say 2011 is unity 3.5 - if (string.IsNullOrEmpty(version) == true && data[1].ToLower().IndexOf("unity3d.com,2011") > -1) - { - version = "3.5.7f1"; - } - + // if still nothing, TODO probably could find closer version info, if know what features were added to playersettings.assets and checking serializedVersion: .. number } } } @@ -176,30 +176,56 @@ public static string GetFileVersionData(string path) return res; } - public static void ExploreProjectFolder(Project proj) + public static void ExploreFolder(string path) { - if (proj != null) + if (path != null) { - if (proj.Path != null) + if (LaunchExplorer(path) == false) { - if (LaunchExplorer(proj.Path) == false) - { - //SetStatus("Error> Directory not found: " + folder); - } + //SetStatus("Error> Directory not found: " + folder); + } + } + } + + // this runs before unity editor starts, so the project is not yet in registry (unless it already was there) + public static void AddProjectToHistory(string projectPath) + { + // fix backslashes + projectPath = projectPath.Replace('\\', '/'); + + if (Properties.Settings.Default.projectPaths.Contains(projectPath) == false) + { + // TODO do we need to add as first? + Properties.Settings.Default.projectPaths.Insert(0, projectPath); + + // remove last item, if too many + if (Properties.Settings.Default.projectPaths.Count > MainWindow.maxProjectCount) + { + Properties.Settings.Default.projectPaths.RemoveAt(Properties.Settings.Default.projectPaths.Count - 1); } + + //Console.WriteLine("AddProjectToHistory, count: " + Properties.Settings.Default.projectPaths.Count); + + // TODO no need to save everytime? + Properties.Settings.Default.Save(); + + // TODO need to add into recent grid also? if old items disappear? } } // NOTE holding alt key (when using alt+o) brings up unity project selector - public static Process LaunchProject(Project proj, DataGrid dataGridRef = null) + public static Process LaunchProject(Project proj, DataGrid dataGridRef = null, bool useInitScript = false, bool upgrade = false) { - // validate if (proj == null) return null; + + Console.WriteLine("Launching project " + proj?.Title + " at " + proj?.Path); + if (Directory.Exists(proj.Path) == false) return null; - Console.WriteLine("Launching project " + proj.Title + " at " + proj.Path); + // add this project to recent projects in preferences TODO only if enabled +40 projecs + AddProjectToHistory(proj.Path); - // check if this project path has unity already running? (from lock file or process) + // check if this project path has unity already running? (from process) // NOTE this check only works if previous unity instance was started while we were running if (ProcessHandler.IsRunning(proj.Path)) { @@ -227,17 +253,27 @@ public static Process LaunchProject(Project proj, DataGrid dataGridRef = null) return null; } + // if its upgrade, we dont want to check current version + if (upgrade == false) + { + // check if project version has changed? (list is not updated, for example pulled new version from git) + var version = GetProjectVersion(proj.Path); + if (string.IsNullOrEmpty(version) == false && version != proj.Version) + { + Console.WriteLine("Project version has changed from " + proj.Version + " to " + version); + proj.Version = version; + } + } + + // check if we have this unity version installed var unityExePath = GetUnityExePath(proj.Version); if (unityExePath == null) { - DisplayUpgradeDialog(proj, null); + DisplayUpgradeDialog(proj, null, useInitScript); return null; } - // SetStatus("Launching project in Unity " + version); - Process newProcess = new Process(); - try { var cmd = "\"" + unityExePath + "\""; @@ -257,10 +293,16 @@ public static Process LaunchProject(Project proj, DataGrid dataGridRef = null) unitycommandlineparameters += " -buildTarget " + projTargetPlatform; } + if (useInitScript == true) + { + unitycommandlineparameters += " -executeMethod UnityLauncherProTools.InitializeProject.Init"; + } + Console.WriteLine("Start process: " + cmd + " " + unitycommandlineparameters); newProcess.StartInfo.Arguments = unitycommandlineparameters; newProcess.EnableRaisingEvents = true; + //newProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; // needed for unity 2023 for some reason? (otherwise console popups briefly), Cannot use this, whole Editor is invisible then newProcess.Start(); if (Properties.Settings.Default.closeAfterProject) @@ -335,6 +377,7 @@ public static bool LaunchExplorer(string folder) { for (int i = folder.Length - 1; i > -1; i--) { + // TODO path.separator if (folder[i] == '/') { if (Directory.Exists(folder.Substring(0, i))) @@ -374,25 +417,30 @@ public static bool LaunchExplorerSelectFile(string fileName) return false; } - // run any exe - public static bool LaunchExe(string path, string param = null) + // run any exe, return process + public static Process LaunchExe(string path, string param = null) { - if (string.IsNullOrEmpty(path)) return false; + if (string.IsNullOrEmpty(path)) return null; - if (File.Exists(path) == true) + // not needed for exe's in PATH + //if (File.Exists(path) == true) { + Process newProcess = null; if (string.IsNullOrEmpty(param) == true) { - Console.WriteLine("LaunchExe=" + path); - Process.Start(path); + Console.WriteLine("LaunchExe= " + path); + newProcess = Process.Start(path); } else { - var newProcess = new Process(); + Console.WriteLine("LaunchExe= " + path + " param=" + param); + try { + newProcess = new Process(); newProcess.StartInfo.FileName = "\"" + path + "\""; newProcess.StartInfo.Arguments = param; + newProcess.EnableRaisingEvents = true; // needed to get Exited event newProcess.Start(); } catch (Exception e) @@ -400,9 +448,10 @@ public static bool LaunchExe(string path, string param = null) Console.WriteLine(e); } } - return true; + return newProcess; } - return false; + Console.WriteLine("Failed to run exe: " + path + " " + param); + return null; } @@ -413,31 +462,23 @@ public static string GetUnityReleaseURL(string version) string url = ""; if (VersionIsArchived(version) == true) { - // remove f# - version = Regex.Replace(version, @"f.", "", RegexOptions.IgnoreCase); + // remove f#, TODO should remove c# from china version ? + version = Regex.Replace(version, @"f[0-9]{1,2}", "", RegexOptions.IgnoreCase); string padding = "unity-"; string whatsnew = "whats-new"; if (version.Contains("5.6")) padding = ""; - if (version.Contains("2017.1")) whatsnew = "whatsnew"; if (version.Contains("2018.2")) whatsnew = "whatsnew"; if (version.Contains("2018.3")) padding = ""; if (version.Contains("2018.1")) whatsnew = "whatsnew"; if (version.Contains("2017.4.")) padding = ""; if (version.Contains("2018.4.")) padding = ""; - if (version.Contains("2019")) padding = ""; - if (version.Contains("2020")) padding = ""; - if (version.Contains("2021")) padding = ""; - if (version.Contains("2022")) padding = ""; - if (version.Contains("2023")) padding = ""; - if (version.Contains("2024")) padding = ""; - if (version.Contains("2025")) padding = ""; - if (version.Contains("2026")) padding = ""; - if (version.Contains("2027")) padding = ""; - if (version.Contains("2028")) padding = ""; - if (version.Contains("2029")) padding = ""; - if (version.Contains("2030")) padding = ""; + + // later versions seem to follow this + var year = int.Parse(version.Split('.')[0]); + if (year >= 2019) padding = ""; + url = "https://unity3d.com/unity/" + whatsnew + "/" + padding + version; } else @@ -481,8 +522,7 @@ public static bool VersionIsAlpha(string version) public static bool VersionIsChinese(string version) { - // return version.Contains("c1"); - return Regex.IsMatch(version, "c\\d+"); + return version.Contains("c1"); } // open release notes page in browser @@ -508,9 +548,9 @@ public static void DownloadInBrowser(string url, string version) { string exeURL = ParseDownloadURLFromWebpage(version); - Console.WriteLine("exeURL=" + exeURL); + Console.WriteLine("download exeURL= (" + exeURL + ")"); - if (string.IsNullOrEmpty(exeURL) == false) + if (string.IsNullOrEmpty(exeURL) == false && exeURL.StartsWith("https") == true) { //SetStatus("Download installer in browser: " + exeURL); Process.Start(exeURL); @@ -523,19 +563,101 @@ public static void DownloadInBrowser(string url, string version) } } + public static void DownloadAndInstall(string url, string version) + { + string exeURL = ParseDownloadURLFromWebpage(version); + + Console.WriteLine("download exeURL= (" + exeURL + ")"); + + if (string.IsNullOrEmpty(exeURL) == false && exeURL.StartsWith("https") == true) + { + //SetStatus("Download installer in browser: " + exeURL); + // download url file to temp + string tempFile = Path.GetTempPath() + "UnityDownloadAssistant-" + version.Replace(".", "_") + ".exe"; + //Console.WriteLine("download tempFile= (" + tempFile + ")"); + if (File.Exists(tempFile) == true) File.Delete(tempFile); + + // TODO make async + if (DownloadFile(exeURL, tempFile) == true) + { + // run installer, copy current existing version path to clipboard, NOTE this probably never happens? unless install same version again.. + if (MainWindow.unityInstalledVersions.ContainsKey(version) == true) + { + string path = MainWindow.unityInstalledVersions[version]; + if (string.IsNullOrEmpty(path) == false) + { + // copy to clipboard + Clipboard.SetText(path); + } + } + else // no same version, copy last item from root folders + { + if (Properties.Settings.Default.rootFolders.Count > 0) + { + string path = Properties.Settings.Default.rootFolders[Properties.Settings.Default.rootFolders.Count - 1]; + if (string.IsNullOrEmpty(path) == false) + { + Clipboard.SetText(path); + } + } + } + + Process process = Process.Start(tempFile); + process.EnableRaisingEvents = true; + process.Exited += (sender, e) => DeleteTempFile(tempFile); + // TODO refresh upgrade dialog after installer finished + } + } + else // not found + { + //SetStatus("Error> Cannot find installer executable ... opening website instead"); + url = "https://unity3d.com/get-unity/download/archive"; + Process.Start(url + "#installer-not-found---version-" + version); + } + } + + static void DeleteTempFile(string path) + { + if (File.Exists(path) == true) + { + Console.WriteLine("DeleteTempFile: " + path); + File.Delete(path); + } + } + + static bool DownloadFile(string url, string tempFile) + { + bool result = false; + try + { + using (WebClient client = new WebClient()) + { + client.DownloadFile(url, tempFile); + result = true; + } + } + catch (Exception e) + { + Console.WriteLine("Error> DownloadFile: " + e); + } + return result; + } + // parse Unity installer exe from release page // thanks to https://github.com/softfruit public static string ParseDownloadURLFromWebpage(string version) { string url = ""; + if (string.IsNullOrEmpty(version)) return null; + using (WebClient client = new WebClient()) { // get correct page url string website = "https://unity3d.com/get-unity/download/archive"; - if (Tools.VersionIsPatch(version)) website = "https://unity3d.com/unity/qa/patch-releases"; - if (Tools.VersionIsBeta(version)) website = "https://unity3d.com/unity/beta/" + version; - if (Tools.VersionIsAlpha(version)) website = "https://unity3d.com/unity/alpha/" + version; + if (VersionIsPatch(version)) website = "https://unity3d.com/unity/qa/patch-releases"; + if (VersionIsBeta(version)) website = "https://unity.com/releases/editor/beta/" + version; + if (VersionIsAlpha(version)) website = "https://unity.com/releases/editor/alpha/" + version; // fix unity server problem, some pages says 404 found if no url params website += "?unitylauncherpro"; @@ -593,9 +715,14 @@ public static string ParseDownloadURLFromWebpage(string version) { if (lines[i].Contains("UnityDownloadAssistant.exe")) { - int start = lines[i].IndexOf('"') + 1; - int end = lines[i].IndexOf('"', start); - url = lines[i].Substring(start, end - start) + "#version=" + version; + // parse download url from this html line + string pattern = @"https://beta\.unity3d\.com/download/[a-zA-Z0-9]+/UnityDownloadAssistant\.exe"; + Match match = Regex.Match(lines[i], pattern); + if (match.Success) + { + url = match.Value; + //Console.WriteLine("url= " + url); + } break; } } @@ -606,6 +733,7 @@ public static string ParseDownloadURLFromWebpage(string version) if (string.IsNullOrEmpty(url)) { //SetStatus("Cannot find UnityDownloadAssistant.exe for this version."); + Console.WriteLine("Installer not found from URL.."); } return url; } @@ -614,11 +742,11 @@ public static string FindNearestVersion(string currentVersion, List allA { string result = null; - // add current version to list + // add current version to list, to sort it with others allAvailable.Add(currentVersion); // sort list - allAvailable.Sort((s1, s2) => VersionAsInt(s2).CompareTo(VersionAsInt(s1))); + allAvailable.Sort((s1, s2) => VersionAsLong(s2).CompareTo(VersionAsLong(s1))); // check version above our current version int currentIndex = allAvailable.IndexOf(currentVersion); @@ -631,61 +759,36 @@ public static string FindNearestVersion(string currentVersion, List allA return result; } - // string to integer for sorting by version 2017.1.5f1 > 2017010501 - public static int VersionAsInt(string version) + // returns version as integer, for easier sorting between versions: 2019.4.19f1 = 2019041901 + public static long VersionAsLong(string version) { - int result = 0; - if (string.IsNullOrEmpty(version)) return result; + long result = 0; - // cleanup 32bit version name + // cleanup 32bit version name, TODO is this needed anymore? string cleanVersion = version.Replace("(32-bit)", ""); - // remove a,b,f,p - cleanVersion = cleanVersion.Replace("a", "."); - cleanVersion = cleanVersion.Replace("b", "."); - // cleanVersion = cleanVersion.Replace("c1", ""); - cleanVersion = Regex.Replace(cleanVersion, "c\\d+", ""); - cleanVersion = cleanVersion.Replace("f", "."); - cleanVersion = cleanVersion.Replace("p", "."); + // remove a (alpha),b (beta),f (final?),p (path),c (china final) + cleanVersion = cleanVersion.Replace("a", ".1."); + cleanVersion = cleanVersion.Replace("b", ".2."); + cleanVersion = cleanVersion.Replace("c", ".3."); // NOTE this was 'c1' + cleanVersion = cleanVersion.Replace("f", ".4."); + cleanVersion = cleanVersion.Replace("p", ".5."); // split values string[] splitted = cleanVersion.Split('.'); - if (splitted != null && splitted.Length > 0) + if (splitted.Length > 1) { - int multiplier = 1; - for (int i = 0, length = splitted.Length; i < length; i++) + long multiplier = 1; + for (long i = 0, length = splitted.Length; i < length; i++) { - int n = int.Parse(splitted[splitted.Length - 1 - i]); + long n = int.Parse(splitted[length - 1 - i]); result += n * multiplier; - multiplier *= 100; + multiplier *= 50; } } return result; } - private static string FindNearestVersionFromSimilarVersions(string version, IEnumerable allAvailable) - { - Dictionary stripped = new Dictionary(); - var enumerable = allAvailable as string[] ?? allAvailable.ToArray(); - - foreach (var t in enumerable) - { - stripped.Add(new Regex("[a-zA-z]").Replace(t, "."), t); - } - - var comparableVersion = new Regex("[a-zA-z]").Replace(version, "."); - if (!stripped.ContainsKey(comparableVersion)) - { - stripped.Add(comparableVersion, version); - } - - var comparables = stripped.Keys.OrderBy(x => x).ToList(); - var actualIndex = comparables.IndexOf(comparableVersion); - - if (actualIndex < stripped.Count - 1) return stripped[comparables[actualIndex + 1]]; - return null; - } - // https://stackoverflow.com/a/1619103/5452781 public static KeyValuePair GetEntry(this IDictionary dictionary, TKey key) { @@ -724,7 +827,19 @@ public static void HandleDataGridScrollKeys(object sender, KeyEventArgs e) */ } - public static void DisplayUpgradeDialog(Project proj, MainWindow owner) + // NOTE this doesnt modify the 2nd line in ProjectVersion.txt + static void SaveProjectVersion(Project proj) + { + var settingsPath = Path.Combine(proj.Path, "ProjectSettings", "ProjectVersion.txt"); + if (File.Exists(settingsPath)) + { + var versionRows = File.ReadAllLines(settingsPath); + versionRows[0] = "m_EditorVersion: " + proj.Version; + File.WriteAllLines(settingsPath, versionRows); + } + } + + public static void DisplayUpgradeDialog(Project proj, MainWindow owner, bool useInitScript = false) { UpgradeWindow modalWindow = new UpgradeWindow(proj.Version, proj.Path, proj.Arguments); modalWindow.ShowInTaskbar = owner == null; @@ -743,9 +858,12 @@ public static void DisplayUpgradeDialog(Project proj, MainWindow owner) // get selected version to upgrade for Console.WriteLine("Upgrade to " + upgradeToVersion); - // inject new version for this item + // inject new version for this item, TODO inject version to ProjectSettings file, so then no alert from unity wrong version dialog proj.Version = upgradeToVersion; - var proc = LaunchProject(proj); + SaveProjectVersion(proj); + var proc = LaunchProject(proj, dataGridRef: null, useInitScript: false, upgrade: true); + + // TODO update datagrid row for new version } else { @@ -823,10 +941,10 @@ public static void RemoveContextMenuRegistry(string contextRegRoot) public static string ReadGitBranchInfo(string projectPath) { string results = null; - DirectoryInfo gitDirectory = FindDir(".git", projectPath); - if (gitDirectory != null) + DirectoryInfo dirName = FindDir(".git", projectPath); + if (dirName != null) { - string branchFile = Path.Combine(gitDirectory.FullName, "HEAD"); + string branchFile = Path.Combine(dirName.FullName, "HEAD"); if (File.Exists(branchFile)) { // removes extra end of line @@ -839,6 +957,26 @@ public static string ReadGitBranchInfo(string projectPath) return results; } + public static string ReadPlasticBranchInfo(string projectPath) + { + string results = null; + DirectoryInfo dirName = FindDir(".plastic", projectPath); + if (dirName != null) + { + string branchFile = Path.Combine(dirName.FullName, "plastic.selector"); + if (File.Exists(branchFile)) + { + // removes extra end of line + results = string.Join(" ", File.ReadAllText(branchFile)); + // get branch only + int pos = results.LastIndexOf("\"/") + 1; + // -1 to remove last " + results = results.Substring(pos, results.Length - pos - 1); + } + } + return results; + } + //public static Platform GetTargetPlatform(string projectPath) static string GetTargetPlatformRaw(string projectPath) { @@ -993,12 +1131,12 @@ public static void SetFocusToGrid(DataGrid targetGrid, int index = -1) Keyboard.Focus(row); } - public static string BrowseForOutputFolder(string title) + public static string BrowseForOutputFolder(string title, string initialDirectory = null) { // https://stackoverflow.com/a/50261723/5452781 // Create a "Save As" dialog for selecting a directory (HACK) var dialog = new Microsoft.Win32.SaveFileDialog(); - //dialog.InitialDirectory = "c:"; // Use current value for initial dir + if (initialDirectory != null) dialog.InitialDirectory = initialDirectory; dialog.Title = title; dialog.Filter = "Project Folder|*.Folder"; // Prevents displaying files dialog.FileName = "Project"; // Filename will then be "select.this.directory" @@ -1018,7 +1156,8 @@ public static string BrowseForOutputFolder(string title) return null; } - public static Project FastCreateProject(string version, string baseFolder, string projectName = null, string templateZipPath = null, string[] platformsForThisUnity = null, string platform = null) + // TODO too many params.. + public static Project FastCreateProject(string version, string baseFolder, string projectName = null, string templateZipPath = null, string[] platformsForThisUnity = null, string platform = null, bool useInitScript = false, string initScriptPath = null) { // check for base folders in settings tab if (string.IsNullOrEmpty(baseFolder) == true) @@ -1045,8 +1184,6 @@ public static Project FastCreateProject(string version, string baseFolder, strin // if we didnt have name yet if (string.IsNullOrEmpty(projectName) == true) { - //Console.WriteLine("version=" + version); - //Console.WriteLine("baseFolder=" + baseFolder); projectName = GetSuggestedProjectName(version, baseFolder); // failed getting new path a-z if (projectName == null) return null; @@ -1062,6 +1199,21 @@ public static Project FastCreateProject(string version, string baseFolder, strin TarLib.Tar.ExtractTarGz(templateZipPath, newPath); } + // copy init file into project + if (useInitScript == true) + { + var initScriptFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Scripts", initScriptPath); + Console.WriteLine("initScriptFile: " + initScriptFile); + if (File.Exists(initScriptFile) == true) + { + var editorTargetFolder = Path.Combine(baseFolder, projectName, "Assets", "Editor"); + Console.WriteLine(editorTargetFolder); + if (Directory.Exists(editorTargetFolder) == false) Directory.CreateDirectory(editorTargetFolder); + var targetScriptFile = Path.Combine(editorTargetFolder, initScriptPath); + if (File.Exists(targetScriptFile) == false) File.Copy(initScriptFile, targetScriptFile); + } + } + // launch empty project var proj = new Project(); proj.Title = projectName; @@ -1070,8 +1222,10 @@ public static Project FastCreateProject(string version, string baseFolder, strin proj.TargetPlatforms = platformsForThisUnity; proj.TargetPlatform = platform; proj.Modified = DateTime.Now; - var proc = LaunchProject(proj); + + var proc = LaunchProject(proj, null, useInitScript); ProcessHandler.Add(proj, proc); + return proj; } // FastCreateProject @@ -1127,7 +1281,7 @@ public static string[] GetPlatformsForUnityVersion(string version) { // get platforms array for this unity version // TODO use dictionary instead of looping versions - for (int i = 0; i < MainWindow.unityInstallationsSource.Length; i++) + for (int i = 0; i < MainWindow.unityInstallationsSource.Count; i++) { if (MainWindow.unityInstallationsSource[i].Version == version) { @@ -1304,6 +1458,479 @@ public static void OpenAppdataSpecialFolder(string subfolder) } } + // NOTE android only at the moment + public static void BuildProject(Project proj, Platform platform) + { + Console.WriteLine("Building " + proj.Title + " for " + platform); + SetStatus("Build process started: " + DateTime.Now.ToString("HH:mm:ss")); + + // TODO use theme colors, keep list of multiple builds, if click status button show list of builds, if click for single build (show output folder) + SetBuildStatus(Colors.Red); + + if (string.IsNullOrEmpty(proj.Path)) return; + + // create builder script template (with template string, that can be replaced with project related paths or names?) + // copy editor build script to Assets/Editor/ folder (if already exists then what? Use UnityLauncherBuildSomething.cs name, so can overwrite..) + var editorScriptFolder = Path.Combine(proj.Path, "Assets", "Editor"); + if (Directory.Exists(editorScriptFolder) == false) Directory.CreateDirectory(editorScriptFolder); + // TODO check if creation failed + + // create output file for editor script + var editorScriptFile = Path.Combine(editorScriptFolder, "UnityLauncherProBuilder.cs"); + + // check build folder and create if missing + var outputFolder = Path.Combine(proj.Path, "Builds/" + platform + "/"); + outputFolder = outputFolder.Replace('\\', '/'); // fix backslashes + Console.WriteLine("outputFolder= " + outputFolder); + if (Directory.Exists(outputFolder) == false) Directory.CreateDirectory(outputFolder); + // TODO check if creation failed + + // cleanup filename from project name + var invalidChars = Path.GetInvalidFileNameChars(); + var outputFile = String.Join("_", proj.Title.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries)).TrimEnd('.'); + // replace spaces also, for old time(r)s + outputFile = outputFile.Replace(' ', '_'); + outputFile = Path.Combine(outputFolder, outputFile + ".apk"); + Console.WriteLine("outputFile= " + outputFile); + + // TODO move to txt resource? and later load from local custom file if exists, and later open window or add settings for build options + // TODO different unity versions? wont work in older unitys right now + var builderScript = @"using System.Linq; +using UnityEditor; +using UnityEngine; +public static class UnityLauncherProTools +{ + public static void BuildAndroid() + { + EditorUserBuildSettings.buildAppBundle = false; + EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; + PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP); + PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64; + var settings = new BuildPlayerOptions(); + settings.scenes = GetScenes(); + settings.locationPathName = ""###OUTPUTFILE###""; + settings.target = BuildTarget.Android; + settings.options = BuildOptions.None; + var report = BuildPipeline.BuildPlayer(settings); + } + public static void BuildiOS() // Note need to match platform name + { + PlayerSettings.iOS.targetDevice = iOSTargetDevice.iPhoneAndiPad; + var settings = new BuildPlayerOptions(); + settings.scenes = GetScenes(); + settings.locationPathName = ""###OUTPUTFOLDER###""; + settings.target = BuildTarget.iOS; + settings.options = BuildOptions.None; + var report = BuildPipeline.BuildPlayer(settings); + } + static string[] GetScenes() + { + return EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path).ToArray(); + } +}"; + + // fill in project specific data + builderScript = builderScript.Replace("###OUTPUTFILE###", outputFile); // android + builderScript = builderScript.Replace("###OUTPUTFOLDER###", outputFolder); // ios + Console.WriteLine("builderScript=" + builderScript); + + File.WriteAllText(editorScriptFile, builderScript); + // TODO check if write failed + + // get selected project unity exe path + var unityExePath = Tools.GetUnityExePath(proj.Version); + if (unityExePath == null) return; + + // create commandline string for building and launch it + //var buildcmd = $"\"{unityExePath}\" -quit -batchmode -nographics -projectPath \"{proj.Path}\" -executeMethod \"Builder.BuildAndroid\" -buildTarget android -logFile -"; + var buildParams = $" -quit -batchmode -nographics -projectPath \"{proj.Path}\" -executeMethod \"UnityLauncherProTools.Build{platform}\" -buildTarget {platform} -logFile \"{outputFolder}/../build.log\""; + Console.WriteLine("buildcmd= " + buildParams); + + // launch build + var proc = Tools.LaunchExe(unityExePath, buildParams); + + // wait for process exit then open output folder + proc.Exited += (o, i) => + { + Console.WriteLine("Build process exited: " + outputFolder); + Tools.ExploreFolder(outputFolder); + SetStatus("Build process finished: " + DateTime.Now.ToString("HH:mm:ss")); + // TODO set color based on results + SetBuildStatus(Colors.Green); + }; + + } + + // runs unity SimpleWebServer.exe and launches default Browser into project build/ folder' + public static void LaunchWebGL(Project proj, string relativeFolder) + { + var projPath = proj?.Path.Replace('/', '\\'); + if (string.IsNullOrEmpty(projPath) == true) return; + + var buildPath = Path.Combine(projPath, "Builds", relativeFolder); + if (Directory.Exists(buildPath) == false) return; + + if (MainWindow.unityInstalledVersions.ContainsKey(proj.Version) == false) return; + + // get mono and server exe paths + var editorPath = Path.GetDirectoryName(MainWindow.unityInstalledVersions[proj.Version]); + + var monoToolsPath = Path.Combine(editorPath, "Data/MonoBleedingEdge/bin"); + if (Directory.Exists(monoToolsPath) == false) return; + + var webglToolsPath = Path.Combine(editorPath, "Data/PlaybackEngines/WebGLSupport/BuildTools"); + if (Directory.Exists(webglToolsPath) == false) return; + + var monoExe = Path.Combine(monoToolsPath, "mono.exe"); + if (File.Exists(monoExe) == false) return; + + var webExe = Path.Combine(webglToolsPath, "SimpleWebServer.exe"); + if (File.Exists(webExe) == false) return; + + int port = MainWindow.webglPort; + if (port < 50000) port = 50000; + if (port > 65534) port = 65534; + + // check if this project already has server running and process is not closed + if (webglServerProcesses.ContainsKey(port) && webglServerProcesses[port].HasExited == false) + { + Console.WriteLine("Port found in cache: " + port + " process=" + webglServerProcesses[port]); + + // check if project matches + if (webglServerProcesses[port].StartInfo.Arguments.IndexOf("\"" + buildPath + "\"") > -1) + { + Console.WriteLine("this project already has webgl server running.. lets open browser url only"); + // then open browser url only + Tools.OpenURL("http://localhost:" + port); + return; + + } + else + { + Console.WriteLine("Port in use, but its different project: " + port); + Console.WriteLine(webglServerProcesses[port].StartInfo.Arguments + " == " + "\"" + buildPath + "\""); + + // then open new port and process + // ----------------------------------------------------------- + // check if port is available https://stackoverflow.com/a/2793289 + bool isAvailable = true; + IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + IPEndPoint[] objEndPoints = ipGlobalProperties.GetActiveTcpListeners(); + + // NOTE instead of iterating all ports, just try to open port, if fails, open next one + // compare with existing ports, if available + for (int i = 0; i < objEndPoints.Length; i++) + { + if (objEndPoints[i].Port == port) + { + port++; + if (port > 65534) + { + Console.WriteLine("Failed to find open port.."); + isAvailable = false; + return; + } + } + } + + Console.WriteLine("Found available port: " + port); + + if (isAvailable == false) + { + Console.WriteLine("failed to open port " + port + " (should be open already, or something else is using it?)"); + } + else + { + // take process id from unity, if have it (then webserver closes automatically when unity is closed) + var proc = ProcessHandler.Get(proj.Path); + int pid = proc == null ? -1 : proc.Id; + var param = "\"" + webExe + "\" \"" + buildPath + "\" " + port + (pid == -1 ? "" : " " + pid); // server exe path, build folder and port + + var webglServerProcess = Tools.LaunchExe(monoExe, param); + + if (webglServerProcesses.ContainsKey(port)) + { + Console.WriteLine("Error> Should not happen - this port is already in dictionary! port: " + port); + } + else // keep reference to this process on this port + { + // TODO how to remove process once its closed? (or unlikely to have many processes in total? can also remove during check, if process already null) + webglServerProcesses.Add(port, webglServerProcess); + Console.WriteLine("Added port " + port); + } + + Tools.OpenURL("http://localhost:" + port); + } + // ----------------------------------------------------------- + + } + } + else + { + Console.WriteLine("Port not running in cache or process already closed, remove it from cache: " + port); + if (webglServerProcesses.ContainsKey(port)) webglServerProcesses.Remove(port); + + // TODO remove duplicate code + // then open new process + // ----------------------------------------------------------- + // check if port is available https://stackoverflow.com/a/2793289 + bool isAvailable = true; + IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + IPEndPoint[] objEndPoints = ipGlobalProperties.GetActiveTcpListeners(); + + // compare with existing ports, if available + for (int i = 0; i < objEndPoints.Length; i++) + { + if (objEndPoints[i].Port == port) + { + if (port > 65535) + { + Console.WriteLine("Failed to find open port.."); + isAvailable = false; + return; + } + port++; + } + } + + Console.WriteLine("Found available port: " + port); + + if (isAvailable == false) + { + Console.WriteLine("failed to open port " + port + " (should be open already, or something else is using it?)"); + } + else + { + // take process id from unity, if have it(then webserver closes automatically when unity is closed) + var proc = ProcessHandler.Get(proj.Path); + int pid = proc == null ? -1 : proc.Id; + var param = "\"" + webExe + "\" \"" + buildPath + "\" " + port + (pid == -1 ? "" : " " + pid); // server exe path, build folder and port + + var webglServerProcess = Tools.LaunchExe(monoExe, param); + + if (webglServerProcess == null) + { + Console.WriteLine("Failed to start exe.."); + } + + if (webglServerProcesses.ContainsKey(port)) + { + Console.WriteLine("Error> Should not happen - this port is already in dictionary! port: " + port); + } + else // keep reference to this process on this port + { + // TODO how to remove process once its closed? (or unlikely to have many processes in total? can also remove during check, if process already null) + webglServerProcesses.Add(port, webglServerProcess); + Console.WriteLine("Added port " + port); + } + + Tools.OpenURL("http://localhost:" + port); + } + // ----------------------------------------------------------- + + } + } // LaunchWebGL() + + // creates .bat file to launch UnityLauncherPro and then .url link file on desktop, into that .bat file + public static bool CreateDesktopShortCut(Project proj, string batchFolder) + { + string lnkFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop)); + + if (string.IsNullOrEmpty(batchFolder)) return false; + + //string batchFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "UnityLauncherPro"); + if (Directory.Exists(batchFolder) == false) Directory.CreateDirectory(batchFolder); + string batFileName = Path.Combine(batchFolder, proj.Title + ".bat"); + string launcherExe = Process.GetCurrentProcess().MainModule.FileName; + string args = "-projectPath " + "\"" + proj.Path + "\" " + proj.Arguments; + string description = "Unity Project: " + proj.Title; + + // create .bat file + var batLauncherData = "start \"\" \"" + launcherExe + "\"" + " " + args; + File.WriteAllText(batFileName, batLauncherData); + + // create desktop link file + using (StreamWriter writer = new StreamWriter(lnkFileName + "\\" + proj.Title + ".url")) + { + writer.WriteLine("[InternetShortcut]"); + writer.WriteLine("URL=file:///" + batFileName); + //writer.WriteLine("ShowCommand=7"); // doesnt work for minimized + writer.WriteLine("IconIndex=0"); + writer.WriteLine("Arguments=-projectPath " + proj.Path); + // TODO maybe could take icon from project (but then need to convert into .ico) + string iconExe = GetUnityExePath(proj.Version); + if (iconExe == null) iconExe = launcherExe; + string icon = iconExe.Replace('\\', '/'); + writer.WriteLine("IconFile=" + icon); + } + + // TODO check for streamwriter and file write success + + return true; + } + + + internal static long GetFolderSizeInBytes(string currentBuildReportProjectPath) + { + // FIXME: 0 is not really correct for missing folder.. + if (Directory.Exists(currentBuildReportProjectPath) == false) return 0; + + return DirSize(new DirectoryInfo(currentBuildReportProjectPath)); + } + + // https://stackoverflow.com/a/468131/5452781 + static long DirSize(DirectoryInfo d) + { + long size = 0; + // Add file sizes. + FileInfo[] fis = d.GetFiles(); + foreach (FileInfo fi in fis) + { + size += fi.Length; + } + // Add subdirectory sizes. + DirectoryInfo[] dis = d.GetDirectories(); + foreach (DirectoryInfo di in dis) + { + size += DirSize(di); + } + return size; + } + + // Returns the human-readable file size for an arbitrary, 64-bit file size + // The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB" + internal static string GetBytesReadable(long i) + { + // Get absolute value + long absolute_i = (i < 0 ? -i : i); + // Determine the suffix and readable value + string suffix; + double readable; + if (absolute_i >= 0x1000000000000000) // Exabyte + { + suffix = "EB"; + readable = (i >> 50); + } + else if (absolute_i >= 0x4000000000000) // Petabyte + { + suffix = "PB"; + readable = (i >> 40); + } + else if (absolute_i >= 0x10000000000) // Terabyte + { + suffix = "TB"; + readable = (i >> 30); + } + else if (absolute_i >= 0x40000000) // Gigabyte + { + suffix = "GB"; + readable = (i >> 20); + } + else if (absolute_i >= 0x100000) // Megabyte + { + suffix = "MB"; + readable = (i >> 10); + } + else if (absolute_i >= 0x400) // Kilobyte + { + suffix = "KB"; + readable = i; + } + else + { + return i.ToString("0 B"); // Byte + } + // Divide by 1024 to get fractional value + readable = (readable / 1024); + // Return formatted number with suffix + return readable.ToString("0.### ") + suffix; + } + + public static MainWindow mainWindow; + + // set status bar in main thread + public static void SetStatus(string text) + { + mainWindow.Dispatcher.Invoke(() => { mainWindow.SetStatus(text); }); + } + + public static void SetBuildStatus(Color color) + { + mainWindow.Dispatcher.Invoke(() => { mainWindow.SetBuildStatus(color); }); + } + + // https://unity3d.com/unity/alpha + public static bool IsAlpha(string version) + { + if (string.IsNullOrEmpty(version)) return false; + return version.IndexOf("a", 0, StringComparison.CurrentCultureIgnoreCase) > -1; + } + + // https://unity3d.com/beta/ + public static bool IsBeta(string version) + { + if (string.IsNullOrEmpty(version)) return false; + return version.IndexOf("b", 0, StringComparison.CurrentCultureIgnoreCase) > -1; + } + + // https://unity3d.com/unity/qa/lts-releases + public static bool IsLTS(string versionRaw) + { + if (string.IsNullOrEmpty(versionRaw)) return false; + var version = versionRaw.Split('.'); + var versionInt = int.Parse(version[0]); + var versionMinor = int.Parse(version[1]); + return (versionInt >= 2017 && versionMinor == 4) || (versionInt > 2019 && versionMinor == 3); + } + + internal static void UninstallEditor(string path, string version) + { + if (string.IsNullOrEmpty(path)) return; + if (string.IsNullOrEmpty(version)) return; + + // run uninstaller from path + var installFolder = Path.GetDirectoryName(path); + var uninstaller = Path.Combine(installFolder, "Uninstall.exe"); + // TODO could be optional setting for non-silent uninstall + LaunchExe(uninstaller, "/S"); + // remove firewall settings + var cmd = "netsh advfirewall firewall delete rule name=all program=\"" + path + "\""; + Console.WriteLine("Cleanup firewall: " + cmd); + LaunchExe("cmd.exe", "/c " + cmd); + + if (int.Parse(version.Substring(0, 4)) <= 2017) + { + var nodeFolder = Path.Combine(installFolder, "Editor", "Data", "Tools", "nodejs", "node.exe"); + cmd = "netsh advfirewall firewall delete rule name=all program=\"" + nodeFolder + "\""; + Console.WriteLine("Cleanup firewall <= 2017: " + cmd); + LaunchExe("cmd.exe", "/c " + cmd); + } + // remove registry entries + var unityKeyName = "HKEY_CURRENT_USER\\Software\\Unity Technologies\\Installer\\Unity " + version; + cmd = "reg delete " + unityKeyName + " /f"; + Console.WriteLine("Removing registry key: " + cmd); + LaunchExe("cmd.exe", "/c " + cmd); + + // remove startmenu item + var startMenuFolder = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu); + var unityIcon = Path.Combine(startMenuFolder, "Unity " + version + "(64-bit)"); + if (Directory.Exists(unityIcon)) + { + Console.WriteLine("Removing startmenu folder: " + unityIcon); + Directory.Delete(unityIcon, true); + } + + // remove desktop icon + var desktopFolder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); + unityIcon = Path.Combine(startMenuFolder, "Unity " + version + ".lnk"); + if (File.Exists(unityIcon)) + { + Console.WriteLine("Removing desktop icon: " + unityIcon); + File.Delete(unityIcon); + } + + + } } // class -} // namespace +} // namespace diff --git a/UnityLauncherPro/UnityLauncherPro.csproj b/UnityLauncherPro/UnityLauncherPro.csproj index 005b7a8..3818e4f 100644 --- a/UnityLauncherPro/UnityLauncherPro.csproj +++ b/UnityLauncherPro/UnityLauncherPro.csproj @@ -8,7 +8,7 @@ WinExe UnityLauncherPro UnityLauncherPro - v4.6.1 + v4.8 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 @@ -29,6 +29,7 @@ false false true + AnyCPU @@ -52,8 +53,13 @@ Images/icon.ico + + + app.manifest + + @@ -75,7 +81,9 @@ MSBuild:Compile Designer + + @@ -146,6 +154,7 @@ ResXFileCodeGenerator Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/UnityLauncherPro/UpgradeWindow.xaml b/UnityLauncherPro/UpgradeWindow.xaml index 7dec75a..1e45905 100644 --- a/UnityLauncherPro/UpgradeWindow.xaml +++ b/UnityLauncherPro/UpgradeWindow.xaml @@ -5,196 +5,48 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:UnityLauncherPro" mc:Ignorable="d" - Title="Upgrade Project Version" Height="533.165" Width="418.684" Background="{DynamicResource ThemeDarkestBackground}" MinWidth="319" MinHeight="555" ResizeMode="NoResize" WindowStartupLocation="CenterOwner" HorizontalAlignment="Left" VerticalAlignment="Top" PreviewKeyDown="Window_PreviewKeyDown" ShowInTaskbar="False"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/UnityLauncherPro/UpgradeWindow.xaml.cs b/UnityLauncherPro/UpgradeWindow.xaml.cs index 4d93594..7953b89 100644 --- a/UnityLauncherPro/UpgradeWindow.xaml.cs +++ b/UnityLauncherPro/UpgradeWindow.xaml.cs @@ -20,16 +20,22 @@ public UpgradeWindow(string currentVersion, string projectPath, string commandLi InitializeComponent(); txtCurrentVersion.Text = currentVersion; gridAvailableVersions.ItemsSource = MainWindow.unityInstalledVersions; - gridAvailableVersions.SelectedItem = null; - // autoselect nearest one FIXME doesnt work with 5.x (should suggest next highest installed in 201x.x) + // we have current version info in project if (string.IsNullOrEmpty(currentVersion) == false) { // enable release and dl buttons btnOpenReleasePage.IsEnabled = true; btnDownload.IsEnabled = true; + // if dont have exact version, show red outline + if (MainWindow.unityInstalledVersions.ContainsKey(currentVersion) == false) + { + txtCurrentVersion.BorderBrush = Brushes.Red; + txtCurrentVersion.BorderThickness = new Thickness(1); + } + // find nearest version string nearestVersion = Tools.FindNearestVersion(currentVersion, MainWindow.unityInstalledVersions.Keys.ToList()); if (nearestVersion != null) @@ -48,7 +54,7 @@ public UpgradeWindow(string currentVersion, string projectPath, string commandLi } } } - else // we dont have current version + else // we dont have current version info in project { btnOpenReleasePage.IsEnabled = false; btnDownload.IsEnabled = false; @@ -110,6 +116,19 @@ private void BtnDownload_Click(object sender, RoutedEventArgs e) } } + private void btnInstall_Click(object sender, RoutedEventArgs e) + { + string url = Tools.GetUnityReleaseURL(txtCurrentVersion.Text); + if (string.IsNullOrEmpty(url) == false) + { + Tools.DownloadAndInstall(url, txtCurrentVersion.Text); + } + else + { + Console.WriteLine("Failed getting Unity Installer URL for " + txtCurrentVersion.Text); + } + } + private void Window_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { // override Enter for datagrid @@ -144,6 +163,12 @@ private void GridAvailableVersions_PreviewKeyDown(object sender, KeyEventArgs e) private void GridAvailableVersions_Loaded(object sender, RoutedEventArgs e) { Tools.SetFocusToGrid(gridAvailableVersions); + + // bolded for current item + DataGridRow row = (DataGridRow)((DataGrid)sender).ItemContainerGenerator.ContainerFromIndex(gridAvailableVersions.SelectedIndex); + if (row == null) return; + row.Foreground = Brushes.White; + row.FontWeight = FontWeights.Bold; } private void GridAvailableVersions_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) @@ -163,5 +188,6 @@ void Upgrade() DialogResult = true; } + } } diff --git a/UnityLauncherPro/app.manifest b/UnityLauncherPro/app.manifest new file mode 100644 index 0000000..7fb5c8b --- /dev/null +++ b/UnityLauncherPro/app.manifest @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PerMonitorV2 + true + + + + + + + + diff --git a/appveyor.yml b/appveyor.yml index 9c740a4..9ad0c9b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,10 @@ version: 1.0.{build} +branches: + only: + - dev skip_tags: true -image: Visual Studio 2017 +skip_branch_with_pr: true +image: Visual Studio 2022 configuration: Release only_commits: message: /#build/ @@ -16,3 +20,6 @@ deploy: auth_token: secure: kmYSrl7Mx/PFDGcyC5gS/vpW2UJCVguEXZsQ0LtkfwmSzx+3noZOyPrbZQ8uWX2B artifact: deploy + prerelease: true + on: + branch: dev \ No newline at end of file