/*=========================================================================== * * File: Dfbsa.CPP * Author: Dave Humphrey (uesp@m0use.net) * Created On: Wednesday, June 20, 2001 * * Implements the base class CDFBsaFile for handling Daggerfall's BSA type * data files. * *=========================================================================*/ /* Include Files */ #include "dfbsa.h" /*=========================================================================== * * Begin Local Variable Definitions * *=========================================================================*/ DEFINE_FILE(); /*=========================================================================== * End of Local Variable Definitions *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Constructor * *=========================================================================*/ CDFBsaFile::CDFBsaFile () { //DEFINE_FUNCTION("CDFBsaFile::CDFBsaFile()"); m_NumRecords = 0; m_DirType = DFBSA_DIR_VALUE; m_ReadHeader = FALSE; m_ReadDir = FALSE; m_pFilename = NULL; /* Initialize arrays */ m_pNameDir = NULL; m_pValueDir = NULL; m_pRecordOffsets = NULL; } /*=========================================================================== * End of Class CDFBsaFile Constructor *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - void Destroy (void); * *=========================================================================*/ void CDFBsaFile::Destroy (void) { DEFINE_FUNCTION("CDFBsaFile::Destroy()"); /* Delete any allocated memory */ DestroyArrayPointer(m_pNameDir); DestroyArrayPointer(m_pValueDir); DestroyArrayPointer(m_pRecordOffsets); DestroyArrayPointer(m_pFilename); /* Clear variables */ m_NumRecords = 0; m_DirType = DFBSA_DIR_VALUE; m_ReadHeader = FALSE; m_ReadDir = FALSE; /* Chain to the base class method */ CGenFile::Destroy(); } /*=========================================================================== * End of Class Method CDFBsaFile::Destroy() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - void AllocateDirectory (void); * * Deletes any current directory array and allocates a new one based on * the current directory type and directory size. Also allocates the * array of file offsets. * *=========================================================================*/ void CDFBsaFile::AllocateDirectory (void) { DEFINE_FUNCTION("CDFBsaFile::AllocateDirectory()"); /* Delete any allocated arrays */ DestroyArrayPointer(m_pNameDir); DestroyArrayPointer(m_pValueDir); DestroyArrayPointer(m_pRecordOffsets); /* Ensure a valid number of directory entries */ if (m_NumRecords <= 0) return; /* Allocate the appropiate structure array */ if (m_DirType == DFBSA_DIR_NAME) { CreateArrayPointer(m_pNameDir, dfbsa_namedir_t, GetNumRecords()); } else if (m_DirType == DFBSA_DIR_VALUE) { CreateArrayPointer(m_pValueDir, dfbsa_valuedir_t, GetNumRecords()); } else { ASSERT(FALSE); } /* Allocate the record offsets array */ CreateArrayPointer(m_pRecordOffsets, long, GetNumRecords()); } /*=========================================================================== * End of Class Method CDFBsaFile::AllocateDirectory() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - boolean AssertValid (void); * * Tests the current contents to ensure validity. ASSERTs if any object * error is encountered. * *=========================================================================*/ boolean CDFBsaFile::AssertValid (void) { DEFINE_FUNCTION("CDFBsaFile::AssertValid()"); int Index; long LastFileSize; /* Ignore if nothing currently read */ if (!m_ReadDir) return (TRUE); ASSERT(m_ReadHeader == TRUE); ASSERT(m_pFilename != NULL); ASSERT(m_NumRecords > 0); ASSERT(m_pRecordOffsets != NULL); ASSERT(m_pRecordOffsets[0] == 4); /* Ensure a valid directory type */ ASSERT(m_DirType == DFBSA_DIR_NAME || m_DirType == DFBSA_DIR_VALUE); /* Check the data arrays */ if (m_DirType == DFBSA_DIR_NAME) { ASSERT(m_pNameDir != NULL); } else { ASSERT(m_pValueDir != NULL); } /* Check the directory array */ for (Index = 0; Index < m_NumRecords; Index++) { if (m_DirType == DFBSA_DIR_NAME) { ASSERT(m_pNameDir[Index].Name [0] != NULL_CHAR); ASSERT(m_pNameDir[Index].Size >= 0); LastFileSize = m_pNameDir[Index].Size; } else { ASSERT(m_pValueDir[Index].Size >= 0); LastFileSize = m_pValueDir[Index].Size; } } /* Ensure a correct file length */ long FileSize = ::GetFileSize(m_pFilename); ASSERT(FileSize == m_pRecordOffsets[m_NumRecords-1] + LastFileSize + GetDirSize()); return (TRUE); } /*=========================================================================== * End of Class Method CDFBsaFile::AssertValid() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - void Close (void); * * Closes an opened BSA file. * *=========================================================================*/ void CDFBsaFile::Close (void) { /* Ignore if not currently open */ if (IsOpen()) { DestroyArrayPointer(m_pFilename); } /* Chain to base class method */ CGenFile::Close(); } /*=========================================================================== * End of Class Method CDFBsaFile::Close() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - void ComputeRecordOffsets (void); * * Protected class method which computes the offsets of the files relative * to the start of the BSA file. * *=========================================================================*/ void CDFBsaFile::ComputeRecordOffsets (void) { DEFINE_FUNCTION("CDFBsaFile::ComputeRecordOffsets()"); short LoopCounter; long Offset = 4; /* Start after the BSA header */ /* Ensure directory has been read */ ASSERT(m_ReadDir && m_pRecordOffsets != NULL); if (m_DirType == DFBSA_DIR_NAME) { for (LoopCounter = 0; LoopCounter < m_NumRecords; LoopCounter++) { m_pRecordOffsets[LoopCounter] = Offset; Offset += m_pNameDir[LoopCounter].Size; } } else if (m_DirType == DFBSA_DIR_VALUE) { for (LoopCounter = 0; LoopCounter < m_NumRecords; LoopCounter++) { m_pRecordOffsets[LoopCounter] = Offset; Offset += m_pValueDir[LoopCounter].Size; } } } /*=========================================================================== * End of Class Method CDFBsaFile::ComputeRecordOffsets() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - void Dump (pFileHandle); * * Outputs object information to the given file stream and the current * system log. * *=========================================================================*/ void CDFBsaFile::Dump (FILE* pFileHandle) { int LoopCounter; int MaxCount; int FirstCount; /* Output basic object information */ SystemLog.Printf (pFileHandle, "CDFBbsaFile Object (0x%08p)", this); SystemLog.Printf (pFileHandle, "\tFilename = %s", m_pFilename); SystemLog.Printf (pFileHandle, "\tNumRecords = %d", m_NumRecords); SystemLog.Printf (pFileHandle, "\tDirType = 0x%04X", m_DirType); SystemLog.Printf (pFileHandle, "\tOpen = %s", BooleanToString(IsOpen())); /* Output first five directory records */ MaxCount = (m_NumRecords >= 5) ? 5 : (int) m_NumRecords; SystemLog.Printf (pFileHandle, "\tDirectory Entries..."); for (LoopCounter = 0; LoopCounter < MaxCount; LoopCounter++) DumpDirEntry(pFileHandle, LoopCounter); /* Output last five directory records */ FirstCount = (m_NumRecords >= 10) ? m_NumRecords - 5 : MaxCount; SystemLog.Printf (pFileHandle, "\t\t...."); for (LoopCounter = FirstCount; LoopCounter < (int)m_NumRecords; LoopCounter++) DumpDirEntry(pFileHandle, LoopCounter); } /*=========================================================================== * End of Class Method CDFBsaFile::Dump() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - void DumpDirEntry (pFileHandle, Index); * * Dumps the specified directory entry to the given file stream. * Protected class method. * *=========================================================================*/ void CDFBsaFile::DumpDirEntry (FILE* pFileHandle, const int Index) { DEFINE_FUNCTION("CDFBsaFile::DumpDirEntry()"); /* Ensure a valid index */ ASSERT(IsValidIndex(Index)); /* Output based on the directory type */ switch (m_DirType) { case DFBSA_DIR_NAME: ASSERT(m_pNameDir != NULL); SystemLog.Printf (pFileHandle, "\t\t%d) '%s' = 0x%08lX bytes at 0x%08lX", Index, m_pNameDir[Index].Name,m_pNameDir[Index].Size, m_pRecordOffsets[Index]); break; case DFBSA_DIR_VALUE: ASSERT(m_pValueDir != NULL); SystemLog.Printf (pFileHandle, "\t\t%d) 0x%08lX = 0x%08lX bytes at 0x%08lX", Index, m_pValueDir[Index].Value, m_pValueDir[Index].Size, m_pRecordOffsets[Index]); break; default: SystemLog.Printf (pFileHandle, "\t\t%d) Unknown directory type!", Index); break; } } /*=========================================================================== * End of Class Method CDFBsaFile::DumpDirEntry() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - boolean Extract (pFilename, Index); * * Extracts the given record index to the specified filename. Returns FALSE * on any error. * *=========================================================================*/ boolean CDFBsaFile::Extract (const char* pFilename, const size_t Index) { DEFINE_FUNCTION("CDFBsaFile::Extract()"); CGenFile OutputFile; boolean Result; char Buffer[DFBSA_EXTRACT_BUFFERSIZE+1]; size_t BytesRead; size_t InputSize; long TotalBytesRead; long FileSize; /* Ensure valid input */ ASSERT(IsValidIndex(Index) && pFilename != NULL); /* Attempt to jump to start of file offset */ Result = Seek(m_pRecordOffsets[Index], SEEK_SET); if (!Result) return (FALSE); /* Attempt to open file for output */ Result = OutputFile.Open(pFilename, "wb"); if (!Result) return (FALSE); FileSize = GetRecordSize(Index); TotalBytesRead = 0; /* Read/write file in sections */ while (TotalBytesRead < FileSize) { if (TotalBytesRead + DFBSA_EXTRACT_BUFFERSIZE > FileSize) InputSize = (size_t)(FileSize - TotalBytesRead); /* Input data */ Result = Read(Buffer, BytesRead, InputSize); if (!Result) return (FALSE); /* Output data */ Result = OutputFile.Write(Buffer, InputSize); if (!Result) return (FALSE); TotalBytesRead += (long) BytesRead; } /* Ensure an exact output */ ASSERT (TotalBytesRead == FileSize); return (TRUE); } /*=========================================================================== * End of Class Method CDFBsaFile::Extract() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - int FindRecord (Value); * * Attempts to find the directory entry with the given value. Returns * a negative value if the file was not found or the BSA file does not * contain a value type directory. Returns the directory index on success. * The BSA directory must have been previously read. * *=========================================================================*/ int CDFBsaFile::FindRecord (const long Value) { DEFINE_FUNCTION("CDFBsaFile::FindRecord(long)"); short Index; /* Ensure valid input */ ASSERT(m_pValueDir != NULL && m_ReadDir && m_DirType == DFBSA_DIR_VALUE); /* Ensure correct directory type */ if (m_DirType != DFBSA_DIR_VALUE) { ErrorHandler.AddError(DFERR_BSA_DIRTYPE); return (-1); } /* Search array for value match */ for (Index = 0; Index < m_NumRecords; Index++) { if (m_pValueDir[Index].Value == Value) return (Index); } /* No match found! */ ErrorHandler.AddError(DFERR_BSA_NOTFOUND, "Value=%ld", Value); return (-1); } /*=========================================================================== * End of Class Method CDFBsaFile::CFindFile() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - int FindRecord (pName); * * Attempts to find the directory entry with the given name. Returns * a negative value if the file was not found or the BSA file does not * contain a name type directory. Returns the directory index on success. * The BSA directory must have been previously read. The string comparison * is case insensitive. * *=========================================================================*/ int CDFBsaFile::FindRecord (const char* pName) { DEFINE_FUNCTION("CDFBsaFile::FindRecord(char*)"); short Index; /* Ensure valid input */ ASSERT(pName != NULL && m_pNameDir != NULL); ASSERT(m_ReadDir && m_DirType == DFBSA_DIR_NAME); /* Ensure correct directory type */ if (m_DirType != DFBSA_DIR_NAME) { ErrorHandler.AddError(DFERR_BSA_DIRTYPE); return (-1); } /* Search array for name match */ for (Index = 0; Index < m_NumRecords; Index++) { if (stricmp(m_pNameDir[Index].Name, pName) == 0) return (Index); } /* No match found! */ ErrorHandler.AddError(DFERR_BSA_NOTFOUND, "Name='%s'", pName); return (-1); } /*=========================================================================== * End of Class Method CDFBsaFile::FindRecord() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - void* GetDirectory (void); * * Attempts to return a pointer to the directory array depending on the * current directory type. ASSERTs if the current array is not valid. * Protected class method. * *=========================================================================*/ void* CDFBsaFile::GetDirectory (void) { DEFINE_FUNCTION("CDFBsaFile::GetDirectory()"); switch (m_DirType) { case DFBSA_DIR_NAME: ASSERT(m_pNameDir != NULL); return (void *)(m_pNameDir); case DFBSA_DIR_VALUE: ASSERT(m_pValueDir != NULL); return (void*)(m_pValueDir); } /* Unknown directory type? */ ASSERT(FALSE); return (NULL); } /*=========================================================================== * End of Class Method CDFBsaFile::GetDirectory() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - boolean Open (pFilename, pMode); * * Attempt to open the given BSA file for input/output. Returns FALSE * on any error. Currently only binary input modes are supported. On * success, the BSA header and directory are input. * *=========================================================================*/ boolean CDFBsaFile::Open (const char* pFilename, const char* pMode) { DEFINE_FUNCTION("CDFBsaFile::Open()"); boolean Result; char NewMode[4] = "rb"; /* Ensure valid input */ ASSERT(pMode != NULL); /* Check mode types */ if (pMode[0] == 'r') strcpy(NewMode, "rb"); else { ErrorHandler.AddError(DFERR_BSA_READONLY); return (FALSE); } /* Attempt to open file */ Result = CGenFile::Open(pFilename, NewMode); /* Attempt to read the BSA file header and directory */ if (Result) Result = ReadHeader(); if (Result) Result = ReadDirectory(); /* Close file on any error */ if (!Result) Close(); else SetFilename(pFilename); return (Result); } /*=========================================================================== * End of Class Method CDFBsaFile::Open() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - boolean ReadHeader (void); * * Protected class method which attempts to input the 4 byte BSA header * from the file. The file must be open. Returns FALSE on any error. * *=========================================================================*/ boolean CDFBsaFile::ReadHeader (void) { DEFINE_FUNCTION("CDFBsaFile::ReadHeader()"); boolean Result; /* File must be open */ ASSERT(IsOpen()); m_ReadHeader = FALSE; /* Read in the 4 byte BSA header from start of file */ Result = Seek(0, SEEK_SET); if (Result) Result = ReadShort(m_NumRecords); if (Result) Result = ReadShort(m_DirType); if (!Result) return (FALSE); /* Do a quick check to ensure valid input */ if (m_DirType != DFBSA_DIR_NAME && m_DirType != DFBSA_DIR_VALUE) { ErrorHandler.AddError(DFERR_BSA_DIRTYPE, "File may not be a valid BSA file!"); return (FALSE); } else if (m_NumRecords <= 0 || m_NumRecords > 32000) { ErrorHandler.AddError(DFERR_BSA_NUMFILES, "NumFiles=%d", m_NumRecords); return (FALSE); } /* Return success */ m_ReadHeader = TRUE; return (TRUE); } /*=========================================================================== * End of Class Method CDFBsaFile::ReadHeader() *=========================================================================*/ /*=========================================================================== * * Class CDFBsaFile Method - boolean ReadDirectory (void); * * Protected class method which attempts to input the directory from the * BSA file. The file must be open and the BSA header previously read. * Returns FALSE on any error. * *=========================================================================*/ boolean CDFBsaFile::ReadDirectory (void) { DEFINE_FUNCTION("CDFBsaFile::ReadDirectory()"); boolean Result; /* File must be open and header previously read */ ASSERT(IsOpen() && m_ReadHeader); /* Create the directory array to store input */ AllocateDirectory(); /* Move to start of the directory and read */ Result = Seek(-GetDirSize(), SEEK_END); if (Result) Result = Read((char *)GetDirectory(), (size_t)GetDirSize()); if (!Result) return (FALSE); m_ReadDir = TRUE; /* Compute the file offsets */ ComputeRecordOffsets(); /* Return success */ m_ReadDir = TRUE; return (TRUE); } /*=========================================================================== * End of Class Method CDFBsaFile::ReadDirectory() *=========================================================================*/