/****************************************************** * ROMVault2 is written by Gordon J. * * Contact gordon@romvault.com * * Copyright 2014 * ******************************************************/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using ROMVault2.IO; using System.Threading; using ROMVault2.Properties; using ROMVault2.RvDB; using ROMVault2.SupportedFiles; using ROMVault2.SupportedFiles.CHD; using ROMVault2.SupportedFiles.Files; using ROMVault2.SupportedFiles.SevenZip; using ROMVault2.SupportedFiles.Zip; using ROMVault2.Utils; using FileInfo = ROMVault2.IO.FileInfo; namespace ROMVault2 { public static class FileScanning { private static Stopwatch _cacheSaveTimer; private static long _lastUpdateTime; private static BackgroundWorker _bgw; public static eScanLevel EScanLevel; private static bool _fileErrorAbort; public static void ScanFiles(object sender, DoWorkEventArgs e) { #if !Debug try { #endif _fileErrorAbort = false; _cacheSaveTimer = new Stopwatch(); _cacheSaveTimer.Reset(); if (Settings.CacheSaveTimerEnabled) _cacheSaveTimer.Start(); _bgw = sender as BackgroundWorker; if (_bgw == null) return; Program.SyncCont = e.Argument as SynchronizationContext; if (Program.SyncCont == null) { _bgw = null; return; } _bgw.ReportProgress(0, new bgwText("Clearing DB Status")); RepairStatus.ReportStatusReset(DB.DirTree); _bgw.ReportProgress(0, new bgwText("Finding Dir's to Scan")); //Next get a list of all the directories to be scanned List lstDir = new List(); DBHelper.GetSelectedDirList(ref lstDir); _bgw.ReportProgress(0, new bgwText("Scanning Dir's")); _bgw.ReportProgress(0, new bgwSetRange(lstDir.Count - 1)); //Scan the list of directories. for (int i = 0; i < lstDir.Count; i++) { _bgw.ReportProgress(i); _bgw.ReportProgress(0, new bgwText("Scanning Dir : " + lstDir[i].FullName)); string lDir = lstDir[i].FullName; Console.WriteLine(lDir); if (Directory.Exists(lDir)) CheckADir(lstDir[i], true); else MarkAsMissing(lstDir[i]); if (_bgw.CancellationPending || _fileErrorAbort) break; } _bgw.ReportProgress(0, new bgwText("Updating Cache")); DB.Write(); _bgw.ReportProgress(0, new bgwText("File Scan Complete")); _bgw = null; Program.SyncCont = null; #if !Debug } catch (Exception exc) { ReportError.UnhandledExceptionHandler(exc); if (_bgw != null) _bgw.ReportProgress(0, new bgwText("Updating Cache")); DB.Write(); if (_bgw != null) _bgw.ReportProgress(0, new bgwText("Complete")); _bgw = null; Program.SyncCont = null; } #endif } private static void CheckADir(RvDir dbDir, bool report) { if (_cacheSaveTimer.Elapsed.TotalMinutes > Settings.CacheSaveTimePeriod) { _bgw.ReportProgress(0, "Saving Cache"); DB.Write(); _bgw.ReportProgress(0, "Saving Cache Complete"); _cacheSaveTimer.Reset(); _cacheSaveTimer.Start(); } string fullDir = dbDir.FullName; if (report) _bgw.ReportProgress(0, new bgwText2(fullDir)); DatStatus chechingDatStatus = dbDir.IsInToSort ? DatStatus.InToSort : DatStatus.NotInDat; // this is a temporary rvDir structure to store the data about the actual directory/files we are scanning // we will first populate this variable with the real file data from the directory, and then compare it // with the data in dbDir. RvDir fileDir = null; Debug.WriteLine(fullDir); FileType ft = dbDir.FileType; #region "Populate fileDir" // if we are scanning a ZIP file then populate scanDir from the ZIP file switch (ft) { case FileType.Zip: case FileType.SevenZip: { fileDir = new RvDir(ft); // open the zip file ICompress checkZ; if (ft == FileType.Zip) checkZ = new ZipFile(); else checkZ = new SevenZ(); ZipReturn zr = checkZ.ZipFileOpen(fullDir, dbDir.TimeStamp, true); if (zr == ZipReturn.ZipGood) { dbDir.ZipStatus = checkZ.ZipStatus; // to be Scanning a ZIP file means it is either new or has changed. // as the code below only calls back here if that is true. // // Level1: Only use header CRC's // Just get the CRC for the ZIP headers. // // Level2: Fully checksum changed only files // We know this file has been changed to do a full checksum scan. // // Level3: Fully checksum everything // So do a full checksum scan. if (EScanLevel == eScanLevel.Level2 || EScanLevel == eScanLevel.Level3) checkZ.DeepScan(); // add all of the file information from the zip file into scanDir for (int i = 0; i < checkZ.LocalFilesCount(); i++) { RvFile tFile = new RvFile(DBTypeGet.FileFromDir(ft)) { Name = checkZ.Filename(i), ZipFileIndex = i, ZipFileHeaderPosition = checkZ.LocalHeader(i), Size = checkZ.UncompressedSize(i), CRC = checkZ.CRC32(i) }; // all 3 levels read the CRC from the ZIP header tFile.SetStatus(chechingDatStatus, GotStatus.Got); tFile.FileStatusSet(FileStatus.SizeFromHeader | FileStatus.CRCFromHeader); // if we are in level 2 or level 3 then do a full CheckSum Scan. if (EScanLevel == eScanLevel.Level2 || EScanLevel == eScanLevel.Level3) { // DeepScan will return ZipReturn.ZipCRCDecodeError if the headers CRC and // the actual CRC do not match. // So we just need to set the MD5 and SHA1 from the ZIP file. zr = checkZ.FileStatus(i); if (zr == ZipReturn.ZipUntested) { _bgw.ReportProgress(0, new bgwShowCorrupt(zr, fullDir + " : " + checkZ.Filename(i))); } else if (zr != ZipReturn.ZipGood) { _bgw.ReportProgress(0, new bgwShowCorrupt(zr, fullDir + " : " + checkZ.Filename(i))); tFile.GotStatus = GotStatus.Corrupt; } else { tFile.MD5 = checkZ.MD5(i); tFile.SHA1 = checkZ.SHA1(i); tFile.FileStatusSet(FileStatus.SizeVerified | FileStatus.CRCVerified | FileStatus.SHA1Verified | FileStatus.MD5Verified); } } fileDir.ChildAdd(tFile); } } else if (zr == ZipReturn.ZipFileLocked) { _bgw.ReportProgress(0, new bgwShowError(fullDir, "Zip File Locked")); dbDir.TimeStamp = 0; dbDir.GotStatus = GotStatus.FileLocked; } else { _bgw.ReportProgress(0, new bgwShowCorrupt(zr, fullDir)); dbDir.GotStatus = GotStatus.Corrupt; } checkZ.ZipFileClose(); } break; case FileType.Dir: { fileDir = new RvDir(FileType.Dir); DirectoryInfo oDir = new DirectoryInfo(fullDir); DirectoryInfo[] oDirs = oDir.GetDirectories(); FileInfo[] oFiles = oDir.GetFiles(); // add all the subdirectories into scanDir foreach (DirectoryInfo dir in oDirs) { RvBase tDir = new RvDir(FileType.Dir) { Name = dir.Name, TimeStamp = dir.LastWriteTime, }; tDir.SetStatus(chechingDatStatus, GotStatus.Got); fileDir.ChildAdd(tDir); } // add all the files into scanDir foreach (FileInfo oFile in oFiles) { // if we find any zip files add them as zip files. string fExt = Path.GetExtension(oFile.Name); switch (fExt.ToLower()) { #if sevenzip case ".7z": { RvDir tGame = new RvDir(FileType.SevenZip) { Name = Path.GetFileNameWithoutExtension(oFile.Name), TimeStamp = oFile.LastWriteTime, }; tGame.SetStatus(chechingDatStatus, GotStatus.Got); fileDir.ChildAdd(tGame); } break; #endif case ".zip": { RvDir tGame = new RvDir(FileType.Zip) { Name = Path.GetFileNameWithoutExtension(oFile.Name), TimeStamp = oFile.LastWriteTime, }; tGame.SetStatus(chechingDatStatus, GotStatus.Got); fileDir.ChildAdd(tGame); } break; default: { string fName = oFile.Name; if (fName == "__RomVault.tmp") { File.Delete(oFile.FullName); continue; } // Scanning a file // // Level1 & 2 : (are the same for files) Fully checksum changed only files // Here we are just getting the TimeStamp of the File, and later // if the TimeStamp was not matched we will have to read the files CRC, MD5 & SHA1 // // Level3: Fully checksum everything // Get everything about the file right here so // read CRC, MD5 & SHA1 // add all the files in the sub-directory to scanDir RvFile tFile = new RvFile(FileType.File) { Name = fName, Size = (ulong)oFile.Length, TimeStamp = oFile.LastWriteTime }; tFile.FileStatusSet(FileStatus.SizeVerified); int errorCode = CHD.CheckFile(oFile, out tFile.SHA1CHD, out tFile.MD5CHD, out tFile.CHDVersion); if (errorCode == 0) { if (tFile.SHA1CHD != null) tFile.FileStatusSet(FileStatus.SHA1CHDFromHeader); if (tFile.MD5CHD != null) tFile.FileStatusSet(FileStatus.MD5CHDFromHeader); tFile.SetStatus(chechingDatStatus, GotStatus.Got); // if we are scanning at Level3 then we get all the info here if (EScanLevel == eScanLevel.Level3) { DeepScanFile(fullDir, tFile); ChdManCheck(fullDir, tFile); } } else if (errorCode == 32) { tFile.GotStatus = GotStatus.FileLocked; _bgw.ReportProgress(0, new bgwShowError(fullDir, "File Locked")); } else { string filename = Path.Combine(fullDir, tFile.Name); ReportError.Show("File: " + filename + " Error: " + new Win32Exception(errorCode).Message + ". Scan Aborted."); _fileErrorAbort = true; return; } fileDir.ChildAdd(tFile); } break; } } } break; default: ReportError.SendAndShow("Un supported file type in CheckADir " + ft); break; } #endregion if (fileDir == null) { ReportError.SendAndShow("Unknown Reading File Type in Dir Scanner"); return; } if (report) { _bgw.ReportProgress(0, new bgwSetRange2(fileDir.ChildCount - 1)); _bgw.ReportProgress(0, new bgwRange2Visible(true)); } if (!DBTypeGet.isCompressedDir(ft) && _bgw.CancellationPending) return; Compare(dbDir, fileDir, report, true); } public static void Compare(RvDir dbDir, RvDir fileDir, bool report, bool enableCancel) { string fullDir = dbDir.FullName; FileType ft = dbDir.FileType; // now we scan down the dbDir and the scanDir, comparing them. // if we find a match we mark dbDir as found. // if we are missing a file in scanDir we mark that file in dbDir as missing. // if we find extra files in scanDir we add it to dbDir and mark it as unknown. // we also recurse into any sub directories. int dbIndex = 0; int fileIndex = 0; while (dbIndex < dbDir.ChildCount || fileIndex < fileDir.ChildCount) { RvBase dbChild = null; RvBase fileChild = null; int res = 0; if (dbIndex < dbDir.ChildCount && fileIndex < fileDir.ChildCount) { dbChild = dbDir.Child(dbIndex); fileChild = fileDir.Child(fileIndex); res = DBHelper.CompareName(dbChild, fileChild); } else if (fileIndex < fileDir.ChildCount) { //Get any remaining filedir's fileChild = fileDir.Child(fileIndex); res = 1; } else if (dbIndex < dbDir.ChildCount) { //Get any remaining dbDir's dbChild = dbDir.Child(dbIndex); res = -1; } if (report) { if (fileChild != null) { long timenow = DateTime.Now.Ticks; if ((timenow - _lastUpdateTime) > (TimeSpan.TicksPerSecond / 10)) { _lastUpdateTime = timenow; _bgw.ReportProgress(0, new bgwValue2(fileIndex)); _bgw.ReportProgress(0, new bgwText2(Path.Combine(fullDir, fileChild.Name))); } } } // if this file was found in the DB switch (res) { case 0: if (dbChild == null || fileChild == null) { ReportError.SendAndShow(Resources.FileScanning_CheckADir_Error_in_File_Scanning_Code); break; } //Complete MultiName Compare List dbs = new List(); List files = new List(); int dbsCount = 1; int filesCount = 1; dbs.Add(dbChild); files.Add(fileChild); while (dbIndex + dbsCount < dbDir.ChildCount && DBHelper.CompareName(dbChild, dbDir.Child(dbIndex + dbsCount)) == 0) { dbs.Add(dbDir.Child(dbIndex + dbsCount)); dbsCount += 1; } while (fileIndex + filesCount < fileDir.ChildCount && DBHelper.CompareName(fileChild, fileDir.Child(fileIndex + filesCount)) == 0) { files.Add(fileDir.Child(fileIndex + filesCount)); filesCount += 1; } for (int indexfile = 0; indexfile < filesCount; indexfile++) { if (files[indexfile].SearchFound) continue; for (int indexdb = 0; indexdb < dbsCount; indexdb++) { if (dbs[indexdb].SearchFound) continue; bool matched = FullCompare(dbs[indexdb], files[indexfile], false, fullDir, EScanLevel); if (!matched) continue; MatchFound(dbs[indexdb], files[indexfile]); dbs[indexdb].SearchFound = true; files[indexfile].SearchFound = true; } if (files[indexfile].SearchFound) continue; for (int indexdb = 0; indexdb < dbsCount; indexdb++) { if (dbs[indexdb].SearchFound) continue; bool matched = FullCompare(dbs[indexdb], files[indexfile], true, fullDir, EScanLevel); if (!matched) continue; MatchFound(dbs[indexdb], files[indexfile]); dbs[indexdb].SearchFound = true; files[indexfile].SearchFound = true; } } for (int indexdb = 0; indexdb < dbsCount; indexdb++) { if (dbs[indexdb].SearchFound) { dbIndex++; continue; } DBFileNotFound(dbs[indexdb], dbDir, ref dbIndex); } for (int indexfile = 0; indexfile < filesCount; indexfile++) { if (files[indexfile].SearchFound) continue; NewFileFound(files[indexfile], dbDir, dbIndex); dbIndex++; } fileIndex += filesCount; break; case 1: NewFileFound(fileChild, dbDir, dbIndex); dbIndex++; fileIndex++; break; case -1: DBFileNotFound(dbChild, dbDir, ref dbIndex); break; } if (_fileErrorAbort) return; if (enableCancel && !DBTypeGet.isCompressedDir(ft) && _bgw.CancellationPending) return; } } private static void MatchFound(RvBase dbChild, RvBase fileChild) { // only check a zip if the filestamp has changed, we asume it is the same if the filestamp has not changed. switch (dbChild.FileType) { case FileType.Zip: case FileType.SevenZip: if (dbChild.TimeStamp != fileChild.TimeStamp || EScanLevel == eScanLevel.Level3 || (EScanLevel == eScanLevel.Level2 && !IsDeepScanned((RvDir)dbChild))) { // this is done first as the CheckADir could change this value if the zip turns out to be corrupt dbChild.FileAdd(fileChild); CheckADir((RvDir)dbChild, false); } else // this is still needed incase the filenames case (upper/lower characters) have changed, but nothing else dbChild.FileCheckName(fileChild); break; case FileType.Dir: RvDir tDir = (RvDir)dbChild; if (tDir.Tree == null) // do not recurse into directories that are in the tree, as they are processed by the top level code. CheckADir(tDir, true); if (_fileErrorAbort) return; dbChild.FileAdd(fileChild); break; case FileType.File: case FileType.ZipFile: case FileType.SevenZipFile: if (dbChild.TimeStamp == fileChild.TimeStamp && dbChild.GotStatus == GotStatus.Corrupt) fileChild.GotStatus = GotStatus.Corrupt; dbChild.FileAdd(fileChild); break; default: throw new Exception("Unsuported file type " + dbChild.FileType); } } private static void NewFileFound(RvBase fileChild, RvDir dbDir, int dbIndex) { if (fileChild == null) { ReportError.SendAndShow(Resources.FileScanning_CheckADir_Error_in_File_Scanning_Code); return; } // this could be an unknown file, or dirctory. // if item is a directory add the directory and call back in again // add the newly found item switch (fileChild.FileType) { case FileType.Zip: case FileType.SevenZip: dbDir.ChildAdd(fileChild, dbIndex); CheckADir((RvDir)fileChild, false); break; case FileType.Dir: dbDir.ChildAdd(fileChild, dbIndex); CheckADir((RvDir)fileChild, true); break; case FileType.File: RvFile tChild = (RvFile)fileChild; // if we have not read the files CRC in the checking code, we need to read it now. if (tChild.GotStatus != GotStatus.FileLocked) { if (!IsDeepScanned(tChild)) DeepScanFile(dbDir.FullName, tChild); if (!IschdmanScanned(tChild) && EScanLevel == eScanLevel.Level2) ChdManCheck(dbDir.FullName, tChild); } dbDir.ChildAdd(fileChild, dbIndex); break; case FileType.ZipFile: case FileType.SevenZipFile: dbDir.ChildAdd(fileChild, dbIndex); break; default: throw new Exception("Unsuported file type " + fileChild.FileType); } } private static void DBFileNotFound(RvBase dbChild, RvDir dbDir, ref int dbIndex) { if (dbChild == null) { ReportError.SendAndShow(Resources.FileScanning_CheckADir_Error_in_File_Scanning_Code); return; } if (dbChild.FileRemove() == EFile.Delete) dbDir.ChildRemove(dbIndex); else { switch (dbChild.FileType) { case FileType.Zip: MarkAsMissing((RvDir)dbChild); break; case FileType.Dir: RvDir tDir = (RvDir)dbChild; if (tDir.Tree == null) MarkAsMissing(tDir); break; } dbIndex++; } } private static bool IsDeepScanned(RvDir tZip) { for (int i = 0; i < tZip.ChildCount; i++) { RvFile zFile = tZip.Child(i) as RvFile; if (zFile != null && zFile.GotStatus == GotStatus.Got && (!zFile.FileStatusIs(FileStatus.SizeVerified) || !zFile.FileStatusIs(FileStatus.CRCVerified) || !zFile.FileStatusIs(FileStatus.SHA1Verified) || !zFile.FileStatusIs(FileStatus.MD5Verified))) return false; } return true; } private static void MarkAsMissing(RvDir dbDir) { for (int i = 0; i < dbDir.ChildCount; i++) { RvBase dbChild = dbDir.Child(i); if (dbChild.FileRemove() == EFile.Delete) { dbDir.ChildRemove(i); i--; } else { switch (dbChild.FileType) { case FileType.Zip: MarkAsMissing((RvDir)dbChild); break; case FileType.Dir: RvDir tDir = (RvDir)dbChild; if (tDir.Tree == null) MarkAsMissing(tDir); break; } } } } private static bool FullCompare(RvBase dbFile, RvBase testFile, bool secondPass, string fullDir = "", eScanLevel sLevel = eScanLevel.Level3) { Debug.WriteLine("Comparing Dat File " + dbFile.TreeFullName); Debug.WriteLine("Comparing File " + testFile.TreeFullName); int retv = DBHelper.CompareName(dbFile, testFile); if (retv != 0) return false; FileType dbfileType = dbFile.FileType; FileType dbtestFile = testFile.FileType; retv = Math.Sign(dbfileType.CompareTo(dbtestFile)); if (retv != 0) return false; // filetypes are now know to be the same // Dir's and Zip's are not deep scanned so matching here is done if ((dbfileType == FileType.Dir) || (dbfileType == FileType.Zip) || (dbfileType==FileType.SevenZip)) return true; RvFile dbFileF = (RvFile)dbFile; RvFile testFileF = (RvFile)testFile; if (dbFileF.Size != null && testFileF.Size != null) { retv = ULong.iCompare(dbFileF.Size, testFileF.Size); if (retv != 0) return false; } if (dbFileF.CRC != null && testFileF.CRC != null) { retv = ArrByte.iCompare(dbFileF.CRC, testFileF.CRC); if (retv != 0) return false; } if (dbFileF.SHA1 != null && testFileF.SHA1 != null) { retv = ArrByte.iCompare(dbFileF.SHA1, testFileF.SHA1); if (retv != 0) return false; } if (dbFileF.MD5 != null && testFileF.MD5 != null) { retv = ArrByte.iCompare(dbFileF.MD5, testFileF.MD5); if (retv != 0) return false; } if (dbFileF.SHA1CHD != null && testFileF.SHA1CHD != null) { retv = ArrByte.iCompare(dbFileF.SHA1CHD, testFileF.SHA1CHD); if (retv != 0) return false; } if (dbFileF.MD5CHD != null && testFileF.MD5CHD != null) { retv = ArrByte.iCompare(dbFileF.MD5CHD, testFileF.MD5CHD); if (retv != 0) return false; } // beyond here we only test files if (dbtestFile != FileType.File) return true; // if scanning at level 3 then we have already deep checked the file so all is OK. if (sLevel == eScanLevel.Level3) return true; // if we got this far then everything we have so far has matched, but that may not be enough. // now we see if we need to get any more info and try again. // if the file stamps do not match or the file in the DB we are comparing with has not been deep scanned // and the file we are scanning has not already been deep scanned, then we need to do a deep scan, and check the deep scan values // files are always deep scanned, so the test for IsDeepScanned(dbFileF) is probably not really needed here. if ((dbFileF.TimeStamp != testFileF.TimeStamp || !IsDeepScanned(dbFileF)) && !IsDeepScanned(testFileF)) { if (!secondPass) return false; DeepScanFile(fullDir, testFileF); if (dbFileF.CRC != null && testFileF.CRC != null) { retv = ArrByte.iCompare(dbFileF.CRC, testFileF.CRC); if (retv != 0) return false; } if (dbFileF.SHA1 != null && testFileF.SHA1 != null) { retv = ArrByte.iCompare(dbFileF.SHA1, testFileF.SHA1); if (retv != 0) return false; } if (dbFileF.MD5 != null && testFileF.MD5 != null) { retv = ArrByte.iCompare(dbFileF.MD5, testFileF.MD5); if (retv != 0) return false; } } // CHDman test, if we are only scanning at level 1 then don't do CHDman test so we are done. if (sLevel == eScanLevel.Level1) return true; if ((dbFileF.TimeStamp != testFileF.TimeStamp || (!IschdmanScanned(dbFileF)) && !IschdmanScanned(testFileF))) { ChdManCheck(fullDir, testFileF); } return true; } private static bool IsDeepScanned(RvFile tFile) { return ( tFile.FileStatusIs(FileStatus.SizeVerified) && tFile.FileStatusIs(FileStatus.CRCVerified) && tFile.FileStatusIs(FileStatus.SHA1Verified) && tFile.FileStatusIs(FileStatus.MD5Verified) ); } private static bool IschdmanScanned(RvFile tFile) { //if (!tFile.FileStatusIs(FileStatus.SHA1CHDFromHeader)) // return true; if (tFile.GotStatus == GotStatus.Corrupt) return true; return tFile.FileStatusIs(FileStatus.SHA1CHDVerified); } private static void DeepScanFile(string directory, RvFile tFile) { string filename = Path.Combine(directory, tFile.Name); int errorCode = UnCompFiles.CheckSumRead(filename, true, out tFile.CRC, out tFile.MD5, out tFile.SHA1); if (errorCode == 32) { tFile.GotStatus = GotStatus.FileLocked; return; } if (errorCode != 0) { ReportError.Show("File: " + filename + " Error: " + new Win32Exception(errorCode).Message + ". Scan Aborted."); _fileErrorAbort = true; return; } tFile.FileStatusSet(FileStatus.SizeVerified | FileStatus.CRCVerified | FileStatus.SHA1Verified | FileStatus.MD5Verified); } private static void ChdManCheck(string directory, RvFile tFile) { string filename = Path.Combine(directory, tFile.Name); if (!tFile.FileStatusIs(FileStatus.SHA1CHDFromHeader)) return; _bgw.ReportProgress(0, new bgwText2(filename)); string error; CHD.CHDManCheck res = CHD.ChdmanCheck(filename, _bgw, out error); switch (res) { case CHD.CHDManCheck.Good: tFile.FileStatusSet(FileStatus.SHA1CHDVerified); return; case CHD.CHDManCheck.Corrupt: _bgw.ReportProgress(0, new bgwShowError(filename, error)); tFile.GotStatus = GotStatus.Corrupt; return; case CHD.CHDManCheck.CHDReturnError: case CHD.CHDManCheck.CHDUnknownError: _bgw.ReportProgress(0, new bgwShowError(filename, error)); return; case CHD.CHDManCheck.ChdmanNotFound: return; case CHD.CHDManCheck.CHDNotFound: ReportError.Show("File: " + filename + " Error: Not Found scan Aborted."); _fileErrorAbort = true; return; default: ReportError.UnhandledExceptionHandler(error); return; } } } }