/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** 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 *** *********************************************************************************************** * * * Project Name : LevelEdit * * * * $Archive:: /Commando/Code/Tools/LevelEdit/Export.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 6/26/02 2:13p $* * * * $Revision:: 34 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "stdafx.h" #include "leveledit.h" #include "leveleditdoc.h" #include "export.h" #include "editorini.h" #include "utils.h" #include "resource.h" #include "nodemgr.h" #include "vector3.h" #include "matrix3d.h" #include "ffactory.h" #include "filemgr.h" #include "_assetmgr.h" #include "editorassetmgr.h" #include "..\..\commando\lev_file.h" #include "chunkio.h" #include "sceneeditor.h" #include "filelocations.h" #include "inisections.h" #include "hlod.h" #include "w3derr.h" #include "saveload.h" #include "physstaticsavesystem.h" #include "physdynamicsavesystem.h" #include "combatsaveload.h" #include "terrainnode.h" #include "staticphys.h" #include "savegame.h" #include "regkeys.h" #include "combat.h" #include "assetdep.h" #include "presetslibform.h" #include "pathfind.h" #include "gameobjmanager.h" #include "stringsmgr.h" #include "assetpackagemgr.h" #include "presetmgr.h" ///////////////////////////////////////////////////////// // Local constants ///////////////////////////////////////////////////////// const TCHAR * const STATIC_EXTENSION = TEXT (".lsd"); const TCHAR * const DYNAMIC_EXTENSION = TEXT (".ldd"); const TCHAR * const MIX_EXTENSION = TEXT (".mix"); const TCHAR * const ALWAYS_ROOT_NAME = TEXT ("always"); const TCHAR * const ALWAYS_DATA_FILENAME = TEXT ("always.dat"); const TCHAR * const ALWAYS_DB_FILENAME = TEXT ("always.dbs"); ///////////////////////////////////////////////////////// // // Export_Level // ///////////////////////////////////////////////////////// void ExporterClass::Export_Level (LPCTSTR filename) { CWaitCursor wait_cursor; m_AssetList.Delete_All (); // // Prepare the level for exporting... // DynamicVectorClass node_list; Pre_Level_Export (node_list); // // Get the base destination path // Path = ::Strip_Filename_From_Path (filename); // // Strip the path and extension from the filename to give // us a base for use with other filenames // FilenameBase = ::Get_Filename_From_Path (filename); int extension_index = FilenameBase.ReverseFind ('.'); if (extension_index != -1) { FilenameBase = FilenameBase.Left (extension_index); } // // Setup the temporary directory // Make_Temp_Directory (); // // Export the level files // Export_Level_File (); Export_Level_Data (); Export_Dependency_File (); Export_Definition_Databases (false); // // Now build the mix file... // MixFileCreator.Generate_Mix_File (filename); // // Cleanup any temp files we needed // Delete_Temp_Directory (); // // Resetore the level after exporting // Post_Level_Export (node_list); // // Cache the directory path we just exported into // theApp.WriteProfileString (CONFIG_KEY, LAST_EXPORT_DIR_VALUE, Path); return ; } ///////////////////////////////////////////////////////// // // Make_Temp_Directory // ///////////////////////////////////////////////////////// void ExporterClass::Make_Temp_Directory (void) { // // Get the path of the temp directory // char temp_dir[MAX_PATH] = { 0 }; ::GetTempPath (sizeof (temp_dir), temp_dir); CString temp_path = ::Make_Path (temp_dir, FilenameBase); // // Try to find a unique temp directory to store our data // int index = 0; do { TempDirectory.Format ("%s%.2d.DIR", (const char *)temp_path, index++); } while (GetFileAttributes (TempDirectory) != 0xFFFFFFFF); // // Create the directory // ::CreateDirectory (TempDirectory, NULL); ::Set_Current_Directory (TempDirectory); ::SetCurrentDirectory (TempDirectory); return ; } ///////////////////////////////////////////////////////// // // Delete_Temp_Directory // ///////////////////////////////////////////////////////// void ExporterClass::Delete_Temp_Directory (void) { // // Change the current directory so we can remove the temporary one // ::Set_Current_Directory (Path); ::SetCurrentDirectory (Path); // // Remove the temporary directory // Clean_Directory (TempDirectory); ::RemoveDirectory (TempDirectory); return ; } ///////////////////////////////////////////////////////// // // Export_Dependency_File // ///////////////////////////////////////////////////////// void ExporterClass::Export_Dependency_File (void) { // // Make a file that contains a list of all the W3D files that // were part of this export // StringClass dep_filename = FilenameBase + ".dep"; StringClass dep_path = ::Make_Path (TempDirectory, dep_filename); AssetDependencyManager::Save_Level_Dependencies (dep_path, m_AssetList); // // Add this file to the mix // MixFileCreator.Add_File (dep_path, ::Get_Filename_From_Path (dep_path)); return ; } ///////////////////////////////////////////////////////// // // Export_Level_File // ///////////////////////////////////////////////////////// bool ExporterClass::Export_Level_File (void) { // // Create filenames for both the static and dynamic data // CString ldd_filename = FilenameBase + DYNAMIC_EXTENSION; CString lsd_filename = FilenameBase + STATIC_EXTENSION; // // Save the LSD and LDD files to a temporary directory // SaveGameManager::Set_Map_Filename (lsd_filename); SaveGameManager::Save_Level (); SaveGameManager::Save_Game (ldd_filename, NULL); // // Now add these files to the mix file // CString lsd_path = Make_Path (TempDirectory, lsd_filename); CString ldd_path = Make_Path (TempDirectory, ldd_filename); MixFileCreator.Add_File (lsd_path, lsd_filename); MixFileCreator.Add_File (ldd_path, ldd_filename); return true; } ///////////////////////////////////////////////////////// // // Export_Always_Files // ///////////////////////////////////////////////////////// void ExporterClass::Export_Always_Files (LPCTSTR path) { CWaitCursor wait_cursor; m_AssetList.Delete_All (); // // Does the path exist? // if (::GetFileAttributes (path) != 0xFFFFFFFF) { // // Get the base destination path // Path = path; FilenameBase = ALWAYS_ROOT_NAME; // // Setup the temporary directory // Make_Temp_Directory (); // // Copy all the files used by the framework to the directory // STRING_LIST &global_list = ::Get_File_Mgr ()->Get_Global_Include_File_List (); Process_Include_Files (global_list); // // Get the always dependency search list // STRING_LIST search_list; CString always_dep_search_ini = ::Get_File_Mgr ()->Make_Full_Path (ALWAYS_DEP_INI_PATH); ::Get_File_Mgr ()->Build_Include_List (always_dep_search_ini, search_list); // // Find all the files in the search list and add them to a list // STRING_LIST file_list; Build_File_List (search_list, file_list); // // Now add all the W3D files from the list to another list. *sigh* // DynamicVectorClass dep_list; for (int index = 0; index < file_list.Count (); index ++) { CString &filename = file_list[index]; if (::Is_W3D_Filename (filename)) { dep_list.Add ((LPCTSTR)::Get_Filename_From_Path (filename)); } } // // Make a file that contains a list of all these W3D files // AssetDependencyManager::Save_Always_Dependencies (TempDirectory, dep_list); CString dep_file_path = Make_Path (TempDirectory, "always.dep"); MixFileCreator.Add_File (dep_file_path, "always.dep"); // // Now build the mix file... // CString mix_filename = Make_Path (Path, ALWAYS_DATA_FILENAME); MixFileCreator.Generate_Mix_File (mix_filename); // // Cleanup any temp files we needed // Delete_Temp_Directory (); } else { // // Invalid file, let the user know // ::Message_Box (::AfxGetMainWnd ()->m_hWnd, IDS_CANT_EXPORT_MSG, IDS_CANT_EXPORT_TITLE, MB_ICONERROR | MB_OK); } return ; } ///////////////////////////////////////////////////////// // // Export_Level_Data // ///////////////////////////////////////////////////////// bool ExporterClass::Export_Level_Data (void) { bool retval = true; // // Loop through all the files used by this level and copy them // to the same directory that the level definition was saved in. // int count = ::Get_File_Mgr ()->Get_File_Count (); for (int file_index = 0; file_index < count; file_index ++) { // // Build the complete paths of both the original file location and the new file location // CString file_path = ::Get_File_Mgr ()->Get_File_Name (file_index); CString entry_name; // // Is this file's subdirectory important? // bool path_important = ::Get_File_Mgr ()->Is_File_Path_Important (file_index); if (path_important) { // // Pre-pend the subdirectory name to form the entry name // CString sub_dir = ::Get_Subdir_From_Full_Path (file_path); entry_name = Make_Path (sub_dir, ::Get_Filename_From_Path (file_path)); } else { entry_name = ::Get_Filename_From_Path (file_path); } // // Add this file to the mix file with the given name // MixFileCreator.Add_File (file_path, entry_name); // // Add this file to the level-asset list if its a W3D file // if (::Is_W3D_Filename (entry_name)) { m_AssetList.Add ((LPCTSTR)::Get_Filename_From_Path (entry_name)); } } // // Add the temp definition database // CString temp_db_filename = ::Get_File_Mgr ()->Make_Full_Path (TEMP_DB_PATH); CString temp_db_name = FilenameBase + ".ddb"; MixFileCreator.Add_File (temp_db_filename, temp_db_name); // // Copy all the files used by the framework to the directory // STRING_LIST &level_list = ::Get_File_Mgr ()->Get_Include_File_List (); Process_Include_Files (level_list); return retval; } ///////////////////////////////////////////////////////// // // Export_Database_Mix // ///////////////////////////////////////////////////////// void ExporterClass::Export_Database_Mix (LPCTSTR path) { CWaitCursor wait_cursor; m_AssetList.Delete_All (); Path = path; FilenameBase = "always.dbs"; // // Setup the temporary directory // Make_Temp_Directory (); // // Export the definition databases // Export_Definition_Databases (true); // // Cleanup any temp files we needed // Delete_Temp_Directory (); return ; } ///////////////////////////////////////////////////////// // // Export_Definition_Databases // ///////////////////////////////////////////////////////// bool ExporterClass::Export_Definition_Databases (bool use_temp_strings_library) { EditorMixFileCreator always_db_mix_creator; // // Save the preset library to the temp directory // PresetsFormClass *presets_form = ::Get_Presets_Form (); if (presets_form != NULL) { CString path = ::Make_Path (TempDirectory, "objects.ddb"); presets_form->Save_Presets (path, 0, false, false); always_db_mix_creator.Add_File (path, ::Get_Filename_From_Path (path)); } // // Add the strings table // if (use_temp_strings_library == false) { CString filename = ::Get_File_Mgr ()->Make_Full_Path (STRINGS_DB_PATH); always_db_mix_creator.Add_File (filename, ::Get_Filename_From_Path (filename)); } else { // // Save the strings table to the temporary directory, then // add it to the mix file // CString path = ::Make_Path (TempDirectory, STRINGS_DB_NAME); StringsMgrClass::Save_Translation_Database (path); always_db_mix_creator.Add_File (path, STRINGS_DB_NAME); } // // Add the global conversation database // CString filename = ::Get_File_Mgr ()->Make_Full_Path (CONV_DB_PATH); always_db_mix_creator.Add_File (filename, ::Get_Filename_From_Path (filename)); // // Now generate the definition databases mix file // CString mix_filename = ::Make_Path (Path, ALWAYS_DB_FILENAME); always_db_mix_creator.Generate_Mix_File (mix_filename); return true; } ///////////////////////////////////////////////////////// // // Build_File_List // ///////////////////////////////////////////////////////// bool ExporterClass::Build_File_List (const STRING_LIST &search_list, STRING_LIST &file_list) { bool retval = true; // // Loop through all the files in our include list // for (int index = 0; index < search_list.Count (); index ++) { CString wildcard = search_list[index]; CString search = ::Get_File_Mgr ()->Make_Full_Path (wildcard); // // Make sure we have all the assets for this always group // if (DontGetVersionsFromVSS == false) { ::Get_File_Mgr ()->Get_Subproject (::Strip_Filename_From_Path (wildcard)); } // // Add the all the files that match the search mask to this list // Find_Files (search, file_list); } return retval; } ///////////////////////////////////////////////////////// // // Find_Files // ///////////////////////////////////////////////////////// void ExporterClass::Find_Files (const char *search_mask, STRING_LIST &file_list) { // // Get the directory where we'll be copying the files from // CString directory = ::Strip_Filename_From_Path (search_mask); CString mask = ::Get_Filename_From_Path (search_mask); // // Find all files that match this wildcard // WIN32_FIND_DATA find_info = { 0 }; BOOL keep_going = TRUE; for (HANDLE hfile_find = ::FindFirstFile (search_mask, &find_info); (hfile_find != INVALID_HANDLE_VALUE) && keep_going; keep_going = ::FindNextFile (hfile_find, &find_info)) { // // Don't do this if its a directory // if (!(find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { // // Add this file to the list // CString full_path = Make_Path (directory, ::Get_Filename_From_Path (find_info.cFileName)); file_list.Add (full_path); } } if (hfile_find != INVALID_HANDLE_VALUE) { ::FindClose (hfile_find); } CString full_search_mask = Make_Path (directory, "*.*"); // // Now recurse into any sub-directories // keep_going = TRUE; for (hfile_find = ::FindFirstFile (full_search_mask, &find_info); (hfile_find != INVALID_HANDLE_VALUE) && keep_going; keep_going = ::FindNextFile (hfile_find, &find_info)) { // // Don't do this if its a directory // if ((find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (find_info.cFileName[0] != '.')) { // // Build a search mask for this sub-directory // CString full_path = Make_Path (directory, ::Get_Filename_From_Path (find_info.cFileName)); full_path = Make_Path (full_path, mask); // // Recurse into this sub-directory // Find_Files (full_path, file_list); } } if (hfile_find != INVALID_HANDLE_VALUE) { ::FindClose (hfile_find); } return ; } ///////////////////////////////////////////////////////// // // Process_Include_Files // ///////////////////////////////////////////////////////// void ExporterClass::Process_Include_Files (const STRING_LIST &search_list) { STRING_LIST file_list; Build_File_List (search_list, file_list); Export_Include_Files (file_list); return ; } ///////////////////////////////////////////////////////// // // Export_Include_Files // ///////////////////////////////////////////////////////// bool ExporterClass::Export_Include_Files (const STRING_LIST &file_list) { // // Add all the files from the list // for (int index = 0; index < file_list.Count (); index ++) { const CString &full_path = file_list[index]; // // Add this file to the mix file // MixFileCreator.Add_File (full_path, ::Get_Filename_From_Path (full_path)); // // Add this file to the level-asset list if its a W3D file // if (::Is_W3D_Filename (full_path)) { m_AssetList.Add ((LPCTSTR)::Get_Filename_From_Path (full_path)); } } return true; } //////////////////////////////////////////////////////////////////////////// // // Delete_File // //////////////////////////////////////////////////////////////////////////// bool ExporterClass::Delete_File (LPCTSTR filename) { bool retval = false; ASSERT (filename != NULL); if (filename != NULL) { // // Strip the readonly bit off if necessary // DWORD attributes = ::GetFileAttributes (filename); if ((attributes != 0xFFFFFFFF) && ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY)) { ::SetFileAttributes (filename, attributes & (~FILE_ATTRIBUTE_READONLY)); } // // Perform the delete operation! // if ((attributes != 0xFFFFFFFF) && ((attributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) { retval = (::RemoveDirectory (filename) == TRUE); } else { retval = (::DeleteFile (filename) == TRUE); } } return retval; } ////////////////////////////////////////////////////////////////////////////////// // // Clean_Directory // ////////////////////////////////////////////////////////////////////////////////// bool ExporterClass::Clean_Directory (LPCTSTR local_dir) { bool retval = true; // // Build a search mask from the directory // CString search_mask = CString (local_dir) + "\\*.*"; // // Loop through all the files in this directory and add them // to our list // DynamicVectorClass file_list; BOOL keep_going = TRUE; WIN32_FIND_DATA find_info = { 0 }; for (HANDLE hfind = ::FindFirstFile (search_mask, &find_info); (hfind != INVALID_HANDLE_VALUE) && keep_going; keep_going = ::FindNextFile (hfind, &find_info)) { // // If this file isn't a directory, add it to the list // if (!(find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { CString filename = find_info.cFileName; file_list.Add (filename); } else if (find_info.cFileName[0] != '.') { // // Recurse into this subdirectory // CString full_path = local_dir; ::Delimit_Path (full_path); full_path += find_info.cFileName; Clean_Directory (full_path); // // Add this directory to the list so it will get // deleted with the files... // CString filename = find_info.cFileName; file_list.Add (filename); } } // // Close the search handle // if (hfind != NULL) { ::FindClose (hfind); } // // Now loop through all the files and delete them // for (int index = 0; index < file_list.Count (); index ++) { CString &filename = file_list[index]; CString full_path = ::Make_Path (local_dir, filename); Delete_File (full_path); } return retval; } ////////////////////////////////////////////////////////////////////////////////// // // Pre_Level_Export // ////////////////////////////////////////////////////////////////////////////////// void ExporterClass::Pre_Level_Export (DynamicVectorClass &node_list) { // // Force static anim phys to be displayed // ::Get_Scene_Editor ()->Display_Static_Anim_Phys (true); ::Get_Scene_Editor ()->Display_Editor_Objects (true); // // Make sure the scenes are repartitioned // (gth) can't re-partition the object culling systems or // we'll lose hierarchical vis // ::Get_Scene_Editor ()->Re_Partition_Static_Lights (); ::Get_Scene_Editor ()->Re_Partition_Audio_System (); ::Get_Scene_Editor ()->Set_Selection (NULL); ::Get_Scene_Editor ()->Display_Vis_Points (false); // // Make sure the buildings are in a good state // GameObjManager::Update_Building_Collection_Spheres (); GameObjManager::Init_Buildings (); // // Let the nodes know we are about to start exporting them. // This way they can remove any 'editor-only' artifacts from // the system. // NodeMgrClass::Build_Full_Node_List (node_list); for (int index = 0; index < node_list.Count (); index ++) { node_list[index]->Pre_Export (); } // // Force the pathfind system to use any available waypath data // in its evaluation // PathfindClass::Get_Instance ()->Generate_Waypath_Sectors_And_Portals (); return ; } ////////////////////////////////////////////////////////////////////////////////// // // Post_Level_Export // ////////////////////////////////////////////////////////////////////////////////// void ExporterClass::Post_Level_Export (DynamicVectorClass &node_list) { // // Let the nodes know we're done exporting // for (int index = 0; index < node_list.Count (); index ++) { NodeClass *node = node_list[index]; node->Post_Export (); } return ; } ///////////////////////////////////////////////////////// // // Export_Package // ///////////////////////////////////////////////////////// void ExporterClass::Export_Package (LPCTSTR full_path) { CWaitCursor wait_cursor; // // Save the preset changes to disk as necessary // if (PresetMgrClass::Are_Presets_Dirty ()) { PresetMgrClass::Check_In_Presets (); } // // Get the base destination path // Path = ::Strip_Filename_From_Path (full_path); // // Strip the path and extension from the filename to give // us a base for use with other filenames // FilenameBase = ::Get_Filename_From_Path (full_path); int extension_index = FilenameBase.ReverseFind ('.'); if (extension_index != -1) { FilenameBase = FilenameBase.Left (extension_index); } // // Add all the files from our asset tree to this package // Add_Files_To_Mod_Package (AssetPackageMgrClass::Get_Current_Package_Path ()); // // Create the mix file... // MixFileCreator.Generate_Mix_File (full_path); // // Cache the directory path we just exported into // theApp.WriteProfileString (CONFIG_KEY, LAST_EXPORT_DIR_VALUE, Path); return ; } ///////////////////////////////////////////////////////// // // Add_Files_To_Mod_Package // ///////////////////////////////////////////////////////// void ExporterClass::Add_Files_To_Mod_Package (LPCTSTR full_path) { // // Build a search path for all the files in this directory // CString search_path = ::Make_Path (full_path, "*.*"); // // Search for all the files in this directory // WIN32_FIND_DATA find_info = { 0 }; BOOL keep_going = TRUE; HANDLE file_find = NULL; for (file_find = ::FindFirstFile (search_path, &find_info); (file_find != INVALID_HANDLE_VALUE) && keep_going; keep_going = ::FindNextFile (file_find, &find_info)) { if (find_info.cFileName[0] != '.') { if ((find_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { // // Recurse into this sub-directory // CString subdir_path = ::Make_Path (full_path, find_info.cFileName); Add_Files_To_Mod_Package (subdir_path); } else { // // Skip all lvl files // if (::strstr (find_info.cFileName, ".lvl") == NULL) { // // Simply add this file to the mix // CString file_path = ::Make_Path (full_path, find_info.cFileName); MixFileCreator.Add_File (file_path, find_info.cFileName); } } } } // // Close the search handle // if (file_find != INVALID_HANDLE_VALUE) { ::FindClose (file_find); } return ; } ///////////////////////////////////////////////////////// // // Export_Level_Only // ///////////////////////////////////////////////////////// void ExporterClass::Export_Level_Only (LPCTSTR path) { CWaitCursor wait_cursor; m_AssetList.Delete_All (); // // Prepare the level for exporting... // DynamicVectorClass node_list; Pre_Level_Export (node_list); // // Get the base destination path // Path = ::Strip_Filename_From_Path (path); TempDirectory = Path; ::Set_Current_Directory (TempDirectory); ::SetCurrentDirectory (TempDirectory); // // Strip the path and extension from the filename to give // us a base for use with other filenames // FilenameBase = ::Get_Filename_From_Path (path); int extension_index = FilenameBase.ReverseFind ('.'); if (extension_index != -1) { FilenameBase = FilenameBase.Left (extension_index); } // // Export the level in game format // Export_Level_File (); // // Add the temp definition database // CString temp_db_source = ::Get_File_Mgr ()->Make_Full_Path (TEMP_DB_PATH); CString temp_db_dest = ::Make_Path(Path,FilenameBase + ".ddb"); ::CopyFile(temp_db_source,temp_db_dest,false); // // Resetore the level after exporting // Post_Level_Export (node_list); // // Cache the directory path we just exported into // theApp.WriteProfileString (CONFIG_KEY, LAST_EXPORT_DIR_VALUE, Path); return ; }