Skip to content

Commit f09c733

Browse files
authored
Merge pull request #229 from ekt-/fix-missing-projects
Add support for reading unityhub projects.json file, fixes #228 updated issue #168 for removing projects
2 parents 6fb083e + b524520 commit f09c733

4 files changed

Lines changed: 203 additions & 42 deletions

File tree

UnityLauncherPro/GetProjects.cs

Lines changed: 156 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,90 @@ public static class GetProjects
1313
static readonly string[] registryPathsToCheck = new string[] { @"SOFTWARE\Unity Technologies\Unity Editor 5.x", @"SOFTWARE\Unity Technologies\Unity Editor 4.x" };
1414

1515
// convert target platform name into valid buildtarget platform name, NOTE this depends on unity version, now only 2019 and later are supported
16-
public static Dictionary<string, string> remapPlatformNames = new Dictionary<string, string> { { "StandaloneWindows64", "Win64" }, { "StandaloneWindows", "Win" }, { "Android", "Android" }, { "WebGL", "WebGL" } };
16+
public static Dictionary<string, string> remapPlatformNames = new Dictionary<string, string> {
17+
{ "StandaloneWindows64", "Win64" },
18+
{ "StandaloneWindows", "Win" },
19+
{ "Android", "Android" },
20+
{ "WebGL", "WebGL" } };
1721

1822
public static List<Project> Scan(bool getGitBranch = false, bool getPlasticBranch = false, bool getArguments = false, bool showMissingFolders = false, bool showTargetPlatform = false, StringCollection AllProjectPaths = null, bool searchGitbranchRecursively = false, bool showSRP = false)
1923
{
2024
List<Project> projectsFound = new List<Project>();
2125

26+
// first, scan projects from unity hub json file
27+
VisitProjectsInUnityHubJson
28+
(
29+
getGitBranch, getPlasticBranch, getArguments,
30+
showMissingFolders, showTargetPlatform , searchGitbranchRecursively , showSRP,
31+
project =>
32+
{
33+
if (!projectsFound.ContainsProjectWithPath(project.Path))
34+
projectsFound.Add(project);
35+
36+
// add found projects to history also (gets added only if its not already there)
37+
Tools.AddProjectToHistory(project.Path);
38+
});
39+
40+
// then scan projects from registry
41+
VisitProjectsInRegistry
42+
(
43+
getGitBranch, getPlasticBranch, getArguments,
44+
showMissingFolders, showTargetPlatform , searchGitbranchRecursively , showSRP,
45+
project =>
46+
{
47+
if (!projectsFound.ContainsProjectWithPath(project.Path))
48+
projectsFound.Add(project);
49+
50+
// TODO FIXME, this gets called everytime for same projects?
51+
// add found projects to history also (gets added only if its not already there)
52+
Tools.AddProjectToHistory(project.Path);
53+
});
54+
55+
// 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..)
56+
// 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
57+
58+
// scan info for custom folders (if not already on the list)
59+
if (AllProjectPaths != null)
60+
{
61+
// iterate custom full projects history
62+
foreach (var projectPath in AllProjectPaths)
63+
{
64+
// check if registry list contains this path already
65+
// if not found from registry, add to recent projects list
66+
if (!projectsFound.ContainsProjectWithPath(projectPath))
67+
{
68+
var p = GetProjectInfo(projectPath, getGitBranch, getPlasticBranch, getArguments, showMissingFolders, showTargetPlatform, searchGitbranchRecursively, showSRP);
69+
if (p != null) projectsFound.Add(p);
70+
}
71+
}
72+
}
73+
74+
// 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)
75+
// 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)
76+
projectsFound.Sort((x, y) =>
77+
{
78+
if (x.Modified == null && y.Modified == null) return 0; // cannot be -1, https://stackoverflow.com/a/42821992/5452781
79+
if (x.Modified == null) return 1;
80+
if (y.Modified == null) return -1;
81+
return y.Modified.Value.CompareTo(x.Modified.Value);
82+
//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) ?
83+
});
84+
85+
// trim list to max amount (older ones are dropped)
86+
if (projectsFound.Count > MainWindow.maxProjectCount)
87+
{
88+
//Console.WriteLine("Trimming projects list to " + MainWindow.maxProjectCount + " projects");
89+
projectsFound.RemoveRange(MainWindow.maxProjectCount, projectsFound.Count - MainWindow.maxProjectCount);
90+
}
91+
return projectsFound;
92+
} // Scan()
93+
94+
// visits each project stored in the Unity registry, invoking the visitor for each one
95+
private static void VisitProjectsInRegistry(
96+
bool getGitBranch, bool getPlasticBranch, bool getArguments,
97+
bool showMissingFolders, bool showTargetPlatform , bool searchGitbranchRecursively , bool showSRP,
98+
Action<Project> visitor)
99+
{
22100
var hklm = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
23101

24102
// check each version path
@@ -59,67 +137,103 @@ public static List<Project> Scan(bool getGitBranch = false, bool getPlasticBranc
59137
// if want to hide project and folder path for screenshot
60138
//p.Title = "Example Project";
61139
//p.Path = "C:/Projects/MyProj";
62-
63140
if (p != null)
64141
{
65-
projectsFound.Add(p);
66-
67-
// TODO FIXME, this gets called everytime for same projects?
68-
// add found projects to history also (gets added only if its not already there)
69-
Tools.AddProjectToHistory(p.Path);
142+
visitor(p);
70143
}
71144
} // valid key
72145
} // each key
73146
} // for each registry root
147+
} // VisitProjectsInRegistry()
74148

