This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Tools/LevelEdit/mixfiledatabase.cpp

634 lines
16 KiB
C++
Raw Permalink Normal View History

/*
** 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 <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** 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/mixfiledatabase.cpp $*
* *
* Author:: Patrick Smith *
* *
* $Modtime:: 6/19/02 3:58p $*
* *
* $Revision:: 5 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "stdafx.h"
#include "mixfiledatabase.h"
#include "registry.h"
#include "utils.h"
#include "mixfile.h"
#include "rawfile.h"
#include "shlwapi.h"
//////////////////////////////////////////////////////////////////////
// Static member initialization
//////////////////////////////////////////////////////////////////////
MixFileDatabaseClass *MixFileDatabaseClass::_TheInstance = NULL;
//////////////////////////////////////////////////////////////////////
//
// MixFileDatabaseClass
//
//////////////////////////////////////////////////////////////////////
MixFileDatabaseClass::MixFileDatabaseClass (void)
{
//
// Open Renegade's registry
//
const char * const RENEGADE_REG_KEY = "Software\\Westwood\\Renegade";
RegistryClass registry (RENEGADE_REG_KEY);
if (registry.Is_Valid ()) {
//
// Read the installation path from the registry
//
StringClass install_path;
const char * const RENEGADE_INSTALL_VALUE = "InstallPath";
registry.Get_String (RENEGADE_INSTALL_VALUE, install_path);
if (install_path.Get_Length () > 0) {
//
// The mix files are contained in the data sub-directory
//
install_path = ::Strip_Filename_From_Path (install_path);
MixFilePath = ::Make_Path (install_path, "DATA");
}
}
_TheInstance = this;
return ;
}
//////////////////////////////////////////////////////////////////////
//
// ~MixFileDatabaseClass
//
//////////////////////////////////////////////////////////////////////
MixFileDatabaseClass::~MixFileDatabaseClass (void)
{
_TheInstance = NULL;
return ;
}
//////////////////////////////////////////////////////////////////////
//
// Open_Database
//
//////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Open_Database
(
LPCTSTR ini_filename,
LPCTSTR username,
LPCTSTR password
)
{
//
// Add the always mix files to the file factory list
//
CString always_dbs_path = ::Make_Path (MixFilePath, "Always.dbs");
CString always_dat_path = ::Make_Path (MixFilePath, "Always.dat");
MainFileFactory.Add_FileFactory (new MixFileFactoryClass (always_dbs_path, &RenegadeDataFileFactory), "always.dat");
MainFileFactory.Add_FileFactory (new MixFileFactoryClass (always_dat_path, &RenegadeDataFileFactory), "always.dbs");
//
// Build a search path for mix files in the data directory
//
CString search_path = ::Make_Path (MixFilePath, "*.mix");
//
// Search for all mix files in the installation 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))
{
//
// Add this mix file to our mix file factory list
//
CString full_path = ::Make_Path (MixFilePath, find_info.cFileName);
MainFileFactory.Add_FileFactory (new MixFileFactoryClass (full_path, &RenegadeDataFileFactory), find_info.cFileName);
}
//
// Close the search handle
//
if (file_find != INVALID_HANDLE_VALUE) {
::FindClose (file_find);
}
return true;
}
//////////////////////////////////////////////////////////////////////
//
// Check_Out
//
//////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Check_Out (LPCTSTR local_filename, bool get_locally)
{
bool retval = true;
//
// Simply do a get as necessary
//
if (get_locally) {
retval = Get (local_filename);
}
return retval;
}
//////////////////////////////////////////////////////////////////////
//
// Get
//
//////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Get (LPCTSTR local_filename)
{
StringClass filename;
Get_Filename (local_filename, filename);
StringClass full_local_path = local_filename;
//
// Check to see if we need to swap texture extensions
//
if (Internal_Does_File_Exist (filename) == false && Is_Texture (filename)) {
Swap_Texture_Extension (filename);
StringClass local_path = (const char *)::Strip_Filename_From_Path (local_filename);
full_local_path = ::Make_Path (local_path, filename);
}
//
// Get the file
//
return Internal_Get (filename, full_local_path);
}
//////////////////////////////////////////////////////////////////////
//
// Internal_Get
//
//////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Internal_Get (LPCTSTR filename, LPCSTR local_path)
{
bool retval = false;
//
// Get the file from one of the mix files
//
FileClass *file = MainFileFactory.Get_File (filename);
if (file != NULL) {
//
// Copy the file if it exists
//
if (file->Is_Available () && file->Open ()) {
retval = Copy_File (file, local_path);
file->Close ();
}
MainFileFactory.Return_File (file);
}
return retval;
}
//////////////////////////////////////////////////////////////////////
//
// Copy_File
//
//////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Copy_File (FileClass *src_file, LPCTSTR local_filename)
{
if ( src_file == NULL || local_filename == NULL ||
::GetFileAttributes (local_filename) != 0xFFFFFFFF)
{
return false;
}
bool retval = false;
//
// Ensure the directory structure exists before we attempt to create the file
//
StringClass path = (const char *)::Strip_Filename_From_Path (local_filename);
Create_Directory_Structure (path);
//
// Create the destination file
//
RawFileClass dest_file;
dest_file.Set_Name (local_filename);
if (dest_file.Open (RawFileClass::WRITE)) {
retval = true;
//
// Copy the data from the source file to the destination file
//
int file_size = src_file->Size ();
uint8 buffer[4096];
while (file_size > 0) {
//
// Read the data from the source file
//
int bytes = min (file_size, (int)sizeof (buffer));
int copied_size = src_file->Read (buffer, bytes);
file_size -= copied_size;
if (copied_size <= 0) {
break;
}
//
// Copy the data to the dest file (kick out of the loop on error)
//
if (dest_file.Write (buffer, copied_size) != copied_size) {
break;
}
}
//
// Close the destination file
//
dest_file.Close ();
}
return retval;
}
//////////////////////////////////////////////////////////////////////
//
// Does_File_Exist
//
//////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Does_File_Exist (LPCTSTR local_filename)
{
bool retval = false;
//
// Test to see if the file exists
//
StringClass filename;
Get_Filename (local_filename, filename);
retval = Internal_Does_File_Exist (filename);
//
// If the file did not exists, check to see if its a texture...
//
if (retval == false && Is_Texture (filename)) {
//
// Check to see if either the compressed or uncompressed
// texture exists
//
Swap_Texture_Extension (filename);
retval = Internal_Does_File_Exist (filename);
}
return retval;
}
//////////////////////////////////////////////////////////////////////
//
// Internal_Does_File_Exist
//
//////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Internal_Does_File_Exist (LPCSTR filename)
{
bool retval = false;
//
// Get the file from one of the mix files
//
FileClass *file = MainFileFactory.Get_File (filename);
if (file != NULL) {
//
// Does the file exist?
//
if (file->Is_Available ()) {
retval = true;
}
MainFileFactory.Return_File (file);
}
return retval;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Create_Directory_Structure
//
//////////////////////////////////////////////////////////////////////////////////
void
MixFileDatabaseClass::Create_Directory_Structure (LPCTSTR path)
{
if (path != NULL && path[0] != 0 && ::GetFileAttributes (path) == 0xFFFFFFFF) {
Create_Directory_Structure (::Strip_Filename_From_Path (path));
::CreateDirectory (path, NULL);
}
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Is_Texture
//
//////////////////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Is_Texture (LPCSTR filename)
{
StringClass temp_str (filename, true);
::strlwr (temp_str.Peek_Buffer ());
//
// Check to see if this is either a compressed or uncompressed texture
//
bool retval = false;
if (::strstr (temp_str.Peek_Buffer (), ".tga") || ::strstr (temp_str.Peek_Buffer (), ".dds")) {
retval = true;
}
return retval;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Swap_Texture_Extension
//
//////////////////////////////////////////////////////////////////////////////////
void
MixFileDatabaseClass::Swap_Texture_Extension (StringClass &filename)
{
::strlwr (filename.Peek_Buffer ());
//
// Is this a tga file (uncompressed), or a dds file (compressed)?
//
char *tga_extension = ::strstr (filename.Peek_Buffer (), ".tga");
if (tga_extension != NULL) {
//
// Simply copy the new extension into the string
//
::strcpy (tga_extension, ".dds");
} else {
char *dds_extension = ::strstr (filename.Peek_Buffer (), ".dds");
if (dds_extension != NULL) {
//
// Simply copy the new extension into the string
//
::strcpy (dds_extension, ".tga");
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Find_Files
//
//////////////////////////////////////////////////////////////////////////////////
void
MixFileDatabaseClass::Find_Files (DynamicVectorClass<StringClass> &file_list, LPCTSTR search_mask)
{
//
// Loop over all the factories
//
int factory_count = MainFileFactory.Get_Factory_Count ();
for (int index = 0; index < factory_count; index ++) {
//
// Get a pointer to the current factory
//
FileFactoryClass *factory = MainFileFactory.Get_Factory (index);
if (factory != NULL) {
//
// We assume that this is a mix file factory (since all the factories
// we're adding are mix file factories). Note: This can easily
// break.
//
MixFileFactoryClass *mix_factory = static_cast<MixFileFactoryClass *> (factory);
//
// Get a list of all the files in this mix file
//
DynamicVectorClass<StringClass> files_in_mix;
files_in_mix.Set_Growth_Step (1000);
mix_factory->Build_Filename_List (files_in_mix);
//
// Now, add any files that match the supplied wildcard to our master list
//
for (int file_index = 0; file_index < files_in_mix.Count (); file_index ++) {
const char *filename = files_in_mix[file_index];
//
// Add this file to our list if it matches the mask
//
if (::PathMatchSpec (filename, search_mask)) {
file_list.Add (filename);
}
}
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Get_All
//
//////////////////////////////////////////////////////////////////////////////////
bool
MixFileDatabaseClass::Get_All (LPCTSTR dest_path, LPCTSTR search_mask)
{
DynamicVectorClass<StringClass> file_list;
//
// Loop over all the factories
//
int factory_count = MainFileFactory.Get_Factory_Count ();
for (int index = 0; index < factory_count; index ++) {
//
// Get a pointer to the current factory
//
FileFactoryClass *factory = MainFileFactory.Get_Factory (index);
if (factory != NULL) {
//
// We assume that this is a mix file factory (since all the factories
// we're adding are mix file factories). Note: This can easily
// break.
//
MixFileFactoryClass *mix_factory = static_cast<MixFileFactoryClass *> (factory);
//
// Get a list of all the files in this mix file
//
DynamicVectorClass<StringClass> files_in_mix;
files_in_mix.Set_Growth_Step (1000);
mix_factory->Build_Filename_List (files_in_mix);
//
// Now, add any files that match the supplied wildcard to our master list
//
for (int file_index = 0; file_index < files_in_mix.Count (); file_index ++) {
const char *filename = files_in_mix[file_index];
//
// Add this file to our list if it matches the mask
//
if (::PathMatchSpec (filename, search_mask)) {
file_list.Add (filename);
}
}
}
}
//
// Now, get all the matching files to the specified path
//
for (index = 0; index < file_list.Count (); index ++) {
StringClass full_path = (const char *)::Make_Path (dest_path, file_list[index]);
Get (full_path);
}
return true;
}
///////////////////////////////////////////////////////////////////
//
// Get_File
//
///////////////////////////////////////////////////////////////////
FileClass *
MixFileDatabaseClass::Get_File (LPCTSTR local_filename)
{
FileClass *retval = NULL;
if (::GetFileAttributes (local_filename) != 0xFFFFFFFF) {
//
// Do this to get around an oddity of the class when you pass in
// the filename to the constructor
//
retval = new RawFileClass;
retval->Set_Name (local_filename);
} else {
//
// Get a pointer to the file in the mix file
//
StringClass filename;
Get_Filename (local_filename, filename);
retval = MainFileFactory.Get_File (filename);
}
return retval;
}
///////////////////////////////////////////////////////////////////
//
// Get_Filename
//
///////////////////////////////////////////////////////////////////
void
MixFileDatabaseClass::Get_Filename (LPCTSTR path, StringClass &filename)
{
filename = path;
::strlwr (filename.Peek_Buffer ());
//
// Check to see if the sub-directory is important, if it is, then
// return the sub-directory as well
//
char *subdir_token = ::strstr (filename, "+\\");
if (subdir_token != NULL) {
//
// Try to find the preceeding directory delimiter
//
int index = (subdir_token - filename.Peek_Buffer ());
for (; index >= 0; index --) {
if (filename[index] == '\\') {
StringClass temp_str = filename;
filename = (temp_str.Peek_Buffer () + index + 1);
break;
}
}
} else {
//
// Simply strip the filename from the path
//
filename = (const char *)::Get_Filename_From_Path (filename);
}
return ;
}