1- using Hi3Helper . Plugin . Core ;
1+ // Endpoint discovery and core implementation idea credit: DynamiByte
2+ // References:
3+ // https://gist.github.com/DynamiByte/d839bf9f671c975b6666d0f6e6634641
4+ // https://github.com/Cheu3172/Wuwa-Web-Request
5+ using Hi3Helper . Plugin . Core ;
26using Hi3Helper . Plugin . Core . Management . Api ;
37using Hi3Helper . Plugin . Core . Utility ;
48using Hi3Helper . Plugin . Wuwa . Utils ;
1721namespace Hi3Helper . Plugin . Wuwa . Management . Api ;
1822
1923[ GeneratedComClass ]
20- internal partial class WuwaGlobalLauncherApiMedia ( string apiResponseBaseUrl , string gameTag , string authenticationHash , string apiOptions , string hash1 ) : LauncherApiMediaBase
24+ internal partial class WuwaGlobalLauncherApiMedia ( string apiResponseBaseUrl , string gameTag , string authenticationHash ) : LauncherApiMediaBase
2125{
2226 [ field: AllowNull , MaybeNull ]
23- protected override HttpClient ApiResponseHttpClient {
24- get => field ??= WuwaUtils . CreateApiHttpClient ( ApiResponseBaseUrl , gameTag . AeonPlsHelpMe ( ) , authenticationHash . AeonPlsHelpMe ( ) , apiOptions , hash1 . AeonPlsHelpMe ( ) ) ;
27+ protected override HttpClient ApiResponseHttpClient
28+ {
29+ get => field ??= WuwaUtils . CreateApiHttpClient ( ApiResponseBaseUrl ) ;
2530 set ;
2631 }
2732
@@ -72,57 +77,105 @@ public override void GetBackgroundEntries(out nint handle, out int count, out bo
7277 }
7378
7479 public override void GetBackgroundFlag ( out LauncherBackgroundFlag result )
75- => result = LauncherBackgroundFlag . TypeIsVideo | LauncherBackgroundFlag . TypeIsImage ;
80+ // backgroundFileType == 2 indicates a video background; everything else is an image.
81+ => result = ApiResponse ? . BackgroundFileType == 2
82+ ? LauncherBackgroundFlag . TypeIsVideo
83+ : LauncherBackgroundFlag . TypeIsImage ;
7684
7785 public override void GetLogoFlag ( out LauncherBackgroundFlag result )
78- => result = LauncherBackgroundFlag . None ;
86+ => result = ApiResponse ? . SloganUrl != null
87+ ? LauncherBackgroundFlag . TypeIsImage
88+ : LauncherBackgroundFlag . None ;
7989
8090 public override void GetLogoOverlayEntries ( out nint handle , out int count , out bool isDisposable , out bool isAllocated )
8191 {
82- isDisposable = false ;
83- handle = nint . Zero ;
84- count = 0 ;
85- isAllocated = false ;
92+ if ( ApiResponse ? . SloganUrl == null )
93+ {
94+ isDisposable = false ;
95+ handle = nint . Zero ;
96+ count = 0 ;
97+ isAllocated = false ;
98+ return ;
99+ }
100+
101+ using ( ThisInstanceLock . EnterScope ( ) )
102+ {
103+ PluginDisposableMemory < LauncherPathEntry > logoEntries = PluginDisposableMemory < LauncherPathEntry > . Alloc ( ) ;
104+ ref LauncherPathEntry entry = ref logoEntries [ 0 ] ;
105+ entry . Write ( ApiResponse . SloganUrl , Span < byte > . Empty ) ;
106+
107+ isDisposable = logoEntries . IsDisposable == 1 ;
108+ handle = logoEntries . AsSafePointer ( ) ;
109+ count = logoEntries . Length ;
110+ isAllocated = true ;
111+ }
86112 }
87113
88114 protected override async Task < int > InitAsync ( CancellationToken token )
89115 {
90- // Resolve request URL: prefer HttpClient.BaseAddress if set, otherwise fallback to ApiResponseBaseUrl
91- string requestUrl = ApiResponseHttpClient ? . BaseAddress ? . ToString ( ) ?? ApiResponseBaseUrl ;
116+ string decodedAuthHash = authenticationHash . AeonPlsHelpMe ( ) ;
117+ string decodedGameTag = gameTag . AeonPlsHelpMe ( ) ;
118+
119+ // Step 1 — fetch the stable launcher-config endpoint to resolve the current (rotating) background hash.
120+ // URL: {CDN}/launcher/launcher/{clientId}/{gameId}/index.json
121+ string launcherConfigUrl = ApiResponseBaseUrl
122+ . CombineUrlFromString ( "launcher" , "launcher" , decodedAuthHash , decodedGameTag , "index.json" ) ;
123+
92124#if DEBUG
93- SharedStatic . InstanceLogger . LogDebug ( "[WuwaGlobalLauncherApiMedia::InitAsync] Requesting media URL: {RequestUrl}" , requestUrl ) ;
125+ SharedStatic . InstanceLogger . LogDebug (
126+ "[WuwaGlobalLauncherApiMedia::InitAsync] Fetching launcher-config: {Url}" , launcherConfigUrl ) ;
94127#endif
95- using HttpResponseMessage response = await ApiResponseHttpClient ! . GetAsync ( requestUrl , token ) ;
96-
97- // Log status and body on failure to aid debugging (but avoid reading unnecessarily on success)
98- if ( ! response . IsSuccessStatusCode )
99- {
100- string body = string . Empty ;
101- try
102- {
103- body = await response . Content . ReadAsStringAsync ( token ) ;
104- }
105- catch ( Exception ex )
106- {
107- SharedStatic . InstanceLogger . LogError ( ex , "[WuwaGlobalLauncherApiMedia::InitAsync] Failed to read response body." ) ;
108- }
128+
129+ using HttpResponseMessage configResponse = await ApiResponseHttpClient ! . GetAsync ( launcherConfigUrl , token ) ;
130+ if ( ! configResponse . IsSuccessStatusCode )
131+ {
132+ SharedStatic . InstanceLogger . LogError (
133+ "[WuwaGlobalLauncherApiMedia::InitAsync] launcher-config request failed: {StatusCode}" , ( int ) configResponse . StatusCode ) ;
134+ }
135+ configResponse . EnsureSuccessStatusCode ( ) ;
136+
137+ string configJson = await configResponse . Content . ReadAsStringAsync ( token ) ;
138+ SharedStatic . InstanceLogger . LogTrace ( "[WuwaGlobalLauncherApiMedia::InitAsync] Launcher-config response: {Json}" , configJson ) ;
139+
140+ WuwaApiResponseLauncherConfig ? launcherConfig = JsonSerializer . Deserialize (
141+ configJson , WuwaApiResponseContext . Default . WuwaApiResponseLauncherConfig ) ;
142+
143+ string ? backgroundHash = launcherConfig ? . FunctionCode ? . Background ;
144+ if ( string . IsNullOrEmpty ( backgroundHash ) )
145+ throw new InvalidOperationException (
146+ "launcher-config response did not contain a valid functionCode.background hash." ) ;
147+
148+ #if DEBUG
149+ SharedStatic . InstanceLogger . LogDebug (
150+ "[WuwaGlobalLauncherApiMedia::InitAsync] Resolved background hash: {Hash}" , backgroundHash ) ;
151+ #endif
152+
153+ // Step 2 — fetch the wallpapers-slogan endpoint using the dynamic hash.
154+ // URL: {CDN}/launcher/{clientId}/{gameId}/background/{hash}/en.json
155+ string wallpaperUrl = ApiResponseBaseUrl
156+ . CombineUrlFromString ( "launcher" , decodedAuthHash , decodedGameTag , "background" , backgroundHash , "en.json" ) ;
157+
109158#if DEBUG
110- SharedStatic . InstanceLogger . LogError (
111- "[WuwaGlobalLauncherApiMedia::InitAsync] Request to {RequestUrl} failed with status {StatusCode}. Response body: {ResponseBody}" ,
112- requestUrl , ( int ) response . StatusCode , body ) ;
159+ SharedStatic . InstanceLogger . LogDebug (
160+ "[WuwaGlobalLauncherApiMedia::InitAsync] Fetching wallpaper: {Url}" , wallpaperUrl ) ;
113161#endif
114- }
115162
116- response . EnsureSuccessStatusCode ( ) ;
163+ using HttpResponseMessage wallpaperResponse = await ApiResponseHttpClient . GetAsync ( wallpaperUrl , token ) ;
164+ if ( ! wallpaperResponse . IsSuccessStatusCode )
165+ {
166+ SharedStatic . InstanceLogger . LogError (
167+ "[WuwaGlobalLauncherApiMedia::InitAsync] wallpaper request failed: {StatusCode}" , ( int ) wallpaperResponse . StatusCode ) ;
168+ }
169+ wallpaperResponse . EnsureSuccessStatusCode ( ) ;
117170
118- string jsonResponse = await response . Content . ReadAsStringAsync ( token ) ;
119- SharedStatic . InstanceLogger . LogTrace ( "API Media response: {JsonResponse}" , jsonResponse ) ;
120- ApiResponse = JsonSerializer . Deserialize < WuwaApiResponseMedia > ( jsonResponse , WuwaApiResponseContext . Default . WuwaApiResponseMedia )
121- ?? throw new NullReferenceException ( "Background Media API Returns null response!" ) ;
171+ string wallpaperJson = await wallpaperResponse . Content . ReadAsStringAsync ( token ) ;
172+ SharedStatic . InstanceLogger . LogTrace ( "[WuwaGlobalLauncherApiMedia::InitAsync] Wallpaper response: {Json}" , wallpaperJson ) ;
122173
123- // We don't have a way to check if the API response is valid, so we assume it is valid if we reach this point.
124- return 0 ;
125- }
174+ ApiResponse = JsonSerializer . Deserialize ( wallpaperJson , WuwaApiResponseContext . Default . WuwaApiResponseMedia )
175+ ?? throw new NullReferenceException ( "Wallpaper API returned a null response!" ) ;
176+
177+ return 0 ;
178+ }
126179
127180 protected override async Task DownloadAssetAsyncInner ( HttpClient ? client , string fileUrl , Stream outputStream ,
128181 PluginDisposableMemory < byte > fileChecksum , PluginFiles . FileReadProgressDelegate ? downloadProgress , CancellationToken token )
@@ -134,12 +187,12 @@ public override void Dispose()
134187 {
135188 if ( IsDisposed )
136189 return ;
137-
190+
138191 using ( ThisInstanceLock . EnterScope ( ) )
139192 {
140193 ApiResponseHttpClient . Dispose ( ) ;
141194 ApiDownloadHttpClient . Dispose ( ) ;
142-
195+
143196 ApiResponse = null ;
144197 base . Dispose ( ) ;
145198 }
0 commit comments