Added ability to descend into multi-ROM ZIP archives.

git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2450 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
stephena 2012-04-27 13:34:30 +00:00
parent e3ba9f0ec2
commit 973a8edf30
5 changed files with 150 additions and 16 deletions

View File

@ -12,7 +12,7 @@
Release History Release History
=========================================================================== ===========================================================================
3.6.1 to 3.7: (April xx, 2012) 3.6.1 to 3.7: (May xx, 2012)
* Updated the CompuMate keyboard handler to recognize more keys on an * Updated the CompuMate keyboard handler to recognize more keys on an
actual keyboard, instead of having to remember the weird combinations actual keyboard, instead of having to remember the weird combinations
@ -27,6 +27,11 @@
* Updated FA2 bankswitch scheme (Star Castle) to emulate load/save * Updated FA2 bankswitch scheme (Star Castle) to emulate load/save
high score functionality to the Harmony cart flash RAM. high score functionality to the Harmony cart flash RAM.
* Added ability for ROM launcher to 'descend' into ZIP files when it
contains more than one ROM file. This means you no longer have to unzip
a multi-file archive before using each ROM. Thanks go to Roland
Schabenberger (webOS maintainer) for this idea and sample code.
* Replaced commandline argument 'uselauncher' with 'exitlauncher'. The * Replaced commandline argument 'uselauncher' with 'exitlauncher'. The
new option specifies the behaviour of the ROM launcher when exiting new option specifies the behaviour of the ROM launcher when exiting
a ROM (always exit to launcher, or only when the launcher was actually a ROM (always exit to launcher, or only when the launcher was actually

View File

@ -130,6 +130,20 @@ inline string BSPF_toString(int num)
return buf.str(); return buf.str();
} }
// Test whether two characters are equal (case insensitive)
static bool BSPF_equalsIgnoreCaseChar(char ch1, char ch2)
{
return toupper((unsigned char)ch1) == toupper((unsigned char)ch2);
}
// Find location (if any) of the second string within the first,
// starting from 'startpos' in the first string
inline size_t BSPF_findIgnoreCase(const string& s1, const string& s2, int startpos = 0)
{
string::const_iterator pos = std::search(s1.begin()+startpos, s1.end(),
s2.begin(), s2.end(), BSPF_equalsIgnoreCaseChar);
return pos == s1.end() ? string::npos : pos - (s1.begin()+startpos);
}
// Test whether two strings are equal (case insensitive) // Test whether two strings are equal (case insensitive)
inline bool BSPF_equalsIgnoreCase(const string& s1, const string& s2) inline bool BSPF_equalsIgnoreCase(const string& s1, const string& s2)
{ {
@ -150,17 +164,12 @@ inline bool BSPF_startsWithIgnoreCase(const char* s1, const char* s2)
return BSPF_strncasecmp(s1, s2, strlen(s2)) == 0; return BSPF_strncasecmp(s1, s2, strlen(s2)) == 0;
} }
// Test whether two characters are equal (case insensitive) // Test whether the first string ends with the second one (case insensitive)
static bool BSPF_equalsIgnoreCaseChar(char ch1, char ch2) inline bool BSPF_endsWithIgnoreCase(const string& s1, const string& s2)
{ {
return toupper((unsigned char)ch1) == toupper((unsigned char)ch2); return (s1.length() >= s2.length()) ?
} (BSPF_findIgnoreCase(s1, s2, s1.length() - s2.length()) != string::npos) :
// Find location (if any) of the second string within the first false;
inline size_t BSPF_findIgnoreCase(const string& s1, const string& s2)
{
string::const_iterator pos = std::search(s1.begin(), s1.end(),
s2.begin(), s2.end(), BSPF_equalsIgnoreCaseChar);
return pos == s1.end() ? string::npos : pos - s1.begin();
} }
static const string EmptyString(""); static const string EmptyString("");

View File

@ -758,6 +758,26 @@ uInt8* OSystem::openROM(string file, string& md5, uInt32& size)
uInt8* image = 0; uInt8* image = 0;
// Split zip file path and file name within zip archive
string fileInZip = "";
FilesystemNode fileNode(file);
if (!fileNode.exists())
{
size_t slashPos = file.rfind(BSPF_PATH_SEPARATOR);
if(slashPos != string::npos)
{
string parent = file.substr(0, slashPos);
string name = file.substr(slashPos+1);
FilesystemNode parentNode(parent);
if(parentNode.exists() && !parentNode.isDirectory() &&
BSPF_endsWithIgnoreCase(parent, ".zip"))
{
fileInZip = name;
file = parent;
}
}
}
// Try to open the file as a zipped archive // Try to open the file as a zipped archive
// If that fails, we assume it's just a gzipped or normal data file // If that fails, we assume it's just a gzipped or normal data file
unzFile tz; unzFile tz;
@ -784,8 +804,11 @@ uInt8* OSystem::openROM(string file, string& md5, uInt32& size)
if(BSPF_equalsIgnoreCase(ext, ".a26") || BSPF_equalsIgnoreCase(ext, ".bin") || if(BSPF_equalsIgnoreCase(ext, ".a26") || BSPF_equalsIgnoreCase(ext, ".bin") ||
BSPF_equalsIgnoreCase(ext, ".rom")) BSPF_equalsIgnoreCase(ext, ".rom"))
{ {
file = filename; if(fileInZip.empty() || fileInZip == filename)
break; {
file = filename;
break;
}
} }
} }

View File