75-
// 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..)
76-
// 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
149+
private static void VisitProjectsInUnityHubJson(
150+
bool getGitBranch, bool getPlasticBranch, bool getArguments,
151+
bool showMissingFolders, bool showTargetPlatform , bool searchGitbranchRecursively , bool showSRP,
152+
Action<Project> visitor)
153+
{
154+
string hubProjectsFile = Path.Combine(
155+
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
156+
"UnityHub", "projects-v1.json");
77157

78-
// scan info for custom folders (if not already on the list)
79-
if (AllProjectPaths != null)
158+
if (!File.Exists(hubProjectsFile))
159+
return;
160+
161+
string json;
162+
try { json = File.ReadAllText(hubProjectsFile); }
163+
catch { return; }
164+
165+
int dataIndex = json.IndexOf("\"data\":");
166+
if (dataIndex == -1) return;
167+
168+
// find the opening { of the data object
169+
int dataStart = json.IndexOf('{', dataIndex + 7);
170+
if (dataStart == -1) return;
171+
172+
int searchFrom = dataStart + 1;
173+
while (true)
80174
{
81-
// iterate custom full projects history
82-
foreach (var projectPath in AllProjectPaths)
175+
// find the start of the next project entry object
176+
int entryStart = json.IndexOf('{', searchFrom);
177+
if (entryStart == -1) break;
178+
179+
// find the matching closing }
180+
int entryEnd = JsonParser.FindMatchingBrace(json, entryStart);
181+
if (entryEnd == -1) break;
182+
183+
string entry = json.Substring(entryStart, entryEnd - entryStart + 1);
184+
185+
string projectPath = JsonParser.GetStringValue(entry, "path");
186+
if (!string.IsNullOrEmpty(projectPath))
83187
{
84-
// check if registry list contains this path already, then skip it
85-
bool found = false;
86-
for (int i = 0, len = projectsFound.Count; i < len; i++)
188+
// unescape JSON backslashes and convert to normal path separators
189+
projectPath = projectPath.Replace(@"\\", @"/");
190+
191+
// collect project info from disk, but override with hub json data where its more authoritative
192+
// todo: an optimization could be to only get data from disk that is missing from json,
193+
// instead of getting all data and then overriding.
194+
var p = GetProjectInfo(projectPath, getGitBranch, getPlasticBranch, getArguments, showMissingFolders, showTargetPlatform, searchGitbranchRecursively, showSRP);
195+
if (p != null)
87196
{
88-
if (projectsFound[i].Path == projectPath)
197+
string title = JsonParser.GetStringValue(entry, "title");
198+
if (!string.IsNullOrEmpty(title)) p.Title = title;
199+
200+
// lastModified is a Unix millisecond timestamp
201+
string lastModifiedStr = JsonParser.GetNumberValue(entry, "lastModified");
202+
if (long.TryParse(lastModifiedStr, out long lastModifiedMs))
203+
p.Modified = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(lastModifiedMs).ToLocalTime();
204+
205+
string version = JsonParser.GetStringValue(entry, "version");
206+
if (!string.IsNullOrEmpty(version)) p.Version = version;
207+
208+
if (showTargetPlatform)
89209
{
90-
found = true;
91-
break;
210+
string buildTarget = JsonParser.GetStringValue(entry, "buildTarget");
211+
p.TargetPlatform = Tools.GetTargetPlatformFromRaw(buildTarget);
92212
}
93-
}
94213

95-
// if not found from registry, add to recent projects list
96-
if (found == false)
97-
{
98-
var p = GetProjectInfo(projectPath, getGitBranch, getPlasticBranch, getArguments, showMissingFolders, showTargetPlatform, searchGitbranchRecursively, showSRP);
99-
if (p != null) projectsFound.Add(p);
214+
if (showSRP)
215+
{
216+
string renderPipeline = JsonParser.GetStringValue(entry, "renderPipeline");
217+
if (!string.IsNullOrEmpty(renderPipeline)) p.SRP = renderPipeline;
218+
}
219+
220+
visitor(p);
100221
}
101222
}
102-
}
103223

104-
// 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)
105-
// 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)
106-
projectsFound.Sort((x, y) =>
107-
{
108-
if (x.Modified == null && y.Modified == null) return 0; // cannot be -1, https://stackoverflow.com/a/42821992/5452781
109-
if (x.Modified == null) return 1;
110-
if (y.Modified == null) return -1;
111-
return y.Modified.Value.CompareTo(x.Modified.Value);
112-
//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) ?
113-
});
224+
searchFrom = entryEnd + 1;
225+
}
226+
}
114227

