From 86debbd679d20f1188d2ba015e9d108cbbc85f1a Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sat, 25 Apr 2009 06:42:01 +0000 Subject: Added code to enable Discworld 2 to play directly from the CD (only the first CD - Cd swap still doesn't work) or from files copied to the hard disk without the .smp/txt/idx files being properly renamed (again only for the first Cd). svn-id: r40141 --- engines/tinsel/detection.cpp | 158 +++++++++++++++++++++++++++++++++++++++++++ engines/tinsel/drives.cpp | 54 ++++++++++++++- engines/tinsel/drives.h | 12 ++++ engines/tinsel/handle.cpp | 4 ++ engines/tinsel/sound.cpp | 2 +- engines/tinsel/sound.h | 3 +- engines/tinsel/strres.cpp | 3 +- engines/tinsel/tinsel.cpp | 3 + 8 files changed, 234 insertions(+), 5 deletions(-) (limited to 'engines/tinsel') diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp index 4028ea2245..e02eec44a3 100644 --- a/engines/tinsel/detection.cpp +++ b/engines/tinsel/detection.cpp @@ -27,6 +27,7 @@ #include "engines/advancedDetector.h" #include "common/file.h" +#include "common/md5.h" #include "common/savefile.h" #include "tinsel/cursor.h" @@ -475,6 +476,7 @@ public: } virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + const ADGameDescription *fallbackDetect(const Common::FSList &fslist) const; virtual bool hasFeature(MetaEngineFeature f) const; virtual SaveStateList listSaves(const char *target) const; @@ -542,6 +544,162 @@ bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGa return gd != 0; } +struct SizeMD5 { + int size; + char md5[32+1]; +}; +typedef Common::HashMap SizeMD5Map; +typedef Common::HashMap FileMap; +typedef Common::Array ADGameDescList; + +/** + * Fallback detection scans the list of Discworld 2 targets to see if it can detect an installation + * where the files haven't been renamed (i.e. don't have the '1' just before the extension) + */ +const ADGameDescription *TinselMetaEngine::fallbackDetect(const Common::FSList &fslist) const { + Common::String extra; + FileMap allFiles; + SizeMD5Map filesSizeMD5; + + const ADGameFileDescription *fileDesc; + const Tinsel::TinselGameDescription *g; + + if (fslist.empty()) + return NULL; + + // First we compose a hashmap of all files in fslist. + // Includes nifty stuff like removing trailing dots and ignoring case. + for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { + if (file->isDirectory()) { + if (!scumm_stricmp(file->getName().c_str(), "dw2")) { + // Probably Discworld 2 subfolder on CD, so add it's contents as well + Common::FSList files; + if (file->getChildren(files, Common::FSNode::kListAll)) { + Common::FSList::const_iterator file2; + for (file2 = files.begin(); file2 != files.end(); ++file2) { + if (file2->isDirectory()) + continue; + + Common::String fname = file2->getName(); + allFiles[fname] = *file2; + } + } + } + continue; + } + + Common::String tstr = file->getName(); + + allFiles[tstr] = *file; // Record the presence of this file + } + + // Check which files are included in some dw2 ADGameDescription *and* present + // in fslist without a '1' suffix character. Compute MD5s and file sizes for these files. + for (g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) { + if (strcmp(g->desc.gameid, "dw2") != 0) + continue; + + for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) { + // Get the next filename, stripping off any '1' suffix character + char tempFilename[50]; + strcpy(tempFilename, fileDesc->fileName); + char *pOne = strchr(tempFilename, '1'); + if (pOne) strcpy(pOne, pOne + 1); + + Common::String fname(tempFilename); + if (allFiles.contains(fname) && !filesSizeMD5.contains(fname)) { + SizeMD5 tmp; + if (!md5_file_string(allFiles[fname], tmp.md5, detectionParams.md5Bytes)) + tmp.md5[0] = 0; + + Common::File testFile; + if (testFile.open(allFiles[fname])) + tmp.size = (int32)testFile.size(); + else + tmp.size = -1; + + filesSizeMD5[fname] = tmp; + } + } + } + + ADGameDescList matched; + int maxFilesMatched = 0; + bool gotAnyMatchesWithAllFiles = false; + + // MD5 based matching + uint i; + for (i = 0, g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) { + if (strcmp(g->desc.gameid, "dw2") != 0) + continue; + + bool fileMissing = false; + + if ((detectionParams.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->desc.extra != extra) + continue; + + bool allFilesPresent = true; + + // Try to match all files for this game + for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) { + // Get the next filename, stripping off any '1' suffix character + char tempFilename[50]; + strcpy(tempFilename, fileDesc->fileName); + char *pOne = strchr(tempFilename, '1'); + if (pOne) strcpy(pOne, pOne + 1); + + Common::String tstr(tempFilename); + + if (!filesSizeMD5.contains(tstr)) { + fileMissing = true; + allFilesPresent = false; + break; + } + + if (fileDesc->md5 != NULL && 0 != strcmp(fileDesc->md5, filesSizeMD5[tstr].md5)) { + fileMissing = true; + break; + } + + if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSizeMD5[tstr].size) { + fileMissing = true; + break; + } + } + + if (allFilesPresent) + gotAnyMatchesWithAllFiles = true; + + if (!fileMissing) { + // Count the number of matching files. Then, only keep those + // entries which match a maximal amount of files. + int curFilesMatched = 0; + for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) + curFilesMatched++; + + if (curFilesMatched > maxFilesMatched) { + maxFilesMatched = curFilesMatched; + + for (uint j = 0; j < matched.size();) { + if (matched[j]->flags & ADGF_KEEPMATCH) + ++j; + else + matched.remove_at(j); + } + matched.push_back((const ADGameDescription *)g); + } else if (curFilesMatched == maxFilesMatched) { + matched.push_back((const ADGameDescription *)g); + } + } + } + + // We didn't find a match + if (matched.empty()) + return NULL; + + return *matched.begin(); +} + int TinselMetaEngine::getMaximumSaveSlot() const { return 99; } void TinselMetaEngine::removeSaveState(const char *target, int slot) const { diff --git a/engines/tinsel/drives.cpp b/engines/tinsel/drives.cpp index a674211f66..6803ababa2 100644 --- a/engines/tinsel/drives.cpp +++ b/engines/tinsel/drives.cpp @@ -24,6 +24,7 @@ * CD/drive handling functions */ +#include "gui/message.h" #include "tinsel/drives.h" #include "tinsel/scene.h" #include "tinsel/tinsel.h" @@ -32,7 +33,7 @@ namespace Tinsel { -static char currentCD = '1'; +char currentCD = '1'; static uint32 cdFlags[] = { fCd1, fCd2, fCd3, fCd4, fCd5, fCd6, fCd7, fCd8 }; static bool bChangingCD = false; @@ -93,9 +94,29 @@ int GetCD(int flags) { return cd; } +static uint32 lastTime = 0; +extern LANGUAGE sampleLanguage; + void DoCdChange(void) { - if (bChangingCD) { + if (bChangingCD && (g_system->getMillis() > (lastTime + 1000))) { + lastTime = g_system->getMillis(); _vm->_sound->closeSampleStream(); + + // Use the filesize of the sample file to determine, for Discworld 2, which CD it is + if (TinselV2) { + TinselFile f; + if (!f.open(_vm->getSampleFile(sampleLanguage))) + // No CD present + return; + + char sampleCdNumber = (f.size() >= (200 * 1024 * 1024)) ? '1' : '2'; + + f.close(); + + if (currentCD != sampleCdNumber) + return; + } + _vm->_sound->openSampleFiles(); ChangeLanguage(TextLanguage()); bChangingCD = false; @@ -126,4 +147,33 @@ bool GotoCD(void) { return true; } +bool TinselFile::_warningShown = false; + +bool TinselFile::open(const Common::String &filename) { + if (Common::File::open(filename)) { + // If it's the sample file, strip off the CD number from the filename + + + return true; + } + + if (!TinselV2) + return false; + + // Check if the file being requested is the *1.* or *2.* files + const char *fname = filename.c_str(); + const char *p = strchr(fname, '1'); + if (!p) + p = strchr(fname, '2'); + if (!p || (*(p + 1) != '.')) + return false; + + // Form a filename without the CD number character + char newFilename[50]; + strncpy(newFilename, fname, p - fname); + strcpy(newFilename + (p - fname), p + 1); + + return Common::File::open(newFilename); +} + } // end of namespace Tinsel diff --git a/engines/tinsel/drives.h b/engines/tinsel/drives.h index 4441466f82..339f4384d0 100644 --- a/engines/tinsel/drives.h +++ b/engines/tinsel/drives.h @@ -27,6 +27,7 @@ #ifndef TINSEL_DRIVES_H #define TINSEL_DRIVES_H +#include "common/file.h" #include "tinsel/dw.h" #include "tinsel/coroutine.h" @@ -44,6 +45,8 @@ namespace Tinsel { #define fAllCds (fCd1|fCd2|fCd3|fCd4|fCd5|fCd6|fCd7|fCd8) +extern char currentCD; + void DoCdChange(void); void CdCD(CORO_PARAM); @@ -58,6 +61,15 @@ void SetNextCD(int cdNumber); bool GotoCD(void); +class TinselFile: public Common::File { +private: + static bool _warningShown; +public: + virtual bool open(const Common::String &filename); + char getCdNumber(); +}; + + } // end of namespace Tinsel #endif /* TINSEL_DRIVES_H */ diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp index f3ef9b3e4b..cfe91f7216 100644 --- a/engines/tinsel/handle.cpp +++ b/engines/tinsel/handle.cpp @@ -420,6 +420,10 @@ byte *LockMem(SCNHANDLE offset) { if (pH->pNode->pBaseAddr == NULL) error("Out of memory"); + if (TinselV2) { + SetCD(pH->flags2 & fAllCds); + CdCD(nullContext); + } LoadFile(pH, true); // make sure address is valid diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp index 5c4f74e0d2..54178a898a 100644 --- a/engines/tinsel/sound.cpp +++ b/engines/tinsel/sound.cpp @@ -399,7 +399,7 @@ void SoundManager::openSampleFiles(void) { if (_vm->getFeatures() & GF_FLOPPY || _vm->getFeatures() & GF_DEMO) return; - Common::File f; + TinselFile f; if (_sampleIndex) // already allocated diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h index 8b969a5f48..15919f8ec5 100644 --- a/engines/tinsel/sound.h +++ b/engines/tinsel/sound.h @@ -34,6 +34,7 @@ #include "tinsel/dw.h" #include "tinsel/tinsel.h" +#include "tinsel/drives.h" namespace Tinsel { @@ -87,7 +88,7 @@ protected: long _sampleIndexLen; /** file stream for sample file */ - Common::File _sampleStream; + TinselFile _sampleStream; bool offscreenChecks(int x, int &y); int8 getPan(int x); diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp index bbf02301db..b8a92572eb 100644 --- a/engines/tinsel/strres.cpp +++ b/engines/tinsel/strres.cpp @@ -25,6 +25,7 @@ */ #include "tinsel/dw.h" +#include "tinsel/drives.h" #include "tinsel/sound.h" #include "tinsel/strres.h" #include "common/file.h" @@ -80,7 +81,7 @@ LANGUAGE textLanguage, sampleLanguage = TXT_ENGLISH; * @param newLang The new language */ void ChangeLanguage(LANGUAGE newLang) { - Common::File f; + TinselFile f; uint32 textLen = 0; // length of buffer textLanguage = newLang; diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp index 8fe0446932..a3760272b1 100644 --- a/engines/tinsel/tinsel.cpp +++ b/engines/tinsel/tinsel.cpp @@ -862,6 +862,9 @@ TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + // Add DW2 subfolder to search path in case user is running directly from the CDs + Common::File::addDefaultDirectory(_gameDataDir.getChild("dw2")); + const GameSettings *g; const char *gameid = ConfMan.get("gameid").c_str(); -- cgit v1.2.3