1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
|
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/engines/tinsel/detection.cpp $
* $Id: detection.cpp 49788 2010-06-15 10:59:23Z sev $
*
*/
#include "base/plugins.h"
#include "engines/advancedDetector.h"
#include "common/file.h"
#include "common/md5.h"
#include "common/savefile.h"
#include "tinsel/bmv.h"
#include "tinsel/cursor.h"
#include "tinsel/tinsel.h"
#include "tinsel/savescn.h" // needed by TinselMetaEngine::listSaves
namespace Tinsel {
struct TinselGameDescription {
ADGameDescription desc;
int gameID;
int gameType;
uint32 features;
uint16 version;
};
uint32 TinselEngine::getGameID() const {
return _gameDescription->gameID;
}
uint32 TinselEngine::getFeatures() const {
return _gameDescription->features;
}
Common::Language TinselEngine::getLanguage() const {
return _gameDescription->desc.language;
}
Common::Platform TinselEngine::getPlatform() const {
return _gameDescription->desc.platform;
}
uint16 TinselEngine::getVersion() const {
return _gameDescription->version;
}
}
static const PlainGameDescriptor tinselGames[] = {
{"tinsel", "Tinsel engine game"},
{"dw", "Discworld"},
{"dw2", "Discworld 2: Missing Presumed ...!?"},
{0, 0}
};
#include "tinsel/detection_tables.h"
static const ADParams detectionParams = {
// Pointer to ADGameDescription or its superset structure
(const byte *)Tinsel::gameDescriptions,
// Size of that superset structure
sizeof(Tinsel::TinselGameDescription),
// Number of bytes to compute MD5 sum for
5000,
// List of all engine targets
tinselGames,
// Structure for autoupgrading obsolete targets
0,
// Name of single gameid (optional)
"tinsel",
// List of files for file-based fallback detection (optional)
0,
// Flags
0,
// Additional GUI options (for every game}
Common::GUIO_NONE,
// Maximum directory depth
1,
// List of directory globs
0
};
class TinselMetaEngine : public AdvancedMetaEngine {
public:
TinselMetaEngine() : AdvancedMetaEngine(detectionParams) {}
virtual const char *getName() const {
return "Tinsel Engine";
}
virtual const char *getOriginalCopyright() const {
return "Tinsel (C) Psygnosis";
}
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;
virtual int getMaximumSaveSlot() const;
virtual void removeSaveState(const char *target, int slot) const;
};
bool TinselMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave);
}
bool Tinsel::TinselEngine::hasFeature(EngineFeature f) const {
return
#if 0
// FIXME: tinsel does not exit cleanly yet
(f == kSupportsRTL) ||
#endif
(f == kSupportsLoadingDuringRuntime);
}
namespace Tinsel {
extern int getList(Common::SaveFileManager *saveFileMan, const Common::String &target);
}
SaveStateList TinselMetaEngine::listSaves(const char *target) const {
Common::String pattern = target;
pattern = pattern + ".???";
Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern);
sort(files.begin(), files.end()); // Sort (hopefully ensuring we are sorted numerically..)
SaveStateList saveList;
int slotNum = 0;
for (Common::StringArray::const_iterator file = files.begin(); file != files.end(); ++file) {
// Obtain the last 3 digits of the filename, since they correspond to the save slot
slotNum = atoi(file->c_str() + file->size() - 3);
const Common::String &fname = *file;
Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fname);
if (in) {
in->readUint32LE(); // skip id
in->readUint32LE(); // skip size
in->readUint32LE(); // skip version
char saveDesc[Tinsel::SG_DESC_LEN];
in->read(saveDesc, sizeof(saveDesc));
saveDesc[Tinsel::SG_DESC_LEN - 1] = 0;
saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
delete in;
}
}
return saveList;
}
bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc;
if (gd) {
*engine = new Tinsel::TinselEngine(syst, gd);
}
return gd != 0;
}
struct SizeMD5 {
int size;
char md5[32+1];
};
typedef Common::HashMap<Common::String, SizeMD5, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> SizeMD5Map;
typedef Common::HashMap<Common::String, Common::FSNode, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap;
typedef Common::Array<const ADGameDescription*> 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) {
do {
*pOne = *(pOne + 1);
pOne++;
} while (*pOne);
}
Common::String fname(tempFilename);
if (allFiles.contains(fname) && !filesSizeMD5.contains(fname)) {
SizeMD5 tmp;
Common::File testFile;
if (testFile.open(allFiles[fname])) {
tmp.size = (int32)testFile.size();
if (!md5_file_string(testFile, tmp.md5, detectionParams.md5Bytes))
tmp.md5[0] = 0;
} else {
tmp.size = -1;
tmp.md5[0] = 0;
}
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) {
do {
*pOne = *(pOne + 1);
pOne++;
} while (*pOne);
}
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 {
Tinsel::setNeedLoad();
Tinsel::getList(g_system->getSavefileManager(), target);
g_system->getSavefileManager()->removeSavefile(Tinsel::ListEntry(slot, Tinsel::LE_NAME));
Tinsel::setNeedLoad();
Tinsel::getList(g_system->getSavefileManager(), target);
}
#if PLUGIN_ENABLED_DYNAMIC(TINSEL)
REGISTER_PLUGIN_DYNAMIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
#else
REGISTER_PLUGIN_STATIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
#endif
namespace Tinsel {
Common::Error TinselEngine::loadGameState(int slot) {
// FIXME: Hopefully this is only used when loading games via
// the launcher, since we do a hacky savegame slot to savelist
// entry mapping here.
//
// You might wonder why is needed and here is the answer:
// The save/load dialog of the GMM operates with the physical
// savegame slots, while Tinsel internally uses entry numbers in
// a savelist (which is sorted latest to first). Now to allow
// proper loading of (especially Discworld2) saves we need to
// get a savelist entry number instead of the physical slot.
//
// There are different possible solutions:
//
// One way to fix this would be to pass the filename instead of
// the savelist entry number to RestoreGame, though it could make
// problems how DW2 handles CD switches. Normally DW2 would pass
// '-2' as slot when it changes CDs.
//
// Another way would be to convert all of Tinsel to use physical
// slot numbers instead of savelist entry numbers for loading.
// This would also allow '-2' as slot for CD changes without
// any major hackery.
int listSlot = -1;
const int numStates = Tinsel::getList();
for (int i = 0; i < numStates; ++i) {
const char *fileName = Tinsel::ListEntry(i, Tinsel::LE_NAME);
const int saveSlot = atoi(fileName + strlen(fileName) - 3);
if (saveSlot == slot) {
listSlot = i;
break;
}
}
if (listSlot == -1)
return Common::kUnknownError; // TODO: proper error code
RestoreGame(listSlot);
return Common::kNoError; // TODO: return success/failure
}
#if 0
Common::Error TinselEngine::saveGameState(int slot, const char *desc) {
Common::String saveName = _vm->getSavegameFilename((int16)(slot + 1));
char saveDesc[SG_DESC_LEN];
Common::strlcpy(saveDesc, desc, SG_DESC_LEN);
SaveGame((char *)saveName.c_str(), saveDesc);
ProcessSRQueue(); // This shouldn't be needed, but for some reason it is...
return Common::kNoError; // TODO: return success/failure
}
#endif
} // End of namespace Tinsel
|