Skip to content

Commit 29a9394

Browse files
committed
Add Unity Hub projects scanning and JSON parser
Scan Unity Hub's projects-v1.json and include those projects before registry results. Added VisitProjectsInUnityHubJson to parse hub entries (path, title, lastModified (ms), version, buildTarget, renderPipeline), merge with GetProjectInfo, avoid duplicates, and add projects to history. Introduced a minimal Helpers/JsonParser.cs with FindMatchingBrace, GetStringValue and GetNumberValue to extract values without a full JSON dependency. Also refactored Tools to add GetTargetPlatformFromRaw
1 parent 6d58ab2 commit 29a9394

4 files changed

Lines changed: 146 additions & 1 deletion

File tree

UnityLauncherPro/GetProjects.cs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,31 @@ 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
2241
VisitProjectsInRegistry
2342
(
2443
getGitBranch, getPlasticBranch, getArguments,
@@ -127,6 +146,85 @@ private static void VisitProjectsInRegistry(
127146
} // for each registry root
128147
} // VisitProjectsInRegistry()
129148

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");
157+
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)
174+
{
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))
187+
{
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)
196+
{
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)
209+
{
210+
string buildTarget = JsonParser.GetStringValue(entry, "buildTarget");
211+
p.TargetPlatform = Tools.GetTargetPlatformFromRaw(buildTarget);
212+
}
213+
214+
if (showSRP)
215+
{
216+
string renderPipeline = JsonParser.GetStringValue(entry, "renderPipeline");
217+
if (!string.IsNullOrEmpty(renderPipeline)) p.SRP = renderPipeline;
218+
}
219+
220+
visitor(p);
221+
}
222+
}
223+
224+
searchFrom = entryEnd + 1;
225+
}
226+
}
227+
130228
private static bool ContainsProjectWithPath(this List<Project> projects, string projectPath)
131229
{
132230
foreach (var p in projects)
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
@@ -1626,6 +1626,11 @@ public static string GetTargetPlatform(string projectPath)
16261626
{
16271627
var rawPlatformName = GetTargetPlatformRaw(projectPath);
16281628

1629+
return GetTargetPlatformFromRaw(rawPlatformName);
1630+
}
1631+
1632+
public static string GetTargetPlatformFromRaw(string rawPlatformName)
1633+
{
16291634
if (string.IsNullOrEmpty(rawPlatformName) == false && GetProjects.remapPlatformNames.ContainsKey(rawPlatformName))
16301635
{
16311636
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)