@@ -1052,6 +1052,22 @@ await Parallel.ForEachAsync(downloadEntries,
10521052 var fi = new FileInfo ( existingPath ) ;
10531053 if ( fi . Length == ( long ) entry . Size )
10541054 {
1055+ // Size matches — verify MD5 to ensure the content is
1056+ // actually the new version and not a stale file.
1057+ if ( ! string . IsNullOrEmpty ( entry . Md5 ) && fi . Length <= Md5CheckSizeThreshold )
1058+ {
1059+ await using var existFs = File . OpenRead ( existingPath ) ;
1060+ string existMd5 = await WuwaUtils . ComputeMd5HexAsync ( existFs , ct )
1061+ . ConfigureAwait ( false ) ;
1062+ if ( ! string . Equals ( existMd5 , entry . Md5 , StringComparison . OrdinalIgnoreCase ) )
1063+ {
1064+ SharedStatic . InstanceLogger . LogDebug (
1065+ "[Patch::RunAsync] Full-replacement file has correct size but MD5 mismatch, downloading: {Dest}" ,
1066+ entry . Dest ) ;
1067+ goto DownloadFile ;
1068+ }
1069+ }
1070+
10551071 SharedStatic . InstanceLogger . LogDebug (
10561072 "[Patch::RunAsync] Full-replacement file already exists with correct size, skipping: {Dest}" ,
10571073 entry . Dest ) ;
@@ -1064,6 +1080,8 @@ await Parallel.ForEachAsync(downloadEntries,
10641080 }
10651081 }
10661082
1083+ DownloadFile :
1084+
10671085 // Ensure subdirectory exists
10681086 string ? dir = Path . GetDirectoryName ( outputPath ) ;
10691087 if ( ! string . IsNullOrEmpty ( dir ) )
@@ -1126,7 +1144,7 @@ await _owner.TryDownloadWholeFileWithFallbacksAsync(
11261144 string filePath = Path . Combine ( patchTempPath , relativePath ) ;
11271145
11281146 // Full-replacement files may have been skipped during download because
1129- // they already exist in the install directory with the correct size.
1147+ // they already exist in the install directory with the correct size and MD5 .
11301148 // Check both temp and install locations.
11311149 if ( ! File . Exists ( filePath ) && ! isKrpdiff )
11321150 {
@@ -1136,6 +1154,19 @@ await _owner.TryDownloadWholeFileWithFallbacksAsync(
11361154 var installFi = new FileInfo ( installFilePath ) ;
11371155 if ( installFi . Length == ( long ) entry . Size )
11381156 {
1157+ // Also verify MD5 to catch stale files with matching size.
1158+ if ( ! string . IsNullOrEmpty ( entry . Md5 ) && installFi . Length <= Md5CheckSizeThreshold )
1159+ {
1160+ await using var installFs = File . OpenRead ( installFilePath ) ;
1161+ string installMd5 = await WuwaUtils . ComputeMd5HexAsync ( installFs , token )
1162+ . ConfigureAwait ( false ) ;
1163+ if ( ! string . Equals ( installMd5 , entry . Md5 , StringComparison . OrdinalIgnoreCase ) )
1164+ {
1165+ throw new InvalidOperationException (
1166+ $ "Full-replacement file was skipped during download but MD5 does not match in install dir: { entry . Dest } (expected={ entry . Md5 } , computed={ installMd5 } )") ;
1167+ }
1168+ }
1169+
11391170 SharedStatic . InstanceLogger . LogDebug (
11401171 "[Patch::RunAsync] Verification: full-replacement file verified in install dir (skipped download): {Dest}" ,
11411172 entry . Dest ) ;
0 commit comments