115-
// trim list to max amount (older ones are dropped)
116-
if (projectsFound.Count > MainWindow.maxProjectCount)
228+
private static bool ContainsProjectWithPath(this List<Project> projects, string projectPath)
229+
{
230+
foreach (var p in projects)
117231
{
118-
//Console.WriteLine("Trimming projects list to " + MainWindow.maxProjectCount + " projects");
119-
projectsFound.RemoveRange(MainWindow.maxProjectCount, projectsFound.Count - MainWindow.maxProjectCount);
232+
if (string.Equals(p.Path, projectPath, StringComparison.OrdinalIgnoreCase))
233+
return true;
120234
}
121-
return projectsFound;
122-
} // Scan()
235+
return false;
236+
}
123237

124238
static Project GetProjectInfo(string projectPath, bool getGitBranch = false, bool getPlasticBranch = false, bool getArguments = false, bool showMissingFolders = false, bool showTargetPlatform = false, bool searchGitbranchRecursively = false, bool showSRP = false)
125239
{
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace UnityLauncherPro
2+
{
3+
internal static class JsonParser
4+
{
5+
// finds the index of the closing } matching the opening { at startIndex
6+
internal static int FindMatchingBrace(string json, int startIndex)
7+
{
8+
int depth = 0;
9+
for (int i = startIndex; i < json.Length; i++)
10+
{
11+
if (json[i] == '{') depth++;
12+
else if (json[i] == '}') { depth--; if (depth == 0) return i; }
13+
}
14+
return -1;
15+
}
16+
17+
// extracts a JSON string value for the given key
18+
internal static string GetStringValue(string json, string key)
19+
{
20+
int keyIndex = json.IndexOf("\"" + key + "\":");
21+
if (keyIndex == -1) return null;
22+
int valueStart = json.IndexOf('"', keyIndex + key.Length + 2) + 1;
23+
if (valueStart == 0) return null;
24+
int valueEnd = json.IndexOf('"', valueStart);
25+
if (valueEnd == -1) return null;
26+
return json.Substring(valueStart, valueEnd - valueStart);
27+
}
28+
29+
// extracts a JSON number value for the given key (returned as string for flexibility)
30+
internal static string GetNumberValue(string json, string key)
31+
{
32+
int keyIndex = json.IndexOf("\"" + key + "\":");
33+
if (keyIndex == -1) return null;
34+
int valueStart = keyIndex + key.Length + 3; // skip past "key":
35+
while (valueStart < json.Length && json[valueStart] == ' ') valueStart++;
36+
int valueEnd = valueStart;
37+
while (valueEnd < json.Length && (char.IsDigit(json[valueEnd]) || json[valueEnd] == '-')) valueEnd++;
38+
return valueEnd > valueStart ? json.Substring(valueStart, valueEnd - valueStart) : null;
39+
}
40+
}
41+
}

UnityLauncherPro/Tools.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,11 @@ public static string GetTargetPlatform(string projectPath)
16361636
{
16371637
var rawPlatformName = GetTargetPlatformRaw(projectPath);
16381638

1639+
return GetTargetPlatformFromRaw(rawPlatformName);
1640+
}
1641+
1642+
public static string GetTargetPlatformFromRaw(string rawPlatformName)
1643+
{
16391644
if (string.IsNullOrEmpty(rawPlatformName) == false && GetProjects.remapPlatformNames.ContainsKey(rawPlatformName))
16401645
{
16411646
return GetProjects.remapPlatformNames[rawPlatformName];

UnityLauncherPro/UnityLauncherPro.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<Compile Include="GetProjects.cs" />
108108
<Compile Include="GetUnityInstallations.cs" />
109109
<Compile Include="GetUnityUpdates.cs" />
110+
<Compile Include="Helpers\JsonParser.cs" />
110111
<Compile Include="Helpers\ObservableDictionary.cs" />
111112
<Compile Include="Helpers\ProcessHandler.cs" />
112113
<Compile Include="Libraries\ExtractTarGz.cs" />

0 commit comments

Comments
 (0)