/** * @file ldata.c * * @brief Wrapper to handle reading/writing the ldata file. * * Optimizes to minimize the opens and frees, plus tries to read from the * filesystem instead of always looking for a packfile. */ #include "SDL.h" #include "lephisto.h" #include "log.h" #include "md5.h" #include "lxml.h" #include "pack.h" #include "lfile.h" #include "ldata.h" #define LDATA_FILENAME "ldata" /**< Generic ldata file name. */ #ifndef LDATA_DEF #define LDATA_DEF LDATA_FILENAME /**< Default ldata to use. */ #endif #define XML_START_ID "Start" /**< XML document tag of module start file. */ #define START_DATA "../dat/start.xml" /**< Path to module start file. */ /* Packfile. */ static char* ldata_filename = NULL; /**< Packfile name. */ static Packcache_t* ldata_cache = NULL; /**< Actual packfile. */ static char* ldata_packName = NULL; /**< Name of the ldata module. */ /* File list. */ static const char** ldata_fileList = NULL; /**< List of the files in the packfile. */ static uint32_t ldata_fileNList = 0; /**< Number of files in ldata_fileList. */ static char** filterList(const char** list, int nlist, const char* path, uint32_t* nfiles); /** * @brief Check to see if path is an ldata file. * * Should be called before ldata_open. * @param path Path to check to see if it's an ldata file. * @return 1 if it is an ldata file, otherwise 0. */ int ldata_check(char* path) { return pack_check(path); } /** * @brief Set the current ldata path to use. * * Should be called before ldata_open * @param path Path to set. * @return 0 on success. */ int ldata_setPath(char* path) { if(ldata_filename != NULL) free(ldata_filename); ldata_filename = (path == NULL) ? NULL : strdup(path); return 0; } /** * @brief Open a packfile if needed. * @return 0 on success. */ static int ldata_openPackfile(void) { int i; char** files; int nfiles; size_t len; /* Try to find the ldata file. */ if(ldata_filename == NULL) { /* Check ldata with version appended. */ if(lfile_fileExists("%s-%d.%d.%d", LDATA_FILENAME, VMAJOR, VMINOR, VREV)) { ldata_filename = malloc(PATH_MAX); snprintf(ldata_filename, PATH_MAX, "%s-%d.%d.%d", LDATA_FILENAME, VMAJOR, VMINOR, VREV); } /* Check default ldata. */ else if(lfile_fileExists(LDATA_DEF)) ldata_filename = strdup(LDATA_DEF); /* Try to open any ldata in path. */ else { files = lfile_readDir(&nfiles, "."); len = strlen(LDATA_FILENAME); for(i = 0; i < nfiles; i++) { if(strncmp(files[i], LDATA_FILENAME, len)==0) { /* Must be a packfile. */ if(pack_check(files[i])) continue; ldata_filename = strdup(files[i]); break; } } /* Clean up. */ for(i = 0; i < nfiles; i++) free(files[i]); free(files); } } /* Open the cache. */ if(lfile_fileExists(ldata_filename) != 1) { WARN("Cannot find ldata file!"); WARN("Please specify ldata file with -d or data in the conf file."); exit(1); return -1; } ldata_cache = pack_openCache(ldata_filename); if(ldata_cache == NULL) WARN("Unalbe to create Packcache from '%s'.", ldata_filename); return 0; } /** * @brief Open the ldata file. * @return 0 on success. */ int ldata_open(void) { return 0; } /** * @brief Close and clean up the ldata file. */ void ldata_close(void) { /* Destroy the name. */ if(ldata_packName != NULL) { free(ldata_packName); ldata_packName = NULL; } /* Destroy the list. */ if(ldata_fileList != NULL) { /* No need to free memory since cache does that. */ ldata_fileList = NULL; ldata_fileNList = 0; } /* Close the packfile. */ if(ldata_cache) { pack_closeCache(ldata_cache); ldata_cache = NULL; } } /** * @brief Get the ldata's name. * * Thread safe (uses ldata_read). * @return The ldata's name. */ const char* ldata_name(void) { char* buf; uint32_t size; xmlNodePtr node; xmlDocPtr doc; /* Alreay loaded. */ if(ldata_packName != NULL) return ldata_packName; /* We'll just read it and parse it. */ buf = ldata_read(START_DATA, &size); doc = xmlParseMemory(buf, size); /* Make sure it's what we are looking for. */ node = doc->xmlChildrenNode; if(!xml_isNode(node, XML_START_ID)) { ERR("Malformed '"START_DATA"' file: missing root element '"XML_START_ID"'"); return NULL; } /* Check if node is valid. */ node = node->xmlChildrenNode; /* First system node. */ if(node == NULL) { ERR("Malformed '"START_DATA"' file: does not contain elements"); return NULL; } do { xmlr_strd(node, "name", ldata_packName); } while(xml_nextNode(node)); xmlFreeDoc(doc); free(buf); /* Check if data name is found. */ if(ldata_packName == NULL) WARN("No ldata packname found."); return ldata_packName; } /** * @brief Read a file from the ldata. * @param filename Name of the file to read. * @param[out] filesize Stores the size of the file. * @return The file data or NULL on error. */ void* ldata_read(const char* filename, uint32_t* filesize) { char* buf; int nbuf; /* See if needs to load the packfile. */ if(ldata_cache == NULL) { /* Try to read the file as locally. */ buf = lfile_readFile(&nbuf, filename); if(buf != NULL) { *filesize = nbuf; return buf; } /* Load the packfile. */ ldata_openPackfile(); } /* Get data from packfile. */ return pack_readfileCached(ldata_cache, filename, filesize); } /** * @brief Create an rwops from a file in the ldata. * @param filename Name of the file create rwops of. * @retuen rwops that accesses the file in the ldata. */ SDL_RWops* ldata_rwops(const char* filename) { SDL_RWops* rw; if(ldata_cache == NULL) { /* Try to open from file. */ rw = SDL_RWFromFile(filename, "rb"); if(rw != NULL) return rw; /* Load the packfile. */ ldata_openPackfile(); } return pack_rwopsCached(ldata_cache, filename); } /** * @brief Filter a file list to match path. * * Uses read only data, should be thread safe. * * @param list List to filter. * @param nlist Members in list. * @param path Path to filter. * @param[out] nfiles Files that match. */ static char** filterList(const char** list, int nlist, const char* path, uint32_t* nfiles) { char** filtered; int i, j, k; int len; /* Maximum size by default. */ filtered = malloc(sizeof(char*) * nlist); len = strlen(path); /* Filter list. */ j = 0; for(i = 0; i < nlist; i++) { /* Must match path. */ if(strncmp(list[i], path, len) != 0) continue; /* Make sure there are no stray '/'. */ for(k = len; list[i][k] != '\0'; k++) if(list[i][k] != '/') break; if(list[i][k] != '\0') continue; /* Copy the file name without the path. */ filtered[j++] = strdup(&list[i][len]); } /* Return results. */ *nfiles = j; return filtered; } /** * @brief Get the list of files in the ldata. * @param path List files in path. * @param nfiles Number of files found. * @return List of files found. */ char** ldata_list(const char* path, uint32_t* nfiles) { (void)path; char** files; int n; /* Already loaded the list. */ if(ldata_fileList != NULL) return filterList(ldata_fileList, ldata_fileNList, path, nfiles); /* See if we can load from local directory. */ if(ldata_cache == NULL) { files = lfile_readDir(&n, path); /* Found locally. */ if(files != NULL) { *nfiles = n; return files; } /* Open packfile. */ ldata_openPackfile(); } /* Load list. */ ldata_fileList = pack_listfilesCached(ldata_cache, &ldata_fileNList); return filterList(ldata_fileList, ldata_fileNList, path, nfiles); }