@@ -173,6 +173,138 @@ public void IconExtractor_NegativeIndex_TreatedAsResourceId()
173173 Assert . True ( fileInfo . Length > 1000 , "Icon file should have content" ) ;
174174 }
175175
176+ /// <summary>
177+ /// Tests that negative indices (resource IDs) extract successfully.
178+ /// PrivateExtractIcons handles negative indices as resource IDs natively.
179+ /// </summary>
180+ [ Fact ]
181+ public void IconExtractor_NegativeIndex_ExtractsSuccessfully ( )
182+ {
183+ // Arrange
184+ var imageresPath = @"C:\Windows\System32\imageres.dll" ;
185+ if ( ! File . Exists ( imageresPath ) )
186+ return ;
187+
188+ using var extractor = new IconExtractor ( imageresPath ) ;
189+
190+ // Resource ID 183 exists in imageres.dll
191+ Assert . True ( extractor . HasResourceId ( 183 ) , "imageres.dll should have resource ID 183" ) ;
192+
193+ // Act - Extract with -183 (resource ID)
194+ var outputPath = Path . Combine ( _fixture . TestDataPath , "test_imageres_neg183.ico" ) ;
195+ extractor . SaveIcon ( - 183 , outputPath ) ;
196+
197+ // Assert - File was created with content
198+ Assert . True ( File . Exists ( outputPath ) , "Icon file should be created" ) ;
199+ var fileInfo = new FileInfo ( outputPath ) ;
200+ Assert . True ( fileInfo . Length > 1000 , "Icon file should have content" ) ;
201+ }
202+
203+ /// <summary>
204+ /// Tests that GetIcon and SaveIcon produce consistent results for the same index.
205+ /// This is a regression test for the bug where preview showed a different icon than extraction.
206+ /// </summary>
207+ [ Fact ]
208+ public void IconExtractor_GetIconAndSaveIcon_ProduceConsistentResults ( )
209+ {
210+ // Arrange
211+ var imageresPath = @"C:\Windows\System32\imageres.dll" ;
212+ if ( ! File . Exists ( imageresPath ) )
213+ return ;
214+
215+ using var extractor = new IconExtractor ( imageresPath ) ;
216+
217+ // Act - Get icon via GetIcon (preview) and SaveIcon (extraction) with the same index
218+ var previewIcon = extractor . GetIcon ( - 183 , 32 ) ;
219+ Assert . NotNull ( previewIcon ) ;
220+
221+ var outputPath = Path . Combine ( _fixture . TestDataPath , "test_consistency.ico" ) ;
222+ extractor . SaveIcon ( - 183 , outputPath ) ;
223+
224+ // Assert - Both should succeed (we can't easily compare pixels, but both should work)
225+ Assert . True ( File . Exists ( outputPath ) , "Extracted icon file should exist" ) ;
226+ Assert . True ( previewIcon . Width > 0 , "Preview icon should have valid dimensions" ) ;
227+
228+ previewIcon . Dispose ( ) ;
229+ }
230+
231+ /// <summary>
232+ /// End-to-end test for imageres.dll,-183 through the full extraction pipeline.
233+ /// This verifies that negative resource IDs are handled correctly by the entire system.
234+ /// </summary>
235+ [ Fact ]
236+ public void ExtractAndInstall_ImageresDll_Neg183_ExtractsCorrectIcon ( )
237+ {
238+ // Arrange - Create a test folder with imageres.dll,-183 (negative resource ID)
239+ var testFolderPath = Path . Combine ( _fixture . TestDataPath , "NegativeResourceIdTest" ) ;
240+ Directory . CreateDirectory ( testFolderPath ) ;
241+
242+ // Write desktop.ini with negative resource ID (-183)
243+ var iniPath = Path . Combine ( testFolderPath , "desktop.ini" ) ;
244+ File . WriteAllText ( iniPath ,
245+ "[.ShellClassInfo]\r \n " +
246+ "IconResource=%SystemRoot%\\ System32\\ imageres.dll,-183\r \n " ) ;
247+
248+ try
249+ {
250+ var service = new FolderIconService ( ) ;
251+
252+ // Scan
253+ var scanResult = service . Scan ( testFolderPath , recursive : false ) ;
254+ Assert . Single ( scanResult . Folders ) ;
255+
256+ var folder = scanResult . Folders . First ( ) ;
257+ Assert . Equal ( - 183 , folder . CurrentIconResource ? . Index ) ;
258+ Assert . Equal ( FolderIconStatus . ExternalAndValid , folder . Status ) ;
259+
260+ // Act - Fix (extract icon)
261+ var extractResult = service . ExtractAndInstall ( new [ ] { folder } , skipExisting : false ) ;
262+
263+ // Assert
264+ Assert . Single ( extractResult . Succeeded ) ;
265+
266+ // Verify the icon was extracted
267+ var iconPath = Path . Combine ( testFolderPath , "folder.ico" ) ;
268+ Assert . True ( File . Exists ( iconPath ) , "Icon should be extracted" ) ;
269+
270+ // Verify it's a valid ICO file with content
271+ var fileInfo = new FileInfo ( iconPath ) ;
272+ Assert . True ( fileInfo . Length > 1000 , "Icon file should have substantial content" ) ;
273+
274+ // Read the icon file header to verify it's valid
275+ using var fs = File . OpenRead ( iconPath ) ;
276+ var header = new byte [ 6 ] ;
277+ fs . Read ( header , 0 , 6 ) ;
278+
279+ Assert . Equal ( 0 , header [ 0 ] ) ; // Reserved
280+ Assert . Equal ( 0 , header [ 1 ] ) ; // Reserved
281+ Assert . Equal ( 1 , header [ 2 ] ) ; // Type = 1 (ICO)
282+ Assert . Equal ( 0 , header [ 3 ] ) ; // Type high byte
283+
284+ // Icon count should be > 0
285+ var iconCount = header [ 4 ] | ( header [ 5 ] << 8 ) ;
286+ Assert . True ( iconCount > 0 , "Icon should have at least one image" ) ;
287+ }
288+ finally
289+ {
290+ // Cleanup
291+ if ( Directory . Exists ( testFolderPath ) )
292+ {
293+ foreach ( var file in Directory . GetFiles ( testFolderPath ) )
294+ {
295+ try
296+ {
297+ File . SetAttributes ( file , FileAttributes . Normal ) ;
298+ File . Delete ( file ) ;
299+ }
300+ catch { /* Ignore cleanup errors */ }
301+ }
302+ try { Directory . Delete ( testFolderPath ) ; }
303+ catch { /* Ignore cleanup errors */ }
304+ }
305+ }
306+ }
307+
176308 /// <summary>
177309 /// End-to-end test: Create a folder with a high ordinal index reference,
178310 /// fix it, and verify the correct icon is extracted.
0 commit comments