1 // ExtractCallbackConsole.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/IntToString.h"
6 #include "../../../Common/Wildcard.h"
7 
8 #include "../../../Windows/FileDir.h"
9 #include "../../../Windows/FileFind.h"
10 #include "../../../Windows/TimeUtils.h"
11 #include "../../../Windows/ErrorMsg.h"
12 #include "../../../Windows/PropVariantConv.h"
13 
14 #ifndef _7ZIP_ST
15 #include "../../../Windows/Synchronization.h"
16 #endif
17 
18 #include "../../Common/FilePathAutoRename.h"
19 
20 #include "../Common/ExtractingFilePath.h"
21 
22 #include "ConsoleClose.h"
23 #include "ExtractCallbackConsole.h"
24 #include "UserInputUtils.h"
25 
26 using namespace NWindows;
27 using namespace NFile;
28 using namespace NDir;
29 
CheckBreak2()30 static HRESULT CheckBreak2()
31 {
32   return NConsoleClose::TestBreakSignal() ? E_ABORT : S_OK;
33 }
34 
35 static const char *kError = "ERROR: ";
36 
37 
StartScanning()38 void CExtractScanConsole::StartScanning()
39 {
40   if (NeedPercents())
41     _percent.Command = "Scan";
42 }
43 
ScanProgress(const CDirItemsStat & st,const FString & path,bool)44 HRESULT CExtractScanConsole::ScanProgress(const CDirItemsStat &st, const FString &path, bool /* isDir */)
45 {
46   if (NeedPercents())
47   {
48     _percent.Files = st.NumDirs + st.NumFiles;
49     _percent.Completed = st.GetTotalBytes();
50     _percent.FileName = fs2us(path);
51     _percent.Print();
52   }
53 
54   return CheckBreak2();
55 }
56 
ScanError(const FString & path,DWORD systemError)57 HRESULT CExtractScanConsole::ScanError(const FString &path, DWORD systemError)
58 {
59   ClosePercentsAndFlush();
60 
61   if (_se)
62   {
63     *_se << endl << kError << NError::MyFormatMessage(systemError) << endl <<
64         fs2us(path) << endl << endl;
65     _se->Flush();
66   }
67   return HRESULT_FROM_WIN32(systemError);
68 }
69 
70 
Print_UInt64_and_String(AString & s,UInt64 val,const char * name)71 void Print_UInt64_and_String(AString &s, UInt64 val, const char *name)
72 {
73   char temp[32];
74   ConvertUInt64ToString(val, temp);
75   s += temp;
76   s.Add_Space();
77   s += name;
78 }
79 
PrintSize_bytes_Smart(AString & s,UInt64 val)80 void PrintSize_bytes_Smart(AString &s, UInt64 val)
81 {
82   Print_UInt64_and_String(s, val, "bytes");
83 
84   if (val == 0)
85     return;
86 
87   unsigned numBits = 10;
88   char c = 'K';
89   char temp[4] = { 'K', 'i', 'B', 0 };
90        if (val >= ((UInt64)10 << 30)) { numBits = 30; c = 'G'; }
91   else if (val >= ((UInt64)10 << 20)) { numBits = 20; c = 'M'; }
92   temp[0] = c;
93   s += " (";
94   Print_UInt64_and_String(s, ((val + ((UInt64)1 << numBits) - 1) >> numBits), temp);
95   s += ')';
96 }
97 
Print_DirItemsStat(AString & s,const CDirItemsStat & st)98 void Print_DirItemsStat(AString &s, const CDirItemsStat &st)
99 {
100   if (st.NumDirs != 0)
101   {
102     Print_UInt64_and_String(s, st.NumDirs, st.NumDirs == 1 ? "folder" : "folders");
103     s += ", ";
104   }
105   Print_UInt64_and_String(s, st.NumFiles, st.NumFiles == 1 ? "file" : "files");
106   s += ", ";
107   PrintSize_bytes_Smart(s, st.FilesSize);
108   if (st.NumAltStreams != 0)
109   {
110     s.Add_LF();
111     Print_UInt64_and_String(s, st.NumAltStreams, "alternate streams");
112     s += ", ";
113     PrintSize_bytes_Smart(s, st.AltStreamsSize);
114   }
115 }
116 
PrintStat(const CDirItemsStat & st)117 void CExtractScanConsole::PrintStat(const CDirItemsStat &st)
118 {
119   if (_so)
120   {
121     AString s;
122     Print_DirItemsStat(s, st);
123     *_so << s << endl;
124   }
125 }
126 
127 
128 
129 
130 
131 
132 
133 #ifndef _7ZIP_ST
134 static NSynchronization::CCriticalSection g_CriticalSection;
135 #define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection);
136 #else
137 #define MT_LOCK
138 #endif
139 
140 
141 static const char *kTestString    =  "T";
142 static const char *kExtractString =  "-";
143 static const char *kSkipString    =  ".";
144 
145 // static const char *kCantAutoRename = "can not create file with auto name\n";
146 // static const char *kCantRenameFile = "can not rename existing file\n";
147 // static const char *kCantDeleteOutputFile = "can not delete output file ";
148 
149 static const char *kMemoryExceptionMessage = "Can't allocate required memory!";
150 
151 static const char *kExtracting = "Extracting archive: ";
152 static const char *kTesting = "Testing archive: ";
153 
154 static const char *kEverythingIsOk = "Everything is Ok";
155 static const char *kNoFiles = "No files to process";
156 
157 static const char *kUnsupportedMethod = "Unsupported Method";
158 static const char *kCrcFailed = "CRC Failed";
159 static const char *kCrcFailedEncrypted = "CRC Failed in encrypted file. Wrong password?";
160 static const char *kDataError = "Data Error";
161 static const char *kDataErrorEncrypted = "Data Error in encrypted file. Wrong password?";
162 static const char *kUnavailableData = "Unavailable data";
163 static const char *kUnexpectedEnd = "Unexpected end of data";
164 static const char *kDataAfterEnd = "There are some data after the end of the payload data";
165 static const char *kIsNotArc = "Is not archive";
166 static const char *kHeadersError = "Headers Error";
167 static const char *kWrongPassword = "Wrong password";
168 
169 static const char * const k_ErrorFlagsMessages[] =
170 {
171     "Is not archive"
172   , "Headers Error"
173   , "Headers Error in encrypted archive. Wrong password?"
174   , "Unavailable start of archive"
175   , "Unconfirmed start of archive"
176   , "Unexpected end of archive"
177   , "There are data after the end of archive"
178   , "Unsupported method"
179   , "Unsupported feature"
180   , "Data Error"
181   , "CRC Error"
182 };
183 
SetTotal(UInt64 size)184 STDMETHODIMP CExtractCallbackConsole::SetTotal(UInt64 size)
185 {
186   MT_LOCK
187 
188   if (NeedPercents())
189   {
190     _percent.Total = size;
191     _percent.Print();
192   }
193   return CheckBreak2();
194 }
195 
SetCompleted(const UInt64 * completeValue)196 STDMETHODIMP CExtractCallbackConsole::SetCompleted(const UInt64 *completeValue)
197 {
198   MT_LOCK
199 
200   if (NeedPercents())
201   {
202     if (completeValue)
203       _percent.Completed = *completeValue;
204     _percent.Print();
205   }
206   return CheckBreak2();
207 }
208 
209 static const char *kTab = "  ";
210 
PrintFileInfo(CStdOutStream * _so,const wchar_t * path,const FILETIME * ft,const UInt64 * size)211 static void PrintFileInfo(CStdOutStream *_so, const wchar_t *path, const FILETIME *ft, const UInt64 *size)
212 {
213   *_so << kTab << "Path:     " << path << endl;
214   if (size)
215   {
216     AString s;
217     PrintSize_bytes_Smart(s, *size);
218     *_so << kTab << "Size:     " << s << endl;
219   }
220   if (ft)
221   {
222     char temp[64];
223     FILETIME locTime;
224     if (FileTimeToLocalFileTime(ft, &locTime))
225       if (ConvertFileTimeToString(locTime, temp, true, true))
226         *_so << kTab << "Modified: " << temp << endl;
227   }
228 }
229 
AskOverwrite(const wchar_t * existName,const FILETIME * existTime,const UInt64 * existSize,const wchar_t * newName,const FILETIME * newTime,const UInt64 * newSize,Int32 * answer)230 STDMETHODIMP CExtractCallbackConsole::AskOverwrite(
231     const wchar_t *existName, const FILETIME *existTime, const UInt64 *existSize,
232     const wchar_t *newName, const FILETIME *newTime, const UInt64 *newSize,
233     Int32 *answer)
234 {
235   MT_LOCK
236 
237   RINOK(CheckBreak2());
238 
239   ClosePercentsAndFlush();
240 
241   if (_so)
242   {
243     *_so << endl << "Would you like to replace the existing file:\n";
244     PrintFileInfo(_so, existName, existTime, existSize);
245     *_so << "with the file from archive:\n";
246     PrintFileInfo(_so, newName, newTime, newSize);
247   }
248 
249   NUserAnswerMode::EEnum overwriteAnswer = ScanUserYesNoAllQuit(_so);
250 
251   switch (overwriteAnswer)
252   {
253     case NUserAnswerMode::kQuit:  return E_ABORT;
254     case NUserAnswerMode::kNo:     *answer = NOverwriteAnswer::kNo; break;
255     case NUserAnswerMode::kNoAll:  *answer = NOverwriteAnswer::kNoToAll; break;
256     case NUserAnswerMode::kYesAll: *answer = NOverwriteAnswer::kYesToAll; break;
257     case NUserAnswerMode::kYes:    *answer = NOverwriteAnswer::kYes; break;
258     case NUserAnswerMode::kAutoRenameAll: *answer = NOverwriteAnswer::kAutoRename; break;
259     default: return E_FAIL;
260   }
261 
262   if (_so)
263   {
264     *_so << endl;
265     if (NeedFlush)
266       _so->Flush();
267   }
268 
269   return CheckBreak2();
270 }
271 
PrepareOperation(const wchar_t * name,Int32,Int32 askExtractMode,const UInt64 * position)272 STDMETHODIMP CExtractCallbackConsole::PrepareOperation(const wchar_t *name, Int32 /* isFolder */, Int32 askExtractMode, const UInt64 *position)
273 {
274   MT_LOCK
275 
276   _currentName = name;
277 
278   const char *s;
279   unsigned requiredLevel = 1;
280 
281   switch (askExtractMode)
282   {
283     case NArchive::NExtract::NAskMode::kExtract: s = kExtractString; break;
284     case NArchive::NExtract::NAskMode::kTest:    s = kTestString; break;
285     case NArchive::NExtract::NAskMode::kSkip:    s = kSkipString; requiredLevel = 2; break;
286     default: s = "???"; requiredLevel = 2;
287   };
288 
289   bool show2 = (LogLevel >= requiredLevel && _so);
290 
291   if (show2)
292   {
293     ClosePercents_for_so();
294 
295     _tempA = s;
296     if (name)
297       _tempA.Add_Space();
298     *_so << _tempA;
299 
300     _tempU.Empty();
301     if (name)
302       _tempU = name;
303     _so->PrintUString(_tempU, _tempA);
304     if (position)
305       *_so << " <" << *position << ">";
306     *_so << endl;
307 
308     if (NeedFlush)
309       _so->Flush();
310   }
311 
312   if (NeedPercents())
313   {
314     if (PercentsNameLevel >= 1)
315     {
316       _percent.FileName.Empty();
317       _percent.Command.Empty();
318       if (PercentsNameLevel > 1 || !show2)
319       {
320         _percent.Command = s;
321         if (name)
322           _percent.FileName = name;
323       }
324     }
325     _percent.Print();
326   }
327 
328   return CheckBreak2();
329 }
330 
MessageError(const wchar_t * message)331 STDMETHODIMP CExtractCallbackConsole::MessageError(const wchar_t *message)
332 {
333   MT_LOCK
334 
335   RINOK(CheckBreak2());
336 
337   NumFileErrors_in_Current++;
338   NumFileErrors++;
339 
340   ClosePercentsAndFlush();
341   if (_se)
342   {
343     *_se << kError << message << endl;
344     _se->Flush();
345   }
346 
347   return CheckBreak2();
348 }
349 
SetExtractErrorMessage(Int32 opRes,Int32 encrypted,AString & dest)350 void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, AString &dest)
351 {
352   dest.Empty();
353     const char *s = NULL;
354 
355     switch (opRes)
356     {
357       case NArchive::NExtract::NOperationResult::kUnsupportedMethod:
358         s = kUnsupportedMethod;
359         break;
360       case NArchive::NExtract::NOperationResult::kCRCError:
361         s = (encrypted ? kCrcFailedEncrypted : kCrcFailed);
362         break;
363       case NArchive::NExtract::NOperationResult::kDataError:
364         s = (encrypted ? kDataErrorEncrypted : kDataError);
365         break;
366       case NArchive::NExtract::NOperationResult::kUnavailable:
367         s = kUnavailableData;
368         break;
369       case NArchive::NExtract::NOperationResult::kUnexpectedEnd:
370         s = kUnexpectedEnd;
371         break;
372       case NArchive::NExtract::NOperationResult::kDataAfterEnd:
373         s = kDataAfterEnd;
374         break;
375       case NArchive::NExtract::NOperationResult::kIsNotArc:
376         s = kIsNotArc;
377         break;
378       case NArchive::NExtract::NOperationResult::kHeadersError:
379         s = kHeadersError;
380         break;
381       case NArchive::NExtract::NOperationResult::kWrongPassword:
382         s = kWrongPassword;
383         break;
384     }
385 
386     dest += kError;
387     if (s)
388       dest += s;
389     else
390     {
391       char temp[16];
392       ConvertUInt32ToString(opRes, temp);
393       dest += "Error #";
394       dest += temp;
395     }
396 }
397 
SetOperationResult(Int32 opRes,Int32 encrypted)398 STDMETHODIMP CExtractCallbackConsole::SetOperationResult(Int32 opRes, Int32 encrypted)
399 {
400   MT_LOCK
401 
402   if (opRes == NArchive::NExtract::NOperationResult::kOK)
403   {
404     if (NeedPercents())
405     {
406       _percent.Command.Empty();
407       _percent.FileName.Empty();
408       _percent.Files++;
409     }
410   }
411   else
412   {
413     NumFileErrors_in_Current++;
414     NumFileErrors++;
415 
416     if (_se)
417     {
418       ClosePercentsAndFlush();
419 
420       AString s;
421       SetExtractErrorMessage(opRes, encrypted, s);
422 
423       *_se << s;
424       if (!_currentName.IsEmpty())
425         *_se << " : " << _currentName;
426       *_se << endl;
427       _se->Flush();
428     }
429   }
430 
431   return CheckBreak2();
432 }
433 
ReportExtractResult(Int32 opRes,Int32 encrypted,const wchar_t * name)434 STDMETHODIMP CExtractCallbackConsole::ReportExtractResult(Int32 opRes, Int32 encrypted, const wchar_t *name)
435 {
436   if (opRes != NArchive::NExtract::NOperationResult::kOK)
437   {
438     _currentName = name;
439     return SetOperationResult(opRes, encrypted);
440   }
441 
442   return CheckBreak2();
443 }
444 
445 
446 
447 #ifndef _NO_CRYPTO
448 
SetPassword(const UString & password)449 HRESULT CExtractCallbackConsole::SetPassword(const UString &password)
450 {
451   PasswordIsDefined = true;
452   Password = password;
453   return S_OK;
454 }
455 
CryptoGetTextPassword(BSTR * password)456 STDMETHODIMP CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password)
457 {
458   COM_TRY_BEGIN
459   MT_LOCK
460   return Open_CryptoGetTextPassword(password);
461   COM_TRY_END
462 }
463 
464 #endif
465 
BeforeOpen(const wchar_t * name,bool testMode)466 HRESULT CExtractCallbackConsole::BeforeOpen(const wchar_t *name, bool testMode)
467 {
468   RINOK(CheckBreak2());
469 
470   NumTryArcs++;
471   ThereIsError_in_Current = false;
472   ThereIsWarning_in_Current = false;
473   NumFileErrors_in_Current = 0;
474 
475   ClosePercents_for_so();
476   if (_so)
477     *_so << endl << (testMode ? kTesting : kExtracting) << name << endl;
478 
479   if (NeedPercents())
480     _percent.Command = "Open";
481   return S_OK;
482 }
483 
484 HRESULT Print_OpenArchive_Props(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink);
485 HRESULT Print_OpenArchive_Error(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink);
486 
GetOpenArcErrorMessage(UInt32 errorFlags)487 static AString GetOpenArcErrorMessage(UInt32 errorFlags)
488 {
489   AString s;
490 
491   for (unsigned i = 0; i < ARRAY_SIZE(k_ErrorFlagsMessages); i++)
492   {
493     UInt32 f = (1 << i);
494     if ((errorFlags & f) == 0)
495       continue;
496     const char *m = k_ErrorFlagsMessages[i];
497     if (!s.IsEmpty())
498       s.Add_LF();
499     s += m;
500     errorFlags &= ~f;
501   }
502 
503   if (errorFlags != 0)
504   {
505     char sz[16];
506     sz[0] = '0';
507     sz[1] = 'x';
508     ConvertUInt32ToHex(errorFlags, sz + 2);
509     if (!s.IsEmpty())
510       s.Add_LF();
511     s += sz;
512   }
513 
514   return s;
515 }
516 
PrintErrorFlags(CStdOutStream & so,const char * s,UInt32 errorFlags)517 void PrintErrorFlags(CStdOutStream &so, const char *s, UInt32 errorFlags)
518 {
519   if (errorFlags == 0)
520     return;
521   so << s << endl << GetOpenArcErrorMessage(errorFlags) << endl;
522 }
523 
Add_Messsage_Pre_ArcType(UString & s,const char * pre,const wchar_t * arcType)524 void Add_Messsage_Pre_ArcType(UString &s, const char *pre, const wchar_t *arcType)
525 {
526   s.Add_LF();
527   s.AddAscii(pre);
528   s.AddAscii(" as [");
529   s += arcType;
530   s.AddAscii("] archive");
531 }
532 
Print_ErrorFormatIndex_Warning(CStdOutStream * _so,const CCodecs * codecs,const CArc & arc)533 void Print_ErrorFormatIndex_Warning(CStdOutStream *_so, const CCodecs *codecs, const CArc &arc)
534 {
535   const CArcErrorInfo &er = arc.ErrorInfo;
536 
537   UString s = L"WARNING:\n";
538   s += arc.Path;
539   if (arc.FormatIndex == er.ErrorFormatIndex)
540   {
541     s.Add_LF();
542     s.AddAscii("The archive is open with offset");
543   }
544   else
545   {
546     Add_Messsage_Pre_ArcType(s, "Can not open the file", codecs->GetFormatNamePtr(er.ErrorFormatIndex));
547     Add_Messsage_Pre_ArcType(s, "The file is open", codecs->GetFormatNamePtr(arc.FormatIndex));
548   }
549 
550   *_so << s << endl << endl;
551 }
552 
553 
OpenResult(const CCodecs * codecs,const CArchiveLink & arcLink,const wchar_t * name,HRESULT result)554 HRESULT CExtractCallbackConsole::OpenResult(
555     const CCodecs *codecs, const CArchiveLink &arcLink,
556     const wchar_t *name, HRESULT result)
557 {
558   ClosePercents();
559 
560   if (NeedPercents())
561   {
562     _percent.Files = 0;
563     _percent.Command.Empty();
564     _percent.FileName.Empty();
565   }
566 
567 
568   ClosePercentsAndFlush();
569 
570   FOR_VECTOR (level, arcLink.Arcs)
571   {
572     const CArc &arc = arcLink.Arcs[level];
573     const CArcErrorInfo &er = arc.ErrorInfo;
574 
575     UInt32 errorFlags = er.GetErrorFlags();
576 
577     if (errorFlags != 0 || !er.ErrorMessage.IsEmpty())
578     {
579       if (_se)
580       {
581         *_se << endl;
582         if (level != 0)
583           *_se << arc.Path << endl;
584       }
585 
586       if (errorFlags != 0)
587       {
588         if (_se)
589           PrintErrorFlags(*_se, "ERRORS:", errorFlags);
590         NumOpenArcErrors++;
591         ThereIsError_in_Current = true;
592       }
593 
594       if (!er.ErrorMessage.IsEmpty())
595       {
596         if (_se)
597           *_se << "ERRORS:" << endl << er.ErrorMessage << endl;
598         NumOpenArcErrors++;
599         ThereIsError_in_Current = true;
600       }
601 
602       if (_se)
603       {
604         *_se << endl;
605         _se->Flush();
606       }
607     }
608 
609     UInt32 warningFlags = er.GetWarningFlags();
610 
611     if (warningFlags != 0 || !er.WarningMessage.IsEmpty())
612     {
613       if (_so)
614       {
615         *_so << endl;
616         if (level != 0)
617           *_so << arc.Path << endl;
618       }
619 
620       if (warningFlags != 0)
621       {
622         if (_so)
623           PrintErrorFlags(*_so, "WARNINGS:", warningFlags);
624         NumOpenArcWarnings++;
625         ThereIsWarning_in_Current = true;
626       }
627 
628       if (!er.WarningMessage.IsEmpty())
629       {
630         if (_so)
631           *_so << "WARNINGS:" << endl << er.WarningMessage << endl;
632         NumOpenArcWarnings++;
633         ThereIsWarning_in_Current = true;
634       }
635 
636       if (_so)
637       {
638         *_so << endl;
639         if (NeedFlush)
640           _so->Flush();
641       }
642     }
643 
644 
645     if (er.ErrorFormatIndex >= 0)
646     {
647       if (_so)
648       {
649         Print_ErrorFormatIndex_Warning(_so, codecs, arc);
650         if (NeedFlush)
651           _so->Flush();
652       }
653       ThereIsWarning_in_Current = true;
654     }
655   }
656 
657   if (result == S_OK)
658   {
659     if (_so)
660     {
661       RINOK(Print_OpenArchive_Props(*_so, codecs, arcLink));
662       *_so << endl;
663     }
664   }
665   else
666   {
667     NumCantOpenArcs++;
668     if (_so)
669       _so->Flush();
670     if (_se)
671     {
672       *_se << kError << name << endl;
673       HRESULT res = Print_OpenArchive_Error(*_se, codecs, arcLink);
674       RINOK(res);
675       if (result == S_FALSE)
676       {
677       }
678       else
679       {
680         if (result == E_OUTOFMEMORY)
681           *_se << "Can't allocate required memory";
682         else
683           *_se << NError::MyFormatMessage(result);
684         *_se << endl;
685       }
686       _se->Flush();
687     }
688   }
689 
690 
691   return CheckBreak2();
692 }
693 
ThereAreNoFiles()694 HRESULT CExtractCallbackConsole::ThereAreNoFiles()
695 {
696   ClosePercents_for_so();
697 
698   if (_so)
699   {
700     *_so << endl << kNoFiles << endl;
701     if (NeedFlush)
702       _so->Flush();
703   }
704   return CheckBreak2();
705 }
706 
ExtractResult(HRESULT result)707 HRESULT CExtractCallbackConsole::ExtractResult(HRESULT result)
708 {
709   MT_LOCK
710 
711   if (NeedPercents())
712   {
713     _percent.ClosePrint(true);
714     _percent.Command.Empty();
715     _percent.FileName.Empty();
716   }
717 
718   if (_so)
719     _so->Flush();
720 
721   if (result == S_OK)
722   {
723     if (NumFileErrors_in_Current == 0 && !ThereIsError_in_Current)
724     {
725       if (ThereIsWarning_in_Current)
726         NumArcsWithWarnings++;
727       else
728         NumOkArcs++;
729       if (_so)
730         *_so << kEverythingIsOk << endl;
731     }
732     else
733     {
734       NumArcsWithError++;
735       if (_so)
736       {
737         *_so << endl;
738         if (NumFileErrors_in_Current != 0)
739           *_so << "Sub items Errors: " << NumFileErrors_in_Current << endl;
740       }
741     }
742     if (_so && NeedFlush)
743       _so->Flush();
744   }
745   else
746   {
747     NumArcsWithError++;
748     if (result == E_ABORT || result == ERROR_DISK_FULL)
749       return result;
750 
751     if (_se)
752     {
753       *_se << endl << kError;
754       if (result == E_OUTOFMEMORY)
755         *_se << kMemoryExceptionMessage;
756       else
757         *_se << NError::MyFormatMessage(result);
758       *_se << endl;
759       _se->Flush();
760     }
761   }
762 
763   return CheckBreak2();
764 }
765