PageRenderTime 144ms CodeModel.GetById 27ms app.highlight 102ms RepoModel.GetById 1ms app.codeStats 1ms

/Rainman2/archive.cpp

http://modstudio2.googlecode.com/
C++ | 865 lines | 765 code | 67 blank | 33 comment | 141 complexity | 7c2d75164d615a3e93688034dba766c9 MD5 | raw file
  1/*
  2Copyright (c) 2008 Peter "Corsix" Cawley
  3
  4Permission is hereby granted, free of charge, to any person
  5obtaining a copy of this software and associated documentation
  6files (the "Software"), to deal in the Software without
  7restriction, including without limitation the rights to use,
  8copy, modify, merge, publish, distribute, sublicense, and/or sell
  9copies of the Software, and to permit persons to whom the
 10Software is furnished to do so, subject to the following
 11conditions:
 12
 13The above copyright notice and this permission notice shall be
 14included in all copies or substantial portions of the Software.
 15
 16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 17EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 18OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 19NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 20HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 21WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 22FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 23OTHER DEALINGS IN THE SOFTWARE.
 24*/
 25#include "archive.h"
 26#include "exception.h"
 27#include "hash.h"
 28#include "zlib.h"
 29#include "memfile.h"
 30#include <memory.h>
 31#include <string.h>
 32#ifdef RAINMAN2_USE_CRYPTO_WIN32
 33#include <windows.h>
 34#else
 35#define min std::min
 36#endif
 37
 38struct _entry_point_raw_t
 39{
 40  char sDirectoryName[64];
 41  char sAlias[64];
 42  unsigned short int iFirstDirectory;
 43  unsigned short int iLastDirectory;
 44  unsigned short int iFirstFile;
 45  unsigned short int iLastFile;
 46  unsigned long iFolderOffset;
 47};
 48
 49struct _directory_raw_t
 50{
 51  unsigned long iNameOffset;
 52  unsigned short int iFirstDirectory;
 53  unsigned short int iLastDirectory;
 54  unsigned short int iFirstFile;
 55  unsigned short int iLastFile;
 56};
 57
 58struct _file_raw_t
 59{
 60  unsigned long iNameOffset;
 61  unsigned long iDataOffset;
 62	unsigned long iDataLengthCompressed;
 63	unsigned long iDataLength;
 64	unsigned long iModificationTime;
 65  union
 66  {
 67	  unsigned long iFlags32;
 68    unsigned short iFlags16;
 69  };
 70};
 71
 72bool IArchiveFileStore::initNoThrow(IFile* pFile, bool bTakePointerOwnership) throw()
 73{
 74  try
 75  {
 76    init(pFile, bTakePointerOwnership);
 77    return true;
 78  }
 79  catch(RainException* e)
 80  {
 81    delete e;
 82  }
 83  catch(...)
 84  {
 85  }
 86  return false;
 87}
 88
 89void IArchiveFileStore::getCaps(file_store_caps_t& oCaps) const throw()
 90{
 91  oCaps = false;
 92  oCaps.bCanReadFiles = true;
 93  oCaps.bCanOpenDirectories = true;
 94}
 95
 96void IArchiveFileStore::deleteFile(const RainString& sPath) throw(...)
 97{
 98  THROW_SIMPLE_(L"Files cannot be deleted from archive files (attempt to delete \'%s\')", sPath.getCharacters());
 99}
