11using System ;
22using System . Collections . Generic ;
3- using System . Globalization ;
4- using System . Net ;
3+ using System . Configuration ;
4+ using System . IO ;
5+ using System . Linq ;
6+ using System . Net . Http ;
7+ using System . Reflection ;
8+ using System . Text . Json ;
59using System . Threading . Tasks ;
610
711namespace UnityLauncherPro
812{
913 public static class GetUnityUpdates
1014 {
11- static bool isDownloadingUnityList = false ;
12- static readonly string unityVersionsURL = @"https://symbolserver.unity3d.com/000Admin/history.txt" ;
15+ private const string BaseApiUrl = "https://services.api.unity.com/unity/editor/release/v1/releases" ;
16+ private const int BatchSize = 25 ;
17+ private const int RequestsPerBatch = 10 ;
18+ private const int DelayBetweenBatches = 1000 ; // 1 second in milliseconds
19+ private const string CacheFileName = "UnityVersionCache.json" ;
1320
14- public static async Task < string > Scan ( )
21+ private static readonly HttpClient httpClient = new HttpClient ( ) ;
22+
23+ public static async Task < List < UnityVersion > > FetchAll ( )
1524 {
16- if ( isDownloadingUnityList == true )
25+ var cachedVersions = LoadCachedVersions ( ) ;
26+ var latestCachedVersion = cachedVersions . FirstOrDefault ( ) ;
27+
28+ var newVersions = await FetchNewVersions ( latestCachedVersion ) ;
29+
30+ var allVersions = newVersions . Concat ( cachedVersions ) . ToList ( ) ;
31+
32+ if ( newVersions . Count > 0 )
1733 {
18- Console . WriteLine ( "We are already downloading ..." ) ;
19- return null ;
34+ SaveCachedVersions ( allVersions ) ;
2035 }
2136
22- isDownloadingUnityList = true ;
23- //SetStatus("Downloading list of Unity versions ...");
24- string result = null ;
25- // download list of Unity versions
26- using ( WebClient webClient = new WebClient ( ) )
37+ return allVersions ;
38+ }
39+
40+ public static async Task < string > FetchDownloadUrl ( string unityVersion , bool assistantUrl = false )
41+ {
42+ if ( string . IsNullOrEmpty ( unityVersion ) )
43+ {
44+ return null ;
45+ }
46+
47+ string apiUrl = $ "{ BaseApiUrl } ?limit=1&version={ unityVersion } &architecture=X86_64&platform=WINDOWS";
48+
49+ try
50+ {
51+ string responseString = await httpClient . GetStringAsync ( apiUrl ) ;
52+ JsonDocument doc = JsonDocument . Parse ( responseString ) ;
53+ try
2754 {
28- Task < string > downloadStringTask = webClient . DownloadStringTaskAsync ( new Uri ( unityVersionsURL ) ) ;
29- try
55+ var root = doc . RootElement ;
56+ var results = root . GetProperty ( "results" ) ;
57+
58+ if ( results . GetArrayLength ( ) > 0 )
3059 {
31- result = await downloadStringTask ;
60+ var entry = results [ 0 ] ;
61+ string downloadUrl = null ;
62+ string shortRevision = null ;
63+
64+ if ( entry . TryGetProperty ( "downloads" , out var downloads ) &&
65+ downloads . GetArrayLength ( ) > 0 &&
66+ downloads [ 0 ] . TryGetProperty ( "url" , out var urlProperty ) )
67+ {
68+ downloadUrl = urlProperty . GetString ( ) ;
69+ }
70+
71+ if ( entry . TryGetProperty ( "shortRevision" , out var revisionProperty ) )
72+ {
73+ shortRevision = revisionProperty . GetString ( ) ;
74+ }
75+
76+ if ( ! string . IsNullOrEmpty ( downloadUrl ) )
77+ {
78+ if ( ! assistantUrl ) return downloadUrl ;
79+
80+ if ( ! string . IsNullOrEmpty ( shortRevision ) )
81+ {
82+ var startIndex = downloadUrl . LastIndexOf ( shortRevision , StringComparison . Ordinal ) + shortRevision . Length + 1 ;
83+ var endIndex = downloadUrl . Length - startIndex ;
84+ return downloadUrl . Replace ( downloadUrl . Substring ( startIndex , endIndex ) ,
85+ $ "UnityDownloadAssistant-{ unityVersion } .exe") ;
86+ }
87+ else
88+ {
89+ Console . WriteLine ( "ShortRevision not found, returning original download URL." ) ;
90+ return downloadUrl ;
91+ }
92+ }
3293 }
33- catch ( WebException )
94+
95+ Console . WriteLine ( $ "No download URL found for version { unityVersion } ") ;
96+ return null ;
97+ }
98+ finally
99+ {
100+ doc . Dispose ( ) ;
101+ }
102+ }
103+ catch ( Exception e )
104+ {
105+ Console . WriteLine ( $ "Error fetching download URL: { e . Message } ") ;
106+ return null ;
107+ }
108+ }
109+
110+ private static async Task < List < UnityVersion > > FetchNewVersions ( UnityVersion latestCachedVersion )
111+ {
112+ var newVersions = new List < UnityVersion > ( ) ;
113+ int offset = 0 ;
114+ int total = int . MaxValue ;
115+
116+ while ( offset < total )
117+ {
118+ var batchUpdates = await FetchBatch ( offset ) ;
119+ if ( batchUpdates ? . Results == null || batchUpdates . Results . Count == 0 )
120+ break ;
121+
122+ foreach ( var version in batchUpdates . Results )
34123 {
35- Console . WriteLine ( "It's a web exception" ) ;
124+ if ( version . Version == latestCachedVersion ? . Version )
125+ return newVersions ;
126+
127+ newVersions . Add ( version ) ;
36128 }
37- catch ( Exception )
129+
130+ total = batchUpdates . Total ;
131+ offset += batchUpdates . Results . Count ;
132+
133+ if ( offset % ( BatchSize * RequestsPerBatch ) == 0 )
38134 {
39- Console . WriteLine ( "It's not a web exception" ) ;
135+ await Task . Delay ( DelayBetweenBatches ) ;
40136 }
41-
42- isDownloadingUnityList = false ;
43137 }
44- return result ;
138+
139+ return newVersions ;
45140 }
46141
47- public static Updates [ ] Parse ( string items , ref List < string > updatesAsString )
142+ private static async Task < UnityVersionResponse > FetchBatch ( int offset )
48143 {
49- if ( updatesAsString == null )
50- updatesAsString = new List < string > ( ) ;
51- updatesAsString . Clear ( ) ;
52-
53- isDownloadingUnityList = false ;
54- //SetStatus("Downloading list of Unity versions ... done");
55- var receivedList = items . Split ( new [ ] { Environment . NewLine } , StringSplitOptions . None ) ;
56- if ( receivedList == null && receivedList . Length < 1 ) return null ;
57- Array . Reverse ( receivedList ) ;
58- var releases = new Dictionary < string , Updates > ( ) ;
59- // parse into data
60- string prevVersion = null ;
61- for ( int i = 0 , len = receivedList . Length ; i < len ; i ++ )
62- {
63- var row = receivedList [ i ] . Split ( ',' ) ;
64- var versionTemp = row [ 6 ] . Trim ( '"' ) ;
144+ string url = $ "{ BaseApiUrl } ?limit={ BatchSize } &offset={ offset } &architecture=X86_64&platform=WINDOWS";
65145
66- if ( versionTemp . Length < 1 ) continue ;
67- if ( prevVersion == versionTemp ) continue ;
146+ try
147+ {
148+ var response = await httpClient . GetStringAsync ( url ) ;
149+ return JsonSerializer . Deserialize < UnityVersionResponse > ( response ) ;
150+ }
151+ catch ( Exception e )
152+ {
153+ Console . WriteLine ( $ "Error fetching batch: { e . Message } ") ;
154+ return null ;
155+ }
156+ }
68157
69- if ( releases . ContainsKey ( versionTemp ) == false )
158+ private static List < UnityVersion > LoadCachedVersions ( )
159+ {
160+ // Check if the file is locally saved
161+ string configFilePath = ConfigurationManager . OpenExeConfiguration ( ConfigurationUserLevel . PerUserRoamingAndLocal ) . FilePath ;
162+ string configDirectory = Path . GetDirectoryName ( configFilePath ) ;
163+
164+ if ( configDirectory != null && Path . Combine ( configDirectory , CacheFileName ) is string cacheFilePath )
165+ {
166+ if ( File . Exists ( cacheFilePath ) )
70167 {
71- var u = new Updates ( ) ;
72- u . ReleaseDate = DateTime . ParseExact ( row [ 3 ] , "MM/dd/yyyy" , CultureInfo . InvariantCulture ) ;
73- u . Version = versionTemp ;
74- releases . Add ( versionTemp , u ) ;
75- updatesAsString . Add ( versionTemp ) ;
168+ var json = File . ReadAllText ( cacheFilePath ) ;
169+ return JsonSerializer . Deserialize < List < UnityVersion > > ( json ) ?? new List < UnityVersion > ( ) ;
76170 }
77-
78- prevVersion = versionTemp ;
79171 }
172+ else
173+ {
174+ return new List < UnityVersion > ( ) ;
175+ }
176+
177+ // Take the embedded file and save it locally, then rerun this method when that is successful
178+ var assembly = Assembly . GetExecutingAssembly ( ) ;
179+ using ( var stream = assembly . GetManifestResourceStream ( $ "{ assembly . GetName ( ) . Name } .Resources.{ CacheFileName } ") )
180+ {
181+ if ( stream == null )
182+ return new List < UnityVersion > ( ) ;
80183
81- // convert to array
82- var results = new Updates [ releases . Count ] ;
83- releases . Values . CopyTo ( results , 0 ) ;
84- return results ;
184+ using ( var reader = new StreamReader ( stream ) )
185+ {
186+ var json = reader . ReadToEnd ( ) ;
187+ File . WriteAllText ( cacheFilePath , json ) ;
188+ return JsonSerializer . Deserialize < List < UnityVersion > > ( json ) ?? new List < UnityVersion > ( ) ;
189+ }
190+ }
85191 }
192+
193+ private static void SaveCachedVersions ( List < UnityVersion > versions )
194+ {
195+ var json = JsonSerializer . Serialize ( versions ) ;
196+
197+ string configFilePath = ConfigurationManager . OpenExeConfiguration ( ConfigurationUserLevel . PerUserRoamingAndLocal ) . FilePath ;
198+ string configDirectory = Path . GetDirectoryName ( configFilePath ) ;
86199
200+ if ( configDirectory != null && Path . Combine ( configDirectory , CacheFileName ) is string cacheFilePath )
201+ {
202+ File . WriteAllText ( cacheFilePath , json ) ;
203+ }
204+ }
87205 }
88- }
206+ }
0 commit comments