diff --git a/Changes.txt b/Changes.txt index 3ed158917..8d02cc2d6 100644 --- a/Changes.txt +++ b/Changes.txt @@ -12,7 +12,7 @@ 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 actual keyboard, instead of having to remember the weird combinations @@ -27,6 +27,11 @@ * Updated FA2 bankswitch scheme (Star Castle) to emulate load/save 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 new option specifies the behaviour of the ROM launcher when exiting a ROM (always exit to launcher, or only when the launcher was actually diff --git a/src/common/bspf.hxx b/src/common/bspf.hxx index 49c34441b..afbf60dd3 100644 --- a/src/common/bspf.hxx +++ b/src/common/bspf.hxx @@ -130,6 +130,20 @@ inline string BSPF_toString(int num) 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) 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; } -// Test whether two characters are equal (case insensitive) -static bool BSPF_equalsIgnoreCaseChar(char ch1, char ch2) +// Test whether the first string ends with the second one (case insensitive) +inline bool BSPF_endsWithIgnoreCase(const string& s1, const string& s2) { - return toupper((unsigned char)ch1) == toupper((unsigned char)ch2); -} -// Find location (if any) of the second string within the first -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(); + return (s1.length() >= s2.length()) ? + (BSPF_findIgnoreCase(s1, s2, s1.length() - s2.length()) != string::npos) : + false; } static const string EmptyString(""); diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 4a83d65a4..6ba2697fa 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -758,6 +758,26 @@ uInt8* OSystem::openROM(string file, string& md5, uInt32& size) 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 // If that fails, we assume it's just a gzipped or normal data file unzFile tz; @@ -784,8 +804,11 @@ uInt8* OSystem::openROM(string file, string& md5, uInt32& size) if(BSPF_equalsIgnoreCase(ext, ".a26") || BSPF_equalsIgnoreCase(ext, ".bin") || BSPF_equalsIgnoreCase(ext, ".rom")) { - file = filename; - break; + if(fileInZip.empty() || fileInZip == filename) + { + file = filename; + break; + } } } diff --git a/src/gui/LauncherDialog.cxx b/src/gui/LauncherDialog.cxx index 2de240e00..6f3877c8d 100644 --- a/src/gui/LauncherDialog.cxx +++ b/src/gui/LauncherDialog.cxx @@ -44,6 +44,7 @@ #include "StringList.hxx" #include "StringListWidget.hxx" #include "Widget.hxx" +#include "unzip.h" #include "LauncherDialog.hxx" @@ -341,12 +342,55 @@ void LauncherDialog::updateListing(const string& nameToSelect) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void LauncherDialog::loadDirListing() { - if(!myCurrentNode.isDirectory()) + if(!myCurrentNode.isDirectory() && !myCurrentNode.exists()) return; FSList files; 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 if(myCurrentNode.hasParent()) @@ -364,7 +408,9 @@ void LauncherDialog::loadDirListing() // 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' if(isDir) + { name = " [" + name + "]"; + } else if(myRomExts.size() > 0) { // Skip over those names we've filtered out @@ -478,6 +524,46 @@ bool LauncherDialog::matchPattern(const string& s, const string& pattern) const 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) { @@ -519,8 +605,18 @@ void LauncherDialog::handleCommand(CommandSender* sender, int cmd, const string& md5 = myGameList->md5(item); 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) - 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 = ""; if(myGameList->name(item) == " [..]") diff --git a/src/gui/LauncherDialog.hxx b/src/gui/LauncherDialog.hxx index e1e6fdad0..6f4e01cad 100644 --- a/src/gui/LauncherDialog.hxx +++ b/src/gui/LauncherDialog.hxx @@ -96,6 +96,7 @@ class LauncherDialog : public Dialog void handleContextMenu(); void setListFilters(); bool matchPattern(const string& s, const string& pattern) const; + int filesInArchive(const string& archive); private: ButtonWidget* myStartButton;