100
101bool IArchiveFileStore::deleteFileNoThrow(const RainString& sPath) throw()
102{
103  return false;
104}
105
106void IArchiveFileStore::createDirectory(const RainString& sPath) throw(...)
107{
108  THROW_SIMPLE_(L"Directories cannot be created in archive files (attempt to create \'%s\')", sPath.getCharacters());
109}
110
111bool IArchiveFileStore::createDirectoryNoThrow(const RainString& sPath) throw()
112{
113  return false;
114}
115
116void IArchiveFileStore::deleteDirectory(const RainString& sPath) throw(...)
117{
118  THROW_SIMPLE_(L"Directories cannot be deleted from archive files (attempt to delete \'%s\')", sPath.getCharacters());
119}
120
121bool IArchiveFileStore::deleteDirectoryNoThrow(const RainString& sPath) throw()
122{
123  return false;
124}
125
126SgaArchive::SgaArchive()
127{
128  _zeroSelf();
129}
130
131SgaArchive::~SgaArchive()
132{
133  _cleanSelf();
134}
135
136void SgaArchive::_zeroSelf() throw()
137{
138  m_pRawFile = 0;
139  m_bDeleteRawFileLater = false;
140  memset(&m_oFileHeader, 0, sizeof m_oFileHeader);
141  m_pEntryPoints = 0;
142  m_pDirectories = 0;
143  m_pFiles = 0;
144  m_iDataHeaderOffset = 0;
145  m_iNumEntryPointsLoaded = 0;
146  m_iNumDirectoriesLoaded = 0;
147  m_iNumFilesLoaded = 0;
148  m_sStringBlob = 0;
149}
150
151void SgaArchive::_cleanSelf() throw()
152{
153  if(m_bDeleteRawFileLater)
154    delete m_pRawFile;
155  delete[] m_pEntryPoints;
156  delete[] m_pDirectories;
157  delete[] m_pFiles;
158  delete[] m_sStringBlob;
159
160  _zeroSelf();
161}
162
163bool SgaArchive::doesFileResemble(IFile* pFile) throw()
164{
165  char sSig[8];
166  size_t iCount = pFile->readArrayNoThrow(sSig, 8);
167  pFile->seekNoThrow(-static_cast<seek_offset_t>(iCount), SR_Current);
168  return (iCount == 8) && memcmp(sSig, "_ARCHIVE", 8) == 0;
169}
170
171void SgaArchive::init(IFile* pSgaFile, bool bTakePointerOwnership) throw(...)
172{
173  _cleanSelf();
174  m_pRawFile = pSgaFile;
175  m_bDeleteRawFileLater = bTakePointerOwnership;
176  
177  try
178  {
179    pSgaFile->readArray(m_oFileHeader.sIdentifier, 8);
180    if(strncmp(m_oFileHeader.sIdentifier, "_ARCHIVE", 8) != 0)
181      THROW_SIMPLE(L"Identifier is not _ARCHIVE");
182    pSgaFile->readOne(m_oFileHeader.iVersionMajor);
183    pSgaFile->readOne(m_oFileHeader.iVersionMinor);
184    if((m_oFileHeader.iVersionMajor == 2 && m_oFileHeader.iVersionMinor == 0) ||
185       (m_oFileHeader.iVersionMajor == 4 && m_oFileHeader.iVersionMinor <= 1) ||
186       (m_oFileHeader.iVersionMajor == 5 && m_oFileHeader.iVersionMinor == 0) )
187    {}
188    else
189      throw new RainException(__WFILE__, __LINE__, 0, L"Only version 2.0, 4.0, 4.1 or 5.0 SGA archives are supported, not version %u.%u - Please show this archive to corsix@corsix.org", m_oFileHeader.iVersionMajor, m_oFileHeader.iVersionMinor);
190#ifndef RAINMAN2_USE_CRYPTO_WIN32
191    if(m_oFileHeader.iVersionMajor == 4 && m_oFileHeader.iVersionMinor == 1)
192      throw new RainException(__WFILE__, __LINE__, 0, L"Rainman not compiled with support for version 4.1 SGA archives");
193#endif
194    pSgaFile->readArray(m_oFileHeader.iContentsMD5, 4);
195    pSgaFile->readArray(m_oFileHeader.sArchiveName, 64);
196    pSgaFile->readArray(m_oFileHeader.iHeaderMD5, 4);
197    pSgaFile->readOne(m_oFileHeader.iDataHeaderSize);
198    pSgaFile->readOne(m_oFileHeader.iDataOffset);
199    if(m_oFileHeader.iVersionMajor == 5)
200    {
201      pSgaFile->readOne(m_iDataHeaderOffset);
202    }
203    if(m_oFileHeader.iVersionMajor >= 4)
204    {
205      pSgaFile->readOne(m_oFileHeader.iPlatform);
206      if(m_oFileHeader.iPlatform != 1)
207        throw new RainException(__WFILE__, __LINE__, 0, L"Only win32/x86 (platform #1) SGA archives are supported, not platform #%lu. Please show this archive to corsix@corsix.org", m_oFileHeader.iPlatform);
208    }
209    else
210      m_oFileHeader.iPlatform = 1;
211  }
212  CATCH_THROW_SIMPLE(_cleanSelf(), L"Could not load valid file header")
213
214  if(m_oFileHeader.iVersionMajor == 5)
215  {
216    pSgaFile->seek(m_iDataHeaderOffset, SR_Start);
217  }
218  else
219  {
220    m_iDataHeaderOffset = pSgaFile->tell();
221  }
222  try
223  {
224    MD5Hash oHeaderHash;
225    long iHeaderHash[4];
226    oHeaderHash.updateFromString("DFC9AF62-FC1B-4180-BC27-11CCE87D3EFF");
227    oHeaderHash.updateFromFile(pSgaFile, m_oFileHeader.iDataHeaderSize);
228    oHeaderHash.finalise((unsigned char*)iHeaderHash);
229    if(memcmp(iHeaderHash, m_oFileHeader.iHeaderMD5, 16) != 0)
230      THROW_SIMPLE(L"Stored header hash does not match computed header hash");
231  }
232  CATCH_THROW_SIMPLE(_cleanSelf(), L"File is not an SGA archive because of invalid header / hash");
233
234  try
235  {
236    pSgaFile->seek(m_iDataHeaderOffset, SR_Start);
237    pSgaFile->readOne(m_oFileHeader.iEntryPointOffset);
238    pSgaFile->readOne(m_oFileHeader.iEntryPointCount);
239    pSgaFile->readOne(m_oFileHeader.iDirectoryOffset);
240    pSgaFile->readOne(m_oFileHeader.iDirectoryCount);
241    pSgaFile->readOne(m_oFileHeader.iFileOffset);
242    pSgaFile->readOne(m_oFileHeader.iFileCount);
243    pSgaFile->readOne(m_oFileHeader.iStringOffset);
244    pSgaFile->readOne(m_oFileHeader.iStringCount);
245  }
246  CATCH_THROW_SIMPLE(_cleanSelf(), L"Cannot load data header overview");
247
248#ifdef RAINMAN2_USE_CRYPTO_WIN32
249  if(m_oFileHeader.iVersionMajor == 4 && m_oFileHeader.iVersionMinor == 1)
250  {
251    // This code reverse-engineered from CoH:Online's Filesystem.dll
252    HCRYPTPROV hCryptoProvider = 0;
253    HCRYPTKEY hCryptoKey = 0;
254    unsigned char* pKeyData = 0;
255    try
256    {
257      pSgaFile->seek(m_iDataHeaderOffset + m_oFileHeader.iStringOffset, SR_Start);
258      unsigned long iKeyLength;
259      pSgaFile->readOne(iKeyLength);
260      CHECK_ALLOCATION(pKeyData = new (std::nothrow) unsigned char[iKeyLength]);
261      pSgaFile->readArray(pKeyData, iKeyLength);
262      unsigned long iStringsLength;
263      pSgaFile->readOne(iStringsLength);
264      CHECK_ALLOCATION(m_sStringBlob = new (std::nothrow) char[iStringsLength]);
265      pSgaFile->readArray(m_sStringBlob, iStringsLength);
266
267      if(CryptAcquireContext(&hCryptoProvider, NULL, L"Microsoft Enhanced Cryptographic Provider v1.0", PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE)
268        THROW_SIMPLE(L"Cannot acquire cryptographic context from Windows");
269      if(CryptImportKey(hCryptoProvider, pKeyData, iKeyLength, NULL, 0, &hCryptoKey) == FALSE)
270        THROW_SIMPLE(L"Cannot import cryptographic key from archive");
271      if(CryptDecrypt(hCryptoKey, NULL, TRUE, 0, reinterpret_cast<BYTE*>(m_sStringBlob), &iStringsLength) == FALSE)
272        THROW_SIMPLE(L"Could not decrypt archive\'s string table");
273    }
274    CATCH_THROW_SIMPLE({
275      if(hCryptoKey)
276        CryptDestroyKey(hCryptoKey);
277      if(hCryptoProvider)
278        CryptReleaseContext(hCryptoProvider, 0);
279      if(pKeyData)
280        delete[] pKeyData;
281      _cleanSelf();
282    }, L"Cannot load file and directory names");
283    CryptDestroyKey(hCryptoKey);
284    CryptReleaseContext(hCryptoProvider, 0);
285    delete[] pKeyData;
286  }
287  else
288#endif
289  {
290    try
291    {
292      pSgaFile->seek(m_iDataHeaderOffset + m_oFileHeader.iStringOffset, SR_Start);
293      size_t iStringsLength = m_oFileHeader.iDataHeaderSize - m_oFileHeader.iStringOffset;
294      CHECK_ALLOCATION(m_sStringBlob = new (std::nothrow) char[iStringsLength]);
295      pSgaFile->readArray(m_sStringBlob, iStringsLength);
296    }
297    CATCH_THROW_SIMPLE(_cleanSelf(), L"Cannot load file and directory names");
298  }
299}
300
301IFile* SgaArchive::openFile(const RainString& sPath, eFileOpenMode eMode) throw(...)
302{
303  if(eMode != FM_Read)
304    THROW_SIMPLE_(L"Files cannot be written to SGA archives (attempt to write \'%s\')", sPath.getCharacters());
305  _directory_info_t* pDirInfo = 0;
306  _file_info_t* pFileInfo = 0;
307  _resolvePath(sPath, &pDirInfo, &pFileInfo, true);
308  if(pFileInfo == 0)
309    THROW_SIMPLE_(L"Cannot open file \'%s\' as it is a directory", sPath.getCharacters());
310  IFile* pFile = CHECK_ALLOCATION(new (std::nothrow) MemoryWriteFile(pFileInfo->iDataLength));
311  try
312  {
313    _pumpFile(pFileInfo, pFile);
314    pFile->seek(0, SR_Start);
315  }
316  CATCH_THROW_SIMPLE_(delete pFile, L"Error opening \'%s\' for reading", sPath.getCharacters());
317  return pFile;
318}
319
320void SgaArchive::_pumpFile(_file_info_t* pInfo, IFile* pSink) throw(...)
321{
322  m_pRawFile->seek(m_oFileHeader.iDataOffset + pInfo->iDataOffset, SR_Start);
323
324  if(pInfo->iDataLength == pInfo->iDataLengthCompressed)
325  {
326    static const size_t BUFFER_SIZE = 8192;
327    unsigned char aBuffer[BUFFER_SIZE];
328    for(size_t iRemaining = static_cast<size_t>(pInfo->iDataLength); iRemaining != 0;)
329    {
330      size_t iNumBytes = m_pRawFile->readArrayNoThrow(aBuffer, min(BUFFER_SIZE, iRemaining));
331      pSink->writeArray(aBuffer, iNumBytes);
332      iRemaining -= iNumBytes;
333    }
334  }
335  else
336  {
337    static const size_t BUFFER_SIZE = 4096;
338    static const wchar_t* Z_ERR[] = {
339      L"zLib error from C errno",
340      L"zLib stream error",
341      L"zLib data error",
342      L"zLib memory error",
343      L"zLib buffer error",
344      L"zLib version error"
345    };
346    unsigned char aBufferComp[BUFFER_SIZE];
347    unsigned char aBufferInft[BUFFER_SIZE];
348
349    size_t iRemaining = static_cast<size_t>(pInfo->iDataLengthCompressed);
350    size_t iNumBytes;
351    z_stream stream;
352    int err;
353
354    iNumBytes = m_pRawFile->readArrayNoThrow(aBufferComp, min(BUFFER_SIZE, iRemaining));
355    iRemaining -= iNumBytes;
356
357    stream.next_in = (Bytef*)aBufferComp;
358    stream.avail_in = (uInt)iNumBytes;
359    stream.next_out = (Bytef*)aBufferInft;
360    stream.avail_out = (uInt)BUFFER_SIZE;
361    stream.zalloc = (alloc_func)0;
362    stream.zfree = (free_func)0;
363
364    err = inflateInit(&stream);
365    if(err != Z_OK)
366      THROW_SIMPLE_(L"Cannot initialise zLib stream; %s", Z_ERR[-err-1]);
367
368    try
369    {
370      while(true)
371      {
372        err = inflate(&stream, Z_SYNC_FLUSH);
373        if(err == Z_STREAM_END)
374          break;
375        else
376        {
377          switch(err)
378          {
379          case Z_NEED_DICT:
380            THROW_SIMPLE(L"Cannot decompress file; zLib requesting dictionary");
381            break;
382
383          case Z_OK:
384            if(stream.avail_in == 0)
385            {
386              iNumBytes = m_pRawFile->readArrayNoThrow(aBufferComp, min(BUFFER_SIZE, iRemaining));
387              iRemaining -= iNumBytes;
388              stream.next_in = (Bytef*)aBufferComp;
389              stream.avail_in = (uInt)iNumBytes;
390            }
391            if(stream.next_out != aBufferInft)
392            {
393              pSink->writeArray(aBufferInft, stream.next_out - aBufferInft);
394              stream.next_out = (Bytef*)aBufferInft;
395              stream.avail_out = (uInt)BUFFER_SIZE;
396            }
397            break;
398
399          default:
400            if(stream.total_out == pInfo->iDataLength && stream.msg && strcmp(stream.msg, "incorrect data check") == 0
401              && m_oFileHeader.iVersionMajor == 5 && m_oFileHeader.iVersionMinor == 0 && m_oFileHeader.iDataOffset == 198
402              && pInfo == (m_pFiles + m_oFileHeader.iFileCount - 1))
403            {
404              // There is a bug in SGA archives produced by early versions of sga4to5.exe, in which the last two bytes of
405              // the last file's data are truncated. If this is the case, then the actual data is still intact, but the
406              // end of the zLib metadata is missing.
407              m_pRawFile->seek(192, SR_Start);
408              m_pRawFile->readArrayNoThrow(aBufferComp, 4);
409              if(memcmp(aBufferComp, "COR6", 4) == 0)
410              {
411                break;
412              }
413            }
414            THROW_SIMPLE_(L"Cannot decompress file; %s (%S)", Z_ERR[-err-1], stream.msg ? stream.msg : "?");
415            break;
416          }
417          if(stream.total_out == pInfo->iDataLength)
418            break;
419        }
420      }
421      if(stream.next_out != aBufferInft)
422        pSink->writeArray(aBufferInft, stream.next_out - aBufferInft);
423    }
424    catch(RainException*)
425    {
426      inflateEnd(&stream);
427      throw;
428    }
429
430    err = inflateEnd(&stream);
431    if(err != Z_OK)
432      THROW_SIMPLE_(L"Cannot cleanly close zLib stream; %s", Z_ERR[-err-1]);
433  }
434}
435
436void SgaArchive::pumpFile(const RainString& sPath, IFile* pSink) throw(...)
437{
438  _directory_info_t* pDirInfo = 0;
439  _file_info_t* pFileInfo = 0;
440  try
441  {
442    _resolvePath(sPath, &pDirInfo, &pFileInfo, true);
443    if(pFileInfo == 0)
444      THROW_SIMPLE(L"Cannot pump a directory");
445
446    _pumpFile(pFileInfo, pSink);
447  }
448  CATCH_THROW_SIMPLE_({}, L"Cannot pump file \'%s\'", sPath.getCharacters());
449}
450
451IFile* SgaArchive::openFileNoThrow(const RainString& sPath, eFileOpenMode eMode) throw()
452{
453  if(eMode != FM_Read)
454    return 0;
455  _directory_info_t* pDirInfo = 0;
456  _file_info_t* pFileInfo = 0;
457  if(!_resolvePath(sPath, &pDirInfo, &pFileInfo, false))
458    return 0;
459  IFile* pFile = new (std::nothrow) MemoryWriteFile(pFileInfo->iDataLength);
460  if(!pFile)
461    return 0;
462  try
463  {
464    _pumpFile(pFileInfo, pFile);
465    pFile->seek(0, SR_Start);
466  }
467  catch(RainException *pE)
468  {
469    delete pE;
470    delete pFile;
471    return 0;
472  }
473  return pFile;
474}
475
476bool SgaArchive::doesFileExist(const RainString& sPath) throw()
477{
478  _directory_info_t* pDirInfo = 0;
479  _file_info_t* pFileInfo = 0;
480  _resolvePath(sPath, &pDirInfo, &pFileInfo, false);
481  return pFileInfo != 0;
482}
483
484size_t SgaArchive::getEntryPointCount() throw()
485{
486  return m_oFileHeader.iEntryPointCount;
487}
488
489const RainString& SgaArchive::getEntryPointName(size_t iIndex) throw(...)
490{
491  CHECK_RANGE(0, iIndex, getEntryPointCount() - 1);
492  _loadEntryPointsUpTo((unsigned short)iIndex);
493  return m_pEntryPoints[iIndex].sName;
494}
495
496class ArchiveDirectoryAdapter : public IDirectory
497{
498public:
499  ArchiveDirectoryAdapter(SgaArchive* pArchive, SgaArchive::_directory_info_t* pDirectory) throw()
500    : m_pArchive(pArchive), m_pDirectory(pDirectory)
501  {
502    m_iCountSubDir = pDirectory->iLastDirectory - pDirectory->iFirstDirectory;
503    m_iCountFiles = pDirectory->iLastFile - pDirectory->iFirstFile;
504  }
505
506  virtual const RainString& getPath() throw()
507  {
508    return m_pDirectory->sPath;
509  }
510
511  virtual IFileStore* getStore() throw()
512  {
513    return m_pArchive;
514  }
515
516  virtual size_t getItemCount() throw()
517  {
518    return m_iCountSubDir + m_iCountFiles;
519  }
520
521  virtual void getItemDetails(size_t iItemIndex, directory_item_t& oDetails) throw(...)
522  {
523    CHECK_RANGE_LTMAX(0, iItemIndex, m_iCountSubDir + m_iCountFiles);
524    if(iItemIndex < m_iCountSubDir)
525    {
526      iItemIndex += m_pDirectory->iFirstDirectory;
527      m_pArchive->_loadDirectoriesUpTo((unsigned short)iItemIndex);
528      SgaArchive::_directory_info_t* pInfo = m_pArchive->m_pDirectories + iItemIndex;
529      if(oDetails.oFields.name)
530        oDetails.sName = pInfo->sName;
531      if(oDetails.oFields.dir)
532        oDetails.bIsDirectory = true;
533      if(oDetails.oFields.size)
534        oDetails.iSize.iLower = 0, oDetails.iSize.iUpper = 0;
535      if(oDetails.oFields.time)
536        oDetails.iTimestamp = 0;
537    }
538    else
539    {
540      iItemIndex -= m_iCountSubDir;
541      iItemIndex += m_pDirectory->iFirstFile;
542      m_pArchive->_loadFilesUpTo((unsigned short)iItemIndex);
543      SgaArchive::_file_info_t* pInfo = m_pArchive->m_pFiles + iItemIndex;
544      if(oDetails.oFields.name)
545        oDetails.sName = RainString(m_pArchive->m_sStringBlob + pInfo->iName); //pInfo->sName;
546      if(oDetails.oFields.dir)
547        oDetails.bIsDirectory = false;
548      if(oDetails.oFields.size)
549        oDetails.iSize.iLower = pInfo->iDataLength, oDetails.iSize.iUpper = 0;
550      if(oDetails.oFields.time)
551        oDetails.iTimestamp = pInfo->iModificationTime;
552    }
553  }
554
555  virtual void pumpFile(size_t iIndex, IFile* pSink) throw(...)
556  {
557    CHECK_RANGE_LTMAX(m_iCountSubDir, iIndex, m_iCountSubDir + m_iCountFiles);
558    iIndex -= m_iCountSubDir;
559    iIndex += m_pDirectory->iFirstFile;
560    m_pArchive->_loadFilesUpTo((unsigned short)iIndex);
561    m_pArchive->_pumpFile(m_pArchive->m_pFiles + iIndex, pSink);
562  }
563
564  virtual IDirectory* openDirectory(size_t iIndex) throw(...)
565  {
566    CHECK_RANGE_LTMAX(0, iIndex, m_iCountSubDir);
567    iIndex += m_pDirectory->iFirstDirectory;
568    m_pArchive->_loadDirectoriesUpTo((unsigned short)iIndex);
569    return CHECK_ALLOCATION(new (std::nothrow) ArchiveDirectoryAdapter(m_pArchive, m_pArchive->m_pDirectories + iIndex));
570  }
571
572  virtual IDirectory* openDirectoryNoThrow(size_t iIndex) throw()
573  {
574    if(iIndex >= m_iCountSubDir)
575      return 0;
576    iIndex += m_pDirectory->iFirstDirectory;
577    try
578    {
579      m_pArchive->_loadDirectoriesUpTo((unsigned short)iIndex);
580    }
581    catch(RainException *pE)
582    {
583      delete pE;
584      return 0;
585    }
586    return new (std::nothrow) ArchiveDirectoryAdapter(m_pArchive, m_pArchive->m_pDirectories + iIndex);
587  }
588
589protected:
590  SgaArchive *m_pArchive;
591  SgaArchive::_directory_info_t *m_pDirectory;
592  size_t m_iCountSubDir;
593  size_t m_iCountFiles;
594};
595
596IDirectory* SgaArchive::openDirectory(const RainString& sPath) throw(...)
597{
598  _directory_info_t* pDirectoryInfo;
599  _file_info_t* pFileInfo;
600  _resolvePath(sPath, &pDirectoryInfo, &pFileInfo, true);
601  if(pDirectoryInfo == 0)
602    THROW_SIMPLE_(L"Cannot open \'%s\' as a directory because it is a file", sPath.getCharacters());
603  return CHECK_ALLOCATION(new (std::nothrow) ArchiveDirectoryAdapter(this, pDirectoryInfo));
604}
605
606IDirectory* SgaArchive::openDirectoryNoThrow(const RainString& sPath) throw()
607{
608  _directory_info_t* pDirectoryInfo;
609  _file_info_t* pFileInfo;
610  if(!_resolvePath(sPath, &pDirectoryInfo, &pFileInfo, false) || pDirectoryInfo == 0)
611    return 0;
612  return new (std::nothrow) ArchiveDirectoryAdapter(this, pDirectoryInfo);
613}
614
615bool SgaArchive::doesDirectoryExist(const RainString& sPath) throw()
616{
617  _directory_info_t* pDirInfo = 0;
618  _file_info_t* pFileInfo = 0;
619  _resolvePath(sPath, &pDirInfo, &pFileInfo, false);
620  return pDirInfo != 0;
621}
622
623void SgaArchive::_loadEntryPointsUpTo(unsigned short int iEnsureLoaded) throw(...)
624{
625  unsigned short int iFirstToLoad = m_iNumEntryPointsLoaded + 1;
626  if(m_pEntryPoints == 0)
627  {
628    iFirstToLoad = 0;
629    CHECK_ALLOCATION(m_pEntryPoints = new _directory_info_t[getEntryPointCount()]);
630  }
631  if(iFirstToLoad <= iEnsureLoaded)
632  {
633    CHECK_RANGE_LTMAX(0, iEnsureLoaded, m_oFileHeader.iEntryPointCount);
634    try
635    {
636      unsigned long iEntryPointSize = 140;
637      if((m_oFileHeader.iVersionMajor == 4 && m_oFileHeader.iVersionMinor == 1)
638      || (m_oFileHeader.iVersionMajor == 5))
639      {
640        iEntryPointSize = 138;
641      }
642      m_pRawFile->seek(m_iDataHeaderOffset + m_oFileHeader.iEntryPointOffset + static_cast<unsigned long>(iFirstToLoad) * iEntryPointSize, SR_Start);
643      for(unsigned short int iToLoad = iFirstToLoad; iToLoad <= iEnsureLoaded; m_iNumEntryPointsLoaded = iToLoad++)
644      {
645        _entry_point_raw_t oRaw;
646        m_pRawFile->readArray(oRaw.sDirectoryName, 64);
647        m_pRawFile->readArray(oRaw.sAlias, 64);
648        m_pRawFile->readOne(oRaw.iFirstDirectory);
649        m_pRawFile->readOne(oRaw.iLastDirectory);
650        m_pRawFile->readOne(oRaw.iFirstFile);
651        m_pRawFile->readOne(oRaw.iLastFile);
652        if((m_oFileHeader.iVersionMajor == 4 && m_oFileHeader.iVersionMinor == 1)
653        || (m_oFileHeader.iVersionMajor == 5) )
654        {
655          unsigned short iFolderOffset;
656          m_pRawFile->readOne(iFolderOffset);
657          oRaw.iFolderOffset = iFolderOffset;
658        }
659        else
660        {
661          m_pRawFile->readOne(oRaw.iFolderOffset);
662        }
663
664        m_pEntryPoints[iToLoad].sName = oRaw.sDirectoryName;
665        m_pEntryPoints[iToLoad].sPath = m_pEntryPoints[iToLoad].sName + L"\\";
666        m_pEntryPoints[iToLoad].iFirstDirectory = oRaw.iFirstDirectory;
667        m_pEntryPoints[iToLoad].iLastDirectory = oRaw.iLastDirectory;
668        m_pEntryPoints[iToLoad].iFirstFile = oRaw.iFirstFile;
669        m_pEntryPoints[iToLoad].iLastFile = oRaw.iLastFile;
670
671        _loadDirectoriesUpTo(oRaw.iFirstDirectory);
672      }
673    }
674    CATCH_THROW_SIMPLE({}, L"Unable to load entry point details")
675  }
676}
677
678const RainString& SgaArchive::_getDirectoryEntryPoint(_directory_info_t* pDirectory) throw()
679{
680  _loadEntryPointsUpTo(m_oFileHeader.iEntryPointCount - 1);
681  if(m_oFileHeader.iEntryPointCount != 1)
682  {
683    unsigned short int iIndex = (unsigned short)(pDirectory - m_pDirectories);
684    for(unsigned short int iEntryPoint = 0; iEntryPoint < m_oFileHeader.iEntryPointCount; ++iEntryPoint)
685    {
686      if(m_pEntryPoints[iEntryPoint].iFirstDirectory <= iIndex && iIndex < m_pEntryPoints[iEntryPoint].iLastDirectory)
687        return m_pEntryPoints[iEntryPoint].sPath;
688    }
689  }
690  return m_pEntryPoints->sPath;
691}
692
693void SgaArchive::_loadDirectoriesUpTo(unsigned short int iEnsureLoaded) throw(...)
694{
695  unsigned short int iFirstToLoad = m_iNumDirectoriesLoaded + 1;
696  if(m_pDirectories == 0)
697  {
698    iFirstToLoad = 0;
699    CHECK_ALLOCATION(m_pDirectories = new _directory_info_t[m_oFileHeader.iDirectoryCount]);
700  }
701  if(iFirstToLoad <= iEnsureLoaded)
702  {
703    CHECK_RANGE_LTMAX(0, iEnsureLoaded, m_oFileHeader.iDirectoryCount);
704    try
705    {
706      m_pRawFile->seek(m_iDataHeaderOffset + m_oFileHeader.iDirectoryOffset + iFirstToLoad * 12, SR_Start);
707      for(unsigned short int iToLoad = iFirstToLoad; iToLoad <= iEnsureLoaded; m_iNumDirectoriesLoaded = iToLoad++)
708      {
709        _directory_raw_t oRaw;
710        m_pRawFile->readOne(oRaw.iNameOffset);
711        m_pRawFile->readOne(oRaw.iFirstDirectory);
712        m_pRawFile->readOne(oRaw.iLastDirectory);
713        m_pRawFile->readOne(oRaw.iFirstFile);
714        m_pRawFile->readOne(oRaw.iLastFile);
715
716        m_pDirectories[iToLoad].sPath = RainString(m_sStringBlob + oRaw.iNameOffset);
717        if(m_pDirectories[iToLoad].sPath.isEmpty())
718        {
719          m_pDirectories[iToLoad].sPath = _getDirectoryEntryPoint(m_pDirectories + iToLoad);
720          m_pDirectories[iToLoad].sName = m_pDirectories[iToLoad].sPath.beforeLast('\\');
721        }
722        else
723        {
724          m_pDirectories[iToLoad].sPath = _getDirectoryEntryPoint(m_pDirectories + iToLoad) + m_pDirectories[iToLoad].sPath;
725          m_pDirectories[iToLoad].sName = m_pDirectories[iToLoad].sPath.afterLast('\\');
726          m_pDirectories[iToLoad].sPath += L"\\";
727        }
728        m_pDirectories[iToLoad].iFirstDirectory = oRaw.iFirstDirectory;
729        m_pDirectories[iToLoad].iLastDirectory = oRaw.iLastDirectory;
730        m_pDirectories[iToLoad].iFirstFile = oRaw.iFirstFile;
731        m_pDirectories[iToLoad].iLastFile = oRaw.iLastFile;
732      }
733    }
734    CATCH_THROW_SIMPLE({}, L"Unable to load directory details")
735  }
736}
737
738void SgaArchive::_loadFilesUpTo_v2(unsigned short int iFirstToLoad, unsigned short int iEnsureLoaded) throw(...)
739{
740  try
741  {
742    m_pRawFile->seek(m_iDataHeaderOffset + m_oFileHeader.iFileOffset + iFirstToLoad * 20, SR_Start);
743    for(unsigned short int iToLoad = iFirstToLoad; iToLoad <= iEnsureLoaded; m_iNumFilesLoaded = iToLoad++)
744    {
745      _file_raw_t oRaw;
746      m_pRawFile->readOne(oRaw.iNameOffset);
747      m_pRawFile->readOne(oRaw.iFlags32);
748      m_pRawFile->readOne(oRaw.iDataOffset);
749      m_pRawFile->readOne(oRaw.iDataLengthCompressed);
750      m_pRawFile->readOne(oRaw.iDataLength);
751
752      m_pFiles[iToLoad].iName = oRaw.iNameOffset; //RainString(m_sStringBlob + oRaw.iNameOffset);
753      m_pFiles[iToLoad].iDataOffset = oRaw.iDataOffset;
754      m_pFiles[iToLoad].iDataLength = oRaw.iDataLength;
755      m_pFiles[iToLoad].iDataLengthCompressed = oRaw.iDataLengthCompressed;
756      m_pFiles[iToLoad].iModificationTime = 0;
757    }
758  }
759  CATCH_THROW_SIMPLE({}, L"Unable to load file details")
760}
761
762void SgaArchive::_loadFilesUpTo_v4(unsigned short int iFirstToLoad, unsigned short int iEnsureLoaded) throw(...)
763{
764  try
765  {
766    m_pRawFile->seek(m_iDataHeaderOffset + m_oFileHeader.iFileOffset + iFirstToLoad * 22, SR_Start);
767    for(unsigned short int iToLoad = iFirstToLoad; iToLoad <= iEnsureLoaded; m_iNumFilesLoaded = iToLoad++)
768    {
769      _file_raw_t oRaw;
770      m_pRawFile->readOne(oRaw.iNameOffset);
771      m_pRawFile->readOne(oRaw.iDataOffset);
772      m_pRawFile->readOne(oRaw.iDataLengthCompressed);
773      m_pRawFile->readOne(oRaw.iDataLength);
774      m_pRawFile->readOne(oRaw.iModificationTime);
775      m_pRawFile->readOne(oRaw.iFlags16);
776
777      m_pFiles[iToLoad].iName = oRaw.iNameOffset; //RainString(m_sStringBlob + oRaw.iNameOffset);
778      m_pFiles[iToLoad].iDataOffset = oRaw.iDataOffset;
779      m_pFiles[iToLoad].iDataLength = oRaw.iDataLength;
780      m_pFiles[iToLoad].iDataLengthCompressed = oRaw.iDataLengthCompressed;
781      m_pFiles[iToLoad].iModificationTime = oRaw.iModificationTime;
782    }
783  }
784  CATCH_THROW_SIMPLE({}, L"Unable to load file details")
785}
786
787void SgaArchive::_loadFilesUpTo(unsigned short int iEnsureLoaded) throw(...)
788{
789  unsigned short int iFirstToLoad = m_iNumFilesLoaded + 1;
790  if(m_pFiles == 0)
791  {
792    iFirstToLoad = 0;
793    CHECK_ALLOCATION(m_pFiles = new _file_info_t[m_oFileHeader.iFileCount]);
794  }
795  if(iFirstToLoad <= iEnsureLoaded)
796  {
797    CHECK_RANGE_LTMAX(0, iEnsureLoaded, m_oFileHeader.iFileCount);
798    if(m_oFileHeader.iVersionMajor == 2)
799      _loadFilesUpTo_v2(iFirstToLoad, iEnsureLoaded);
800    else if(m_oFileHeader.iVersionMajor >= 4)
801      _loadFilesUpTo_v4(iFirstToLoad, iEnsureLoaded);
802    else
803      THROW_SIMPLE(L"Unsupported SGA version for file info");
804  }
805}
806
807void SgaArchive::_loadChildren(_directory_info_t* pInfo, bool bJustDirectories) throw(...)
808{
809  if(pInfo->iLastDirectory != 0)
810    _loadDirectoriesUpTo(pInfo->iLastDirectory - 1);
811  if(!bJustDirectories && pInfo->iLastFile != 0)
812    _loadFilesUpTo(pInfo->iLastFile - 1);
813}
814
815bool SgaArchive::_resolvePath(const RainString& sPath, _directory_info_t** ppDirectory, _file_info_t **ppFile, bool bThrow) throw(...)
816{
817  // Clear return values
818  *ppDirectory = 0;
819  *ppFile = 0;
820
821  // Load all the entry points
822  try { _loadEntryPointsUpTo(m_oFileHeader.iEntryPointCount - 1); }
823  CATCH_THROW_SIMPLE({ if(!bThrow){delete e; return false;} }, L"Unable to load entry point details");
824
825  // Find entry point
826  RainString sPart = sPath.beforeFirst('\\');
827  RainString sPathRemain = sPath.afterFirst('\\');
828  _directory_info_t* pDirectory = _resolveArray(m_pEntryPoints, 0, m_oFileHeader.iEntryPointCount, sPart, sPath, bThrow);
829  if(!pDirectory)
830    return false;
831  pDirectory = m_pDirectories + pDirectory->iFirstDirectory;
832  if(sPathRemain.isEmpty())
833  {
834    *ppDirectory = pDirectory;
835    return true;
836  }
837
838  // Find subsequent directories
839  sPart = sPathRemain.beforeFirst('\\');
840  sPathRemain = sPathRemain.afterFirst('\\');
841  while(!sPathRemain.isEmpty())
842  {
843    try { _loadChildren(pDirectory, true); }
844    CATCH_THROW_SIMPLE({ if(!bThrow){delete e; return false;} }, L"Unable to load directory details");
845
846    pDirectory = _resolveArray(m_pDirectories, pDirectory->iFirstDirectory, pDirectory->iLastDirectory, sPart, sPath, bThrow);
847    if(!pDirectory)
848      return false;
849    sPart = sPathRemain.beforeFirst('\\');
850    sPathRemain = sPathRemain.afterFirst('\\');
851  }
852
853  // Find last directory / file
854  try { _loadChildren(pDirectory, false); }
855  CATCH_THROW_SIMPLE({ if(!bThrow){delete e; return false;} }, L"Unable to load directory details");
856
857  *ppFile = _resolveArray(m_pFiles, pDirectory->iFirstFile, pDirectory->iLastFile, sPart, sPath, false);
858  if(*ppFile == 0)
859  {
860    *ppDirectory = _resolveArray(m_pDirectories, pDirectory->iFirstDirectory, pDirectory->iLastDirectory, sPart, sPath, bThrow);
861    if(*ppDirectory == 0)
862      return false;
863  }
864  return true;
865}