Lephisto/src/pack.c

907 lines
23 KiB
C

#include <stdio.h>
#include <fcntl.h> /* creat() etc. */
#include <stdint.h> /* uint32_t */
#if HAS_POSIX
#include <sys/types.h> /* ssize_t */
#include <sys/stat.h> /* S_IRUSR */
#endif /* HAS_POSIX */
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "pack.h"
#include "log.h"
#include "md5.h"
/**
* @file pack.c
*
* @brief Stores data in funky format.
*
* Format:
* -- Index.
* -- Magic number (16 bytes).
* -- Number of files (uint32_t).
* -- Files in format name/location.
* -- File name (128 bytes max, ending in NUL).
* -- File location (uint32_t).
* -- File data in format Size/Data.
* -- File size (uint32_t).
* -- File data (char*).
* -- File MD5 (16 byte char*).
* -- EOF.
*
* -- Write magic number and number of files.
* -- Write the index.
* -- Pack the files.
*/
/**
* @brief Abstracts around packfiles.
*/
struct Packfile_s {
#if HAS_POSIX
int fd; /**< File descriptor. */
#else
char* name; /**< Hack to emulate dup2 by calling fopen again. */
FILE* fp; /**< For non-posix. */
#endif /* HAS_POSIX */
uint32_t pos; /**< Cursor position. */
uint32_t start; /**< File start. */
uint32_t end; /**< File end. */
uint32_t flags; /**< Special control flags. */
};
/**
* @brief Allows much faster creation of packfiles.
*/
struct Packcache_s {
#if HAS_POSIX
int fd; /**< File descriptor. */
#else
FILE* fp; /* For non-posix. */
#endif /* HAS_POSIX */
char** index; /**< Cached index for faster lookups. */
uint32_t* start; /**< Cached index starts. */
uint32_t* nindex; /**< Number of index entries. */
};
/* Helper defines. */
#if HAS_POSIX
#define READ(f, b, n) if(read((f)->fd, (b), (n)) != (n)) { \
ERR("Fewer bytes read then expected"); \
return NULL; }
#else
#define READ(f, b, n) if(fread((b), 1, (n),(f)->fp) != (n)) { \
ERR("Fewer bytes read then expected"); \
return NULL; }
#endif /* HAS_POSIX */
#undef DEBUG /* This will be spammy. */
#define DEBUG(str, args...) do{;} while(0) /**< Hack to disable debugging. */
#define BLOCKSIZE 128*1024 /**< The read/write block size */
/* Max filename length. */
#ifndef PATH_MAX
#define PATH_MAX 256
#endif
#define PERMS S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH /**< Default permissions. */
const uint64_t magic = 0x25524573; /**< File magic number: sER% */
/* Flags. */
#define PACKFILE_FROMCACHE (1<<0) /**< Packfile comes from a packcache. */
static off_t getfilesize(const char* filename);
/* RWops stuff. */
static int packrw_seek(SDL_RWops* rw, int offset, int whence);
static int packrw_read(SDL_RWops* rw, void* ptr, int size, int maxnum);
static int packrw_write(SDL_RWops* rw, const void* ptr, int size, int num);
static int packrw_close(SDL_RWops* rw);
/**
* @brief Open a packfile as a cache.
* @param packfile Name of the packfile to cache.
* @return NULL if an error occured or the Packcache.
*/
Packcache_t* pack_openCache(const char* packfile) {
int j;
uint32_t i;
char buf[PATH_MAX];
Packcache_t* cache;
/* Allocate memory. */
cache = calloc(1, sizeof(Packcache_t));
if(cache == NULL) {
ERR("Out of memory.");
return NULL;
}
/* Open file. */
#if HAS_POSIX
cache->fd = open(packfile, O_RDONLY);
if(cache->fd == -1) {
#else
cache->name = strdup(packfile);
cache->fp = fopen(packfile, "rb");
if(cache->fp == NULL) {
#endif /* HAS_POSIX */
ERR("Error opening %s: %s", packfile, strerror(errno));
return NULL;
}
/* Check for validity. */
READ(cache, buf, sizeof(magic));
if(memcmp(buf, &magic, sizeof(magic))) {
ERR("File %s is not a valid packfile", packfile);
return NULL;
}
/* Get the number of files and allocate memory. */
READ(cache, &cache->nindex, 4);
cache->index = calloc(cache->nindex, sizeof(char*));
cache->start = calloc(cache->nindex, sizeof(uint32_t));
/* Read index. */
for(i = 0; i < cache->nindex; i++) { /* Start to search files. */
j = 0;
READ(cache, &buf[j], 1); /* Get the name. */
while(buf[j++] != '\0')
READ(cache, &buf[j], 1);
cache->index[i] = strdup(buf);
READ(cache, &cache->start[i], 4);
DEBUG("'%s' found at %d", filename, cache->start[i]);
}
/* Return the built cache. */
return cache;
}
/**
* @brief Close a Packcache.
* @param cache Packcache to close.
*/
void pack_closeCache(Packcache_t* cache) {
uint32_t i;
/* Close file. */
#if HAS_POSIX
close(cache->fd);
#else
free(cache->name);
fclose(cache->fp);
#endif /* HAS_POSIX */
/* Free memory. */
if(cache->nindex > 0) {
for(i = 0; i < cache->nindex; i++)
free(cache->index[i]);
free(cache->index);
free(cache->start);
}
free(cache);
}
/**
* @brief Open a packfile from a Packcache.
* @param cache Packcache to create Packfile from.
* @param filename Name of the file to open in the Cache.
* @return A packfile for filename from cache.
*/
Packfile_t* pack_openFromCache(Packcache_t* cache, const char* filename) {
uint32_t i;
Packfile_t* file;
file = calloc(1, sizeof(Packfile_t));
for(i = 0; i < cache->nindex; i++) {
if(strcmp(cache->index[i], filename)==0) {
/* Copy file. */
#if HAS_POSIX
file->fd = dup(cache->fd);
#else
file->fp = fopen(cache->name, "rb");
#endif
/* Copy information. */
file->flags |= PACKFILE_FROMCACHE;
file->start = cache->start[i];
/* Seek. */
if(file->start) { /* Go to the beginning of the file. */
#if HAS_POSIX
if((uint32_t)lseek(file->fd, file->start, SEEK_SET) != file->start) {
#else
fseek(file->fp, file->start, SEEK_SET);
if(errno) {
#endif /* HAS_POSIX */
ERR("Failure to seek to file start: %s", strerror(errno));
return NULL;
}
READ(file, &file->end, 4);
DEBUG("\t%d bytes", file->end);
file->pos = file->start;
file->end += file->start;
}
return file;
}
}
free(file);
WARN("File '%s' not found in packfile.", filename);
return NULL;
}
/**
* @brief Get the file size.
* @param filename File to get the size of.
* @return The size of the file.
*/
static off_t getfilesize(const char* filename) {
#if HAS_POSIX
struct stat file;
if(!stat(filename, &file))
return file.st_size;
ERR("Unable to get filesize of %s", filename);
return 0;
#else
long size;
FILE* fp = fopen(filename, "rb");
if(fp == NULL) return 0;
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fclose(fp);
return size;
#endif /* HAS_POSIX */
}
/**
* @fn int pack_check(const char* filename)
*
* @brief Check to see if a file is a packfile
* @param filename Name of the file to check.
* @return 0 if it is a packfile, 1 if it isn't and -1 on error.
*/
int pack_check(const char* filename) {
int ret;
char* buf;
buf = malloc(sizeof(magic));
#if HAS_POSIX
int fd = open(filename, O_RDONLY);
if(fd == -1) {
ERR("Error opening %s: %s", filename, strerror(errno));
return -1;
}
if(read(fd, buf, sizeof(magic)) != sizeof(magic)) {
ERR("Error reading magic number: %s", strerror(errno));
free(buf);
return -1;
}
ret = (memcmp(buf, &magic, sizeof(magic))==0) ? 0 : 1;
close(fd);
#else
FILE* file = fopen(filename, "rb");
if(file == NULL) {
ERR("Error opening '%s': %s", filename, strerror(errno));
return -1;
}
buf = malloc(sizeof(magic));
if(fread(buf, 1, sizeof(magic), file) != sizeof(magic)) {
ERR("Error reading magic number: %s", strerror(errno));
free(buf);
return -1;
}
ret = (memcmp(buf, &magic, sizeof(magic))==0) ? 0 : 1;
fclose(file);
#endif /* HAS_POSIX */
free(buf);
return ret;
}
#if HAS_POSIX
#define WRITE(b,n) if(write(outfd, b, n)==-1) { \
ERR("Error writing to file: %s", strerror(errno)); \
free(buf); return -1; } /**< Macro to help check for errors.
#else
#define WRITE(b,n) if(fwrite(b, 1, n, outf)==0) { \
ERR("Error writing to file: %s", strerror(errno)); \
free(buf); return -1; } /**< Macro to help check for errors. */
#endif /* HAS_POSIX */
/**
* @brief Packages files into a packfile.
* @param outfile Name of the file to output to.
* @param infiles Array of filenames to package.
* @param nfiles Number of filenames in infiles.
* @return 0 on success.
*/
int pack_files(const char* outfile, const char** infiles, const uint32_t nfiles) {
void* buf;
#if HAS_POSIX
struct stat file;
int outfd, infd;
#else
FILE* outf, *inf;
#endif /* HAS_POSIX */
uint32_t i;
int namesize;
uint32_t indexsize, pointer;
int bytes;
const uint8_t b = '\0';
for(namesize = 0, i = 0; i < nfiles; i++) {
/* Make sure file exists before writing. */
#if HAS_POSIX
if(stat(infiles[i], &file)) {
#else
if(getfilesize(infiles[i]) == 0) {
#endif /* HAS_POSIX */
ERR("File %s does not exist", infiles[i]);
return -1;
}
if(strlen(infiles[i]) > PATH_MAX) {
ERR("filename '%s' is too long, should be only %d characters", infiles[i],
PATH_MAX);
return -1;
}
namesize += strlen(infiles[i]);
}
indexsize = (sizeof(magic) + 4 + /* Magic number and number of files. */
namesize + /* Total length of file names. */
(1+4)*nfiles); /* File size and extra end of string char '\0'. */
DEBUG("Index size is %d", indexsize);
/* Create the output file. */
#if HAS_POSIX
outfd = creat(outfile, PERMS);
if(outfd == -1) {
#else
outf = fopen(outfile, "wb");
if(outf == NULL) {
#endif /* HAS_POSIX */
ERR("Unable to open %s for writing", outfile);
return -1;
}
/* Index! */
buf = malloc(BLOCKSIZE);
/* Magic number. */
WRITE(&magic, sizeof(magic));
DEBUG("Wrote magic number");
/* Number of files. */
WRITE(&nfiles, sizeof(nfiles));
DEBUG("Wrote number of files: %d", nfiles);
/* Create file dependent index part. */
pointer = indexsize;
for(i = 0; i < nfiles; i++) {
WRITE(infiles[i], strlen(infiles[i]));
DEBUG("File: '%s' at %d", infiles[i], pointer);
WRITE(&b, 1);
WRITE(&pointer, 4);
pointer += 4 + getfilesize(infiles[i]) + 16; /* Set pointer to be next file pos. */
}
/* Data! */
md5_state_t md5;
md5_byte_t* md5val = malloc(16);
for(i = 0; i < nfiles; i++) {
bytes = (uint32_t)getfilesize(infiles[i]);
WRITE(&bytes, 4); /* filesize. */
DEBUG("About to write '%s' of %d bytes", infiles[i], bytes);
md5_init(&md5);
#if HAS_POSIX
infd = open(infiles[i], O_RDONLY);
while((bytes = read(infd, buf, BLOCKSIZE))) {
#else
inf = fopen(infiles[i], "rb");
while((bytes = fread(buf, 1, BLOCKSIZE, inf))) {
#endif /* HAS_POSIX */
WRITE(buf, bytes); /* Data. */
md5_append(&md5, buf, bytes);
}
md5_finish(&md5, md5val);
WRITE(md5val, 16);
#if HAS_POSIX
close(infd);
#else
fclose(inf);
#endif /* HAS_POSIX */
DEBUG("Wrote file '%s'", infiles[i]);
}
free(md5val);
#if HAS_POSIX
close(outfd);
#else
fclose(outf);
#endif /* HAS_POSIX */
free(buf);
DEBUG("Packfile success\n\t%d files\n\t%d bytes",
nfiles, (int)getfilesize(outfile));
return 0;
}
#undef WRITE
/**
* @brief Open a file in the packfile for reading.
* @param packfile Path to real packfile.
* @param filename Name of the file within th. packfile.
* @return The newly created packfile or NULL on error.
*/
Packfile_t* pack_open(const char* packfile, const char* filename) {
int j;
uint32_t nfiles, i;
char buf[PATH_MAX];
Packfile_t* file;
/* Allocate memory. */
file = malloc(sizeof(Packfile_t));
memset(file, 0, sizeof(Packfile_t));
#if HAS_POSIX
file->fd = open(packfile, O_RDONLY);
if(file->fd == -1) {
#else
file->fp = fopen(packfile, "rb");
if(file->fp == NULL) {
#endif /* HAS_POSIX */
ERR("Error opening %s: %s", filename, strerror(errno));
return NULL;
}
READ(file, buf, sizeof(magic)); /* Make sure it's a packfile. */
if(memcmp(buf, &magic, sizeof(magic))) {
ERR("File %s is not a valid packfile", filename);
return NULL;
}
READ(file, &nfiles, 4);
for(i = 0; i < nfiles; i++) {
/* Start to search files. */
j = 0;
READ(file, &buf[j], 1); /* Get the name. */
while(buf[j++] != '\0')
READ(file, &buf[j], 1);
if(strcmp(filename, buf) == 0) {
/* We found the file. */
READ(file, &file->start, 4);
DEBUG("'%s' found at %d", filename, file->start);
break;
}
#if HAS_POSIX
lseek(file->fd, 4, SEEK_CUR); /* Ignore the file location. */
#else
fseek(file->fp, 4, SEEK_CUR);
#endif /* HAS_POSIX */
}
if(file->start) {
/* Go to the beginning of the file. */
#if HAS_POSIX
if((uint32_t)lseek(file->fd, file->start, SEEK_SET) != file->start) {
#else
fseek(file->fp, file->start, SEEK_SET);
if(errno) {
#endif /* HAS_POSIX */
ERR("Failure to seek to file start: %s", strerror(errno));
return NULL;
}
READ(file, &file->end, 4);
DEBUG("\t%d bytes", file->end);
file->pos = file->start;
file->end += file->start;
} else {
ERR("File '%s' not found in packfile '%s'", filename, packfile);
return NULL;
}
return file;
}
/**
* @brief Reads data from a packfile.
*
* Behaves like POSIX read.
* @param file Opened packfile to read data from.
* @param buf Allocated buffer to read into.
* @param count Bytes to read.
* @return Bytes to read or -1 on error.
*/
ssize_t pack_read(Packfile_t* file, void* buf, size_t count) {
int bytes;
if(file->pos + count > file->end)
count = file->end - file->pos; /* Can't go past the end! */
if(count == 0) return 0;
#if HAS_POSIX
if((bytes = read(file->fd, buf, count)) == -1) {
#else
if((bytes = fread(buf, 1, count, file->fp)) == -1) {
#endif /* HAS_POSIX */
ERR("Error while reading file: %s", strerror(errno));
return -1;
}
file->pos += bytes;
return bytes;
}
/**
* @brief Seeks within a file inside a packfile.
*
* Behaves like lseek/fseek.
*
* @todo It's broken, needs fixing.
* @param file File to seek.
* @param offset Position to seek to.
* @param whence Either SEEK_SET, SEEK_CUR or SEEK_END.
* @return The position moved to.
*/
off_t pack_seek(Packfile_t* file, off_t offset, int whence) {
off_t ret;
DEBUG("Attempting to seek offset: %d, whence: %d", offset, whence);
switch(whence) {
#if HAS_POSIX
case SEEK_SET:
if((file->start + offset) > file->end) return -1;
ret = lseek(file->fd, file->start + offset, SEEK_SET);
if(ret != ((off_t)file->start + offset)) return -1;
break;
case SEEK_CUR:
if((file->start + offset) > file->end) return -1;
ret = lseek(file->fd, file->pos + offset, SEEK_SET);
if(ret != ((off_t)file->pos + offset)) return -1;
break;
case SEEK_END:
if((file->end + offset) < file->start) return -1;
ret = lseek(file->fd, file->end + offset, SEEK_SET);
if(ret != ((off_t)file->end - offset)) return -1;
break;
#else
case SEEK_SET:
if((file->start + offset) > file->end) return -1;
ret = fseek(file->fd, file->start + offset, SEEK_SET);
if(ret != (file->start + offset)) return -1;
break;
case SEEK_CUR:
if((file->start + offset) > file->end) return -1;
ret = fseek(file->fd, file->pos + offset -1, SEEK_SET);
if(ret != (file->pos + offset)) return -1;
break;
case SEEK_END:
if((file->end + offset) < file->start) return -1;
ret = fseek(file->fd, file->end + offset, SEEK_SET);
if(ret != (file->end - offset)) return -1;
break;
#endif /* HAS_POSIX */
default:
ERR("Whence is not one of SEEK_SET, SEEK_CUR or SEEK_END");
return -1;
}
return ret - file->start;
}
/**
* @brief Get the current position in the file.
* @param file Packfile to get the position from.
* @return The current position in the file.
*/
long pack_tell(Packfile_t* file) {
return file->pos - file->start;
}
/**
* @brief Read a file from a packfile.
*/
static void* pack_readfilePack(Packfile_t* file,
const char* filename, uint32_t* filesize) {
void* buf;
char* str;
int size, bytes;
/* Read the entire file. */
size = file->end - file->start;
buf = malloc(size+1);
if(buf == NULL) {
ERR("Unable to allocate %d bytes of memory!", size+1);
free(file);
return NULL;
}
if((bytes = pack_read(file, buf, size)) != size) {
ERR("Reading '%s' from packfile. Expected %d bytes got %d bytes",
filename, size, bytes);
free(buf);
free(file);
return NULL;
}
DEBUG("Read %d bytes from '%s'", bytes, filename);
str = buf;
str[size] = '\0'; /* Append size '\0' for it to validate as a string. */
/* Check the md5. */
md5_state_t md5;
md5_byte_t* md5val = malloc(16);
md5_byte_t* md5fd = malloc(16);
md5_init(&md5);
md5_append(&md5, buf, bytes);
md5_finish(&md5, md5val);
#if HAS_POSIX
if((bytes = read(file->fd, md5fd, 16)) == -1)
#else
if((bytes = fread(md5fd, 1, 16, file->fp)) == -1)
#endif /* HAS_POSIX */
WARN("Failure to read MD5, continuing anyway..");
else if(memcmp(md5val, md5fd, 16))
WARN("MD5 gives different value, possible memory corruption, continuing..");
free(md5val);
free(md5fd);
/* Cleanup. */
if(pack_close(file) == -1) {
ERR("Closing packfile");
free(file);
return NULL;
}
DEBUG("Closed '%s' in '%s'", filename, packfile);
if(filesize)
*filesize = size;
return buf;
}
/**
* @brief Read an entire file into memory.
* @param packfile Name of the packfile to read from.
* @param filename Name of the packed file to read.
* @param filesize Is set to the size of the file.
* @return A pointer to the sata in the file or NULL if an error accured.
*/
void* pack_readfile(const char* packfile, const char* filename, uint32_t* filesize) {
Packfile_t* file;
/* Initialize size to 0. */
if(filesize)
*filesize = 0;
/* Open the packfile. */
file = pack_open(packfile, filename);
if(file == NULL) {
ERR("Opening packfile '%s'.", packfile);
return NULL;
}
DEBUG("Opened file '%s' from '%s'", filename, packfile);
return pack_readfilePack(file, filename, filesize);
}
/**
* @fn char** pack_listfiles(const char* packfile, uint32_t* nfiles)
*
* @brief Get what files are in the packfile.
*
* Each name must be freed individually afterwords and the array of names too.
* @param packfile Packfile to query it's internal files.
* @param nfiles Stores the amount of files in packfile.
* @return An array of filenames in packfile.
*/
char** pack_listfiles(const char* packfile, uint32_t* nfiles) {
int j;
uint32_t i;
Packfile_t file;
char** filenames;
char* buf = malloc(sizeof(magic));
*nfiles = 0;
#if HAS_POSIX
file.fd = open(packfile, O_RDONLY);
if(file.fd == -1) {
#else
file.fp = fopen(packfile, "rb");
if(file.fp == NULL) {
#endif /* HAS_POSIX */
ERR("opening %s: %s", packfile, strerror(errno));
return NULL;
}
READ(&file, buf, sizeof(magic)); /* Make sure it's a packfile. */
if(memcmp(buf, &magic, sizeof(magic))) {
ERR("File %s is not a valid packfile", packfile);
return NULL;
}
READ(&file, nfiles, 4);
filenames = malloc(((*nfiles)+1)*sizeof(char*));
for(i = 0; i < *nfiles; i++) {
/* Start searching files. */
j = 0;
filenames[i] = malloc(PATH_MAX * sizeof(char));
READ(&file, &filenames[i][j], 1); /* Get the name. */
while(filenames[i][j++] != '\0')
READ(&file, &filenames[i][j], 1);
READ(&file, buf, 4); /* skip the location. */
}
free(buf);
#if HAS_POSIX
close(file.fd);
#else
fclose(file.fp);
#endif /* HAS_POSIX */
return filenames;
}
/**
* @brief Read an entire file from the cache.
*/
void* pack_readfileCached(Packcache_t* cache, const char* filename, uint32_t* filesize) {
Packfile_t* file;
file = pack_openFromCache(cache, filename);
if(file == NULL)
ERR("Unable to create packfile from packcache.");
return pack_readfilePack(file, filename, filesize);
}
/**
* @brief Get the list of files in a packcache.
* @param cache Cache to get list of files from.
* @param nfiles Number of files in the list.
* @return A read only list of the files from the pack cache.
*/
const char** pack_listfilesCached(Packcache_t* cache, uint32_t* nfiles) {
*nfiles = cache->nindex;
return (const char**)cache->index;
}
/**
* @brief Closes a packfile.
* @param file Packfile to close.
* @return 0 on success.
*/
int pack_close(Packfile_t* file) {
int i;
/* Close files. */
#if HAS_POSIX
i = close(file->fd);
#else
i = fclose(file->fp);
#endif /* HAS_POSIX */
/* Free memory. */
free(file);
return (i) ? -1 : 0;
}
static int packrw_seek(SDL_RWops* rw, int offset, int whence) {
Packfile_t* packfile;
packfile = rw->hidden.unknown.data1;
return pack_seek(packfile, offset, whence);
}
static int packrw_read(SDL_RWops* rw, void* ptr, int size, int maxnum) {
int i;
ssize_t ret;
char* buf;
Packfile_t* packfile;
packfile = rw->hidden.unknown.data1;
buf = ptr;
/* Read the data. */
for(i = 0; i < maxnum; i++) {
ret = pack_read(packfile, &buf[i*size], size);
if(ret != size)
break;
}
return i;
}
static int packrw_write(SDL_RWops* rw, const void* ptr, int size, int num) {
(void) rw;
(void) ptr;
(void) size;
(void) num;
return -1;
}
static int packrw_close(SDL_RWops* rw) {
Packfile_t* packfile;
packfile = rw->hidden.unknown.data1;
return pack_close(packfile);
}
/**
* @brief Create a rwops from a packfile.
* @param packfile Packfile to create rwops from.
* @return rwops created from packfile.
*/
static SDL_RWops* pack_rwopsRaw(Packfile_t* packfile) {
SDL_RWops* rw;
/* Create the rwops. */
rw = SDL_AllocRW();
if(rw == NULL) {
WARN("Unable to allocate SDL_RWops.");
return NULL;
}
/* Set the functions. */
rw->seek = packrw_seek;
rw->read = packrw_read;
rw->write = packrw_write;
rw->close = packrw_close;
/* Set the packfile as the hidden data. */
rw->hidden.unknown.data1 = packfile;
return rw;
}
/**
* @brief Create an rwops for a file in a packfile.
* @param packfile Packfile to get file from.
* @param filename File within packfile to create rwops from.
* @return SDL_RWops interacting with the file in the packfile.
*/
SDL_RWops* pack_rwops(const char* packfile, const char* filename) {
Packfile_t* pack;
/* Open the packfile. */
pack = pack_open(packfile, filename);
if(pack == NULL)
return NULL;
/* Return the rwops. */
return pack_rwopsRaw(pack);
}
/**
* @brief Create an rwops for a file in a packcache.
* @param cache Packcache to get file from.
* @param filename File within the cache to create rwops from.
* @return SDL_RWops interacting with the file in the packcache.
*/
SDL_RWops* pack_rwopsCached(Packcache_t* cache, const char* filename) {
Packfile_t* packfile;
/* Open the packfile. */
packfile = pack_openFromCache(cache, filename);
if(packfile == NULL)
return NULL;
/* Return the rwops. */
return pack_rwopsRaw(packfile);
}