22 #ifndef SCUMM_DETECTION_INTERNAL_H 23 #define SCUMM_DETECTION_INTERNAL_H 25 #include "common/debug.h" 27 #include "common/md5.h" 28 #include "common/punycode.h" 29 #include "common/translation.h" 31 #include "gui/error.h" 33 #include "scumm/detection_tables.h" 34 #include "scumm/scumm-md5.h" 35 #include "scumm/file_nes.h" 40 #include "scumm/detection_steam.h" 46 kMD5FileSizeLimit = 1024 * 1024
49 static int compareMD5Table(
const void *a,
const void *b) {
50 const char *key = (
const char *)a;
52 return strcmp(key, elem->md5);
55 static const MD5Table *findInMD5Table(
const char *md5) {
56 uint32 arraySize =
ARRAYSIZE(md5table) - 1;
57 return (
const MD5Table *)bsearch(md5, md5table, arraySize,
sizeof(
MD5Table), compareMD5Table);
70 case kGenDiskNumSteam:
71 case kGenRoomNumSteam: {
72 const SteamIndexFile *indexFile = lookUpSteamIndexFile(pattern, platform);
74 error(
"Unable to find Steam executable from detection pattern");
76 result = indexFile->executableName;
89 case kGenHEMacNoParens:
98 error(
"generateFilenameForDetection: Unsupported genMethod");
121 if (!scumm_stricmp(file->getName().c_str(), name.c_str())) {
135 if (disk1.hasSuffix(
".prg")) {
144 if (strcmp(gfp->pattern,
"maniacdemo.d64") == 0)
149 for (Common::String::iterator it = disk2.begin(); it != disk2.end(); ++it) {
161 if (diskImg->open(disk1.c_str()) && diskImg->openSubFile(
"00.LFL")) {
162 debugC(0, kDebugGlobalDetection,
"Success");
180 if (gs->
id == GID_MONKEY || gs->
id == GID_MONKEY2) {
182 if (gs->
platform == Common::kPlatformFMTowns)
185 const char *
const basenames[] = { gs->
gameid,
"monster", 0 };
186 static const char *
const extensions[] = {
"sou",
199 if (file->isDirectory())
202 for (
int i = 0; basenames[i]; ++i) {
205 for (
int j = 0; extensions[j]; ++j) {
206 if ((basename + extensions[j]).equalsIgnoreCase(file->getName()))
220 if (searchFSNode(fslist,
"chinese_gb16x12.fnt", fontFile) || (searchFSNode(fslist,
"video", fontFile) && fontFile.
getChild(
"chinese_gb16x12.fnt").
exists())) {
221 debugC(0, kDebugGlobalDetection,
"Chinese detected");
222 return Common::ZH_CHN;
225 for (uint i = 0; ruScummPatcherTable[i].patcherName; i++) {
227 if (ruScummPatcherTable[i].gameid ==
id && (variant ==
nullptr || strcmp(variant, ruScummPatcherTable[i].variant) == 0)
229 debugC(0, kDebugGlobalDetection,
"Russian detected");
230 return Common::RU_RUS;
234 if (
id != GID_CMI &&
id != GID_DIG) {
237 if (searchFSNode(fslist,
"korean.trs", langFile)) {
238 debugC(0, kDebugGlobalDetection,
"Korean fan translation detected");
239 return Common::KO_KOR;
242 if (
id == GID_REBEL2) {
246 if (searchFSNode(fslist,
"SYSTM", systmDir)
248 && systmDir.
getChildren(systmList, Common::FSNode::kListFilesOnly)
249 && searchFSNode(systmList,
"GAME.TRS", langFile)
250 && trs.
open(langFile)) {
251 switch (trs.
size()) {
253 return Common::EN_ANY;
255 return Common::IT_ITA;
257 return Common::ES_ESP;
259 return Common::DE_DEU;
261 return Common::FR_FRA;
268 return originalLanguage;
277 const char *filename = (
id == GID_CMI) ?
"LANGUAGE.TAB" :
"LANGUAGE.BND";
280 if (searchFSNode(fslist, filename, langFile))
286 if (searchFSNode(fslist,
"RESOURCE", resDir)
288 && resDir.
getChildren(tmpList, Common::FSNode::kListFilesOnly)
289 && searchFSNode(tmpList, filename, langFile)) {
295 && searchFSNode(fslist,
"DIG", resDir)
297 && resDir.
getChildren(tmpList, Common::FSNode::kListFilesOnly)
298 && searchFSNode(tmpList, filename, langFile)) {
304 && searchFSNode(fslist,
"VIDEO", resDir)
306 && resDir.
getChildren(tmpList, Common::FSNode::kListFilesOnly)
307 && searchFSNode(tmpList, filename, langFile)) {
312 uint size = tmp.
size();
316 return Common::EN_ANY;
318 return Common::ZH_TWN;
320 return Common::DE_DEU;
322 return Common::FR_FRA;
324 return Common::IT_ITA;
326 return Common::KO_KOR;
328 return Common::PT_BRA;
331 return Common::RU_RUS;
333 return Common::ES_ESP;
340 return Common::DE_DEU;
342 return Common::FR_FRA;
344 return Common::IT_ITA;
346 return Common::PT_BRA;
348 return Common::ES_ESP;
350 return Common::JA_JPN;
352 return Common::ZH_TWN;
359 return originalLanguage;
364 dr.language = md5Entry->language;
365 dr.extra = md5Entry->extra;
369 if (g->
gameid[0] == 0 || !scumm_stricmp(md5Entry->gameid, g->
gameid)) {
372 if (g->
variant == 0 || !scumm_stricmp(md5Entry->variant, g->
variant)) {
381 if (g->
id == GID_MONKEY_EGA && g->
platform == Common::kPlatformDOS) {
386 if (searchFSNode(fslist,
"903.LFL", resFile))
393 if (searchFSNode(fslist,
"DISK03.LEC", resFile))
400 if (searchFSNode(fslist,
"DISK04.LEC", resFile))
407 if ((!md5Lfl903.empty() && md5Lfl903 ==
"54d4e17df08953b483d17416043345b9") ||
408 (!md5Disk03.empty() && md5Disk03 ==
"a8ab7e8eaa322d825beb6c5dee28f17d") ||
409 (!md5Disk04.empty() && md5Disk04 ==
"f338cc1d3117c1077a3a9d0c1d70b1e8")) {
410 ::GUI::displayErrorDialog(_(
"This version of Monkey Island can't be played, because Limited Run Games " 411 "provided corrupted DISK03.LEC, DISK04.LEC and 903.LFL files.\n\nPlease contact their technical " 412 "support for replacement files, or look online for some guides which can help you recover valid " 413 "files from the KryoFlux dumps that Limited Run Games also provided."));
420 dr.game.
gameid = md5Entry->gameid;
425 if (md5Entry->platform != Common::kPlatformUnknown) {
426 dr.game.
platform = md5Entry->platform;
427 }
else if (gfp->platform != Common::kPlatformUnknown) {
433 if (dr.game.
id == GID_MANIAC && !strcmp(gfp->pattern,
"%02d.MAN")) {
434 dr.extra =
"V1 Demo";
439 if (dr.language == UNK_LANG || dr.language == Common::EN_ANY) {
440 dr.language = detectLanguage(fslist, dr.game.
id, g->
variant, dr.language);
444 if (dr.game.
platform == Common::kPlatformMacintosh && dr.game.
version >= 5 && dr.game.
heversion == 0 && strstr(gfp->pattern,
"Data"))
453 static void composeFileHashMap(DescMap &fileMD5Map,
const Common::FSList &fslist,
int depth,
const char *
const *globs) {
461 if (!file->isDirectory()) {
465 fileMD5Map[file->getName()] = d;
470 bool matched =
false;
471 for (
const char *
const *glob = globs; *glob; glob++)
481 if (file->getChildren(files, Common::FSNode::kListAll)) {
482 composeFileHashMap(fileMD5Map, files, depth - 1, globs);
508 composeFileHashMap(fileMD5Map, fslist, 3, directoryGlobs);
514 if (gameid && scumm_stricmp(gameid, gfp->gameid))
520 Common::String file(generateFilenameForDetection(gfp->pattern, gfp->genMethod, gfp->platform));
523 const bool isRebel1Mac = !scumm_stricmp(gfp->gameid,
"rebel1") && platform == Common::kPlatformMacintosh;
525 if (fileMD5Map.
contains(file +
".bin") && (platform == Common::Platform::kPlatformMacintosh || platform == Common::Platform::kPlatformUnknown)) {
527 platform = Common::Platform::kPlatformMacintosh;
528 }
else if (isRebel1Mac && fileMD5Map.
contains(file +
".rsrc")) {
530 platform = Common::Platform::kPlatformMacintosh;
536 dr.fp.pattern = gfp->pattern;
537 dr.fp.genMethod = gfp->genMethod;
539 dr.language = gfp->language;
561 bool isDiskImg = (file.hasSuffix(
".d64") || file.hasSuffix(
".dsk") || file.hasSuffix(
".prg"));
564 tmp = openDiskImage(d.node, gfp);
566 debugC(2, kDebugGlobalDetection,
"Falling back to disk-based detection");
574 if (tmp && !md5str.empty()) {
575 int64 filesize = tmp->
size();
578 d.md5Entry = findInMD5Table(md5str.c_str());
580 if (!d.md5Entry && (platform == Common::Platform::kPlatformMacintosh || platform == Common::Platform::kPlatformUnknown)) {
585 const MD5Table *dataMD5Entry = findInMD5Table(dataMD5.c_str());
588 d.md5Entry = dataMD5Entry;
589 filesize = dataStream->
size();
590 platform = Common::Platform::kPlatformMacintosh;
595 if (!d.md5Entry && isRebel1Mac) {
598 if (computeRebel1MacResourceForkMD5(d, baseFile, resourceMD5, resourceSize)) {
599 const MD5Table *resourceMD5Entry = findInMD5Table(resourceMD5.c_str());
600 if (resourceMD5Entry) {
602 d.md5Entry = resourceMD5Entry;
603 filesize = resourceSize;
604 platform = Common::Platform::kPlatformMacintosh;
614 computeGameSettingsFromMD5(fslist, gfp, d.md5Entry, dr);
617 debugC(1, kDebugGlobalDetection,
"SCUMM detector found matching file '%s' with MD5 %s, size %" PRId64
"\n",
618 file.c_str(), d.md5.c_str(), filesize);
643 if (gfp->genMethod == kGenRoomNumSteam || gfp->genMethod == kGenDiskNumSteam)
661 if (g->
gameid[0] == 0 || scumm_stricmp(gfp->gameid, g->
gameid))
667 if (platform != Common::kPlatformUnknown)
673 if (!scumm_stricmp(gfp->variant, g->
variant)) {
682 dr.language = detectLanguage(fslist, g->
id, g->
variant);
685 if (detectSpeech(fslist, g)) {
686 if (strstr(dr.game.
guioptions, GUIO_NOSPEECH) != NULL) {
687 if (g->
id == GID_MONKEY || g->
id == GID_MONKEY2)
692 warning(
"FIXME: fix NOSPEECH fallback");
698 if (testGame(g, fileMD5Map, file))
714 if (!tmp.
open(d.node)) {
719 if (file ==
"maniac1.d64" || file ==
"maniac1.dsk" || file ==
"zak1.d64") {
721 }
else if (file ==
"00.LFL") {
730 if (buf[0] == 0xbc && buf[1] == 0xb9) {
732 if (g->
id == GID_MANIAC && g->
platform == Common::kPlatformNES) {
736 }
else if ((buf[0] == 0xCE && buf[1] == 0xF5) ||
737 (buf[0] == 0xCD && buf[1] == 0xFE)) {
745 const bool has58LFL = fileMD5Map.
contains(
"58.LFL");
746 if (g->
id == GID_MANIAC && !has58LFL) {
747 }
else if (g->
id == GID_ZAK && has58LFL) {
750 }
else if (buf[0] == 0xFF && buf[1] == 0xFE) {
773 const bool has58LFL = fileMD5Map.
contains(
"58.LFL");
774 const bool has84LFL = fileMD5Map.
contains(
"84.LFL");
775 const bool has86LFL = fileMD5Map.
contains(
"86.LFL");
776 const bool has98LFL = fileMD5Map.
contains(
"98.LFL");
778 if (g->
id == GID_INDY3 && has98LFL && has84LFL) {
779 }
else if (g->
id == GID_ZAK && !has98LFL && !has86LFL && !has84LFL && has58LFL) {
780 }
else if (g->
id == GID_MANIAC && !has98LFL && !has86LFL && !has84LFL && !has58LFL) {
781 }
else if (g->
id == GID_LOOM && !has98LFL && (has86LFL != has84LFL)) {
784 }
else if (buf[4] ==
'0' && buf[5] ==
'R') {
833 if (g->
id == GID_INDY3 && fileMD5Map.
contains(
"05.LFL"))
837 if (g->
id != GID_INDY3 && fileMD5Map.
contains(
"93.LFL"))
841 if (g->
id == GID_LOOM && fileMD5Map.
contains(
"48.LFL"))
845 if (g->
id == GID_ZAK && fileMD5Map.
contains(
"60.LFL"))
849 if (g->
id == GID_LOOM && g->
platform != Common::kPlatformPCEngine && fileMD5Map.
contains(
"98.LFL"))
859 }
else if (file ==
"000.LFL") {
888 const bool has903LFL = fileMD5Map.
contains(
"903.LFL");
889 const bool hasDisk02 = fileMD5Map.
contains(
"DISK02.LEC");
894 if (g->
id == GID_PASS && !has903LFL && !hasDisk02) {
895 }
else if (g->
id == GID_LOOM && has903LFL && !hasDisk02) {
896 }
else if (g->
id == GID_MONKEY_VGA) {
897 }
else if (g->
id == GID_MONKEY_EGA) {
925 static const uint mtypes[] = {MT_PCSPK, MT_CMS, MT_PCJR, MT_ADLIB, MT_C64, MT_AMIGA, MT_APPLEIIGS, MT_TOWNS, MT_PC98, MT_SEGACD, 0, 0, 0, 0, MT_MACINTOSH};
926 int midiflags = res.game.
midi;
930 if (res.game.
platform == Common::kPlatformAmiga || (res.game.
platform == Common::kPlatformMacintosh && strncmp(res.extra,
"Steam", 6)) || res.game.
platform == Common::kPlatformC64) {
931 midiflags = MDT_NONE;
933 for (
int i = 0; i <
ARRAYSIZE(mtypes); ++i) {
940 for (
int i = 0; i <
ARRAYSIZE(mtypes); ++i) {
941 if (mtypes[i] && (midiflags & (1 << i)))
942 guiOptions += MidiDriver::musicType2GUIO(mtypes[i]);
945 if (midiflags & MDT_MIDI) {
946 guiOptions += MidiDriver::musicType2GUIO(MT_GM);
947 guiOptions += MidiDriver::musicType2GUIO(MT_MT32);
952 static const char *
const rmodes[] = { GUIO_RENDERHERCGREEN, GUIO_RENDERHERCAMBER, GUIO_RENDERCGABW, GUIO_RENDERCGACOMP, GUIO_RENDERCGA };
953 if (res.game.
platform == Common::kPlatformAmiga) {
954 for (
int i = 0; i <
ARRAYSIZE(rmodes); ++i) {
966 case Common::kPlatformC64:
967 defaultRenderOption = GUIO_RENDERC64;
968 defaultSoundOption = GUIO_MIDIC64;
970 case Common::kPlatformAmiga:
971 defaultRenderOption = GUIO_RENDERAMIGA;
972 defaultSoundOption = GUIO_MIDIAMIGA;
974 case Common::kPlatformApple2GS:
975 defaultRenderOption = GUIO_RENDERAPPLE2GS;
978 case Common::kPlatformMacintosh:
979 if (!strncmp(res.extra,
"Steam", 6)) {
980 defaultRenderOption = GUIO_RENDERVGA;
982 defaultRenderOption = GUIO_RENDERMACINTOSH;
983 defaultSoundOption = GUIO_MIDIMAC;
986 case Common::kPlatformFMTowns:
987 defaultRenderOption = GUIO_RENDERFMTOWNS;
990 case Common::kPlatformAtariST:
991 defaultRenderOption = GUIO_RENDERATARIST;
994 case Common::kPlatformDOS:
995 defaultRenderOption = (!strncmp(res.extra,
"EGA", 4) || !strncmp(res.extra,
"V1", 3) || !strncmp(res.extra,
"V2", 3)) ? GUIO_RENDEREGA : GUIO_RENDERVGA;
997 case Common::kPlatformUnknown:
999 defaultRenderOption = GUIO_RENDERVGA;
1009 if (!guiOptions.contains(defaultRenderOption))
1010 guiOptions += defaultRenderOption;
1012 if (!defaultSoundOption.empty() && !guiOptions.contains(defaultSoundOption))
1013 guiOptions += defaultSoundOption;
1020 #endif // SCUMM_DETECTION_INTERNAL_H
#define ARRAYSIZE(x)
Definition: util.h:114
virtual int64 size() const =0
Definition: macresman.h:126
uint32 read(void *dataPtr, uint32 dataSize) override
Definition: file_nes.h:30
FSNode getChild(const String &name) const
uint32 getResForkDataSize() const
String getName() const override
static String format(MSVC_PRINTF const char *fmt,...) GCC_PRINTF(1
bool matchString(const char *pat, bool ignoreCase=false, const char *wildcardExclusions=NULL) const
void addDirectory(const String &name, const Path &directory, int priority=0, int depth=1, bool flat=false)
Definition: detection.h:130
void warning(MSVC_PRINTF const char *s,...) GCC_PRINTF(1
virtual bool seek(int64 offset, int whence=SEEK_SET)=0
iterator end()
Definition: array.h:380
iterator begin()
Definition: array.h:375
byte heversion
Definition: detection.h:89
byte id
Definition: detection.h:83
virtual bool open(const Path &filename)
Definition: detection.h:154
Common::Platform platform
Definition: detection.h:105
void void void void void debugC(int level, uint32 debugChannel, MSVC_PRINTF const char *s,...) GCC_PRINTF(3
static const char kNativeSeparator
Definition: path.h:195
String computeResForkMD5AsString(uint32 length=0, bool tail=false, ProgressUpdateCallback progressUpdateCallback=nullptr, void *callbackParameter=nullptr) const
byte version
Definition: detection.h:86
Definition: detection.h:175
bool empty() const
Definition: array.h:352
bool isDirectory() const override
void replace(It begin, It end, const Dat &original, const Dat &replaced)
Definition: algorithm.h:448
bool open(const Path &fileName)
const char * guioptions
Definition: detection.h:110
Definition: detection.h:52
const char * variant
Definition: detection.h:68
Definition: archive.h:330
#define SearchMan
Definition: archive.h:498
uint32 features
Definition: detection.h:98
int64 size() const override
Definition: scumm-md5.h:12
String toString(char separator='/') const
bool contains(const Key &key) const
Definition: hashmap.h:598
static SeekableReadStream * openDataForkFromMacBinary(SeekableReadStream *inStream, DisposeAfterUse::Flag disposeAfterUse=DisposeAfterUse::NO)
void NORETURN_PRE error(MSVC_PRINTF const char *s,...) GCC_PRINTF(1
U32String punycode_decode(const String &src, bool *error=nullptr)
String computeStreamMD5AsString(ReadStream &stream, uint32 length=0, ProgressUpdateCallback progressUpdateCallback=nullptr, void *callbackParameter=nullptr)
Definition: detection_internal.h:104
bool getChildren(FSList &fslist, ListMode mode=kListDirectoriesOnly, bool hidden=true) const
void push_back(const t_T &element)
Definition: list.h:174
int midi
Definition: detection.h:92
Definition: detection.h:202
SeekableReadStream * createReadStream() const override
const char * gameid
Definition: detection.h:56
Definition: detection.h:139
Language
Definition: language.h:45