Skip to content

Commit 9869976

Browse files
committed
feat(bg-api): Rewrite background media pipeline for dynamic asset fetching
1 parent eb1ec98 commit 9869976

6 files changed

Lines changed: 134 additions & 46 deletions

File tree

Hi3Helper.Plugin.Wuwa/Management/Api/WuwaApiResponse.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ namespace Hi3Helper.Plugin.Wuwa.Management.Api;
1010
[JsonSerializable(typeof(WuwaApiResponsePatchIndex))]
1111
[JsonSerializable(typeof(WuwaLauncherDownloadConfig))]
1212
[JsonSerializable(typeof(WuwaApiResponseResourceIndex))]
13+
[JsonSerializable(typeof(WuwaApiResponseLauncherConfig))]
1314
public partial class WuwaApiResponseContext : JsonSerializerContext;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 System.Text.Json.Serialization;
6+
// ReSharper disable IdentifierTypo
7+
8+
namespace Hi3Helper.Plugin.Wuwa.Management.Api;
9+
10+
public class WuwaApiResponseLauncherConfig
11+
{
12+
[JsonPropertyName("functionCode")] // Mapping: root -> functionCode
13+
public WuwaApiResponseLauncherConfigFunctionCode? FunctionCode { get; set; }
14+
}
15+
16+
public class WuwaApiResponseLauncherConfigFunctionCode
17+
{
18+
/// <summary>
19+
/// The current background hash that rotates with each launcher background update.
20+
/// Used as the <c>&lt;BACKGROUND_HASH&gt;</c> path segment in the wallpapers-slogan URL:
21+
/// <c>.../background/{Background}/en.json</c>
22+
/// </summary>
23+
[JsonPropertyName("background")] // Mapping: root -> functionCode -> background
24+
public string? Background { get; set; }
25+
}

Hi3Helper.Plugin.Wuwa/Management/Api/WuwaApiResponseMedia.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,17 @@ public class WuwaApiResponseMedia
77
{
88
[JsonPropertyName("backgroundFile")] // Mapping: root -> backgroundFile
99
public string? BackgroundImageUrl { get; set; }
10+
11+
/// <summary>URL of the first-frame thumbnail for the background video.</summary>
12+
[JsonPropertyName("firstFrameImage")] // Mapping: root -> firstFrameImage
13+
public string? FirstFrameImageUrl { get; set; }
14+
15+
/// <summary>URL of the slogan/watermark image displayed over the background.</summary>
16+
[JsonPropertyName("slogan")] // Mapping: root -> slogan
17+
public string? SloganUrl { get; set; }
18+
19+
/// <summary>Background media type: 2 = video, otherwise image.</summary>
20+
[JsonPropertyName("backgroundFileType")] // Mapping: root -> backgroundFileType
21+
public int BackgroundFileType { get; set; }
1022
}
1123

Hi3Helper.Plugin.Wuwa/Management/Api/WuwaGlobalLauncherApiMedia.cs

Lines changed: 94 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
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;
26
using Hi3Helper.Plugin.Core.Management.Api;
37
using Hi3Helper.Plugin.Core.Utility;
48
using Hi3Helper.Plugin.Wuwa.Utils;
@@ -17,11 +21,12 @@
1721
namespace 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
}

Hi3Helper.Plugin.Wuwa/Management/PresetConfig/WuwaGlobalPresetConfig.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ public override string GameAppDataPath
9595
"Spanish"
9696
];
9797

98-
public override ILauncherApiMedia? LauncherApiMedia
98+
public override ILauncherApiMedia? LauncherApiMedia
9999
{
100-
get => field ??= new WuwaGlobalLauncherApiMedia(ApiResponseUrl, CurrentTag, AuthenticationHash, "bg", Hash1);
100+
get => field ??= new WuwaGlobalLauncherApiMedia(ApiResponseUrl, CurrentTag, AuthenticationHash);
101101
set;
102102
}
103103

Hi3Helper.Plugin.Wuwa/Utils/WuwaUtils.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ private static PluginHttpClientBuilder CreateApiHttpClientBuilder(string? apiBas
4444
case "news":
4545
builder.SetBaseUrl(apiBaseUrl.CombineUrlFromString("launcher", authCdnToken, gameTag, "information", "en.json"));
4646
break;
47-
case "bg":
48-
builder.SetBaseUrl(apiBaseUrl.CombineUrlFromString("launcher", authCdnToken, gameTag, "background", hash1, "en.json"));
49-
break;
5047
case "media":
5148
builder.SetBaseUrl(apiBaseUrl.CombineUrlFromString("launcher", gameTag, authCdnToken, "social", "en.json"));
5249
break;

0 commit comments

Comments
 (0)