/Rainman2/archive.cpp

http://modstudio2.googlecode.com/ · C++ · 865 lines · 761 code · 71 blank · 33 comment · 154 complexity · 7c2d75164d615a3e93688034dba766c9 MD5 · raw file

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