using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using UnityLauncherPro.Helpers;
namespace UnityLauncherPro
{
public static class Tools
{
const int SW_RESTORE = 9;
[DllImport("user32", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindow(string cls, string win);
[DllImport("user32")]
static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[DllImport("user32")]
static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32")]
static extern bool OpenIcon(IntPtr hWnd);
[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)
{
if (File.Exists(path) == true || Directory.Exists(path) == true)
{
DateTime modification = File.GetLastWriteTime(path);
return modification;
}
else
{
return null;
}
}
///
/// parse project version from ProjectSettings/ data
///
/// project base path
///
public static string GetProjectVersion(string path)
{
string version = null;
if (File.Exists(Path.Combine(path, "ProjectVersionOverride.txt")))
{
version = File.ReadAllText(Path.Combine(path, "ProjectVersionOverride.txt"));
}
else if (Directory.Exists(Path.Combine(path, "ProjectSettings")))
{
var versionPath = Path.Combine(path, "ProjectSettings", "ProjectVersion.txt");
if (File.Exists(versionPath) == true) // 5.x and later
{
var data = File.ReadAllLines(versionPath);
if (data != null && data.Length > 0)
{
var dd = data[0];
// check string
if (dd.Contains("m_EditorVersion"))
{
var t = dd.Split(new string[] { "m_EditorVersion: " }, StringSplitOptions.None);
if (t != null && t.Length > 0)
{
version = t[1].Trim();
}
else
{
throw new InvalidDataException("invalid version data:" + data);
}
}
else
{
Console.WriteLine("Cannot find m_EditorVersion in '" + versionPath + "'.\n\nFile Content:\n" + string.Join("\n", data).ToString());
}
}
else
{
Console.WriteLine("Invalid projectversion data found in '" + versionPath + "'.\n\nFile Content:\n" + string.Join("\n", data).ToString());
}
}
else // maybe its 4.x?
{
versionPath = Path.Combine(path, "ProjectSettings", "ProjectSettings.asset");
if (File.Exists(versionPath) == true)
{
// first try if its ascii format
var data = File.ReadAllLines(versionPath);
if (data != null && data.Length > 0 && data[0].IndexOf("YAML") > -1) // we have ascii
{
// check library if available
var newVersionPath = Path.Combine(path, "Library", "AnnotationManager");
if (File.Exists(newVersionPath) == true)
{
versionPath = newVersionPath;
}
}
// try to get version data out from binary asset
var binData = File.ReadAllBytes(versionPath);
if (binData != null && binData.Length > 0)
{
int dataLen = 7;
int startIndex = 20;
var bytes = new byte[dataLen];
for (int i = 0; i < dataLen; i++)
{
bytes[i] = binData[startIndex + i];
}
var vertemp = Encoding.UTF8.GetString(bytes);
// probably failed if no dots
if (vertemp.IndexOf(".") > -1) version = vertemp;
}
// if still nothing, TODO probably could find closer version info, if know what features were added to playersettings.assets and checking serializedVersion: .. number
}
}
}
return version;
}
internal static string ReadProjectName(string projectPath)
{
string results = null;
var versionPath = Path.Combine(projectPath, "ProjectSettings", "ProjectSettings.asset");
if (File.Exists(versionPath) == true) // 5.x and later
{
var data = File.ReadAllLines(versionPath);
if (data != null && data.Length > 0)
{
for (int i = 0; i < data.Length; i++)
{
// check row
if (data[i].IndexOf("productName: ") > -1)
{
var t = data[i].Split(new string[] { "productName: " }, StringSplitOptions.None);
if (t != null && t.Length > 0)
{
results = t[1].Trim();
break;
}
else
{
throw new InvalidDataException("invalid productName data:" + data);
}
}
}
}
}
return results;
}
// returns unity version number string from file
public static string GetFileVersionData(string path)
{
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(path);
var ver = fvi.ProductName;
if (string.IsNullOrEmpty(ver) == true)
{
ver = fvi.FileDescription;
if (string.IsNullOrEmpty(ver) == true) return null;
ver = ver.Replace("Installer", "").Trim();
}
var res = ver.Replace("(64-bit)", "").Replace("(32-bit)", "").Replace("Unity", "").Trim();
return res;
}
public static void ExploreFolder(string path)
{
if (path != null)
{
if (LaunchExplorer(path) == false)
{
//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, bool useInitScript = false, bool upgrade = false)
{
if (proj == null) return null;
Console.WriteLine("Launching project " + proj?.Title + " at " + proj?.Path);
if (Directory.Exists(proj.Path) == false) return null;
// 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 process)
// NOTE this check only works if previous unity instance was started while we were running
if (ProcessHandler.IsRunning(proj.Path))
{
Console.WriteLine("Project is already running, lets not launch unity.. because it opens Hub");
BringProcessToFront(ProcessHandler.Get(proj.Path));
return null;
}
else
{
// TODO check lock file?
}
// there is no assets path, probably we want to create new project then
var assetsFolder = Path.Combine(proj.Path, "Assets");
if (Directory.Exists(assetsFolder) == false)
{
// TODO could ask if want to create project..?
Directory.CreateDirectory(assetsFolder);
}
// when opening project, check for crashed backup scene first
var cancelLaunch = CheckCrashBackupScene(proj.Path);
if (cancelLaunch == true)
{
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, useInitScript);
return null;
}
Process newProcess = new Process();
try
{
var cmd = "\"" + unityExePath + "\"";
newProcess.StartInfo.FileName = cmd;
var unitycommandlineparameters = " -projectPath " + "\"" + proj.Path + "\"";
string customArguments = proj.Arguments;
if (string.IsNullOrEmpty(customArguments) == false)
{
unitycommandlineparameters += " " + customArguments;
}
string projTargetPlatform = proj.TargetPlatform;
if (string.IsNullOrEmpty(projTargetPlatform) == false)
{
unitycommandlineparameters += " -buildTarget " + projTargetPlatform;
}
if (useInitScript == true)
{
unitycommandlineparameters += " -executeMethod UnityLauncherProTools.InitializeProject.Init";
}
Console.WriteLine("Start process: " + cmd + " " + unitycommandlineparameters);
// TODO load custom settings per project
//string userSettingsFolder = Path.Combine(proj.Path, "UserSettings");
//string userSettingsPath = Path.Combine(userSettingsFolder, "ULPSettings.txt");
//if (File.Exists(userSettingsPath))
//{
// var rawSettings = File.ReadAllLines(userSettingsPath);
// // needed for env vars.
// newProcess.StartInfo.UseShellExecute = false;
// foreach (var row in rawSettings)
// {
// var split = row.Split('=');
// if (split.Length == 2)
// {
// var key = split[0].Trim();
// var value = split[1].Trim();
// if (string.IsNullOrEmpty(key) == false && string.IsNullOrEmpty(value) == false)
// {
// //Console.WriteLine("key: " + key + " value: " + value);
// //newProcess.StartInfo.EnvironmentVariables[key] = value;
// //System.Environment.SetEnvironmentVariable(key, value, EnvironmentVariableTarget.Machine);
// var dict = newProcess.StartInfo.EnvironmentVariables;
// // print all
// foreach (System.Collections.DictionaryEntry de in dict)
// {
// Console.WriteLine(" {0} = {1}", de.Key, de.Value);
// }
// // check if key exists
// if (dict.ContainsKey(key) == true)
// {
// // modify existing
// //dict[key] = value;
// newProcess.StartInfo.EnvironmentVariables.Remove(key);
// newProcess.StartInfo.EnvironmentVariables.Add(key, value);
// }
// else
// {
// // add new
// dict.Add(key, value);
// }
// //newProcess.StartInfo.EnvironmentVariables.
// //if (newProcess.StartInfo.EnvironmentVariables.ContainsKey(key))
// //{
// // Console.WriteLine("exists: "+key);
// // // Test Modify the existing environment variable
// // newProcess.StartInfo.EnvironmentVariables[key] = "...";
// // this works, maybe because its not a system variable?
// //newProcess.StartInfo.EnvironmentVariables["TESTTEST"] = "...";
// //}
// //else
// //{
// // Console.WriteLine("add new: "+ value);
// // // Optionally, add the environment variable if it does not exist
// // newProcess.StartInfo.EnvironmentVariables.Add(key, value);
// //}
// Console.WriteLine("custom row: " + row + " key=" + key + " value:" + value);
// }
// }
// }
//}
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)
{
Environment.Exit(0);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
// NOTE move project as first, since its opened, disabled for now, since its too jumpy..
//MainWindow wnd = (MainWindow)Application.Current.MainWindow;
//wnd.MoveRecentGridItem(0);
ProcessHandler.Add(proj, newProcess);
return newProcess;
}
static bool CheckCrashBackupScene(string projectPath)
{
var cancelRunningUnity = false;
var recoveryFile = Path.Combine(projectPath, "Temp", "__Backupscenes", "0.backup");
if (File.Exists(recoveryFile))
{
var result = MessageBox.Show("Crash recovery scene found, do you want to copy it into Assets/_Recovery/-folder?", "UnityLauncherPro - Scene Recovery", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
var restoreFolder = Path.Combine(projectPath, "Assets", "_Recovery");
if (Directory.Exists(restoreFolder) == false)
{
Directory.CreateDirectory(restoreFolder);
}
if (Directory.Exists(restoreFolder) == true)
{
Int32 unixTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
var uniqueFileName = "Recovered_Scene" + unixTimestamp + ".unity";
File.Copy(recoveryFile, Path.Combine(restoreFolder, uniqueFileName));
Console.WriteLine("Recovered crashed scene into: " + restoreFolder);
}
else
{
Console.WriteLine("Error: Failed to create restore folder: " + restoreFolder);
cancelRunningUnity = true;
}
}
else if (result == MessageBoxResult.Cancel) // dont do restore, but run Unity
{
cancelRunningUnity = true;
}
}
return cancelRunningUnity;
}
public static string GetUnityExePath(string version)
{
if (string.IsNullOrEmpty(version) == true) return null;
return MainWindow.unityInstalledVersions.ContainsKey(version) ? MainWindow.unityInstalledVersions[version] : null;
}
// opens Explorer to target folder
public static bool LaunchExplorer(string folder)
{
if (Directory.Exists(folder) == true)
{
Process.Start(folder);
return true;
}
else // original folder is missing, try to find parent folder that we can go into
{
for (int i = folder.Length - 1; i > -1; i--)
{
// TODO path.separator
if (folder[i] == '/')
{
if (Directory.Exists(folder.Substring(0, i)))
{
Process.Start(folder.Substring(0, i) + "/");
break;
}
}
}
}
return false;
}
public static bool LaunchExplorerSelectFile(string fileName)
{
if (File.Exists(fileName) == true)
{
fileName = Path.GetFullPath(fileName);
Process.Start("explorer.exe", string.Format("/select,\"{0}\"", fileName));
return true;
}
else // file is missing, try to find parent folder that we can go into
{
for (int i = fileName.Length - 1; i > -1; i--)
{
if (fileName[i] == '/')
{
if (Directory.Exists(fileName.Substring(0, i)))
{
Process.Start(fileName.Substring(0, i) + "/");
break;
}
}
}
}
return false;
}
// run any exe, return process
public static Process LaunchExe(string path, string param = null, bool captureOutput = false)
{
if (string.IsNullOrEmpty(path)) return null;
// not needed for exe's in PATH
//if (File.Exists(path) == true)
{
Process newProcess = null;
if (string.IsNullOrEmpty(param) == true)
{
Console.WriteLine("LaunchExe= " + path);
newProcess = Process.Start(path);
}
else
{
Console.WriteLine("LaunchExe= " + path + " param=" + param);
try
{
newProcess = new Process();
newProcess.StartInfo.FileName = "\"" + path + "\"";
newProcess.StartInfo.Arguments = param;
if (captureOutput)
{
newProcess.StartInfo.RedirectStandardError = true;
newProcess.StartInfo.RedirectStandardOutput = true;
newProcess.StartInfo.UseShellExecute = false;
}
newProcess.EnableRaisingEvents = true; // needed to get Exited event
newProcess.Start();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
return newProcess;
}
// Console.WriteLine("Failed to run exe: " + path + " " + param);
// return null;
}
public static string GetUnityReleaseURL(string version)
{
if (string.IsNullOrEmpty(version)) return null;
var cleanVersion = CleanVersionNumber(version);
string url = $"https://unity.com/releases/editor/whats-new/{cleanVersion}#notes";
//if (VersionIsArchived(version) == true)
//{
// // 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("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 = "";
// // 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
//if (VersionIsPatch(version) == true)
//{
// url = "https://unity3d.com/unity/qa/patch-releases/" + version;
//}
//else
//if (VersionIsBeta(version) == true)
//{
// url = "https://unity3d.com/unity/beta/" + version;
//}
//else
//if (VersionIsAlpha(version) == true)
//{
// url = "https://unity3d.com/unity/alpha/" + version;
//}
return url;
}
// if version contains *f* its archived version
public static bool VersionIsArchived(string version)
{
return version.Contains("f");
}
public static bool VersionIsPatch(string version)
{
return version.Contains("p");
}
public static bool VersionIsBeta(string version)
{
return version.Contains("b");
}
public static bool VersionIsAlpha(string version)
{
return version.Contains("a");
}
public static bool VersionIsChinese(string version)
{
return version.Contains("c1");
}
//as of 21 May 2021, only final 'f' versions are now available on the alpha release notes for Unity 2018 and newer. 2017 and 5 still have patch 'p' versions as well.
public static bool HasAlphaReleaseNotes(string version) => VersionIsArchived(version) || VersionIsPatch(version);
public static string GetAlphaReleaseNotesURL(string fromVersion, string toVersion = null)
=> "https://alpha.release-notes.ds.unity3d.com/search?fromVersion=" + fromVersion + "&toVersion=" + (toVersion != null ? toVersion : fromVersion);
// open release notes page in browser
public static bool OpenReleaseNotes(string version)
{
bool result = false;
if (string.IsNullOrEmpty(version)) return false;
string url = null;
if (Properties.Settings.Default.useAlphaReleaseNotes && HasAlphaReleaseNotes(version))
{
url = GetAlphaReleaseNotesURL(version);
}
else
{
url = GetUnityReleaseURL(version);
}
if (string.IsNullOrEmpty(url)) return false;
OpenURL(url);
result = true;
return result;
}
public static bool OpenReleaseNotes_Cumulative(string version)
{
bool result = false;
if (string.IsNullOrEmpty(version)) return false;
string url = null;
var comparisonVersion = version;
//with the alpha release notes, we want a diff between an installed version and the one selected, but the site just shows all the changes inclusive of "fromVersion=vers"
//so if we find a good installed candidate, we need the version just above it (installed or not) that has release notes page
var closestInstalledVersion = Tools.FindNearestVersion(version, MainWindow.unityInstalledVersions.Keys.ToList(), true);
if (closestInstalledVersion != null)
{
comparisonVersion = closestInstalledVersion;
string nextFinalVersionAfterInstalled = closestInstalledVersion;
//wwe need a loop here, to find the nearest final version. It might be better to warn the user about this before opening the page.
do
nextFinalVersionAfterInstalled = Tools.FindNearestVersion(nextFinalVersionAfterInstalled, MainWindow.updatesAsStrings);
while (nextFinalVersionAfterInstalled != null && !HasAlphaReleaseNotes(nextFinalVersionAfterInstalled));
if (nextFinalVersionAfterInstalled != null) comparisonVersion = nextFinalVersionAfterInstalled;
}
url = GetAlphaReleaseNotesURL(comparisonVersion, version);
OpenURL(url);
result = true;
return result;
}
public static void OpenURL(string url)
{
Process.Start(url);
}
public static async void DownloadInBrowser(string version, bool preferFullInstaller = false)
{
if (version == null) return;
string exeURL = await GetUnityUpdates.FetchDownloadUrl(version);
// null from unity api? then try direct download
// https://beta.unity3d.com/download/330fbefc18b7/UnityDownloadAssistant-6000.1.0a8.exe
if (exeURL == null)
{
Console.WriteLine("TODO DownloadInBrowser");
}
if (preferFullInstaller == true)
{
exeURL = exeURL.Replace("UnityDownloadAssistant-" + version + ".exe", "Windows64EditorInstaller/UnitySetup64-" + version + ".exe");
}
Console.WriteLine("DownloadInBrowser exeURL= '" + exeURL + "'");
if (string.IsNullOrEmpty(exeURL) == false && exeURL.StartsWith("https"))
{
//SetStatus("Download installer in browser: " + exeURL);
Process.Start(exeURL);
}
else // not found
{
//SetStatus("Error> Cannot find installer executable ... opening website instead");
const string url = "https://unity3d.com/get-unity/download/archive";
Process.Start(url + "#installer-not-found-for-version-" + version);
}
}
public static async void DownloadAndInstall(string version)
{
if (version == null)
{
Console.WriteLine("Error> Cannot download and install null version");
return;
}
string exeURL = await GetUnityUpdates.FetchDownloadUrl(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 (await DownloadFileAsync(exeURL, tempFile))
{
// get base version, to use for install path
// FIXME check if have any paths?
string lastRootFolder = Properties.Settings.Default.rootFolders[Properties.Settings.Default.rootFolders.Count - 1];
// check if ends with / or \
if (lastRootFolder.EndsWith("/") == false && lastRootFolder.EndsWith("\\") == false) lastRootFolder += "\\";
string outputVersionFolder = version.Split('.')[0] + "_" + version.Split('.')[1];
string targetPathArgs = " /D=" + lastRootFolder + outputVersionFolder; ;
// if user clicks NO to UAC, this fails (so added try-catch)
try
{
Process process = new Process();
process.StartInfo.FileName = tempFile;
process.StartInfo.Arguments = targetPathArgs;
process.EnableRaisingEvents = true;
process.Exited += (sender, e) => DeleteTempFile(tempFile);
process.Start();
}
catch (Exception)
{
Console.WriteLine("Failed to run exe: " + tempFile);
DeleteTempFile(tempFile);
}
// TODO refresh upgrade dialog after installer finished
}
}
else // not found
{
//SetStatus("Error> Cannot find installer executable ... opening website instead");
var url = "https://unity3d.com/get-unity/download/archive";
Process.Start(url + "#installer-not-found---version-" + version);
}
}
static readonly string initFileDefaultURL = "https://raw.githubusercontent.com/unitycoder/UnityInitializeProject/main/Assets/Editor/InitializeProject.cs";
public static async Task DownloadInitScript(string currentInitScriptFullPath, string currentInitScriptLocationOrURL)
{
string currentInitScriptFolder = Path.GetDirectoryName(currentInitScriptFullPath);
string currentInitScriptFile = Path.GetFileName(currentInitScriptFullPath);
string tempFile = Path.Combine(Path.GetTempPath(), currentInitScriptFile);
bool isLocalFile = false;
if (string.IsNullOrEmpty(currentInitScriptLocationOrURL) == true) currentInitScriptLocationOrURL = initFileDefaultURL;
// check if its URL or local file
if (currentInitScriptLocationOrURL.ToLower().StartsWith("http") == true)
{
// download into temp first
if (await DownloadFileAsync(currentInitScriptLocationOrURL, tempFile) == false) return;
}
else // file is in local folders/drives/projects
{
// check if file exists
if (File.Exists(currentInitScriptLocationOrURL) == false) return;
tempFile = currentInitScriptLocationOrURL;
isLocalFile = true;
}
// if got file
if (File.Exists(tempFile) == true)
{
// just in case file is locked
try
{
// small validation to check if its valid editor script
var tempContent = File.ReadAllText(tempFile);
if (tempContent.IndexOf("public class InitializeProject") > 0 && tempContent.IndexOf("namespace UnityLauncherProTools") > 0 && tempContent.IndexOf("public static void Init()") > 0)
{
// create scripts folder if missing
if (Directory.Exists(currentInitScriptFolder) == false) Directory.CreateDirectory(currentInitScriptFolder);
// move old file as backup
if (File.Exists(currentInitScriptFullPath))
{
string oldScriptFullPath = Path.Combine(currentInitScriptFolder, currentInitScriptFile + ".bak");
if (File.Exists(oldScriptFullPath)) File.Delete(oldScriptFullPath);
File.Move(currentInitScriptFullPath, oldScriptFullPath);
}
// move new file here (need to delete old to overwrite)
if (File.Exists(currentInitScriptFullPath)) File.Delete(currentInitScriptFullPath);
// local file copy, not move
if (isLocalFile == true)
{
File.Copy(tempFile, currentInitScriptFullPath);
}
else
{
File.Move(tempFile, currentInitScriptFullPath);
}
}
else
{
Console.WriteLine("Invalid c# init file..(missing correct Namespace, Class or Method)");
}
}
catch (Exception e)
{
Console.WriteLine("File exception: " + e.Message);
}
}
else
{
Console.WriteLine("Failed to download init script from: " + currentInitScriptLocationOrURL);
}
}
static void DeleteTempFile(string path)
{
if (File.Exists(path) == true)
{
Console.WriteLine("DeleteTempFile: " + path);
File.Delete(path);
}
}
public static string DownloadHTML(string url)
{
Console.WriteLine("DownloadHTML: " + url);
if (string.IsNullOrEmpty(url) == true) return null;
using (WebClient client = new WebClient())
{
try
{
// download page html
return client.DownloadString(url);
}
catch (WebException e)
{
Console.WriteLine("DownloadHTML: " + e.Message);
return null;
}
}
}
public static string CleanVersionNumber(string version)
{
if (string.IsNullOrEmpty(version)) return null;
var split = version.Split('.');
float parsedVersion = float.Parse($"{split[0]}.{split[1]}");
// 2023.3 and newer Alpha releases, no replace
if (IsAlpha(version) && parsedVersion >= 2023.3)
{
// do nothing
}
else
{
// note old patch versions still contains p## in the end
version = Regex.Replace(version, @"[f|a|b][0-9]{1,2}", "", RegexOptions.IgnoreCase);
}
return version;
}
// TODO only hash version is used, cleanup the rest
public static string ParseDownloadURLFromWebpage(string version, string hash = null, bool preferFullInstaller = false, bool useHash = false)
{
string exeURL = "";
//Console.WriteLine("ParseDownloadURLFromWebpage: " + version + ", hash: " + useHash);
if (string.IsNullOrEmpty(version)) return null;
// NOTE no longer uses f# in the end
string url = null;
if (useHash == false)
{
var cleanVersion = CleanVersionNumber(version);
// NOTE 2024 June, installs are now located on separate pages, like https://unity.com/releases/editor/whats-new/6000.0.5#installs
// get correct page url
//url = "https://unity3d.com/get-unity/download/archive";
// fix unity server problem, some pages says 404 found if no url params
url = "https://unity.com/releases/editor/whats-new/" + cleanVersion + "?unitylauncherpro#installs";
//if (VersionIsPatch(version)) url = "https://unity3d.com/unity/qa/patch-releases";
if (VersionIsBeta(version)) url = "https://unity.com/releases/editor/beta/" + version;
if (VersionIsAlpha(version)) url = "https://unity.com/releases/editor/alpha/" + version;
//url += "?unitylauncherpro";
}
else
{
// NOTE version here is actually VERSION|HASH
//string hash = version;
url = $"https://beta.unity3d.com/download/{hash}/download.html";
//Console.WriteLine("hashurl: " + url);
//version = FetchUnityVersionNumberFromHTML(url);
//Console.WriteLine(url);
//Console.WriteLine("got "+version);
if (string.IsNullOrEmpty(version))
{
SetStatus("Failed to get version (" + version + ") number from hash: " + hash);
return null;
}
}
//Console.WriteLine("scanning installers from url: " + url);
//string sourceHTML = DownloadHTML(url);
//if (string.IsNullOrEmpty(sourceHTML) == true)
//{
// Console.WriteLine("Failed to download html from: " + url);
// return null;
//}
//// parse changeset hash from html
//string pattern = $@"href=""unityhub://{version}/([^""]+)""";
//Regex regex = new Regex(pattern);
//Match match = regex.Match(sourceHTML);
//if (match.Success == true)
//{
// string changeSet = match.Groups[1].Value;
// Console.WriteLine("changeSet: " + changeSet);
//}
exeURL = $"https://beta.unity3d.com/download/{hash}/UnityDownloadAssistant-{version}.exe";
//string[] lines = sourceHTML.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
// patch version download assistant finder
//if (useHash == false && VersionIsPatch(version))
//{
// for (int i = 0; i < lines.Length; i++)
// {
// //if (lines[i].Contains("UnityDownloadAssistant-" + version + ".exe"))
// if (lines[i].Contains("UnitySetup64-" + version + ".exe"))
// {
// int start = lines[i].IndexOf('"') + 1;
// int end = lines[i].IndexOf('"', start);
// exeURL = lines[i].Substring(start, end - start);
// break;
// }
// }
//}
//else if (useHash == false && VersionIsArchived(version))
//{
// // archived version download assistant finder
// for (int i = 0; i < lines.Length; i++)
// {
// // find line where full installer is (from archive page)
// if (lines[i].Contains("UnitySetup64-" + version))
// {
// Console.WriteLine(lines[i]);
// // take full exe installer line, to have changeset hash, then replace with download assistant filepath
// string line = lines[i];
// int start = line.IndexOf('"') + 1;
// int end = line.IndexOf('"', start);
// exeURL = line.Substring(start, end - start);
// exeURL = exeURL.Replace("Windows64EditorInstaller/UnitySetup64-", "UnityDownloadAssistant-");
// break;
// }
// }
//}
//else // alpha or beta version download assistant finder
//{
// // regular beta
// //
// // https://beta.unity3d.com/download/21aeb48b6ed2/UnityDownloadAssistant.exe
// // hidden beta
// //
// // https://beta.unity3d.com/download/8008bc0c1b74/UnityDownloadAssistant-6000.0.0b15.exe
// // new 10.06.2024, no more downloadassistant.exe in html
// // check html lines
// for (int i = 0; i < lines.Length; i++)
// {
// //Console.WriteLine(lines[i]);
// //if (lines[i].Contains("UnityDownloadAssistant"))
// if (lines[i].Contains("UnityDownloadAssistant"))
// {
// if (useHash == false)
// {
// string pattern = @"https://beta\.unity3d\.com/download/[a-zA-Z0-9]+/UnityDownloadAssistant\.exe";
// Match match = Regex.Match(lines[i], pattern);
// if (match.Success)
// {
// exeURL = match.Value;
// }
// else
// {
// Console.WriteLine("No match found for download base url..");
// }
// }
// else // hidden download page
// {
// string pattern = @"UnityDownloadAssistant(?:-\d+\.\d+\.\d+[bf]\d*)?\.exe";
// Match match = Regex.Match(lines[i], pattern);
// if (match.Success)
// {
// // append base url
// Regex regex = new Regex(@"(https://beta\.unity3d\.com/download/[a-zA-Z0-9]+/)");
// Match match2 = regex.Match(url);
// //Console.WriteLine("source url: " + url);
// if (match2.Success)
// {
// string capturedUrl = match2.Groups[1].Value;
// exeURL = capturedUrl + match.Value;
// }
// else
// {
// Console.WriteLine("No match found for download base url..");
// }
// }
// break;
// }
// }
// } // for lines
//} // alpha or beta
// download full installer instead, TODO probably not needed anymore?
if (useHash == false && preferFullInstaller == true)
{
exeURL = exeURL.Replace("UnityDownloadAssistant-" + version + ".exe", "Windows64EditorInstaller/UnitySetup64-" + version + ".exe");
// handle alpha/beta
exeURL = exeURL.Replace("UnityDownloadAssistant.exe", "Windows64EditorInstaller/UnitySetup64-" + version + ".exe");
}
// didnt find installer
if (string.IsNullOrEmpty(exeURL))
{
//SetStatus("Cannot find UnityDownloadAssistant.exe for this version.");
Console.WriteLine("Installer not found from URL: " + url);
}
return exeURL;
}
private static string FetchUnityVersionNumberFromHTML(string url)
{
string sourceHTML = DownloadHTML(url);
if (string.IsNullOrEmpty(sourceHTML)) return null;
string pattern = @"\d+\.\d+\.\d+[bf]\d+";
MatchCollection matches = Regex.Matches(sourceHTML, pattern);
if (matches.Count > 0)
{
foreach (Match match in matches)
{
Console.WriteLine("Extracted number: " + match.Value);
return match.Value;
//break;
}
}
else
{
Console.WriteLine("FetchUnityVersionNumberFromHTML: No match found.");
}
return null;
//if (string.IsNullOrEmpty(sourceHTML) == false)
//{
// // find version number from html
// string pattern = @"UnityDownloadAssistant-[0-9]+\.[0-9]+\.[0-9]+[a-z]?\.exe";
// Match match = Regex.Match(sourceHTML, pattern);
// if (match.Success)
// {
// version = match.Value.Replace("UnityDownloadAssistant-", "").Replace(".exe", "");
// }
//}
//return version;
}
public static string FindNearestVersion(string currentVersion, List allAvailable, bool checkBelow = false)
{
if (allAvailable == null)
return null;
string result = null;
// add current version to list, to sort it with others
if (!allAvailable.Contains(currentVersion))
allAvailable.Add(currentVersion);
// sort list
if (checkBelow)
{
allAvailable.Sort((s1, s2) => VersionAsLong(s1).CompareTo(VersionAsLong(s2)));
}
else
{
allAvailable.Sort((s1, s2) => VersionAsLong(s2).CompareTo(VersionAsLong(s1)));
}
// check version above our current version
int currentIndex = allAvailable.IndexOf(currentVersion);
// if its index 0, we select that row anyways later
if (currentIndex > 0 && currentIndex < allAvailable.Count)
{
result = allAvailable[currentIndex - 1];
}
return result;
}
// returns version as integer, for easier sorting between versions: 2019.4.19f1 = 2019041901
public static long VersionAsLong(string version)
{
long result = 0;
// cleanup 32bit version name, TODO is this needed anymore?
string cleanVersion = version.Replace("(32-bit)", "");
// 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.Length > 1)
{
long multiplier = 1;
for (long i = 0, length = splitted.Length; i < length; i++)
{
long n = int.Parse(splitted[length - 1 - i]);
result += n * multiplier;
multiplier *= 50;
}
}
return result;
}
// https://stackoverflow.com/a/1619103/5452781
public static KeyValuePair GetEntry(this IDictionary dictionary, TKey key)
{
return new KeyValuePair(key, dictionary[key]);
}
public static void HandleDataGridScrollKeys(object sender, KeyEventArgs e)
{
/*
DataGrid grid = sender as DataGrid;
switch (e.Key)
{
case Key.Up:
if (grid.SelectedIndex > 0)
{
grid.SelectedIndex--;
}
// disable wrap around
//else
//{
// grid.SelectedIndex = grid.Items.Count - 1;
//}
e.Handled = true;
break;
case Key.Down:
if (grid.SelectedIndex < grid.Items.Count)
{
grid.SelectedIndex++;
}
//grid.SelectedIndex = ++grid.SelectedIndex % grid.Items.Count;
e.Handled = true;
break;
}
grid.ScrollIntoView(grid.Items[grid.SelectedIndex]);
*/
}
// 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;
modalWindow.WindowStartupLocation = owner == null ? WindowStartupLocation.CenterScreen : WindowStartupLocation.CenterOwner;
modalWindow.Topmost = owner == null;
modalWindow.ShowActivated = true;
modalWindow.Owner = owner;
modalWindow.ShowDialog();
var results = modalWindow.DialogResult.HasValue && modalWindow.DialogResult.Value;
if (results == true)
{
var upgradeToVersion = UpgradeWindow.upgradeVersion;
if (string.IsNullOrEmpty(upgradeToVersion)) return;
// get selected version to upgrade for
Console.WriteLine("Upgrade to " + upgradeToVersion);
// inject new version for this item, TODO inject version to ProjectSettings file, so then no alert from unity wrong version dialog
proj.Version = upgradeToVersion;
SaveProjectVersion(proj);
var proc = LaunchProject(proj, dataGridRef: null, useInitScript: false, upgrade: true);
// TODO update datagrid row for new version
}
else
{
//Console.WriteLine("results = " + results);
}
}
///
/// install context menu item to registry
///
///
public static void AddContextMenuRegistry(string contextRegRoot)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(contextRegRoot, true);
// add folder if missing
if (key == null)
{
key = Registry.CurrentUser.CreateSubKey(@"Software\Classes\Directory\Background\Shell");
}
if (key != null)
{
var appName = "UnityLauncherPro";
key.CreateSubKey(appName);
key = key.OpenSubKey(appName, true);
key.SetValue("", "Open with " + appName);
key.SetValue("Icon", "\"" + Process.GetCurrentProcess().MainModule.FileName + "\"");
key.CreateSubKey("command");
key = key.OpenSubKey("command", true);
var executeString = "\"" + Process.GetCurrentProcess().MainModule.FileName + "\"";
executeString += " -projectPath \"%V\"";
key.SetValue("", executeString);
}
else
{
Console.WriteLine("Error> Cannot find registry key: " + contextRegRoot);
}
}
///
/// uninstall context menu item from registry
///
///
public static void RemoveContextMenuRegistry(string contextRegRoot)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(contextRegRoot, true);
if (key != null)
{
var appName = "UnityLauncherPro";
RegistryKey appKey = Registry.CurrentUser.OpenSubKey(contextRegRoot + "\\" + appName, false);
if (appKey != null)
{
key.DeleteSubKeyTree(appName);
//SetStatus("Removed context menu registry items");
}
else
{
//SetStatus("Nothing to uninstall..");
}
}
else
{
//SetStatus("Error> Cannot find registry key: " + contextRegRoot);
}
}
///
/// reads .git/HEAD file from the project to get current branch name
///
///
///
public static string ReadGitBranchInfo(string projectPath, bool searchParentFolders)
{
string results = null;
if (searchParentFolders)
{
DirectoryInfo directoryInfo = new DirectoryInfo(projectPath);
while (directoryInfo != null)
{
string dirName = Path.Combine(directoryInfo.FullName, ".git");
if (Directory.Exists(dirName))
{
string branchFile = Path.Combine(dirName, "HEAD");
if (File.Exists(branchFile))
{
// removes extra end of line
results = string.Join(" ", File.ReadAllLines(branchFile));
// get branch only
int pos = results.LastIndexOf("/") + 1;
results = results.Substring(pos, results.Length - pos);
return results;
}
}
directoryInfo = directoryInfo.Parent;
}
}
else
{
string dirName = Path.Combine(projectPath, ".git");
if (Directory.Exists(dirName))
{
string branchFile = Path.Combine(dirName, "HEAD");
if (File.Exists(branchFile))
{
// removes extra end of line
results = string.Join(" ", File.ReadAllLines(branchFile));
// get branch only
int pos = results.LastIndexOf("/") + 1;
results = results.Substring(pos, results.Length - pos);
}
}
}
return results;
}
public static string ReadPlasticBranchInfo(string projectPath)
{
string results = null;
string dirName = Path.Combine(projectPath, ".plastic");
if (Directory.Exists(dirName))
{
string branchFile = Path.Combine(dirName, "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)
{
string results = null;
//Platform results = Platform.Unknown;
// get buildtarget from .csproj
// StandaloneWindows64:19
// get main csproj file
var csproj = Path.Combine(projectPath, "Assembly-CSharp.csproj");
// TODO check projname also, if no assembly-.., NOTE already checked above
//var csproj = Path.Combine(projectPath, projectName + ".csproj");
if (File.Exists(csproj))
{
var csprojtxt = File.ReadAllText(csproj);
var csprojsplit = csprojtxt.Split(new[] { "" }, StringSplitOptions.None);
if (csprojsplit != null && csprojsplit.Length > 1)
{
var endrow = csprojsplit[1].IndexOf(":");
if (endrow > -1)
{
//Console.WriteLine("build target: " + csprojsplit[1].Substring(0, endrow));
// 5.6 : win32, win64, osx, linux, linux64, ios, android, web, webstreamed, webgl, xboxone, ps4, psp2, wsaplayer, tizen, samsungtv
// 2017: standalone, Win, Win64, OSXUniversal, Linux, Linux64, LinuxUniversal, iOS, Android, Web, WebStreamed, WebGL, XboxOne, PS4, PSP2, WindowsStoreApps, Switch, WiiU, N3DS, tvOS, PSM
// 2018: standalone, Win, Win64, OSXUniversal, Linux, Linux64, LinuxUniversal, iOS, Android, Web, WebStreamed, WebGL, XboxOne, PS4, WindowsStoreApps, Switch, N3DS, tvOS
// 2019: Standalone, Win, Win64, OSXUniversal, Linux64, iOS, Android, WebGL, XboxOne, PS4, WindowsStoreApps, Switch, tvOS
// 2020: Standalone, Win, Win64, OSXUniversal, Linux64, iOS, Android, WebGL, XboxOne, PS4, WindowsStoreApps, Switch, tvOS
// 2021: Standalone, Win, Win64, OSXUniversal, Linux64, iOS, Android, WebGL, XboxOne, PS4, WindowsStoreApps, Switch, tvOS
results = csprojsplit[1].Substring(0, endrow);
//results = (Platform)Enum.Parse(typeof(Platform), csprojsplit[1].Substring(0, endrow));
}
}
}
else
{
//Console.WriteLine("Missing csproj, cannot parse target platform: "+ projectPath);
}
return results;
}
public static string GetTargetPlatform(string projectPath)
{
var rawPlatformName = GetTargetPlatformRaw(projectPath);
if (string.IsNullOrEmpty(rawPlatformName) == false && GetProjects.remapPlatformNames.ContainsKey(rawPlatformName))
{
return GetProjects.remapPlatformNames[rawPlatformName];
}
else
{
if (string.IsNullOrEmpty(rawPlatformName) == false) Console.WriteLine("Missing buildTarget remap name for: " + rawPlatformName);
return null;
}
}
public static string ReadCustomProjectData(string projectPath, string customFile)
{
string results = null;
customFile = Path.Combine(projectPath, "ProjectSettings", customFile);
if (File.Exists(customFile) == true)
{
results = string.Join(" ", File.ReadAllLines(customFile));
}
return results;
}
public static bool SaveCustomProjectData(string projectPath, string customFile, string data)
{
customFile = Path.Combine(projectPath, "ProjectSettings", customFile);
try
{
File.WriteAllText(customFile, data);
return true;
}
catch (Exception)
{
}
return false;
}
public static bool HasFocus(DependencyObject obj, Control control, bool checkChildren)
{
var oFocused = FocusManager.GetFocusedElement(obj) as DependencyObject;
if (!checkChildren)
return oFocused == control;
while (oFocused != null)
{
if (oFocused == control)
return true;
oFocused = VisualTreeHelper.GetParent(oFocused);
}
return false;
}
public static void SetFocusToGrid(DataGrid targetGrid, int index = -1)
{
// set main component focus
//targetGrid.Focus();
//Keyboard.Focus(targetGrid);
// no items
if (targetGrid.Items.Count < 1) return;
// keep current row selected
if (index == -1 && targetGrid.SelectedIndex > -1) index = targetGrid.SelectedIndex;
// if no item selected, pick first
if (index == -1) index = 0;
targetGrid.SelectedIndex = index;
// set full focus
DataGridRow row = (DataGridRow)targetGrid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
targetGrid.UpdateLayout();
if (index < targetGrid.Items.Count)
{
// scroll selected into view
targetGrid.ScrollIntoView(targetGrid.Items[index]);
row = (DataGridRow)targetGrid.ItemContainerGenerator.ContainerFromIndex(index);
}
else
{
Console.WriteLine("selected row out of bounds: " + index);
}
}
// NOTE does this causes move below?
//row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
if (row != null)
{
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Up)); // works better than Up
row.Focus();
Keyboard.Focus(row);
}
}
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 SaveFileDialog();
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"
if (dialog.ShowDialog() == true)
{
string path = dialog.FileName;
// Remove fake filename from resulting path
path = path.Replace("\\Project.Folder", "");
path = path.Replace("Project.Folder", "");
// If user has changed the filename, create the new directory
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path;
}
return 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)
{
Console.WriteLine("Missing baseFolder value");
return null;
}
// check if base folder exists
if (Directory.Exists(baseFolder) == false)
{
Console.WriteLine("Missing baseFolder: " + baseFolder);
return null;
}
// check selected unity version
if (string.IsNullOrEmpty(version) == true)
{
Console.WriteLine("Missing unity version string");
return null;
}
string newPath = null;
// if we didnt have name yet
if (string.IsNullOrEmpty(projectName) == true)
{
projectName = GetSuggestedProjectName(version, baseFolder);
// failed getting new path a-z
if (projectName == null) return null;
}
newPath = Path.Combine(baseFolder, projectName);
// create folder
CreateEmptyProjectFolder(newPath, version);
// unzip template, if any
if (templateZipPath != null)
{
TarLib.Tar.ExtractTarGz(templateZipPath, newPath);
}
// copy init file into project
if (useInitScript == true)
{
if (File.Exists(initScriptPath) == true)
{
var editorTargetFolder = Path.Combine(baseFolder, projectName, "Assets", "Editor");
if (Directory.Exists(editorTargetFolder) == false) Directory.CreateDirectory(editorTargetFolder);
var targetScriptFile = Path.Combine(editorTargetFolder, Path.GetFileName(initScriptPath));
// TODO overwrite old file, there shouldnt be anything here
if (File.Exists(targetScriptFile) == false) File.Copy(initScriptPath, targetScriptFile);
}
}
// launch empty project
var proj = new Project();
proj.Title = projectName;
proj.Path = Path.Combine(baseFolder, newPath).Replace("\\", "/");
proj.Version = version;
proj.TargetPlatforms = platformsForThisUnity;
proj.TargetPlatform = platform;
proj.Modified = DateTime.Now;
proj.folderExists = true; // have to set this value, so item is green on list
var proc = LaunchProject(proj, null, useInitScript);
ProcessHandler.Add(proj, proc);
return proj;
} // FastCreateProject
public static string GetSuggestedProjectName(string version, string baseFolder)
{
// check for base folders in settings tab, could use currently selected project folder parent as base?
if (string.IsNullOrEmpty(baseFolder))
{
Console.WriteLine("Missing txtRootFolderForNewProjects");
return null;
}
// find next free folder checking all "unityversion_a-z" characters
var unityBaseVersion = version.Substring(0, version.LastIndexOf('.'));
unityBaseVersion = unityBaseVersion.Replace(".", "_");
for (int i = 97; i < 122; i++)
{
var newProject = unityBaseVersion + "_" + ((char)i);
var path = Path.Combine(baseFolder, newProject);
if (Directory.Exists(path))
{
//Console.WriteLine("directory exists..trying again");
}
else // its available
{
return newProject;
}
}
// couldnt find free letter to use, lets add timestamp then
return unityBaseVersion + "_" + DateTime.Now.ToString("ddMMyyyy_HHmmss");
}
static void CreateEmptyProjectFolder(string path, string version)
{
Console.WriteLine("Create new project folder: " + path);
Directory.CreateDirectory(path);
// create project version file, to avoid wrong version warning
var settingsPath = Path.Combine(path, "ProjectSettings");
Directory.CreateDirectory(settingsPath);
var settingsFile = Path.Combine(settingsPath, "ProjectVersion.txt");
File.WriteAllText(settingsFile, "m_EditorVersion: " + version);
}
public static string GetEditorLogsFolder()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Unity", "Editor");
}
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.Count; i++)
{
if (MainWindow.unityInstallationsSource[i].Version == version)
{
return MainWindow.unityInstallationsSource[i].Platforms;
}
}
return null;
}
// https://stackoverflow.com/a/675347/5452781
public static void SetStartupRegistry(bool state)
{
RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
if (state == true)
{
rk.SetValue(MainWindow.appName, "\"" + Process.GetCurrentProcess().MainModule.FileName + "\"");
}
else
{
rk.DeleteValue(MainWindow.appName, false);
}
}
public static Dictionary ScanTemplates(string unityInstallPath)
{
var items = new Dictionary();
// add none as default item
items.Add("None", null);
// get list of existing packages
var unityPath = Path.GetDirectoryName(unityInstallPath);
var templateFolder = Path.Combine(unityPath, "Data/Resources/PackageManager/ProjectTemplates/");
if (Directory.Exists(templateFolder) == false) return items;
var fileEntries = Directory.GetFiles(templateFolder).ToList();
// process found files
for (int i = fileEntries.Count - 1; i > -1; i--)
{
// check if its tgz
if (fileEntries[i].IndexOf(".tgz") == -1)
{
fileEntries.RemoveAt(i);
}
else
{
// cleanup name
var name = Path.GetFileName(fileEntries[i]).Replace("com.unity.template.", "").Replace(".tgz", "");
items.Add(name, fileEntries[i]);
}
}
return items;
}
// chatgpt
public static string GetElapsedTime(DateTime datetime)
{
TimeSpan ts = DateTime.Now - datetime;
if (ts.TotalSeconds < 60)
{
return ts.TotalSeconds < 2 ? "Right now" : $"{(int)ts.TotalSeconds} seconds ago";
}
else if (ts.TotalMinutes < 60)
{
return ts.TotalMinutes < 2 ? "1 minute ago" : $"{(int)ts.TotalMinutes} minutes ago";
}
else if (ts.TotalHours < 24)
{
return ts.TotalHours < 2 ? "1 hour ago" : $"{(int)ts.TotalHours} hours ago";
}
else if (ts.TotalDays < 30)
{
return ts.TotalDays < 2 ? "1 day ago" : $"{(int)ts.TotalDays} days ago";
}
else if (ts.TotalDays < 365)
{
if (ts.TotalDays < 60)
{
return "1 month ago";
}
else
{
return $"{(int)(ts.TotalDays / 30)} months ago";
}
}
else
{
return ts.TotalDays < 730 ? "1 year ago" : $"{(int)(ts.TotalDays / 365)} years ago";
}
}
public static bool ValidateDateFormat(string format)
{
try
{
String formattedDate = DateTime.Now.ToString(format);
DateTime.Parse(formattedDate);
return true;
}
catch (Exception)
{
//Console.WriteLine("Invalid custom datetime format: " + format);
return false;
}
}
// https://stackoverflow.com/a/37724335/5452781
public static void BringProcessToFront(Process process)
{
IntPtr handle = process.MainWindowHandle;
if (IsIconic(handle))
{
ShowWindow(handle, SW_RESTORE);
}
SetForegroundWindow(handle);
}
public static void DownloadAdditionalModules(string UnityExePath, string unityVersion, string moduleName)
{
var editorFolder = Path.GetDirectoryName(UnityExePath);
string hash = null;
// get from unity exe (only for 2018.4 and later?)
var versionInfo = FileVersionInfo.GetVersionInfo(UnityExePath);
var versionRaw = versionInfo.ProductVersion.Split('_');
if (versionRaw.Length == 2)
{
hash = versionRaw[1];
}
else // try other files
{
var changeSetFile = Path.Combine(editorFolder, @"Data\PlaybackEngines\windowsstandalonesupport\Source\WindowsPlayer\WindowsPlayer\UnityConfigureRevision.gen.h");
if (File.Exists(changeSetFile) == true)
{
var allText = File.ReadAllText(changeSetFile);
var hashRaw = allText.Split(new string[] { "#define UNITY_VERSION_HASH \"" }, StringSplitOptions.None);
if (hashRaw.Length > 1)
{
hash = hashRaw[1].Replace("\"", "");
}
else
{
Console.WriteLine("Unable to parse UNITY_VERSION_HASH from " + changeSetFile);
}
}
else
{
Console.WriteLine("Changeset hash file not found: " + changeSetFile);
}
}
if (hash == null) return;
var moduleURL = "https://download.unity3d.com/download_unity/" + hash + "/TargetSupportInstaller/UnitySetup-" + moduleName + "-Support-for-Editor-" + unityVersion + ".exe";
OpenURL(moduleURL);
}
public static void OpenAppdataSpecialFolder(string subfolder)
{
var logfolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), subfolder);
if (Directory.Exists(logfolder) == true)
{
if (Tools.LaunchExplorer(logfolder) == false)
{
Console.WriteLine("Cannot open folder.." + logfolder);
}
}
}
// 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 -";
// TODO test without nographics : https://forum.unity.com/threads/batch-build-one-scene-is-black-works-in-normal-file-build.1282823/#post-9456524
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;
string param = null;
// parse proj version year as number 2019.4.1f1 -> 2019
int year = 0;
var versionParts = proj.Version.Split('.');
bool parsedYear = int.TryParse(versionParts[0], out year);
if (parsedYear && year >= 6000)
{
param = "\"" + webExe + "\" \"" + buildPath + "\" " + "http://localhost:" + port + "/" + (pid == -1 ? "" : " " + pid);
}
else // older versions or failed to parse
{
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;
// parse proj version year as number 2019.4.1f1 -> 2019
string param = null;
int year = 0;
var versionParts = proj.Version.Split('.');
bool parsedYear = int.TryParse(versionParts[0], out year);
if (parsedYear && year >= 6000)
{
param = "\"" + webExe + "\" \"" + buildPath + "\" " + "\"http://localhost:" + port + "/\"" + (pid == -1 ? "" : " " + pid);
}
else // older versions or failed to parse
{
param = "\"" + webExe + "\" \"" + buildPath + "\" " + port + (pid == -1 ? "" : " " + pid); // server exe path, build folder and port
}
//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);
int year;
string[] parts = version.Split('.');
// TODO handle unity 6.x
if (parts.Length >= 1 && int.TryParse(parts[0], out year) && year cancellationTokenSource.Cancel());
progressWindow.Show();
var result = false;
try
{
using (var client = new HttpClient())
using (var response = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token))
{
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength ?? 1;
var buffer = new byte[8192];
var totalRead = 0;
using (var contentStream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write,
FileShare.None, buffer.Length, true))
{
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, cancellationTokenSource.Token)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationTokenSource.Token);
totalRead += bytesRead;
progressWindow.UpdateProgress(new DownloadProgress(totalRead, totalBytes));
}
result = true;
}
}
}
catch (TaskCanceledException)
{
Console.WriteLine("Download cancelled");
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
if (!result)
{
DeleteTempFile(destinationPath);
}
progressWindow.Close();
}
return result;
}
internal static string GetSRP(string projectPath)
{
// read projectsettings/graphicsettings file, look for m_SRPDefaultSettings: value
var settingsFile = Path.Combine(projectPath, "ProjectSettings", "GraphicsSettings.asset");
if (File.Exists(settingsFile) == false) return null;
var allText = File.ReadAllText(settingsFile);
var srpIndex = allText.IndexOf("m_SRPDefaultSettings:");
if (srpIndex == -1) return null; // BIRP
// urp = UnityEngine.Rendering.Universal.UniversalRenderPipeline
// hdrp = UnityEngine.Rendering.HighDefinition.HDRenderPipeline
if (allText.IndexOf("UnityEngine.Rendering.Universal.UniversalRenderPipeline", srpIndex) > -1)
{
return "URP";
}
else if (allText.IndexOf("UnityEngine.Rendering.HighDefinition.HDRenderPipeline", srpIndex) > -1)
{
return "HDRP";
}
else
{
return null; // BIRP
}
}
} // class
} // namespace