other-licenses/7zstub/src/7zip/UI/Common/OpenArchive.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

michael@0 1 // OpenArchive.cpp
michael@0 2
michael@0 3 #include "StdAfx.h"
michael@0 4
michael@0 5 #include "OpenArchive.h"
michael@0 6
michael@0 7 #include "Common/Wildcard.h"
michael@0 8
michael@0 9 #include "Windows/FileName.h"
michael@0 10 #include "Windows/FileDir.h"
michael@0 11 #include "Windows/Defs.h"
michael@0 12 #include "Windows/PropVariant.h"
michael@0 13
michael@0 14 #include "../../Common/FileStreams.h"
michael@0 15 #include "../../Common/StreamUtils.h"
michael@0 16
michael@0 17 #include "Common/StringConvert.h"
michael@0 18
michael@0 19 #ifdef FORMAT_7Z
michael@0 20 #include "../../Archive/7z/7zHandler.h"
michael@0 21 #endif
michael@0 22
michael@0 23 #ifdef FORMAT_BZIP2
michael@0 24 #include "../../Archive/BZip2/BZip2Handler.h"
michael@0 25 #endif
michael@0 26
michael@0 27 #ifdef FORMAT_GZIP
michael@0 28 #include "../../Archive/GZip/GZipHandler.h"
michael@0 29 #endif
michael@0 30
michael@0 31 #ifdef FORMAT_SPLIT
michael@0 32 #include "../../Archive/Split/SplitHandler.h"
michael@0 33 #endif
michael@0 34
michael@0 35 #ifdef FORMAT_TAR
michael@0 36 #include "../../Archive/Tar/TarHandler.h"
michael@0 37 #endif
michael@0 38
michael@0 39 #ifdef FORMAT_ZIP
michael@0 40 #include "../../Archive/Zip/ZipHandler.h"
michael@0 41 #endif
michael@0 42
michael@0 43 #ifdef FORMAT_Z
michael@0 44 #include "../../Archive/Z/ZHandler.h"
michael@0 45 #endif
michael@0 46
michael@0 47 #ifndef EXCLUDE_COM
michael@0 48 #include "HandlerLoader.h"
michael@0 49 #endif
michael@0 50
michael@0 51 #include "DefaultName.h"
michael@0 52
michael@0 53 using namespace NWindows;
michael@0 54
michael@0 55 HRESULT GetArchiveItemPath(IInArchive *archive, UInt32 index, UString &result)
michael@0 56 {
michael@0 57 NCOM::CPropVariant prop;
michael@0 58 RINOK(archive->GetProperty(index, kpidPath, &prop));
michael@0 59 if(prop.vt == VT_BSTR)
michael@0 60 result = prop.bstrVal;
michael@0 61 else if (prop.vt == VT_EMPTY)
michael@0 62 result.Empty();
michael@0 63 else
michael@0 64 return E_FAIL;
michael@0 65 return S_OK;
michael@0 66 }
michael@0 67
michael@0 68 HRESULT GetArchiveItemPath(IInArchive *archive, UInt32 index, const UString &defaultName, UString &result)
michael@0 69 {
michael@0 70 RINOK(GetArchiveItemPath(archive, index, result));
michael@0 71 if (result.IsEmpty())
michael@0 72 result = defaultName;
michael@0 73 return S_OK;
michael@0 74 }
michael@0 75
michael@0 76 HRESULT GetArchiveItemFileTime(IInArchive *archive, UInt32 index,
michael@0 77 const FILETIME &defaultFileTime, FILETIME &fileTime)
michael@0 78 {
michael@0 79 NCOM::CPropVariant prop;
michael@0 80 RINOK(archive->GetProperty(index, kpidLastWriteTime, &prop));
michael@0 81 if (prop.vt == VT_FILETIME)
michael@0 82 fileTime = prop.filetime;
michael@0 83 else if (prop.vt == VT_EMPTY)
michael@0 84 fileTime = defaultFileTime;
michael@0 85 else
michael@0 86 return E_FAIL;
michael@0 87 return S_OK;
michael@0 88 }
michael@0 89
michael@0 90 static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
michael@0 91 {
michael@0 92 NCOM::CPropVariant prop;
michael@0 93 RINOK(archive->GetProperty(index, propID, &prop));
michael@0 94 if(prop.vt == VT_BOOL)
michael@0 95 result = VARIANT_BOOLToBool(prop.boolVal);
michael@0 96 else if (prop.vt == VT_EMPTY)
michael@0 97 result = false;
michael@0 98 else
michael@0 99 return E_FAIL;
michael@0 100 return S_OK;
michael@0 101 }
michael@0 102
michael@0 103 HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
michael@0 104 {
michael@0 105 return IsArchiveItemProp(archive, index, kpidIsFolder, result);
michael@0 106 }
michael@0 107
michael@0 108 HRESULT IsArchiveItemAnti(IInArchive *archive, UInt32 index, bool &result)
michael@0 109 {
michael@0 110 return IsArchiveItemProp(archive, index, kpidIsAnti, result);
michael@0 111 }
michael@0 112
michael@0 113 // Static-SFX (for Linux) can be big
michael@0 114 const UInt64 kMaxCheckStartPosition =
michael@0 115 #ifdef _WIN32
michael@0 116 1 << 20;
michael@0 117 #else
michael@0 118 1 << 22;
michael@0 119 #endif
michael@0 120
michael@0 121
michael@0 122 HRESULT ReOpenArchive(IInArchive *archive, const UString &fileName)
michael@0 123 {
michael@0 124 CInFileStream *inStreamSpec = new CInFileStream;
michael@0 125 CMyComPtr<IInStream> inStream(inStreamSpec);
michael@0 126 inStreamSpec->Open(fileName);
michael@0 127 return archive->Open(inStream, &kMaxCheckStartPosition, NULL);
michael@0 128 }
michael@0 129
michael@0 130 #ifndef _SFX
michael@0 131 static inline bool TestSignature(const Byte *p1, const Byte *p2, size_t size)
michael@0 132 {
michael@0 133 for (size_t i = 0; i < size; i++)
michael@0 134 if (p1[i] != p2[i])
michael@0 135 return false;
michael@0 136 return true;
michael@0 137 }
michael@0 138 #endif
michael@0 139
michael@0 140 HRESULT OpenArchive(
michael@0 141 IInStream *inStream,
michael@0 142 const UString &fileName,
michael@0 143 #ifndef EXCLUDE_COM
michael@0 144 HMODULE *module,
michael@0 145 #endif
michael@0 146 IInArchive **archiveResult,
michael@0 147 CArchiverInfo &archiverInfoResult,
michael@0 148 UString &defaultItemName,
michael@0 149 IArchiveOpenCallback *openArchiveCallback)
michael@0 150 {
michael@0 151 *archiveResult = NULL;
michael@0 152 CObjectVector<CArchiverInfo> archiverInfoList;
michael@0 153 ReadArchiverInfoList(archiverInfoList);
michael@0 154 UString extension;
michael@0 155 {
michael@0 156 int dotPos = fileName.ReverseFind(L'.');
michael@0 157 if (dotPos >= 0)
michael@0 158 extension = fileName.Mid(dotPos + 1);
michael@0 159 }
michael@0 160 CIntVector orderIndices;
michael@0 161 int i;
michael@0 162 bool finded = false;
michael@0 163 for(i = 0; i < archiverInfoList.Size(); i++)
michael@0 164 {
michael@0 165 if (archiverInfoList[i].FindExtension(extension) >= 0)
michael@0 166 {
michael@0 167 orderIndices.Insert(0, i);
michael@0 168 finded = true;
michael@0 169 }
michael@0 170 else
michael@0 171 orderIndices.Add(i);
michael@0 172 }
michael@0 173
michael@0 174 #ifndef _SFX
michael@0 175 if (!finded)
michael@0 176 {
michael@0 177 CByteBuffer byteBuffer;
michael@0 178 const UInt32 kBufferSize = (200 << 10);
michael@0 179 byteBuffer.SetCapacity(kBufferSize);
michael@0 180 Byte *buffer = byteBuffer;
michael@0 181 RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
michael@0 182 UInt32 processedSize;
michael@0 183 RINOK(ReadStream(inStream, buffer, kBufferSize, &processedSize));
michael@0 184 int numFinded = 0;
michael@0 185 for (int pos = (int)processedSize; pos >= 0 ; pos--)
michael@0 186 {
michael@0 187 for(int i = numFinded; i < orderIndices.Size(); i++)
michael@0 188 {
michael@0 189 int index = orderIndices[i];
michael@0 190 const CArchiverInfo &ai = archiverInfoList[index];
michael@0 191 const CByteBuffer &sig = ai.StartSignature;
michael@0 192 if (sig.GetCapacity() == 0)
michael@0 193 continue;
michael@0 194 if (pos + sig.GetCapacity() > processedSize)
michael@0 195 continue;
michael@0 196 if (TestSignature(buffer + pos, sig, sig.GetCapacity()))
michael@0 197 {
michael@0 198 orderIndices.Delete(i);
michael@0 199 orderIndices.Insert(0, index);
michael@0 200 numFinded++;
michael@0 201 }
michael@0 202 }
michael@0 203 }
michael@0 204 }
michael@0 205 #endif
michael@0 206
michael@0 207 HRESULT badResult = S_OK;
michael@0 208 for(i = 0; i < orderIndices.Size(); i++)
michael@0 209 {
michael@0 210 inStream->Seek(0, STREAM_SEEK_SET, NULL);
michael@0 211 const CArchiverInfo &archiverInfo = archiverInfoList[orderIndices[i]];
michael@0 212 #ifndef EXCLUDE_COM
michael@0 213 CHandlerLoader loader;
michael@0 214 #endif
michael@0 215 CMyComPtr<IInArchive> archive;
michael@0 216
michael@0 217 #ifdef FORMAT_7Z
michael@0 218 if (archiverInfo.Name.CompareNoCase(L"7z") == 0)
michael@0 219 archive = new NArchive::N7z::CHandler;
michael@0 220 #endif
michael@0 221
michael@0 222 #ifdef FORMAT_BZIP2
michael@0 223 if (archiverInfo.Name.CompareNoCase(L"BZip2") == 0)
michael@0 224 archive = new NArchive::NBZip2::CHandler;
michael@0 225 #endif
michael@0 226
michael@0 227 #ifdef FORMAT_GZIP
michael@0 228 if (archiverInfo.Name.CompareNoCase(L"GZip") == 0)
michael@0 229 archive = new NArchive::NGZip::CHandler;
michael@0 230 #endif
michael@0 231
michael@0 232 #ifdef FORMAT_SPLIT
michael@0 233 if (archiverInfo.Name.CompareNoCase(L"Split") == 0)
michael@0 234 archive = new NArchive::NSplit::CHandler;
michael@0 235 #endif
michael@0 236
michael@0 237 #ifdef FORMAT_TAR
michael@0 238 if (archiverInfo.Name.CompareNoCase(L"Tar") == 0)
michael@0 239 archive = new NArchive::NTar::CHandler;
michael@0 240 #endif
michael@0 241
michael@0 242 #ifdef FORMAT_ZIP
michael@0 243 if (archiverInfo.Name.CompareNoCase(L"Zip") == 0)
michael@0 244 archive = new NArchive::NZip::CHandler;
michael@0 245 #endif
michael@0 246
michael@0 247 #ifdef FORMAT_Z
michael@0 248 if (archiverInfo.Name.CompareNoCase(L"Z") == 0)
michael@0 249 archive = new NArchive::NZ::CHandler;
michael@0 250 #endif
michael@0 251
michael@0 252
michael@0 253 #ifndef EXCLUDE_COM
michael@0 254 if (!archive)
michael@0 255 {
michael@0 256 HRESULT result = loader.CreateHandler(archiverInfo.FilePath,
michael@0 257 archiverInfo.ClassID, (void **)&archive, false);
michael@0 258 if (result != S_OK)
michael@0 259 continue;
michael@0 260 }
michael@0 261 #endif
michael@0 262
michael@0 263 if (!archive)
michael@0 264 return E_FAIL;
michael@0 265
michael@0 266 HRESULT result = archive->Open(inStream, &kMaxCheckStartPosition, openArchiveCallback);
michael@0 267 if(result == S_FALSE)
michael@0 268 continue;
michael@0 269 if(result != S_OK)
michael@0 270 {
michael@0 271 badResult = result;
michael@0 272 if(result == E_ABORT)
michael@0 273 break;
michael@0 274 continue;
michael@0 275 }
michael@0 276 *archiveResult = archive.Detach();
michael@0 277 #ifndef EXCLUDE_COM
michael@0 278 *module = loader.Detach();
michael@0 279 #endif
michael@0 280 archiverInfoResult = archiverInfo;
michael@0 281 int subExtIndex = archiverInfo.FindExtension(extension);
michael@0 282 if (subExtIndex < 0)
michael@0 283 subExtIndex = 0;
michael@0 284 defaultItemName = GetDefaultName2(fileName,
michael@0 285 archiverInfo.Extensions[subExtIndex].Ext,
michael@0 286 archiverInfo.Extensions[subExtIndex].AddExt);
michael@0 287
michael@0 288 return S_OK;
michael@0 289 }
michael@0 290 if (badResult != S_OK)
michael@0 291 return badResult;
michael@0 292 return S_FALSE;
michael@0 293 }
michael@0 294
michael@0 295 HRESULT OpenArchive(const UString &filePath,
michael@0 296 #ifndef EXCLUDE_COM
michael@0 297 HMODULE *module,
michael@0 298 #endif
michael@0 299 IInArchive **archiveResult,
michael@0 300 CArchiverInfo &archiverInfo,
michael@0 301 UString &defaultItemName,
michael@0 302 IArchiveOpenCallback *openArchiveCallback)
michael@0 303 {
michael@0 304 CInFileStream *inStreamSpec = new CInFileStream;
michael@0 305 CMyComPtr<IInStream> inStream(inStreamSpec);
michael@0 306 if (!inStreamSpec->Open(filePath))
michael@0 307 return GetLastError();
michael@0 308 return OpenArchive(inStream, ExtractFileNameFromPath(filePath),
michael@0 309 #ifndef EXCLUDE_COM
michael@0 310 module,
michael@0 311 #endif
michael@0 312 archiveResult, archiverInfo,
michael@0 313 defaultItemName, openArchiveCallback);
michael@0 314 }
michael@0 315
michael@0 316 static void MakeDefaultName(UString &name)
michael@0 317 {
michael@0 318 int dotPos = name.ReverseFind(L'.');
michael@0 319 if (dotPos < 0)
michael@0 320 return;
michael@0 321 UString ext = name.Mid(dotPos + 1);
michael@0 322 if (ext.IsEmpty())
michael@0 323 return;
michael@0 324 for (int pos = 0; pos < ext.Length(); pos++)
michael@0 325 if (ext[pos] < L'0' || ext[pos] > L'9')
michael@0 326 return;
michael@0 327 name = name.Left(dotPos);
michael@0 328 }
michael@0 329
michael@0 330 HRESULT OpenArchive(const UString &fileName,
michael@0 331 #ifndef EXCLUDE_COM
michael@0 332 HMODULE *module0,
michael@0 333 HMODULE *module1,
michael@0 334 #endif
michael@0 335 IInArchive **archive0,
michael@0 336 IInArchive **archive1,
michael@0 337 CArchiverInfo &archiverInfo0,
michael@0 338 CArchiverInfo &archiverInfo1,
michael@0 339 UString &defaultItemName0,
michael@0 340 UString &defaultItemName1,
michael@0 341 IArchiveOpenCallback *openArchiveCallback)
michael@0 342 {
michael@0 343 HRESULT result = OpenArchive(fileName,
michael@0 344 #ifndef EXCLUDE_COM
michael@0 345 module0,
michael@0 346 #endif
michael@0 347 archive0, archiverInfo0, defaultItemName0, openArchiveCallback);
michael@0 348 RINOK(result);
michael@0 349 CMyComPtr<IInArchiveGetStream> getStream;
michael@0 350 result = (*archive0)->QueryInterface(IID_IInArchiveGetStream, (void **)&getStream);
michael@0 351 if (result != S_OK || getStream == 0)
michael@0 352 return S_OK;
michael@0 353
michael@0 354 CMyComPtr<ISequentialInStream> subSeqStream;
michael@0 355 result = getStream->GetStream(0, &subSeqStream);
michael@0 356 if (result != S_OK)
michael@0 357 return S_OK;
michael@0 358
michael@0 359 CMyComPtr<IInStream> subStream;
michael@0 360 if (subSeqStream.QueryInterface(IID_IInStream, &subStream) != S_OK)
michael@0 361 return S_OK;
michael@0 362 if (!subStream)
michael@0 363 return S_OK;
michael@0 364
michael@0 365 UInt32 numItems;
michael@0 366 RINOK((*archive0)->GetNumberOfItems(&numItems));
michael@0 367 if (numItems < 1)
michael@0 368 return S_OK;
michael@0 369
michael@0 370 UString subPath;
michael@0 371 RINOK(GetArchiveItemPath(*archive0, 0, subPath))
michael@0 372 if (subPath.IsEmpty())
michael@0 373 {
michael@0 374 MakeDefaultName(defaultItemName0);
michael@0 375 subPath = defaultItemName0;
michael@0 376 if (archiverInfo0.Name.CompareNoCase(L"7z") == 0)
michael@0 377 {
michael@0 378 if (subPath.Right(3).CompareNoCase(L".7z") != 0)
michael@0 379 subPath += L".7z";
michael@0 380 }
michael@0 381 }
michael@0 382 else
michael@0 383 subPath = ExtractFileNameFromPath(subPath);
michael@0 384
michael@0 385 CMyComPtr<IArchiveOpenSetSubArchiveName> setSubArchiveName;
michael@0 386 openArchiveCallback->QueryInterface(IID_IArchiveOpenSetSubArchiveName, (void **)&setSubArchiveName);
michael@0 387 if (setSubArchiveName)
michael@0 388 setSubArchiveName->SetSubArchiveName(subPath);
michael@0 389
michael@0 390 result = OpenArchive(subStream, subPath,
michael@0 391 #ifndef EXCLUDE_COM
michael@0 392 module1,
michael@0 393 #endif
michael@0 394 archive1, archiverInfo1, defaultItemName1, openArchiveCallback);
michael@0 395 return S_OK;
michael@0 396 }
michael@0 397
michael@0 398 HRESULT MyOpenArchive(const UString &archiveName,
michael@0 399 #ifndef EXCLUDE_COM
michael@0 400 HMODULE *module,
michael@0 401 #endif
michael@0 402 IInArchive **archive,
michael@0 403 UString &defaultItemName,
michael@0 404 IOpenCallbackUI *openCallbackUI)
michael@0 405 {
michael@0 406 COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
michael@0 407 CMyComPtr<IArchiveOpenCallback> openCallback = openCallbackSpec;
michael@0 408 openCallbackSpec->Callback = openCallbackUI;
michael@0 409
michael@0 410 UString fullName;
michael@0 411 int fileNamePartStartIndex;
michael@0 412 NFile::NDirectory::MyGetFullPathName(archiveName, fullName, fileNamePartStartIndex);
michael@0 413 openCallbackSpec->Init(
michael@0 414 fullName.Left(fileNamePartStartIndex),
michael@0 415 fullName.Mid(fileNamePartStartIndex));
michael@0 416
michael@0 417 CArchiverInfo archiverInfo;
michael@0 418 return OpenArchive(archiveName,
michael@0 419 #ifndef EXCLUDE_COM
michael@0 420 module,
michael@0 421 #endif
michael@0 422 archive,
michael@0 423 archiverInfo,
michael@0 424 defaultItemName,
michael@0 425 openCallback);
michael@0 426 }
michael@0 427
michael@0 428 HRESULT MyOpenArchive(const UString &archiveName,
michael@0 429 #ifndef EXCLUDE_COM
michael@0 430 HMODULE *module0,
michael@0 431 HMODULE *module1,
michael@0 432 #endif
michael@0 433 IInArchive **archive0,
michael@0 434 IInArchive **archive1,
michael@0 435 UString &defaultItemName0,
michael@0 436 UString &defaultItemName1,
michael@0 437 UStringVector &volumePaths,
michael@0 438 IOpenCallbackUI *openCallbackUI)
michael@0 439 {
michael@0 440 COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
michael@0 441 CMyComPtr<IArchiveOpenCallback> openCallback = openCallbackSpec;
michael@0 442 openCallbackSpec->Callback = openCallbackUI;
michael@0 443
michael@0 444 UString fullName;
michael@0 445 int fileNamePartStartIndex;
michael@0 446 NFile::NDirectory::MyGetFullPathName(archiveName, fullName, fileNamePartStartIndex);
michael@0 447 UString prefix = fullName.Left(fileNamePartStartIndex);
michael@0 448 UString name = fullName.Mid(fileNamePartStartIndex);
michael@0 449 openCallbackSpec->Init(prefix, name);
michael@0 450
michael@0 451 CArchiverInfo archiverInfo0, archiverInfo1;
michael@0 452 HRESULT result = OpenArchive(archiveName,
michael@0 453 #ifndef EXCLUDE_COM
michael@0 454 module0,
michael@0 455 module1,
michael@0 456 #endif
michael@0 457 archive0,
michael@0 458 archive1,
michael@0 459 archiverInfo0,
michael@0 460 archiverInfo1,
michael@0 461 defaultItemName0,
michael@0 462 defaultItemName1,
michael@0 463 openCallback);
michael@0 464 RINOK(result);
michael@0 465 volumePaths.Add(prefix + name);
michael@0 466 for (int i = 0; i < openCallbackSpec->FileNames.Size(); i++)
michael@0 467 volumePaths.Add(prefix + openCallbackSpec->FileNames[i]);
michael@0 468 return S_OK;
michael@0 469 }
michael@0 470
michael@0 471 HRESULT CArchiveLink::Close()
michael@0 472 {
michael@0 473 if (Archive1 != 0)
michael@0 474 RINOK(Archive1->Close());
michael@0 475 if (Archive0 != 0)
michael@0 476 RINOK(Archive0->Close());
michael@0 477 return S_OK;
michael@0 478 }
michael@0 479
michael@0 480 void CArchiveLink::Release()
michael@0 481 {
michael@0 482 if (Archive1 != 0)
michael@0 483 Archive1.Release();
michael@0 484 if (Archive0 != 0)
michael@0 485 Archive0.Release();
michael@0 486 #ifndef EXCLUDE_COM
michael@0 487 Library1.Free();
michael@0 488 Library0.Free();
michael@0 489 #endif
michael@0 490 }
michael@0 491
michael@0 492 HRESULT OpenArchive(const UString &archiveName,
michael@0 493 CArchiveLink &archiveLink,
michael@0 494 IArchiveOpenCallback *openCallback)
michael@0 495 {
michael@0 496 return OpenArchive(archiveName,
michael@0 497 #ifndef EXCLUDE_COM
michael@0 498 &archiveLink.Library0, &archiveLink.Library1,
michael@0 499 #endif
michael@0 500 &archiveLink.Archive0, &archiveLink.Archive1,
michael@0 501 archiveLink.ArchiverInfo0, archiveLink.ArchiverInfo1,
michael@0 502 archiveLink.DefaultItemName0, archiveLink.DefaultItemName1,
michael@0 503 openCallback);
michael@0 504 }
michael@0 505
michael@0 506 HRESULT MyOpenArchive(const UString &archiveName,
michael@0 507 CArchiveLink &archiveLink,
michael@0 508 IOpenCallbackUI *openCallbackUI)
michael@0 509 {
michael@0 510 return MyOpenArchive(archiveName,
michael@0 511 #ifndef EXCLUDE_COM
michael@0 512 &archiveLink.Library0, &archiveLink.Library1,
michael@0 513 #endif
michael@0 514 &archiveLink.Archive0, &archiveLink.Archive1,
michael@0 515 archiveLink.DefaultItemName0, archiveLink.DefaultItemName1,
michael@0 516 archiveLink.VolumePaths,
michael@0 517 openCallbackUI);
michael@0 518 }
michael@0 519
michael@0 520 HRESULT ReOpenArchive(CArchiveLink &archiveLink,
michael@0 521 const UString &fileName)
michael@0 522 {
michael@0 523 if (archiveLink.GetNumLevels() > 1)
michael@0 524 return E_NOTIMPL;
michael@0 525 if (archiveLink.GetNumLevels() == 0)
michael@0 526 return MyOpenArchive(fileName, archiveLink, 0);
michael@0 527 return ReOpenArchive(archiveLink.GetArchive(), fileName);
michael@0 528 }

mercurial