1
1
using System ;
2
2
using 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 ;
5
9
using System . Threading . Tasks ;
6
10
7
11
namespace UnityLauncherPro
8
12
{
9
13
public static class GetUnityUpdates
10
14
{
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" ;
13
20
14
- public static async Task < string > Scan ( )
21
+ private static readonly HttpClient httpClient = new HttpClient ( ) ;
22
+
23
+ public static async Task < List < UnityVersion > > FetchAll ( )
15
24
{
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 )
17
33
{
18
- Console . WriteLine ( "We are already downloading ..." ) ;
19
- return null ;
34
+ SaveCachedVersions ( allVersions ) ;
20
35
}
21
36
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
27
54
{
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 )
30
59
{
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
+ }
32
93
}
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 )
34
123
{
35
- Console . WriteLine ( "It's a web exception" ) ;
124
+ if ( version . Version == latestCachedVersion ? . Version )
125
+ return newVersions ;
126
+
127
+ newVersions . Add ( version ) ;
36
128
}
37
- catch ( Exception )
129
+
130
+ total = batchUpdates . Total ;
131
+ offset += batchUpdates . Results . Count ;
132
+
133
+ if ( offset % ( BatchSize * RequestsPerBatch ) == 0 )
38
134
{
39
- Console . WriteLine ( "It's not a web exception" ) ;
135
+ await Task . Delay ( DelayBetweenBatches ) ;
40
136
}
41
-
42
- isDownloadingUnityList = false ;
43
137
}
44
- return result ;
138
+
139
+ return newVersions ;
45
140
}
46
141
47
- public static Updates [ ] Parse ( string items , ref List < string > updatesAsString )
142
+ private static async Task < UnityVersionResponse > FetchBatch ( int offset )
48
143
{
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";
65
145
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
+ }
68
157
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 ) )
70
167
{
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 > ( ) ;
76
170
}
77
-
78
- prevVersion = versionTemp ;
79
171
}
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 > ( ) ;
80
183
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
+ }
85
191
}
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 ) ;
86
199
200
+ if ( configDirectory != null && Path . Combine ( configDirectory , CacheFileName ) is string cacheFilePath )
201
+ {
202
+ File . WriteAllText ( cacheFilePath , json ) ;
203
+ }
204
+ }
87
205
}
88
- }
206
+ }
0 commit comments