Lephisto/src/ldata.c

326 lines
7.7 KiB
C

/**
* @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);
}