@@ -27,40 +27,18 @@ internal async Task TryDownloadWholeFileWithFallbacksAsync(Uri originalUri, stri
2727 SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadWholeFileWithFallbacksAsync] Primary download failed: {Uri}. Reason: {Msg}" , originalUri , ex . Message ) ;
2828 }
2929
30- string encodedPath = EncodePathSegments ( rawDest ) ;
31-
32- // Fallback 1: encoded concatenation using the Path portion of the original URI
30+ // Fallback 1: Re-encode each path segment of the original URI to handle special chars
3331 try
3432 {
35- var basePath = originalUri . GetLeftPart ( UriPartial . Path ) ;
36- string encodedConcatUrl = basePath . TrimEnd ( '/' ) + "/" + encodedPath ;
37- SharedStatic . InstanceLogger . LogDebug ( "[WuwaGameInstaller::TryDownloadWholeFileWithFallbacksAsync] Trying encoded concatenation fallback URI: {Uri}" , encodedConcatUrl ) ;
38- Uri fallbackUri = new Uri ( encodedConcatUrl , UriKind . Absolute ) ;
33+ string reEncodedUrl = ReEncodeUriPathSegments ( originalUri ) ;
34+ SharedStatic . InstanceLogger . LogDebug ( "[WuwaGameInstaller::TryDownloadWholeFileWithFallbacksAsync] Trying re-encoded fallback URI: {Uri}" , reEncodedUrl ) ;
35+ Uri fallbackUri = new Uri ( reEncodedUrl , UriKind . Absolute ) ;
3936 await DownloadWholeFileAsync ( fallbackUri , outputPath , token , progressCallback ) . ConfigureAwait ( false ) ;
4037 return ;
4138 }
4239 catch ( Exception ex ) when ( ex is HttpRequestException or IOException )
4340 {
44- SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadWholeFileWithFallbacksAsync] Encoded concatenation fallback failed: {Msg}" , ex . Message ) ;
45- }
46-
47- // Fallback 2: try using a simple concatenation (encoded)
48- try
49- {
50- var baseAuthority = originalUri . GetLeftPart ( UriPartial . Authority ) ;
51- var baseDir = originalUri . AbsolutePath ;
52- int lastSlash = baseDir . LastIndexOf ( '/' ) ;
53- if ( lastSlash >= 0 )
54- baseDir = baseDir [ ..( lastSlash + 1 ) ] ;
55- string tryUrl = baseAuthority + baseDir + encodedPath ;
56- SharedStatic . InstanceLogger . LogDebug ( "[WuwaGameInstaller::TryDownloadWholeFileWithFallbacksAsync] Trying authority+dir fallback URI: {Uri}" , tryUrl ) ;
57- Uri fallbackUri2 = new Uri ( tryUrl , UriKind . Absolute ) ;
58- await DownloadWholeFileAsync ( fallbackUri2 , outputPath , token , progressCallback ) . ConfigureAwait ( false ) ;
59- return ;
60- }
61- catch ( Exception ex ) when ( ex is HttpRequestException or IOException )
62- {
63- SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadWholeFileWithFallbacksAsync] Authority+dir fallback failed: {Msg}" , ex . Message ) ;
41+ SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadWholeFileWithFallbacksAsync] Re-encoded fallback failed: {Msg}" , ex . Message ) ;
6442 }
6543
6644 throw new HttpRequestException ( $ "All download attempts failed for: { rawDest } ") ;
@@ -79,40 +57,18 @@ internal async Task TryDownloadChunkedFileWithFallbacksAsync(Uri originalUri, st
7957 SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadChunkedFileWithFallbacksAsync] Primary chunked download failed: {Uri}. Reason: {Msg}" , originalUri , ex . Message ) ;
8058 }
8159
82- string encodedPath = EncodePathSegments ( rawDest ) ;
83-
84- // Fallback 1: encoded concatenation using the Path portion of the original URI
60+ // Fallback 1: Re-encode each path segment of the original URI to handle special chars
8561 try
8662 {
87- var basePath = originalUri . GetLeftPart ( UriPartial . Path ) ;
88- string encodedConcatUrl = basePath . TrimEnd ( '/' ) + "/" + encodedPath ;
89- SharedStatic . InstanceLogger . LogDebug ( "[WuwaGameInstaller::TryDownloadChunkedFileWithFallbacksAsync] Trying encoded concatenation fallback URI: {Uri}" , encodedConcatUrl ) ;
90- Uri fallbackUri = new Uri ( encodedConcatUrl , UriKind . Absolute ) ;
63+ string reEncodedUrl = ReEncodeUriPathSegments ( originalUri ) ;
64+ SharedStatic . InstanceLogger . LogDebug ( "[WuwaGameInstaller::TryDownloadChunkedFileWithFallbacksAsync] Trying re-encoded fallback URI: {Uri}" , reEncodedUrl ) ;
65+ Uri fallbackUri = new Uri ( reEncodedUrl , UriKind . Absolute ) ;
9166 await DownloadChunkedFileAsync ( fallbackUri , outputPath , chunkInfos , token , progressCallback ) . ConfigureAwait ( false ) ;
9267 return ;
9368 }
9469 catch ( Exception ex ) when ( ex is HttpRequestException or IOException )
9570 {
96- SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadChunkedFileWithFallbacksAsync] Encoded concatenation fallback failed: {Msg}" , ex . Message ) ;
97- }
98-
99- // Fallback 2: authority+dir + encoded path
100- try
101- {
102- var baseAuthority = originalUri . GetLeftPart ( UriPartial . Authority ) ;
103- var baseDir = originalUri . AbsolutePath ;
104- int lastSlash = baseDir . LastIndexOf ( '/' ) ;
105- if ( lastSlash >= 0 )
106- baseDir = baseDir [ ..( lastSlash + 1 ) ] ;
107- string tryUrl = baseAuthority + baseDir + encodedPath ;
108- SharedStatic . InstanceLogger . LogDebug ( "[WuwaGameInstaller::TryDownloadChunkedFileWithFallbacksAsync] Trying authority+dir fallback URI: {Uri}" , tryUrl ) ;
109- Uri fallbackUri2 = new Uri ( tryUrl , UriKind . Absolute ) ;
110- await DownloadChunkedFileAsync ( fallbackUri2 , outputPath , chunkInfos , token , progressCallback ) . ConfigureAwait ( false ) ;
111- return ;
112- }
113- catch ( Exception ex ) when ( ex is HttpRequestException or IOException )
114- {
115- SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadChunkedFileWithFallbacksAsync] Authority+dir fallback failed: {Msg}" , ex . Message ) ;
71+ SharedStatic . InstanceLogger . LogWarning ( "[WuwaGameInstaller::TryDownloadChunkedFileWithFallbacksAsync] Re-encoded fallback failed: {Msg}" , ex . Message ) ;
11672 }
11773
11874 throw new HttpRequestException ( $ "All chunked download attempts failed for: { rawDest } ") ;
@@ -126,6 +82,22 @@ internal static string EncodePathSegments(string path)
12682 return string . Join ( "/" , parts . Select ( Uri . EscapeDataString ) ) ;
12783 }
12884
85+ /// <summary>
86+ /// Re-encodes each path segment of an existing URI. This handles cases where
87+ /// the original URI has un-encoded special characters (spaces, CJK, etc.)
88+ /// that cause the CDN to 404. Does NOT produce a doubled path — it only
89+ /// re-encodes what's already in the URI.
90+ /// </summary>
91+ internal static string ReEncodeUriPathSegments ( Uri uri )
92+ {
93+ string authority = uri . GetLeftPart ( UriPartial . Authority ) ;
94+ // Use decoded AbsolutePath so we get the raw characters, then re-encode each segment
95+ string decodedPath = Uri . UnescapeDataString ( uri . AbsolutePath ) ;
96+ string [ ] segments = decodedPath . Split ( '/' , StringSplitOptions . RemoveEmptyEntries ) ;
97+ string encodedPath = string . Join ( "/" , segments . Select ( Uri . EscapeDataString ) ) ;
98+ return $ "{ authority } /{ encodedPath } ";
99+ }
100+
129101 internal async Task DownloadWholeFileAsync ( Uri uri , string outputPath , CancellationToken token , Action < long > ? progressCallback )
130102 {
131103 string tempPath = outputPath + ".tmp" ;
0 commit comments