FileSystem: Change file lookup to match all extensions

Previously, file lookup (ie. extension supplementing) would only
try out a few predetermined extensions based on the asset type.
This was not accurate in regard to RMXP's behavior, which will
happily match "some_asset" against "some_asset.abcef" and try
to open it.

Some games make use of this quirk and rename their ogg audio files
to "*.dat" or similar to thwart users from copying them.

This change also makes it easier to read arbitrary formats
supported by SDL_image without modifying mkxp.
This commit is contained in:
Jonas Kulla 2015-02-17 01:42:11 +01:00
parent 87462fd7b0
commit 44eaaf5985
5 changed files with 172 additions and 167 deletions

View File

@ -196,8 +196,8 @@ void ALStream::closeSource()
void ALStream::openSource(const std::string &filename) void ALStream::openSource(const std::string &filename)
{ {
const char *ext; char ext[8];
shState->fileSystem().openRead(srcOps, filename.c_str(), FileSystem::Audio, false, &ext); shState->fileSystem().openRead(srcOps, filename.c_str(), false, ext, sizeof(ext));
needsRewind.clear(); needsRewind.clear();
/* Try to read ogg file signature */ /* Try to read ogg file signature */

View File

@ -236,9 +236,10 @@ struct BitmapPrivate
Bitmap::Bitmap(const char *filename) Bitmap::Bitmap(const char *filename)
{ {
SDL_RWops ops; SDL_RWops ops;
const char *extension; char ext[8];
shState->fileSystem().openRead(ops, filename, FileSystem::Image, false, &extension);
SDL_Surface *imgSurf = IMG_LoadTyped_RW(&ops, 1, extension); shState->fileSystem().openRead(ops, filename, false, ext, sizeof(ext));
SDL_Surface *imgSurf = IMG_LoadTyped_RW(&ops, 1, ext);
if (!imgSurf) if (!imgSurf)
throw Exception(Exception::SDLError, "Error loading image '%s': %s", throw Exception(Exception::SDLError, "Error loading image '%s': %s",

View File

@ -230,18 +230,36 @@ static int SDL_RWopsCloseFree(SDL_RWops *ops)
return result; return result;
} }
/* Copies the first srcN characters from src into dst,
* or the full string if srcN == -1. Never writes more
* than dstMax, and guarantees dst to be null terminated.
* Returns copied bytes (minus terminating null) */
static size_t
strcpySafe(char *dst, const char *src,
size_t dstMax, int srcN)
{
if (srcN < 0)
srcN = strlen(src);
size_t cpyMax = std::min<size_t>(dstMax-1, srcN);
memcpy(dst, src, cpyMax);
dst[cpyMax] = '\0';
return cpyMax;
}
const Uint32 SDL_RWOPS_PHYSFS = SDL_RWOPS_UNKNOWN+10; const Uint32 SDL_RWOPS_PHYSFS = SDL_RWOPS_UNKNOWN+10;
struct FileSystemPrivate struct FileSystemPrivate
{ {
/* Maps: lower case filename, To: actual (mixed case) filename. /* Maps: lower case filepath without extension,
* To: mixed case full filepath
* This is for compatibility with games that take Windows' * This is for compatibility with games that take Windows'
* case insensitivity for granted */ * case insensitivity for granted */
BoostHash<std::string, std::string> pathCache; BoostHash<std::string, std::string> pathCache;
bool havePathCache; bool havePathCache;
std::vector<std::string> extensions[FileSystem::Undefined+1];
/* Attempt to locate an extension string in a filename. /* Attempt to locate an extension string in a filename.
* Either a pointer into the input string pointing at the * Either a pointer into the input string pointing at the
* extension, or null is returned */ * extension, or null is returned */
@ -261,120 +279,129 @@ struct FileSystemPrivate
return 0; return 0;
} }
/* Complete filename via regular physfs lookup */ struct CompleteFilenameData
bool completeFilenameReg(const char *filename, {
FileSystem::FileType type, bool found;
/* Contains the incomplete filename we're looking for;
* when found, we write the complete filename into this
* same buffer */
char *outBuf;
/* Length of incomplete file name */
size_t filenameLen;
/* Maximum we can write into outBuf */
size_t outBufN;
};
static void completeFilenameRegCB(void *data, const char *,
const char *fname)
{
CompleteFilenameData &d = *static_cast<CompleteFilenameData*>(data);
if (d.found)
return;
if (strncmp(d.outBuf, fname, d.filenameLen) != 0)
return;
/* If fname matches up to a following '.' (meaning the rest is part
* of the extension), or up to a following '\0' (full match), we've
* found our file */
switch (fname[d.filenameLen])
{
case '.' :
/* Overwrite the incomplete file name we looked for with
* the full version containing any extensions */
strcpySafe(d.outBuf, fname, d.outBufN, -1);
case '\0' :
d.found = true;
}
}
bool completeFilenameReg(const char *filepath,
char *outBuffer, char *outBuffer,
size_t outN, size_t outN)
const char **foundExt)
{ {
/* Try supplementing extensions to find an existing path */ strcpySafe(outBuffer, filepath, outN, -1);
const std::vector<std::string> &extList = extensions[type];
for (size_t i = 0; i < extList.size(); ++i) size_t len = strlen(outBuffer);
char *delim;
/* Find the deliminator separating directory and file name */
for (delim = outBuffer + len; delim > outBuffer; --delim)
if (*delim == '/')
break;
bool root = (delim == outBuffer);
CompleteFilenameData d;
if (!root)
{ {
const char *ext = extList[i].c_str(); /* If we have such a deliminator, we set it to '\0' so we
* can pass the first half to PhysFS as the directory name,
* and compare all filenames against the second half */
d.outBuf = delim+1;
d.filenameLen = len - (delim - outBuffer + 1);
snprintf(outBuffer, outN, "%s.%s", filename, ext); *delim = '\0';
if (PHYSFS_exists(outBuffer))
{
if (foundExt)
*foundExt = ext;
return true;
} }
else
{
/* Otherwise the file is in the root directory */
d.outBuf = outBuffer;
d.filenameLen = len - (delim - outBuffer);
} }
/* Doing the check without supplemented extension d.found = false;
* fits the usage pattern of RMXP games */ d.outBufN = outN - (d.outBuf - outBuffer);
if (PHYSFS_exists(filename))
{
strncpy(outBuffer, filename, outN);
if (foundExt) PHYSFS_enumerateFilesCallback(root ? "" : outBuffer, completeFilenameRegCB, &d);
*foundExt = findExt(filename);
return true;
}
if (!d.found)
return false; return false;
/* Now we put the deliminator back in to form the completed
* file path (if required) */
if (delim != outBuffer)
*delim = '/';
return true;
} }
/* Complete filename via path cache */ bool completeFilenamePC(const char *filepath,
bool completeFilenamePC(const char *filename,
FileSystem::FileType type,
char *outBuffer, char *outBuffer,
size_t outN, size_t outN)
const char **foundExt)
{ {
size_t i; std::string lowCase(filepath);
char lowCase[512];
for (i = 0; i < sizeof(lowCase)-1 && filename[i]; ++i) for (size_t i = 0; i < lowCase.size(); ++i)
lowCase[i] = tolower(filename[i]); lowCase[i] = tolower(lowCase[i]);
lowCase[i] = '\0';
std::string key;
const std::vector<std::string> &extList = extensions[type];
for (size_t i = 0; i < extList.size(); ++i)
{
const char *ext = extList[i].c_str();
snprintf(outBuffer, outN, "%s.%s", lowCase, ext);
key = outBuffer;
if (pathCache.contains(key))
{
strncpy(outBuffer, pathCache[key].c_str(), outN);
if (foundExt)
*foundExt = ext;
return true;
}
}
key = lowCase;
if (pathCache.contains(key))
{
strncpy(outBuffer, pathCache[key].c_str(), outN);
if (foundExt)
*foundExt = findExt(filename);
return true;
}
if (!pathCache.contains(lowCase))
return false; return false;
const std::string &fullPath = pathCache[lowCase];
strcpySafe(outBuffer, fullPath.c_str(), outN, fullPath.size());
return true;
} }
/* Try to complete 'filename' with file extensions bool completeFilename(const char *filepath,
* based on 'type'. If no combination could be found,
* returns false, and 'foundExt' is untouched */
bool completeFileName(const char *filename,
FileSystem::FileType type,
char *outBuffer, char *outBuffer,
size_t outN, size_t outN)
const char **foundExt)
{ {
if (havePathCache) if (havePathCache)
return completeFilenamePC(filename, type, outBuffer, outN, foundExt); return completeFilenamePC(filepath, outBuffer, outN);
else else
return completeFilenameReg(filename, type, outBuffer, outN, foundExt); return completeFilenameReg(filepath, outBuffer, outN);
} }
PHYSFS_File *openReadHandle(const char *filename, PHYSFS_File *openReadHandle(const char *filename,
FileSystem::FileType type, char *extBuf,
const char **foundExt) size_t extBufN)
{ {
char found[512]; char found[512];
if (!completeFileName(filename, type, found, sizeof(found), foundExt)) if (!completeFilename(filename, found, sizeof(found)))
throw Exception(Exception::NoFileError, "%s", filename); throw Exception(Exception::NoFileError, "%s", filename);
PHYSFS_File *handle = PHYSFS_openRead(found); PHYSFS_File *handle = PHYSFS_openRead(found);
@ -382,6 +409,21 @@ struct FileSystemPrivate
if (!handle) if (!handle)
throw Exception(Exception::PHYSFSError, "PhysFS: %s", PHYSFS_getLastError()); throw Exception(Exception::PHYSFSError, "PhysFS: %s", PHYSFS_getLastError());
if (!extBuf)
return handle;
for (char *q = found+strlen(found); q > found; --q)
{
if (*q == '/')
break;
if (*q != '.')
continue;
strcpySafe(extBuf, q+1, extBufN, -1);
break;
}
return handle; return handle;
} }
@ -408,45 +450,8 @@ FileSystem::FileSystem(const char *argv0,
bool allowSymlinks) bool allowSymlinks)
{ {
p = new FileSystemPrivate; p = new FileSystemPrivate;
p->havePathCache = false; p->havePathCache = false;
/* Image extensions */
p->extensions[Image].push_back("jpg");
p->extensions[Image].push_back("png");
/* Audio extensions */
const Sound_DecoderInfo **di;
for (di = Sound_AvailableDecoders(); *di; ++di)
{
const char **ext;
for (ext = (*di)->extensions; *ext; ++ext)
{
/* All reported extensions are uppercase,
* so we need to hammer them down first */
char buf[16];
for (size_t i = 0; i < sizeof(buf); ++i)
{
buf[i] = tolower((*ext)[i]);
if (!buf[i])
break;
}
p->extensions[Audio].push_back(buf);
}
}
if (rgssVer >= 2 && !contains(p->extensions[Audio], std::string("ogg")))
p->extensions[Audio].push_back("ogg");
p->extensions[Audio].push_back("mid");
p->extensions[Audio].push_back("midi");
/* Font extensions */
p->extensions[Font].push_back("ttf");
p->extensions[Font].push_back("otf");
PHYSFS_init(argv0); PHYSFS_init(argv0);
PHYSFS_registerArchiver(&RGSS1_Archiver); PHYSFS_registerArchiver(&RGSS1_Archiver);
@ -544,12 +549,22 @@ static void cacheEnumCB(void *d, const char *origdir,
std::string mixedCase(ptr); std::string mixedCase(ptr);
for (char *p = bufNfc; *p; ++p) for (char *q = bufNfc; *q; ++q)
*p = tolower(*p); *q = tolower(*q);
std::string lowerCase(ptr); p->pathCache.insert(std::string(ptr), mixedCase);
p->pathCache.insert(lowerCase, mixedCase); for (char *q = ptr+strlen(ptr); q > ptr; --q)
{
if (*q == '/')
break;
if (*q != '.')
continue;
*q = '\0';
p->pathCache.insert(std::string(ptr), mixedCase);
}
PHYSFS_enumerateFilesCallback(mixedCase.c_str(), cacheEnumCB, d); PHYSFS_enumerateFilesCallback(mixedCase.c_str(), cacheEnumCB, d);
} }
@ -558,7 +573,7 @@ void FileSystem::createPathCache()
{ {
#ifdef __APPLE__ #ifdef __APPLE__
CacheEnumCBData data(p); CacheEnumCBData data(p);
PHYSFS_enumerateFilesCallback("", cacheEnumCB, &data); PHYSFS_enumerateFilesCallback("", cacheEnumCB2, &data);
#else #else
PHYSFS_enumerateFilesCallback("", cacheEnumCB, p); PHYSFS_enumerateFilesCallback("", cacheEnumCB, p);
#endif #endif
@ -566,12 +581,6 @@ void FileSystem::createPathCache()
p->havePathCache = true; p->havePathCache = true;
} }
static void strToLower(std::string &str)
{
for (size_t i = 0; i < str.size(); ++i)
str[i] = tolower(str[i]);
}
struct FontSetsCBData struct FontSetsCBData
{ {
FileSystemPrivate *p; FileSystemPrivate *p;
@ -590,16 +599,21 @@ static void fontSetEnumCB(void *data, const char *,
if (!ext) if (!ext)
return; return;
std::string lower(ext); char lowExt[8];
strToLower(lower); size_t i;
if (!contains(p->extensions[FileSystem::Font], lower)) for (i = 0; i < sizeof(lowExt)-1 && ext[i]; ++i)
lowExt[i] = tolower(ext[i]);
lowExt[i] = '\0';
if (strcmp(lowExt, "ttf") && strcmp(lowExt, "otf"))
return; return;
std::string filename("Fonts/"); char filename[512];
filename += fname; snprintf(filename, sizeof(filename), "Fonts/%s", fname);
filename[sizeof(filename)-1] = '\0';
PHYSFS_File *handle = PHYSFS_openRead(filename.c_str()); PHYSFS_File *handle = PHYSFS_openRead(filename);
if (!handle) if (!handle)
return; return;
@ -621,11 +635,11 @@ void FileSystem::initFontSets(SharedFontState &sfs)
void FileSystem::openRead(SDL_RWops &ops, void FileSystem::openRead(SDL_RWops &ops,
const char *filename, const char *filename,
FileType type,
bool freeOnClose, bool freeOnClose,
const char **foundExt) char *extBuf,
size_t extBufN)
{ {
PHYSFS_File *handle = p->openReadHandle(filename, type, foundExt); PHYSFS_File *handle = p->openReadHandle(filename, extBuf, extBufN);
p->initReadOps(handle, ops, freeOnClose); p->initReadOps(handle, ops, freeOnClose);
} }
@ -640,9 +654,9 @@ void FileSystem::openReadRaw(SDL_RWops &ops,
p->initReadOps(handle, ops, freeOnClose); p->initReadOps(handle, ops, freeOnClose);
} }
bool FileSystem::exists(const char *filename, FileType type) bool FileSystem::exists(const char *filename)
{ {
char found[512]; char found[512];
return p->completeFileName(filename, type, found, sizeof(found), 0); return p->completeFilename(filename, found, sizeof(found));
} }

View File

@ -43,28 +43,18 @@ public:
* available font assets */ * available font assets */
void initFontSets(SharedFontState &sfs); void initFontSets(SharedFontState &sfs);
/* For extension supplementing */
enum FileType
{
Image = 0,
Audio,
Font,
Undefined
};
void openRead(SDL_RWops &ops, void openRead(SDL_RWops &ops,
const char *filename, const char *filename,
FileType type = Undefined,
bool freeOnClose = false, bool freeOnClose = false,
const char **foundExt = 0); char *extBuf = 0,
size_t extBufN = 0);
/* Circumvents extension supplementing */ /* Circumvents extension supplementing */
void openReadRaw(SDL_RWops &ops, void openReadRaw(SDL_RWops &ops,
const char *filename, const char *filename,
bool freeOnClose = false); bool freeOnClose = false);
bool exists(const char *filename, bool exists(const char *filename);
FileType type = Undefined);
private: private:
FileSystemPrivate *p; FileSystemPrivate *p;

View File

@ -197,12 +197,12 @@ SoundBuffer *SoundEmitter::allocateBuffer(const std::string &filename)
{ {
/* Buffer not in cashe, needs to be loaded */ /* Buffer not in cashe, needs to be loaded */
SDL_RWops dataSource; SDL_RWops dataSource;
const char *extension; char ext[8];
shState->fileSystem().openRead(dataSource, filename.c_str(), shState->fileSystem().openRead(dataSource, filename.c_str(),
FileSystem::Audio, false, &extension); false, ext, sizeof(ext));
Sound_Sample *sampleHandle = Sound_NewSample(&dataSource, extension, 0, STREAM_BUF_SIZE); Sound_Sample *sampleHandle = Sound_NewSample(&dataSource, ext, 0, STREAM_BUF_SIZE);
if (!sampleHandle) if (!sampleHandle)
{ {