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 return originalLanguage;
251 const char *filename = (
id == GID_CMI) ?
"LANGUAGE.TAB" :
"LANGUAGE.BND";
254 if (searchFSNode(fslist, filename, langFile))
260 if (searchFSNode(fslist,
"RESOURCE", resDir)
262 && resDir.
getChildren(tmpList, Common::FSNode::kListFilesOnly)
263 && searchFSNode(tmpList, filename, langFile)) {
269 && searchFSNode(fslist,
"DIG", resDir)
271 && resDir.
getChildren(tmpList, Common::FSNode::kListFilesOnly)
272 && searchFSNode(tmpList, filename, langFile)) {
278 && searchFSNode(fslist,
"VIDEO", resDir)
280 && resDir.
getChildren(tmpList, Common::FSNode::kListFilesOnly)
281 && searchFSNode(tmpList, filename, langFile)) {
286 uint size = tmp.
size();
290 return Common::EN_ANY;
292 return Common::ZH_TWN;
294 return Common::DE_DEU;
296 return Common::FR_FRA;
298 return Common::IT_ITA;
300 return Common::KO_KOR;
302 return Common::PT_BRA;
305 return Common::RU_RUS;
307 return Common::ES_ESP;
314 return Common::DE_DEU;
316 return Common::FR_FRA;
318 return Common::IT_ITA;
320 return Common::PT_BRA;
322 return Common::ES_ESP;
324 return Common::JA_JPN;
326 return Common::ZH_TWN;
333 return originalLanguage;
338 dr.language = md5Entry->language;
339 dr.extra = md5Entry->extra;
343 if (g->
gameid[0] == 0 || !scumm_stricmp(md5Entry->gameid, g->
gameid)) {
346 if (g->
variant == 0 || !scumm_stricmp(md5Entry->variant, g->
variant)) {
355 if (g->
id == GID_MONKEY_EGA && g->
platform == Common::kPlatformDOS) {
360 if (searchFSNode(fslist,
"903.LFL", resFile))
367 if (searchFSNode(fslist,
"DISK03.LEC", resFile))
374 if (searchFSNode(fslist,
"DISK04.LEC", resFile))
381 if ((!md5Lfl903.empty() && md5Lfl903 ==
"54d4e17df08953b483d17416043345b9") ||
382 (!md5Disk03.empty() && md5Disk03 ==
"a8ab7e8eaa322d825beb6c5dee28f17d") ||
383 (!md5Disk04.empty() && md5Disk04 ==
"f338cc1d3117c1077a3a9d0c1d70b1e8")) {
384 ::GUI::displayErrorDialog(_(
"This version of Monkey Island can't be played, because Limited Run Games " 385 "provided corrupted DISK03.LEC, DISK04.LEC and 903.LFL files.\n\nPlease contact their technical " 386 "support for replacement files, or look online for some guides which can help you recover valid " 387 "files from the KryoFlux dumps that Limited Run Games also provided."));
394 dr.game.
gameid = md5Entry->gameid;
399 if (md5Entry->platform != Common::kPlatformUnknown) {
400 dr.game.
platform = md5Entry->platform;
401 }
else if (gfp->platform != Common::kPlatformUnknown) {
407 if (dr.game.
id == GID_MANIAC && !strcmp(gfp->pattern,
"%02d.MAN")) {
408 dr.extra =
"V1 Demo";
413 if (dr.language == UNK_LANG || dr.language == Common::EN_ANY) {
414 dr.language = detectLanguage(fslist, dr.game.
id, g->
variant, dr.language);
418 if (dr.game.
platform == Common::kPlatformMacintosh && dr.game.
version >= 5 && dr.game.
heversion == 0 && strstr(gfp->pattern,
"Data"))
427 static void composeFileHashMap(DescMap &fileMD5Map,
const Common::FSList &fslist,
int depth,
const char *
const *globs) {
435 if (!file->isDirectory()) {
439 fileMD5Map[file->getName()] = d;
444 bool matched =
false;
445 for (
const char *
const *glob = globs; *glob; glob++)
455 if (file->getChildren(files, Common::FSNode::kListAll)) {
456 composeFileHashMap(fileMD5Map, files, depth - 1, globs);
468 composeFileHashMap(fileMD5Map, fslist, 3, directoryGlobs);
474 if (gameid && scumm_stricmp(gameid, gfp->gameid))
480 Common::String file(generateFilenameForDetection(gfp->pattern, gfp->genMethod, gfp->platform));
483 if (fileMD5Map.
contains(file +
".bin") && (platform == Common::Platform::kPlatformMacintosh || platform == Common::Platform::kPlatformUnknown)) {
485 platform = Common::Platform::kPlatformMacintosh;
491 dr.fp.pattern = gfp->pattern;
492 dr.fp.genMethod = gfp->genMethod;
494 dr.language = gfp->language;
516 bool isDiskImg = (file.hasSuffix(
".d64") || file.hasSuffix(
".dsk") || file.hasSuffix(
".prg"));
519 tmp = openDiskImage(d.node, gfp);
521 debugC(2, kDebugGlobalDetection,
"Falling back to disk-based detection");
529 if (!md5str.empty()) {
530 int64 filesize = tmp->
size();
533 d.md5Entry = findInMD5Table(md5str.c_str());
535 if (!d.md5Entry && (platform == Common::Platform::kPlatformMacintosh || platform == Common::Platform::kPlatformUnknown)) {
539 const MD5Table *dataMD5Entry = findInMD5Table(dataMD5.c_str());
542 d.md5Entry = dataMD5Entry;
543 filesize = dataStream->
size();
544 platform = Common::Platform::kPlatformMacintosh;
554 computeGameSettingsFromMD5(fslist, gfp, d.md5Entry, dr);
557 debugC(1, kDebugGlobalDetection,
"SCUMM detector found matching file '%s' with MD5 %s, size %lld\n",
558 file.c_str(), md5str.c_str(), filesize);
583 if (gfp->genMethod == kGenRoomNumSteam || gfp->genMethod == kGenDiskNumSteam)
601 if (g->
gameid[0] == 0 || scumm_stricmp(gfp->gameid, g->
gameid))
607 if (platform != Common::kPlatformUnknown)
613 if (!scumm_stricmp(gfp->variant, g->
variant)) {
622 dr.language = detectLanguage(fslist, g->
id, g->
variant);
625 if (detectSpeech(fslist, g)) {
626 if (strchr(dr.game.
guioptions, GUIO_NOSPEECH[0]) != NULL) {
627 if (g->
id == GID_MONKEY || g->
id == GID_MONKEY2)
632 warning(
"FIXME: fix NOSPEECH fallback");
638 if (testGame(g, fileMD5Map, file))
654 if (!tmp.
open(d.node)) {
659 if (file ==
"maniac1.d64" || file ==
"maniac1.dsk" || file ==
"zak1.d64") {
661 }
else if (file ==
"00.LFL") {
670 if (buf[0] == 0xbc && buf[1] == 0xb9) {
672 if (g->
id == GID_MANIAC && g->
platform == Common::kPlatformNES) {
676 }
else if ((buf[0] == 0xCE && buf[1] == 0xF5) ||
677 (buf[0] == 0xCD && buf[1] == 0xFE)) {
685 const bool has58LFL = fileMD5Map.
contains(
"58.LFL");
686 if (g->
id == GID_MANIAC && !has58LFL) {
687 }
else if (g->
id == GID_ZAK && has58LFL) {
690 }
else if (buf[0] == 0xFF && buf[1] == 0xFE) {
713 const bool has58LFL = fileMD5Map.
contains(
"58.LFL");
714 const bool has84LFL = fileMD5Map.
contains(
"84.LFL");
715 const bool has86LFL = fileMD5Map.
contains(
"86.LFL");
716 const bool has98LFL = fileMD5Map.
contains(
"98.LFL");
718 if (g->
id == GID_INDY3 && has98LFL && has84LFL) {
719 }
else if (g->
id == GID_ZAK && !has98LFL && !has86LFL && !has84LFL && has58LFL) {
720 }
else if (g->
id == GID_MANIAC && !has98LFL && !has86LFL && !has84LFL && !has58LFL) {
721 }
else if (g->
id == GID_LOOM && !has98LFL && (has86LFL != has84LFL)) {
724 }
else if (buf[4] ==
'0' && buf[5] ==
'R') {
773 if (g->
id == GID_INDY3 && fileMD5Map.
contains(
"05.LFL"))
777 if (g->
id != GID_INDY3 && fileMD5Map.
contains(
"93.LFL"))
781 if (g->
id == GID_LOOM && fileMD5Map.
contains(
"48.LFL"))
785 if (g->
id == GID_ZAK && fileMD5Map.
contains(
"60.LFL"))
789 if (g->
id == GID_LOOM && g->
platform != Common::kPlatformPCEngine && fileMD5Map.
contains(
"98.LFL"))
799 }
else if (file ==
"000.LFL") {
828 const bool has903LFL = fileMD5Map.
contains(
"903.LFL");
829 const bool hasDisk02 = fileMD5Map.
contains(
"DISK02.LEC");
834 if (g->
id == GID_PASS && !has903LFL && !hasDisk02) {
835 }
else if (g->
id == GID_LOOM && has903LFL && !hasDisk02) {
836 }
else if (g->
id == GID_MONKEY_VGA) {
837 }
else if (g->
id == GID_MONKEY_EGA) {
865 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};
866 int midiflags = res.game.
midi;
870 if (res.game.
platform == Common::kPlatformAmiga || (res.game.
platform == Common::kPlatformMacintosh && strncmp(res.extra,
"Steam", 6)) || res.game.
platform == Common::kPlatformC64) {
871 midiflags = MDT_NONE;
873 for (
int i = 0; i <
ARRAYSIZE(mtypes); ++i) {
876 uint pos = guiOptions.
findFirstOf(MidiDriver::musicType2GUIO(mtypes[i]));
877 if (pos != Common::String::npos)
878 guiOptions.
erase(pos, 1);
882 for (
int i = 0; i <
ARRAYSIZE(mtypes); ++i) {
883 if (mtypes[i] && (midiflags & (1 << i)))
884 guiOptions += MidiDriver::musicType2GUIO(mtypes[i]);
887 if (midiflags & MDT_MIDI) {
888 guiOptions += MidiDriver::musicType2GUIO(MT_GM);
889 guiOptions += MidiDriver::musicType2GUIO(MT_MT32);
894 static const char *
const rmodes[] = { GUIO_RENDERHERCGREEN, GUIO_RENDERHERCAMBER, GUIO_RENDERCGABW, GUIO_RENDERCGACOMP, GUIO_RENDERCGA };
895 if (res.game.
platform == Common::kPlatformAmiga) {
896 for (
int i = 0; i <
ARRAYSIZE(rmodes); ++i) {
898 if (pos != Common::String::npos)
899 guiOptions.
erase(pos, 1);
910 case Common::kPlatformC64:
911 defaultRenderOption = GUIO_RENDERC64;
912 defaultSoundOption = GUIO_MIDIC64;
914 case Common::kPlatformAmiga:
915 defaultRenderOption = GUIO_RENDERAMIGA;
916 defaultSoundOption = GUIO_MIDIAMIGA;
918 case Common::kPlatformApple2GS:
919 defaultRenderOption = GUIO_RENDERAPPLE2GS;
922 case Common::kPlatformMacintosh:
923 if (!strncmp(res.extra,
"Steam", 6)) {
924 defaultRenderOption = GUIO_RENDERVGA;
926 defaultRenderOption = GUIO_RENDERMACINTOSH;
927 defaultSoundOption = GUIO_MIDIMAC;
930 case Common::kPlatformFMTowns:
931 defaultRenderOption = GUIO_RENDERFMTOWNS;
934 case Common::kPlatformAtariST:
935 defaultRenderOption = GUIO_RENDERATARIST;
938 case Common::kPlatformDOS:
939 defaultRenderOption = (!strncmp(res.extra,
"EGA", 4) || !strncmp(res.extra,
"V1", 3) || !strncmp(res.extra,
"V2", 3)) ? GUIO_RENDEREGA : GUIO_RENDERVGA;
941 case Common::kPlatformUnknown:
943 defaultRenderOption = GUIO_RENDERVGA;
953 if (!guiOptions.contains(defaultRenderOption))
954 guiOptions += defaultRenderOption;
956 if (!defaultSoundOption.empty() && !guiOptions.contains(defaultSoundOption))
957 guiOptions += defaultSoundOption;
964 #endif // SCUMM_DETECTION_INTERNAL_H
#define ARRAYSIZE(x)
Definition: util.h:91
virtual int64 size() const =0
uint32 read(void *dataPtr, uint32 dataSize) override
Definition: file_nes.h:29
FSNode getChild(const String &name) 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
Definition: detection.h:123
void warning(MSVC_PRINTF const char *s,...) GCC_PRINTF(1
iterator end()
Definition: array.h:379
iterator begin()
Definition: array.h:374
byte heversion
Definition: detection.h:82
byte id
Definition: detection.h:76
virtual bool open(const Path &filename)
Definition: detection.h:147
size_t findFirstOf(value_type c, size_t pos=0) const
Common::Platform platform
Definition: detection.h:98
String computeStreamMD5AsString(ReadStream &stream, uint32 length=0)
static const char kNativeSeparator
Definition: path.h:195
byte version
Definition: detection.h:79
Definition: detection.h:165
bool empty() const
Definition: array.h:351
bool isDirectory() const override
const char * guioptions
Definition: detection.h:103
Definition: detection.h:45
const char * variant
Definition: detection.h:61
#define SearchMan
Definition: archive.h:476
uint32 features
Definition: detection.h:91
int64 size() const override
void erase(uint32 p, uint32 len=npos)
Definition: scumm-md5.h:12
String toString(char separator='/') const
bool contains(const Key &key) const
Definition: hashmap.h:594
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)
Definition: detection_internal.h:104
bool getChildren(FSList &fslist, ListMode mode=kListDirectoriesOnly, bool hidden=true) const
void void void void void debugC(int level, uint32 debugChannels, MSVC_PRINTF const char *s,...) GCC_PRINTF(3
void push_back(const t_T &element)
Definition: list.h:140
int midi
Definition: detection.h:85
Definition: detection.h:192
SeekableReadStream * createReadStream() const override
const char * gameid
Definition: detection.h:49
Definition: detection.h:132
Language
Definition: language.h:45