NOTE: This version is old and outdated. There may be bugs, incomplete features, inaccuracies, etc. The latest version is part of AngelLoader: https://github.com/FenPhoenix/AngelLoader/tree/master/FMScanner
A fast, thorough, accurate scanner for Thief 1, Thief 2, and Thief 3 fan missions.
Detects the following:
- Title (along with a list of alternate titles if more than one is detected and they don't all match)
- Titles of campaign missions if it's a campaign
- Author
- Description (if specified in fm.ini)
- Game (Thief 1, Thief 2, or Thief: Deadly Shadows)
- Languages (work in progress)
- Version (if specified; work in progress)
- Whether the mission requires NewDark
- The minimum required NewDark version (if specified; work in progress)
- Last updated date (if specified explicitly; otherwise best guess)
- Size of the FM (compressed size for zips; uncompressed size for folders)
- Whether the mission has any of the following:
- Map
- Automap
- Custom textures
- Custom objects
- Custom AIs (creatures, guards, etc.)
- Custom sounds
- Custom movies
- Custom motions
- Custom scripts
- Custom subtitles (NewDark-style only)
privateasyncvoidFMScannerUsageExample(){// Set these to what you want. The fewer things that are scanned for, the faster the scan will be.// This is optional. If you call Scan() without providing a ScanOptions object, all options will default// to true.varscanOptions=newFMScanner.ScanOptions{ScanTitle=true,ScanCampaignMissionNames=true,ScanAuthor=true,ScanVersion=true,ScanLanguages=true,ScanGameType=true,ScanNewDarkRequired=true,ScanNewDarkMinimumVersion=true,ScanCustomResources=true,ScanSize=true};// In most cases archives will be scanned without requiring an extract to disk, but when that's not the// case, they will be temporarily extracted to this directory.vartempPath="C:\\MyTempDir\\FmScanTemp";// Single-FM scans are synchronous (which is probably what you want for FM loaders).ScanSingleFM(scanOptions,tempPath);// Multi-FM scans are asynchronous.awaitScanMultipleFMs(scanOptions,tempPath);awaitScanMultipleFMsWithProgressReport(scanOptions,tempPath);}privatevoidScanSingleFM(FMScanner.ScanOptionsscanOptions,stringtempPath){// This can be either an archive (.zip, .7z) or a directory. The scanner detects based on extension.varfm="C:\\FMs\\Rocksbourg3.zip";FMScanner.ScannedFMDatafmData;using(varscanner=newFMScanner.Scanner()){fmData=scanner.Scan(fm,tempPath,scanOptions);}// do something with fmData here}privateasyncTaskScanMultipleFMs(FMScanner.ScanOptionsscanOptions,stringtempPath){// The list can contain both archives (.zip, .7z) and directories. The scanner detects based on// extension.varfms=newList<string>{"C:\\FMs\\BrokenTriad.zip","C:\\FMs\\Racket.7z","C:\\Thief2\\FMs\\SevenSisters_The"};List<FMScanner.ScannedFMData>fmDataList;using(varscanner=newFMScanner.Scanner()){fmDataList=awaitscanner.ScanAsync(fms,tempPath,scanOptions);}// do something with fmDataList here}privateSystem.Threading.CancellationTokenSourcects;privateasyncTaskScanMultipleFMsWithProgressReport(FMScanner.ScanOptionsscanOptions,stringtempPath){// The list can contain both archives (.zip, .7z) and directories. The scanner detects based on// extension.varfms=newList<string>{"C:\\FMs\\BrokenTriad.zip","C:\\FMs\\Racket.7z","C:\\Thief2\\FMs\\SevenSisters_The"};voidReportProgress(FMScanner.ProgressReportprogressReport){Console.WriteLine("FM name: "+progressReport.FMName);// stringConsole.WriteLine("FM number: "+progressReport.FMNumber);// intConsole.WriteLine("FMs total: "+progressReport.FMsTotal);// intConsole.WriteLine("Percent: "+progressReport.Percent);// intConsole.WriteLine("Finished: "+progressReport.Finished);// bool}cts=newSystem.Threading.CancellationTokenSource();List<FMScanner.ScannedFMData>fmDataList;try{varprogress=newProgress<FMScanner.ProgressReport>(ReportProgress);using(varscanner=newFMScanner.Scanner()){// You can pass CancellationToken.None if you don't want the ability to cancel.fmDataList=awaitscanner.ScanAsync(fms,tempPath,scanOptions,progress,cts.Token);}}catch(OperationCanceledException){// The user cancelled the scan; act accordingly here}finally{cts?.Dispose();}// do something with fmDataList here}// You can call this from a cancel button or what have you.privatevoidCancelFMScan(){try{cts?.Cancel();}catch(ObjectDisposedException){}}FMScanner is licensed under the CC0 license, but contains portions that are licensed differently (because they're based on others' work). Each differently-licensed component is cleanly separated into its own folder with a LICENSE file in it. The license for each component is also specified at the top of each source file belonging to it. I apologize that legalities can be a pain, but I've done what I can to make it clear. Hope it helps.