@@ -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 )
0 commit comments