/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2007 Warzone Resurrection Project Warzone 2100 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 2 of the License, or (at your option) any later version. Warzone 2100 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 Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * FrameResource.c * * Framework Resource file processing functions * */ #include "frameresource.h" #include "file.h" #include "resly.h" #include "lib/sqlite3/sqlite3.h" // Local prototypes static RES_TYPE *psResTypes=NULL; /* The initial resource directory and the current resource directory */ char aResDir[PATH_MAX]; char aCurrResDir[PATH_MAX]; static sqlite3* currDB = NULL; static char currDBFile[PATH_MAX]; // the current resource block ID static SDWORD resBlockID; // prototypes static void ResetResourceFile(void); // callback to resload screen. static RESLOAD_CALLBACK resLoadCallback=NULL; /* set the callback function for the res loader*/ void resSetLoadCallback(RESLOAD_CALLBACK funcToCall) { resLoadCallback = funcToCall; } /* do the callback for the resload display function */ static inline void resDoResLoadCallback(void) { if(resLoadCallback) { resLoadCallback(); } } /* Initialise the resource module */ BOOL resInitialise(void) { ASSERT( psResTypes == NULL, "resInitialise: resource module hasn't been shut down??" ); psResTypes = NULL; resBlockID = 0; resLoadCallback = NULL; ResetResourceFile(); return true; } /* Shutdown the resource module */ void resShutDown(void) { if (psResTypes != NULL) { debug(LOG_WZ, "resShutDown: warning resources still allocated"); resReleaseAll(); } } // set the base resource directory void resSetBaseDir(const char* pResDir) { sstrcpy(aResDir, pResDir); } /* Parse the res file */ BOOL resLoad(const char *pResFile, SDWORD blockID) { bool retval = true; lexerinput_t input; sstrcpy(aCurrResDir, aResDir); // Note the block id number resBlockID = blockID; debug(LOG_WZ, "resLoad: loading %s", pResFile); // Load the RES file; allocate memory for a wrf, and load it input.type = LEXINPUT_PHYSFS; input.input.physfsfile = openLoadFile(pResFile, true); if (!input.input.physfsfile) { return false; } // and parse it res_set_extra(&input); if (res_parse() != 0) { debug(LOG_ERROR, "resLoad: failed to parse %s", pResFile); retval = false; } // If we have a database opened, make sure to close it if (currDB) { sqlite3_close(currDB); currDB = NULL; } res_lex_destroy(); PHYSFS_close(input.input.physfsfile); return retval; } /* Allocate a RES_TYPE structure */ static RES_TYPE* resAlloc(const char *pType) { RES_TYPE *psT; #ifdef DEBUG // Check for a duplicate type for(psT = psResTypes; psT; psT = psT->psNext) { ASSERT( strcmp(psT->aType, pType) != 0, "resAlloc: Duplicate function for type: %s", pType ); } #endif // Allocate the memory psT = (RES_TYPE *)malloc(sizeof(RES_TYPE)); if (!psT) { debug( LOG_ERROR, "resAlloc: Out of memory" ); abort(); return NULL; } // setup the structure sstrcpy(psT->aType, pType); psT->HashedType = HashString(psT->aType); // store a hased version for super speed ! psT->psRes = NULL; return psT; } /* Add a buffer load function for a file type */ BOOL resAddBufferLoad(const char *pType, RES_BUFFERLOAD buffLoad, RES_FREE release) { RES_TYPE *psT = resAlloc(pType); if (!psT) { return false; } psT->buffLoad = buffLoad; psT->fileLoad = NULL; psT->tableLoad = NULL; psT->release = release; psT->psNext = psResTypes; psResTypes = psT; return true; } /* Add a file name load function for a file type */ BOOL resAddFileLoad(const char *pType, RES_FILELOAD fileLoad, RES_FREE release) { RES_TYPE *psT = resAlloc(pType); if (!psT) { return false; } psT->buffLoad = NULL; psT->fileLoad = fileLoad; psT->tableLoad = NULL; psT->release = release; psT->psNext = psResTypes; psResTypes = psT; return true; } BOOL resAddTableLoad(const char* type, RES_TABLELOAD tableLoad, RES_FREE release) { RES_TYPE* psT = resAlloc(type); if (!psT) { return false; } psT->buffLoad = NULL; psT->fileLoad = NULL; psT->tableLoad = tableLoad; psT->release = release; psT->psNext = psResTypes; psResTypes = psT; return true; } // Make a string lower case void resToLower(char *pStr) { while (*pStr != 0) { if (isupper(*pStr)) { *pStr = (char)(*pStr - (char)('A' - 'a')); } pStr += 1; } } static char LastResourceFilename[PATH_MAX]; /*! * Returns the filename of the last resource file loaded * The filename is always null terminated */ const char *GetLastResourceFilename(void) { return LastResourceFilename; } /*! * Set the resource name of the last resource file loaded */ void SetLastResourceFilename(const char *pName) { sstrcpy(LastResourceFilename, pName); } // Structure for each file currently in use in the resource ... probably only going to be one ... but we will handle upto MAXLOADEDRESOURCE typedef struct { char *pBuffer; // a pointer to the data UDWORD size; // number of bytes UBYTE type; // what type of resource is it } RESOURCEFILE; #define RESFILETYPE_EMPTY (0) // empty entry #define RESFILETYPE_PC_SBL (1) // Johns SBL stuff #define RESFILETYPE_LOADED (2) // Loaded from a file (!) #define RESFILETYPE_WDGPTR (3) // A pointer from the WDG cache #define MAXLOADEDRESOURCES (6) static RESOURCEFILE LoadedResourceFiles[MAXLOADEDRESOURCES]; // Clear out the resource list ... needs to be called during init. static void ResetResourceFile(void) { UWORD i; for (i=0;itype=RESFILETYPE_LOADED; ResData->size=size; ResData->pBuffer=pBuffer; return(true); } // Free up the file depending on what type it is static void FreeResourceFile(RESOURCEFILE *OldResource) { switch (OldResource->type) { case RESFILETYPE_LOADED: free(OldResource->pBuffer); OldResource->pBuffer = NULL; break; default: debug(LOG_WARNING, "resource not freed"); } // Remove from the list OldResource->type=RESFILETYPE_EMPTY; } static inline RES_DATA* resDataInit(const char *DebugName, UDWORD DataIDHash, void *pData, UDWORD BlockID) { char* resID; // Allocate memory to hold the RES_DATA structure plus the identifying string RES_DATA* psRes = malloc(sizeof(RES_DATA) + strlen(DebugName) + 1); if (!psRes) { debug(LOG_ERROR, "resDataInit: Out of memory"); return NULL; } // Initialize the pointer for our ID string resID = (char*)(psRes + 1); // Copy over the identifying string strcpy(resID, DebugName); psRes->aID = resID; psRes->pData = pData; psRes->blockID = BlockID; psRes->HashedID = DataIDHash; psRes->usage = 0; return psRes; } /*! * check if given file exists in a locale dependend subdir * if so, modify given fileName to hold the locale dep. file, * else do not change given fileName * \param[out] fileName must be at least MAX_PATH bytes large */ static void makeLocaleFile(char fileName[]) // given string must have MAX_PATH size { #ifdef ENABLE_NLS const char * language = getLanguage(); char localeFile[PATH_MAX]; if ( language[0] == '\0' || // could not get language strlen(fileName) + strlen(language) + 1 >= PATH_MAX ) { return; } snprintf(localeFile, sizeof(localeFile), "locale/%s/%s", language, fileName); if ( PHYSFS_exists(localeFile) ) { sstrcpy(fileName, localeFile); debug(LOG_WZ, "Found translated file: %s", fileName); } #endif // ENABLE_NLS return; } /*! * Call the load function (registered in data.c) * for this filetype */ BOOL resLoadFile(const char *pType, const char *pFile) { RES_TYPE *psT; void *pData; RES_DATA *psRes; char aFileName[PATH_MAX]; UDWORD HashedName, HashedType = HashString(pType); // Find the resource-type for(psT = psResTypes; psT != NULL; psT = psT->psNext ) { if (psT->HashedType == HashedType) { break; } } if (psT == NULL) { debug(LOG_WZ, "resLoadFile: Unknown type: %s", pType); return false; } // Check for duplicates HashedName = HashStringIgnoreCase(pFile); for (psRes = psT->psRes; psRes; psRes = psRes->psNext) { if(psRes->HashedID == HashedName) { debug(LOG_WZ, "resLoadFile: Duplicate file name: %s (hash %x) for type %s", pFile, HashedName, psT->aType); // assume that they are actually both the same and silently fail // lovely little hack to allow some files to be loaded from disk (believe it or not!). return true; } } // Create the file name if (strlen(aCurrResDir) + strlen(pFile) + 1 >= PATH_MAX) { debug(LOG_ERROR, "resLoadFile: Filename too long!! %s%s", aCurrResDir, pFile); return false; } sstrcpy(aFileName, aCurrResDir); sstrcat(aFileName, pFile); makeLocaleFile(aFileName); // check for translated file SetLastResourceFilename(pFile); // Save the filename in case any routines need it // load the resource if (psT->buffLoad) { RESOURCEFILE *Resource; // Load the file in a buffer if (!RetreiveResourceFile(aFileName, &Resource)) { debug(LOG_ERROR, "resLoadFile: Unable to retreive resource - %s", aFileName); return false; } // Now process the buffer data if (!psT->buffLoad(Resource->pBuffer, Resource->size, &pData)) { debug(LOG_ERROR, "resLoadFile: The load function for resource type \"%s\" failed for file \"%s\"", pType, pFile); FreeResourceFile(Resource); if (psT->release != NULL) { psT->release(pData); } return false; } FreeResourceFile(Resource); } else if(psT->fileLoad) { // Process data directly from file if (!psT->fileLoad(aFileName, &pData)) { debug(LOG_ERROR, "resLoadFile: The load function for resource type \"%s\" failed for file \"%s\"", pType, pFile); if (psT->release != NULL) { psT->release(pData); } return false; } } else { debug(LOG_ERROR, "resLoadFile: No load functions for this type (%s)", pType); return false; } resDoResLoadCallback(); // do callback. // Set up the resource structure if there is something to store if (pData != NULL) { // LastResourceFilename may have been changed (e.g. by TEXPAGE loading) psRes = resDataInit( GetLastResourceFilename(), HashStringIgnoreCase(GetLastResourceFilename()), pData, resBlockID ); if (!psRes) { if (psT->release != NULL) { psT->release(pData); } return false; } // Add the resource to the list psRes->psNext = psT->psRes; psT->psRes = psRes; } return true; } BOOL resLoadTable(const char* type, const char* tableName) { RES_TYPE *psT; void *pData; RES_DATA *psRes; UDWORD HashedType = HashString(type); // Find the resource-type for (psT = psResTypes; psT != NULL; psT = psT->psNext) { if (psT->HashedType == HashedType) { break; } } if (psT == NULL) { debug(LOG_WZ, "Unknown type: %s", type); return false; } // load the resource if (psT->tableLoad) { if (!psT->tableLoad(currDB, tableName, &pData)) { debug(LOG_ERROR, "The load function for resource type \"%s\" failed while loading table \"%s\" from database \"%s\"", type, tableName, currDBFile); if (psT->release != NULL) { psT->release(pData); } return false; } } else { debug(LOG_ERROR, "No table load functions for this type (%s)", type); return false; } resDoResLoadCallback(); // do callback. // Set up the resource structure if there is something to store if (pData != NULL) { char* table; sasprintf(&table, "%s:%s", currDBFile, tableName); // LastResourceFilename may have been changed (e.g. by TEXPAGE loading) psRes = resDataInit(table, HashStringIgnoreCase(table), pData, resBlockID); if (!psRes) { if (psT->release != NULL) { psT->release(pData); } return false; } // Add the resource to the list psRes->psNext = psT->psRes; psT->psRes = psRes; } return true; } BOOL resOpenDB(const char* filename) { // If we already have a database opened, make sure to close it first. if (currDB) { sqlite3_close(currDB); } sstrcpy(currDBFile, aCurrResDir); sstrcat(currDBFile, filename); makeLocaleFile(currDBFile); // check for translated file if (sqlite3_open_v2(currDBFile, &currDB, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) { debug(LOG_ERROR, "Can't open database (%s): %s", currDBFile, sqlite3_errmsg(currDB)); currDB = NULL; return false; } return true; } /* Return the resource for a type and hashedname */ void *resGetDataFromHash(const char *pType, UDWORD HashedID) { RES_TYPE *psT = NULL; RES_DATA *psRes = NULL; // Find the correct type UDWORD HashedType = HashString(pType); for(psT = psResTypes; psT != NULL; psT = psT->psNext ) { if (psT->HashedType==HashedType) { /* We found it */ break; } } ASSERT( psT != NULL, "resGetDataFromHash: Unknown type: %s", pType ); if (psT == NULL) { return NULL; } for(psRes = psT->psRes; psRes != NULL; psRes = psRes->psNext) { if (psRes->HashedID == HashedID) { /* We found it */ break; } } ASSERT( psRes != NULL, "resGetDataFromHash: Unknown ID: %0x Type: %s", HashedID, pType ); if (psRes == NULL) { return NULL; } psRes->usage += 1; return psRes->pData; } /* Return the resource for a type and ID */ void *resGetData(const char *pType, const char *pID) { void * data = resGetDataFromHash(pType, HashStringIgnoreCase(pID)); ASSERT(data != NULL, "resGetData: Unable to find data for %s type %s", pID, pType); return data; } BOOL resGetHashfromData(const char *pType, const void *pData, UDWORD *pHash) { RES_TYPE *psT; RES_DATA *psRes; // Find the correct type UDWORD HashedType=HashString(pType); for(psT = psResTypes; psT != NULL; psT = psT->psNext ) { if (psT->HashedType==HashedType) { break; } } if (psT == NULL) { ASSERT( false, "resGetHashfromData: Unknown type: %x", HashedType ); return false; } // Find the resource for(psRes = psT->psRes; psRes; psRes = psRes->psNext) { if (psRes->pData == pData) { break; } } if (psRes == NULL) { ASSERT( false, "resGetHashfromData:: couldn't find data for type %x\n", HashedType ); return false; } *pHash = psRes->HashedID; return true; } const char* resGetNamefromData(const char* type, const void *data) { RES_TYPE *psT; RES_DATA *psRes; UDWORD HashedType; if (type == NULL || data == NULL) { return ""; } // Find the correct type HashedType = HashString(type); // Find the resource table for the given type for (psT = psResTypes; psT != NULL; psT = psT->psNext) { if (psT->HashedType == HashedType) { break; } } if (psT == NULL) { ASSERT( false, "resGetHashfromData: Unknown type: %x", HashedType ); return ""; } // Find the resource in the resource table for(psRes = psT->psRes; psRes; psRes = psRes->psNext) { if (psRes->pData == data) { break; } } if (psRes == NULL) { ASSERT( false, "resGetHashfromData:: couldn't find data for type %x\n", HashedType ); return ""; } return psRes->aID; } /* Simply returns true if a resource is present */ BOOL resPresent(const char *pType, const char *pID) { RES_TYPE *psT; RES_DATA *psRes; // Find the correct type UDWORD HashedType=HashString(pType); for(psT = psResTypes; psT != NULL; psT = psT->psNext ) { if (psT->HashedType==HashedType) { break; } } /* Bow out if unrecognised type */ ASSERT(psT != NULL, "resPresent: Unknown type"); if (psT == NULL) { return false; } { UDWORD HashedID=HashStringIgnoreCase(pID); for(psRes = psT->psRes; psRes; psRes = psRes->psNext) { if (psRes->HashedID==HashedID) { /* We found it */ break; } } } /* Did we find it? */ if (psRes != NULL) { return (true); } return (false); } /* Release all the resources currently loaded and the resource load functions */ void resReleaseAll(void) { RES_TYPE *psT, *psNT; resReleaseAllData(); for(psT = psResTypes; psT != NULL; psT = psNT) { psNT = psT->psNext; free(psT); } psResTypes = NULL; } /* Release all the resources currently loaded but keep the resource load functions */ void resReleaseAllData(void) { RES_TYPE *psT; RES_DATA *psRes, *psNRes; for (psT = psResTypes; psT != NULL; psT = psT->psNext) { for(psRes = psT->psRes; psRes != NULL; psRes = psNRes) { if (psRes->usage == 0) { debug(LOG_NEVER, "resReleaseAllData: %s resource: %s(%04x) not used", psT->aType, psRes->aID, psRes->HashedID); } if (psT->release != NULL) { psT->release(psRes->pData); } psNRes = psRes->psNext; free(psRes); } psT->psRes = NULL; } } // release the data for a particular block ID void resReleaseBlockData(SDWORD blockID) { RES_TYPE *psT, *psNT; RES_DATA *psPRes, *psRes, *psNRes; for(psT = psResTypes; psT != NULL; psT = psNT) { psPRes = NULL; for(psRes = psT->psRes; psRes; psRes = psNRes) { ASSERT(psRes != NULL, "resReleaseBlockData: null pointer passed into loop"); if (psRes->blockID == blockID) { if (psRes->usage == 0) { debug(LOG_NEVER, "resReleaseBlockData: %s resource: %s(%04x) not used", psT->aType, psRes->aID, psRes->HashedID); } if(psT->release != NULL) { psT->release( psRes->pData ); } else { ASSERT( false,"resReleaseAllData: NULL release function" ); } psNRes = psRes->psNext; free(psRes); if (psPRes == NULL) { psT->psRes = psNRes; } else { psPRes->psNext = psNRes; } } else { psPRes = psRes; psNRes = psRes->psNext; } } psNT = psT->psNext; } }