mixfiledatabase.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : leveledit *
  23. * *
  24. * $Archive:: /Commando/Code/Tools/LevelEdit/mixfiledatabase.cpp $*
  25. * *
  26. * Author:: Patrick Smith *
  27. * *
  28. * $Modtime:: 6/19/02 3:58p $*
  29. * *
  30. * $Revision:: 5 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #include "stdafx.h"
  36. #include "mixfiledatabase.h"
  37. #include "registry.h"
  38. #include "utils.h"
  39. #include "mixfile.h"
  40. #include "rawfile.h"
  41. #include "shlwapi.h"
  42. //////////////////////////////////////////////////////////////////////
  43. // Static member initialization
  44. //////////////////////////////////////////////////////////////////////
  45. MixFileDatabaseClass *MixFileDatabaseClass::_TheInstance = NULL;
  46. //////////////////////////////////////////////////////////////////////
  47. //
  48. // MixFileDatabaseClass
  49. //
  50. //////////////////////////////////////////////////////////////////////
  51. MixFileDatabaseClass::MixFileDatabaseClass (void)
  52. {
  53. //
  54. // Open Renegade's registry
  55. //
  56. const char * const RENEGADE_REG_KEY = "Software\\Westwood\\Renegade";
  57. RegistryClass registry (RENEGADE_REG_KEY);
  58. if (registry.Is_Valid ()) {
  59. //
  60. // Read the installation path from the registry
  61. //
  62. StringClass install_path;
  63. const char * const RENEGADE_INSTALL_VALUE = "InstallPath";
  64. registry.Get_String (RENEGADE_INSTALL_VALUE, install_path);
  65. if (install_path.Get_Length () > 0) {
  66. //
  67. // The mix files are contained in the data sub-directory
  68. //
  69. install_path = ::Strip_Filename_From_Path (install_path);
  70. MixFilePath = ::Make_Path (install_path, "DATA");
  71. }
  72. }
  73. _TheInstance = this;
  74. return ;
  75. }
  76. //////////////////////////////////////////////////////////////////////
  77. //
  78. // ~MixFileDatabaseClass
  79. //
  80. //////////////////////////////////////////////////////////////////////
  81. MixFileDatabaseClass::~MixFileDatabaseClass (void)
  82. {
  83. _TheInstance = NULL;
  84. return ;
  85. }
  86. //////////////////////////////////////////////////////////////////////
  87. //
  88. // Open_Database
  89. //
  90. //////////////////////////////////////////////////////////////////////
  91. bool
  92. MixFileDatabaseClass::Open_Database
  93. (
  94. LPCTSTR ini_filename,
  95. LPCTSTR username,
  96. LPCTSTR password
  97. )
  98. {
  99. //
  100. // Add the always mix files to the file factory list
  101. //
  102. CString always_dbs_path = ::Make_Path (MixFilePath, "Always.dbs");
  103. CString always_dat_path = ::Make_Path (MixFilePath, "Always.dat");
  104. MainFileFactory.Add_FileFactory (new MixFileFactoryClass (always_dbs_path, &RenegadeDataFileFactory), "always.dat");
  105. MainFileFactory.Add_FileFactory (new MixFileFactoryClass (always_dat_path, &RenegadeDataFileFactory), "always.dbs");
  106. //
  107. // Build a search path for mix files in the data directory
  108. //
  109. CString search_path = ::Make_Path (MixFilePath, "*.mix");
  110. //
  111. // Search for all mix files in the installation directory
  112. //
  113. WIN32_FIND_DATA find_info = { 0 };
  114. BOOL keep_going = TRUE;
  115. HANDLE file_find = NULL;
  116. for (file_find = ::FindFirstFile (search_path, &find_info);
  117. (file_find != INVALID_HANDLE_VALUE) && keep_going;
  118. keep_going = ::FindNextFile (file_find, &find_info))
  119. {
  120. //
  121. // Add this mix file to our mix file factory list
  122. //
  123. CString full_path = ::Make_Path (MixFilePath, find_info.cFileName);
  124. MainFileFactory.Add_FileFactory (new MixFileFactoryClass (full_path, &RenegadeDataFileFactory), find_info.cFileName);
  125. }
  126. //
  127. // Close the search handle
  128. //
  129. if (file_find != INVALID_HANDLE_VALUE) {
  130. ::FindClose (file_find);
  131. }
  132. return true;
  133. }
  134. //////////////////////////////////////////////////////////////////////
  135. //
  136. // Check_Out
  137. //
  138. //////////////////////////////////////////////////////////////////////
  139. bool
  140. MixFileDatabaseClass::Check_Out (LPCTSTR local_filename, bool get_locally)
  141. {
  142. bool retval = true;
  143. //
  144. // Simply do a get as necessary
  145. //
  146. if (get_locally) {
  147. retval = Get (local_filename);
  148. }
  149. return retval;
  150. }
  151. //////////////////////////////////////////////////////////////////////
  152. //
  153. // Get
  154. //
  155. //////////////////////////////////////////////////////////////////////
  156. bool
  157. MixFileDatabaseClass::Get (LPCTSTR local_filename)
  158. {
  159. StringClass filename;
  160. Get_Filename (local_filename, filename);
  161. StringClass full_local_path = local_filename;
  162. //
  163. // Check to see if we need to swap texture extensions
  164. //
  165. if (Internal_Does_File_Exist (filename) == false && Is_Texture (filename)) {
  166. Swap_Texture_Extension (filename);
  167. StringClass local_path = (const char *)::Strip_Filename_From_Path (local_filename);
  168. full_local_path = ::Make_Path (local_path, filename);
  169. }
  170. //
  171. // Get the file
  172. //
  173. return Internal_Get (filename, full_local_path);
  174. }
  175. //////////////////////////////////////////////////////////////////////
  176. //
  177. // Internal_Get
  178. //
  179. //////////////////////////////////////////////////////////////////////
  180. bool
  181. MixFileDatabaseClass::Internal_Get (LPCTSTR filename, LPCSTR local_path)
  182. {
  183. bool retval = false;
  184. //
  185. // Get the file from one of the mix files
  186. //
  187. FileClass *file = MainFileFactory.Get_File (filename);
  188. if (file != NULL) {
  189. //
  190. // Copy the file if it exists
  191. //
  192. if (file->Is_Available () && file->Open ()) {
  193. retval = Copy_File (file, local_path);
  194. file->Close ();
  195. }
  196. MainFileFactory.Return_File (file);
  197. }
  198. return retval;
  199. }
  200. //////////////////////////////////////////////////////////////////////
  201. //
  202. // Copy_File
  203. //
  204. //////////////////////////////////////////////////////////////////////
  205. bool
  206. MixFileDatabaseClass::Copy_File (FileClass *src_file, LPCTSTR local_filename)
  207. {
  208. if ( src_file == NULL || local_filename == NULL ||
  209. ::GetFileAttributes (local_filename) != 0xFFFFFFFF)
  210. {
  211. return false;
  212. }
  213. bool retval = false;
  214. //
  215. // Ensure the directory structure exists before we attempt to create the file
  216. //
  217. StringClass path = (const char *)::Strip_Filename_From_Path (local_filename);
  218. Create_Directory_Structure (path);
  219. //
  220. // Create the destination file
  221. //
  222. RawFileClass dest_file;
  223. dest_file.Set_Name (local_filename);
  224. if (dest_file.Open (RawFileClass::WRITE)) {
  225. retval = true;
  226. //
  227. // Copy the data from the source file to the destination file
  228. //
  229. int file_size = src_file->Size ();
  230. uint8 buffer[4096];
  231. while (file_size > 0) {
  232. //
  233. // Read the data from the source file
  234. //
  235. int bytes = min (file_size, (int)sizeof (buffer));
  236. int copied_size = src_file->Read (buffer, bytes);
  237. file_size -= copied_size;
  238. if (copied_size <= 0) {
  239. break;
  240. }
  241. //
  242. // Copy the data to the dest file (kick out of the loop on error)
  243. //
  244. if (dest_file.Write (buffer, copied_size) != copied_size) {
  245. break;
  246. }
  247. }
  248. //
  249. // Close the destination file
  250. //
  251. dest_file.Close ();
  252. }
  253. return retval;
  254. }
  255. //////////////////////////////////////////////////////////////////////
  256. //
  257. // Does_File_Exist
  258. //
  259. //////////////////////////////////////////////////////////////////////
  260. bool
  261. MixFileDatabaseClass::Does_File_Exist (LPCTSTR local_filename)
  262. {
  263. bool retval = false;
  264. //
  265. // Test to see if the file exists
  266. //
  267. StringClass filename;
  268. Get_Filename (local_filename, filename);
  269. retval = Internal_Does_File_Exist (filename);
  270. //
  271. // If the file did not exists, check to see if its a texture...
  272. //
  273. if (retval == false && Is_Texture (filename)) {
  274. //
  275. // Check to see if either the compressed or uncompressed
  276. // texture exists
  277. //
  278. Swap_Texture_Extension (filename);
  279. retval = Internal_Does_File_Exist (filename);
  280. }
  281. return retval;
  282. }
  283. //////////////////////////////////////////////////////////////////////
  284. //
  285. // Internal_Does_File_Exist
  286. //
  287. //////////////////////////////////////////////////////////////////////
  288. bool
  289. MixFileDatabaseClass::Internal_Does_File_Exist (LPCSTR filename)
  290. {
  291. bool retval = false;
  292. //
  293. // Get the file from one of the mix files
  294. //
  295. FileClass *file = MainFileFactory.Get_File (filename);
  296. if (file != NULL) {
  297. //
  298. // Does the file exist?
  299. //
  300. if (file->Is_Available ()) {
  301. retval = true;
  302. }
  303. MainFileFactory.Return_File (file);
  304. }
  305. return retval;
  306. }
  307. //////////////////////////////////////////////////////////////////////////////////
  308. //
  309. // Create_Directory_Structure
  310. //
  311. //////////////////////////////////////////////////////////////////////////////////
  312. void
  313. MixFileDatabaseClass::Create_Directory_Structure (LPCTSTR path)
  314. {
  315. if (path != NULL && path[0] != 0 && ::GetFileAttributes (path) == 0xFFFFFFFF) {
  316. Create_Directory_Structure (::Strip_Filename_From_Path (path));
  317. ::CreateDirectory (path, NULL);
  318. }
  319. return ;
  320. }
  321. //////////////////////////////////////////////////////////////////////////////////
  322. //
  323. // Is_Texture
  324. //
  325. //////////////////////////////////////////////////////////////////////////////////
  326. bool
  327. MixFileDatabaseClass::Is_Texture (LPCSTR filename)
  328. {
  329. StringClass temp_str (filename, true);
  330. ::strlwr (temp_str.Peek_Buffer ());
  331. //
  332. // Check to see if this is either a compressed or uncompressed texture
  333. //
  334. bool retval = false;
  335. if (::strstr (temp_str.Peek_Buffer (), ".tga") || ::strstr (temp_str.Peek_Buffer (), ".dds")) {
  336. retval = true;
  337. }
  338. return retval;
  339. }
  340. //////////////////////////////////////////////////////////////////////////////////
  341. //
  342. // Swap_Texture_Extension
  343. //
  344. //////////////////////////////////////////////////////////////////////////////////
  345. void
  346. MixFileDatabaseClass::Swap_Texture_Extension (StringClass &filename)
  347. {
  348. ::strlwr (filename.Peek_Buffer ());
  349. //
  350. // Is this a tga file (uncompressed), or a dds file (compressed)?
  351. //
  352. char *tga_extension = ::strstr (filename.Peek_Buffer (), ".tga");
  353. if (tga_extension != NULL) {
  354. //
  355. // Simply copy the new extension into the string
  356. //
  357. ::strcpy (tga_extension, ".dds");
  358. } else {
  359. char *dds_extension = ::strstr (filename.Peek_Buffer (), ".dds");
  360. if (dds_extension != NULL) {
  361. //
  362. // Simply copy the new extension into the string
  363. //
  364. ::strcpy (dds_extension, ".tga");
  365. }
  366. }
  367. return ;
  368. }
  369. //////////////////////////////////////////////////////////////////////////////////
  370. //
  371. // Find_Files
  372. //
  373. //////////////////////////////////////////////////////////////////////////////////
  374. void
  375. MixFileDatabaseClass::Find_Files (DynamicVectorClass<StringClass> &file_list, LPCTSTR search_mask)
  376. {
  377. //
  378. // Loop over all the factories
  379. //
  380. int factory_count = MainFileFactory.Get_Factory_Count ();
  381. for (int index = 0; index < factory_count; index ++) {
  382. //
  383. // Get a pointer to the current factory
  384. //
  385. FileFactoryClass *factory = MainFileFactory.Get_Factory (index);
  386. if (factory != NULL) {
  387. //
  388. // We assume that this is a mix file factory (since all the factories
  389. // we're adding are mix file factories). Note: This can easily
  390. // break.
  391. //
  392. MixFileFactoryClass *mix_factory = static_cast<MixFileFactoryClass *> (factory);
  393. //
  394. // Get a list of all the files in this mix file
  395. //
  396. DynamicVectorClass<StringClass> files_in_mix;
  397. files_in_mix.Set_Growth_Step (1000);
  398. mix_factory->Build_Filename_List (files_in_mix);
  399. //
  400. // Now, add any files that match the supplied wildcard to our master list
  401. //
  402. for (int file_index = 0; file_index < files_in_mix.Count (); file_index ++) {
  403. const char *filename = files_in_mix[file_index];
  404. //
  405. // Add this file to our list if it matches the mask
  406. //
  407. if (::PathMatchSpec (filename, search_mask)) {
  408. file_list.Add (filename);
  409. }
  410. }
  411. }
  412. }
  413. return ;
  414. }
  415. //////////////////////////////////////////////////////////////////////////////////
  416. //
  417. // Get_All
  418. //
  419. //////////////////////////////////////////////////////////////////////////////////
  420. bool
  421. MixFileDatabaseClass::Get_All (LPCTSTR dest_path, LPCTSTR search_mask)
  422. {
  423. DynamicVectorClass<StringClass> file_list;
  424. //
  425. // Loop over all the factories
  426. //
  427. int factory_count = MainFileFactory.Get_Factory_Count ();
  428. for (int index = 0; index < factory_count; index ++) {
  429. //
  430. // Get a pointer to the current factory
  431. //
  432. FileFactoryClass *factory = MainFileFactory.Get_Factory (index);
  433. if (factory != NULL) {
  434. //
  435. // We assume that this is a mix file factory (since all the factories
  436. // we're adding are mix file factories). Note: This can easily
  437. // break.
  438. //
  439. MixFileFactoryClass *mix_factory = static_cast<MixFileFactoryClass *> (factory);
  440. //
  441. // Get a list of all the files in this mix file
  442. //
  443. DynamicVectorClass<StringClass> files_in_mix;
  444. files_in_mix.Set_Growth_Step (1000);
  445. mix_factory->Build_Filename_List (files_in_mix);
  446. //
  447. // Now, add any files that match the supplied wildcard to our master list
  448. //
  449. for (int file_index = 0; file_index < files_in_mix.Count (); file_index ++) {
  450. const char *filename = files_in_mix[file_index];
  451. //
  452. // Add this file to our list if it matches the mask
  453. //
  454. if (::PathMatchSpec (filename, search_mask)) {
  455. file_list.Add (filename);
  456. }
  457. }
  458. }
  459. }
  460. //
  461. // Now, get all the matching files to the specified path
  462. //
  463. for (index = 0; index < file_list.Count (); index ++) {
  464. StringClass full_path = (const char *)::Make_Path (dest_path, file_list[index]);
  465. Get (full_path);
  466. }
  467. return true;
  468. }
  469. ///////////////////////////////////////////////////////////////////
  470. //
  471. // Get_File
  472. //
  473. ///////////////////////////////////////////////////////////////////
  474. FileClass *
  475. MixFileDatabaseClass::Get_File (LPCTSTR local_filename)
  476. {
  477. FileClass *retval = NULL;
  478. if (::GetFileAttributes (local_filename) != 0xFFFFFFFF) {
  479. //
  480. // Do this to get around an oddity of the class when you pass in
  481. // the filename to the constructor
  482. //
  483. retval = new RawFileClass;
  484. retval->Set_Name (local_filename);
  485. } else {
  486. //
  487. // Get a pointer to the file in the mix file
  488. //
  489. StringClass filename;
  490. Get_Filename (local_filename, filename);
  491. retval = MainFileFactory.Get_File (filename);
  492. }
  493. return retval;
  494. }
  495. ///////////////////////////////////////////////////////////////////
  496. //
  497. // Get_Filename
  498. //
  499. ///////////////////////////////////////////////////////////////////
  500. void
  501. MixFileDatabaseClass::Get_Filename (LPCTSTR path, StringClass &filename)
  502. {
  503. filename = path;
  504. ::strlwr (filename.Peek_Buffer ());
  505. //
  506. // Check to see if the sub-directory is important, if it is, then
  507. // return the sub-directory as well
  508. //
  509. char *subdir_token = ::strstr (filename, "+\\");
  510. if (subdir_token != NULL) {
  511. //
  512. // Try to find the preceeding directory delimiter
  513. //
  514. int index = (subdir_token - filename.Peek_Buffer ());
  515. for (; index >= 0; index --) {
  516. if (filename[index] == '\\') {
  517. StringClass temp_str = filename;
  518. filename = (temp_str.Peek_Buffer () + index + 1);
  519. break;
  520. }
  521. }
  522. } else {
  523. //
  524. // Simply strip the filename from the path
  525. //
  526. filename = (const char *)::Get_Filename_From_Path (filename);
  527. }
  528. return ;
  529. }