@ -44,6 +44,7 @@
#include "StringList.hxx" #include "StringList.hxx"
#include "StringListWidget.hxx" #include "StringListWidget.hxx"
#include "Widget.hxx" #include "Widget.hxx"
#include "unzip.h"
#include "LauncherDialog.hxx" #include "LauncherDialog.hxx"
@ -341,12 +342,55 @@ void LauncherDialog::updateListing(const string& nameToSelect)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LauncherDialog::loadDirListing() void LauncherDialog::loadDirListing()
{ {
if(!myCurrentNode.isDirectory()) if(!myCurrentNode.isDirectory() && !myCurrentNode.exists())
return; return;
FSList files; FSList files;
files.reserve(2048); files.reserve(2048);
myCurrentNode.getChildren(files, FilesystemNode::kListAll);
if(myCurrentNode.isDirectory())
{
myCurrentNode.getChildren(files, FilesystemNode::kListAll);
}
else
{
unzFile tz;
if((tz = unzOpen(myCurrentNode.getPath().c_str())) != NULL)
{
if(unzGoToFirstFile(tz) == UNZ_OK)
{
unz_file_info ufo;
for(;;) // Loop through all files for valid 2600 images
{
// Longer filenames might be possible, but I don't
// think people would name files that long in zip files...
char filename[1024];
unzGetCurrentFileInfo(tz, &ufo, filename, 1024, 0, 0, 0, 0);
filename[1023] = '\0';
if(strlen(filename) >= 4)
{
// Grab 3-character extension
const char* ext = filename + strlen(filename) - 4;
if(BSPF_equalsIgnoreCase(ext, ".a26") || BSPF_equalsIgnoreCase(ext, ".bin") ||
BSPF_equalsIgnoreCase(ext, ".rom"))
{
FilesystemNode newFile(AbstractFilesystemNode::getAbsolutePath(
filename, myCurrentNode.getPath(), ""));
files.push_back(newFile);
}
}
// Scan the next file in the zip
if(unzGoToNextFile(tz) != UNZ_OK)
break;
}
}
}
}
// Add '[..]' to indicate previous folder // Add '[..]' to indicate previous folder
if(myCurrentNode.hasParent()) if(myCurrentNode.hasParent())
@ -364,7 +408,9 @@ void LauncherDialog::loadDirListing()
// that we want - if there are no extensions, it implies show all files // that we want - if there are no extensions, it implies show all files
// In this way, showing all files is on the 'fast code path' // In this way, showing all files is on the 'fast code path'
if(isDir) if(isDir)
{
name = " [" + name + "]"; name = " [" + name + "]";
}
else if(myRomExts.size() > 0) else if(myRomExts.size() > 0)
{ {
// Skip over those names we've filtered out // Skip over those names we've filtered out
@ -478,6 +524,46 @@ bool LauncherDialog::matchPattern(const string& s, const string& pattern) const
return false; return false;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int LauncherDialog::filesInArchive(const string& archive)
{
int count = -1;
unzFile tz;
if((tz = unzOpen(archive.c_str())) != NULL)
{
count = 0;
if(unzGoToFirstFile(tz) == UNZ_OK)
{
unz_file_info ufo;
for(;;) // Loop through all files for valid 2600 images
{
// Longer filenames might be possible, but I don't
// think people would name files that long in zip files...
char filename[1024];
unzGetCurrentFileInfo(tz, &ufo, filename, 1024, 0, 0, 0, 0);
filename[1023] = '\0';
if(strlen(filename) >= 4)
{
// Grab 3-character extension
const char* ext = filename + strlen(filename) - 4;
if(BSPF_equalsIgnoreCase(ext, ".a26") || BSPF_equalsIgnoreCase(ext, ".bin") ||
BSPF_equalsIgnoreCase(ext, ".rom"))
++count;
}
// Scan the next file in the zip
if(unzGoToNextFile(tz) != UNZ_OK)
break;
}
}
}
return count;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LauncherDialog::handleKeyDown(StellaKey key, StellaMod mod, char ascii) void LauncherDialog::handleKeyDown(StellaKey key, StellaMod mod, char ascii)
{ {
@ -519,8 +605,18 @@ void LauncherDialog::handleCommand(CommandSender* sender, int cmd,
const string& md5 = myGameList->md5(item); const string& md5 = myGameList->md5(item);
string extension; string extension;
int numFilesInArchive = filesInArchive(rom);
bool isArchive = !myGameList->isDir(item) &&
BSPF_endsWithIgnoreCase(rom, ".zip");
// Directory's should be selected (ie, enter them and redisplay) // Directory's should be selected (ie, enter them and redisplay)
if(myGameList->isDir(item)) // Archives should be entered if they contain more than 1 file
if(isArchive && numFilesInArchive < 1)
{
instance().frameBuffer().showMessage("Archive does not contain any valid ROM files",
kMiddleCenter, true);
}
else if((isArchive && numFilesInArchive > 1) || myGameList->isDir(item))
{ {
string dirname = ""; string dirname = "";
if(myGameList->name(item) == " [..]") if(myGameList->name(item) == " [..]")

View File

@ -96,6 +96,7 @@ class LauncherDialog : public Dialog
void handleContextMenu(); void handleContextMenu();
void setListFilters(); void setListFilters();
bool matchPattern(const string& s, const string& pattern) const; bool matchPattern(const string& s, const string& pattern) const;
int filesInArchive(const string& archive);
private: private:
ButtonWidget* myStartButton; ButtonWidget* myStartButton;