diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 162793ccf..2a83750ba 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1009,6 +1009,8 @@ source_group("Code\\Textures\\Formats" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOUR source_group("Code\\Utility" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/utility/.+") source_group("Code\\2D" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/2d/.+") source_group("Code\\Console" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/console/.+") +source_group("Code\\Fonts" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/fonts/.+") +source_group("Code\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+") source_group("Utility\\Audiolib" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/audiolib/.+") source_group("Utility\\Audiolib Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/audiolib/include/.+") source_group("Utility\\Audiolib Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/audiolib/src/.+") diff --git a/source/common/gamecontrol.h b/source/common/gamecontrol.h index 5a0256415..4c354ec4d 100644 --- a/source/common/gamecontrol.h +++ b/source/common/gamecontrol.h @@ -114,3 +114,22 @@ inline bool SoundEnabled() { return snd_enabled && !userConfig.nosound; } + + +enum +{ + GAMEFLAG_DUKE = 0x00000001, + GAMEFLAG_NAM = 0x00000002, + GAMEFLAG_NAPALM = 0x00000004, + GAMEFLAG_WW2GI = 0x00000008, + GAMEFLAG_ADDON = 0x00000010, + GAMEFLAG_SHAREWARE = 0x00000020, + GAMEFLAG_DUKEBETA = 0x00000060, // includes 0x20 since it's a shareware beta + GAMEFLAG_FURY = 0x00000080, + GAMEFLAG_RR = 0x00000100, + GAMEFLAG_RRRA = 0x00000200, + GAMEFLAG_BLOOD = 0x00000400, + GAMEFLAG_STANDALONE = 0x00000800, + GAMEFLAGMASK = 0x000007FF, // flags allowed from grpinfo + +}; \ No newline at end of file diff --git a/source/common/searchpaths.cpp b/source/common/searchpaths.cpp index 2f2eb3d41..d6003fb37 100644 --- a/source/common/searchpaths.cpp +++ b/source/common/searchpaths.cpp @@ -20,8 +20,12 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //------------------------------------------------------------------------- +// +// Search path management. Scan all directories for potential game content and return a list with all proper matches +// #include +#include "m_crc32.h" #include "i_specialpaths.h" #include "compat.h" #include "gameconfigfile.h" @@ -30,9 +34,35 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "sc_man.h" #include "resourcefile.h" #include "printf.h" -// -// Search path management -// +#include "gamecontrol.h" + + + + +// These two structs need to be expoted +struct GrpInfo +{ + FString name; + FString scriptname; + FString dirname; + FString defname; + FString rtsname; + uint32_t CRC = 0; + uint32_t dependencyCRC = 0; + size_t size = 0; + int flags = 0; + TArray loadfiles; + TArray loadart; +}; + + +struct GrpEntry +{ + FString FileName; + GrpInfo FileInfo; + uint32_t FileIndex; +}; + namespace fs = std::filesystem; @@ -584,12 +614,14 @@ struct FileEntry uintmax_t FileLength; uint64_t FileTime; uint32_t CRCValue; + uint32_t Index; }; TArray CollectAllFilesInSearchPath() { TArray filelist; auto paths = CollectSearchPaths(); + int index = 0; for(auto &path : paths) { auto fpath = fs::u8path(path.GetChars()); @@ -604,11 +636,13 @@ TArray CollectAllFilesInSearchPath() flentry.FileName = absolute(entry.path()).u8string().c_str(); flentry.FileLength = entry.file_size(); flentry.FileTime = entry.last_write_time().time_since_epoch().count(); + flentry.Index = index++; // to preserve order when working on the list. filelist.Push(flentry); } } } } + return filelist; } //========================================================================== @@ -617,7 +651,7 @@ TArray CollectAllFilesInSearchPath() // //========================================================================== -static TArray LoadGroupsCache(void) +static TArray LoadCRCCache(void) { auto cachepath = M_GetAppDataPath(false) + "/grpcrccache.txt"; FScanner sc; @@ -639,7 +673,7 @@ static TArray LoadGroupsCache(void) flentry.CRCValue = strtoull(sc.String, nullptr, 0); // Cannot use sc.Number because that's only 32 bit. } } - catch (std::runtime_error & err) + catch (std::runtime_error &) { // If there's a parsing error, return what we got and discard the rest. } @@ -652,12 +686,184 @@ static TArray LoadGroupsCache(void) // //========================================================================== -void ParseAllGrpInfos(TArray& filelist, FResourceFile *baseres) +static TArray ParseGrpInfo(const char *fn, FileReader &fr) { - auto basegrp = baseres->FindLump("demolition/demolition.grpinfo"); - if (basegrp) + TArray groups; + TMap CRCMap; + TMap FlagMap; + + FlagMap.Insert("GAMEFLAG_DUKE", GAMEFLAG_DUKE); + FlagMap.Insert("GAMEFLAG_NAM", GAMEFLAG_NAM); + FlagMap.Insert("GAMEFLAG_NAPALM", GAMEFLAG_NAPALM); + FlagMap.Insert("GAMEFLAG_WW2GI", GAMEFLAG_WW2GI); + FlagMap.Insert("GAMEFLAG_ADDON", GAMEFLAG_ADDON); + FlagMap.Insert("GAMEFLAG_SHAREWARE", GAMEFLAG_SHAREWARE); + FlagMap.Insert("GAMEFLAG_DUKEBETA", GAMEFLAG_DUKEBETA); // includes 0x20 since it's a shareware beta + FlagMap.Insert("GAMEFLAG_FURY", GAMEFLAG_FURY); + FlagMap.Insert("GAMEFLAG_RR", GAMEFLAG_RR); + FlagMap.Insert("GAMEFLAG_RRRA", GAMEFLAG_RRRA); + FlagMap.Insert("GAMEFLAG_BLOOD", GAMEFLAG_BLOOD); + + FScanner sc; + auto mem = fr.Read(); + sc.OpenMem(fn, (const char *)mem.Data(), mem.Size()); + + while (sc.GetToken()) { - auto fr = basegrp->NewReader(); + sc.TokenMustBe(TK_Identifier); + if (sc.Compare("CRC")) + { + sc.MustGetToken('{'); + while (!sc.CheckToken('}')) + { + sc.MustGetToken(TK_Identifier); + FString key = sc.String; + sc.MustGetToken(TK_IntConst); + if (sc.BigNumber < 0 || sc.BigNumber >= UINT_MAX) + { + sc.ScriptError("CRC hash %s out of range", sc.String); + } + CRCMap.Insert(key, (uint32_t)sc.BigNumber); + } + } + if (sc.Compare("grpinfo")) + { + groups.Reserve(1); + auto grp = groups.Last(); + sc.MustGetToken('{'); + while (!sc.CheckToken('}')) + { + sc.MustGetToken(TK_Identifier); + if (sc.Compare("name")) + { + sc.MustGetToken(TK_StringConst); + grp.name = sc.String; + } + else if (sc.Compare("scriptname")) + { + sc.MustGetToken(TK_StringConst); + grp.scriptname = sc.String; + } + else if (sc.Compare("loaddirectory")) + { + sc.MustGetToken(TK_StringConst); + grp.dirname = sc.String; + } + else if (sc.Compare("defname")) + { + sc.MustGetToken(TK_StringConst); + grp.defname = sc.String; + } + else if (sc.Compare("rtsname")) + { + sc.MustGetToken(TK_StringConst); + grp.rtsname = sc.String; + } + else if (sc.Compare("crc")) + { + sc.MustGetAnyToken(); + if (sc.TokenType == TK_IntConst) + { + grp.CRC = (uint32_t)sc.BigNumber; + } + else if (sc.TokenType == TK_Identifier) + { + auto ip = CRCMap.CheckKey(sc.String); + if (ip) grp.CRC = *ip; + else sc.ScriptError("Unknown CRC value %s", sc.String); + } + else sc.TokenMustBe(TK_IntConst); + } + else if (sc.Compare("dependency")) + { + sc.MustGetAnyToken(); + if (sc.TokenType == TK_IntConst) + { + grp.dependencyCRC = (uint32_t)sc.BigNumber; + } + else if (sc.TokenType == TK_Identifier) + { + auto ip = CRCMap.CheckKey(sc.String); + if (ip) grp.dependencyCRC = *ip; + else sc.ScriptError("Unknown CRC value %s", sc.String); + } + else sc.TokenMustBe(TK_IntConst); + } + else if (sc.Compare("size")) + { + sc.MustGetToken(TK_IntConst); + grp.size = sc.BigNumber; + } + else if (sc.Compare("flags")) + { + do + { + sc.MustGetAnyToken(); + if (sc.TokenType == TK_IntConst) + { + grp.flags |= sc.Number; + } + else if (sc.TokenType == TK_Identifier) + { + auto ip = FlagMap.CheckKey(sc.String); + if (ip) grp.dependencyCRC |= *ip; + else sc.ScriptError("Unknown flag value %s", sc.String); + } + else sc.TokenMustBe(TK_IntConst); + } + while (sc.CheckToken('|')); + } + else if (sc.Compare("load")) + { + do + { + sc.MustGetToken(TK_StringConst); + grp.loadfiles.Push(sc.String); + } + while (sc.CheckToken(',')); + } + else if (sc.Compare("loadart")) + { + do + { + sc.MustGetToken(TK_StringConst); + grp.loadfiles.Push(sc.String); + } + while (sc.CheckToken(',')); + } + else sc.ScriptError(nullptr); + } + } + else sc.ScriptError(nullptr); + } + return groups; +} + +//========================================================================== +// +// +// +//========================================================================== + +TArray ParseAllGrpInfos(TArray& filelist) +{ + TArray groups; + extern FString progdir; + // This opens the base resource only for reading the grpinfo from it. + std::unique_ptr engine_res; + FString baseres = progdir + "demolition.pk3"; + engine_res.reset(FResourceFile::OpenResourceFile(baseres, true, true)); + if (engine_res) + { + auto basegrp = engine_res->FindLump("demolition/demolition.grpinfo"); + if (basegrp) + { + auto fr = basegrp->NewReader(); + if (fr.isOpen()) + { + groups = ParseGrpInfo("demolition/demolition.grpinfo", fr); + } + } } for (auto& entry : filelist) { @@ -671,22 +877,146 @@ void ParseAllGrpInfos(TArray& filelist, FResourceFile *baseres) FileReader fr; if (fr.OpenFile(entry.FileName)) { + auto g = ParseGrpInfo(entry.FileName, fr); + groups.Append(g); } } } } + return groups; } - -extern FString progdir; -void GrpGet() +//========================================================================== +// +// +// +//========================================================================== + +void GetCRC(FileEntry *entry, TArray &CRCCache) { - std::unique_ptr engine_res; - // If we get here for the first time, load the engine-internal data. - FString baseres = progdir + "demolition.pk3"; - engine_res.reset(FResourceFile::OpenResourceFile(baseres, true, true)); - if (!engine_res) + for (auto &ce : CRCCache) { - I_Error("Engine resources (%s) not found", baseres.GetChars()); + // File size, modification date snd name all must match exactly to pick an entry. + if (entry->FileLength == ce.FileLength && entry->FileTime == ce.FileTime && entry->FileName.Compare(ce.FileName)) + { + entry->CRCValue = ce.CRCValue; + return; + } + } + FileReader f; + if (f.OpenFile(entry->FileName)) + { + TArray buffer(65536, 1); + uint32_t crcval = 0; + size_t b; + do + { + b = f.Read(buffer.Data(), buffer.Size()); + if (b > 0) crcval = AddCRC32(crcval, buffer.Data(), b); + } + while (b == buffer.Size()); + entry->CRCValue = crcval; + CRCCache.Push(*entry); } } + +GrpInfo *IdentifyGroup(FileEntry *entry, TArray &groups) +{ + for (auto g : groups) + { + if (entry->FileLength == g->size && entry->CRCValue == g->CRC) + return g; + } + return nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +TArray GrpScan() +{ + TArray foundGames; + + TArray sortedFileList; + TArray sortedGroupList; + + auto allFiles = CollectAllFilesInSearchPath(); + auto allGroups = ParseAllGrpInfos(allFiles); + auto cachedCRCs = LoadCRCCache(); + + // Remove all unnecessary content from the file list. Since this contains all data from the search path's directories it can be quite large. + // Sort both lists by file size so that we only need to pass over each list once to weed out all unrelated content. Go backward to avoid too much item movement + // (most will be deleted anyway.) + for (auto &f : allFiles) sortedFileList.Push(&f); + for (auto &g : allGroups) sortedGroupList.Push(&g); + + std::sort(sortedFileList.begin(), sortedFileList.end(), [](FileEntry* lhs, FileEntry* rhs) { return lhs->FileLength < rhs->FileLength; }); + std::sort(sortedGroupList.begin(), sortedGroupList.end(), [](GrpInfo* lhs, GrpInfo* rhs) { return lhs->size < rhs->size; }); + + int findex = sortedFileList.Size(); + int gindex = sortedGroupList.Size(); + int cindex = sortedGroupList.Size(); + + while (findex > 0 || gindex > 0) + { + if (sortedFileList[findex]->FileLength > sortedGroupList[gindex]->size) + { + // File is larger than the largest known group so it cannot be a candidate. + sortedFileList.Delete(findex--); + } + else if (sortedFileList[findex]->FileLength < sortedGroupList[gindex]->size) + { + // The largest available file is smaller than this group so we cannot possibly have it. + sortedGroupList.Delete(gindex--); + } + else + { + // We found a matching file. Skip over all other entries of the same size so we can analyze those later as well + while (findex > 0 && sortedFileList[findex]->FileLength == sortedFileList[findex-1]->FileLength) findex--; + while (gindex > 0 && sortedGroupList[gindex]->size == sortedGroupList[gindex-1]->size) gindex--; + } + } + if (sortedGroupList.Size() == 0 || sortedFileList.Size() == 0) + for (auto entry : sortedFileList) + { + GetCRC(entry, cachedCRCs); + auto grp = IdentifyGroup(entry, sortedGroupList); + if (grp) + { + foundGames.Reserve(1); + auto fg = foundGames.Last(); + fg.FileInfo = *grp; + fg.FileName = entry->FileName; + fg.FileIndex = entry->Index; + } + } + + // One last thing: We must check for all addons if their dependency is present. + for( int i = foundGames.Size()-1; i >= 0; i--) + { + auto crc = foundGames[i].FileInfo.dependencyCRC; + if (crc != 0) + { + bool found = false; + for (auto fg : foundGames) + { + if (fg.FileInfo.CRC == crc) + { + found = true; + break; + } + } + if (!found) foundGames.Delete(i); // Dependent add-on without dependency cannot be played. + } + } + + // Do we have anything left? If not, error out + if (foundGames.Size() == 0) + { + I_Error("No supported games found. Please check your search paths."); + } + return foundGames; +} diff --git a/source/common/utility/sc_man.cpp b/source/common/utility/sc_man.cpp index c35703c15..3c45f0c46 100644 --- a/source/common/utility/sc_man.cpp +++ b/source/common/utility/sc_man.cpp @@ -523,12 +523,14 @@ bool FScanner::GetToken () String[StringLen - 2] == 'u' || String[StringLen - 2] == 'U') { TokenType = TK_UIntConst; - Number = strtoul(String, &stopper, 0); + BigNumber = (int64_t)strtoull(String, &stopper, 0); + Number = (int)clamp(BigNumber, 0, UINT_MAX); Float = (unsigned)Number; } else { - Number = strtol(String, &stopper, 0); + BigNumber = strtoll(String, &stopper, 0); + Number = (int)clamp(BigNumber, INT_MIN, INT_MAX); Float = Number; } } @@ -1003,4 +1005,4 @@ int ParseHex(const char* hex) return num; } - \ No newline at end of file + diff --git a/source/common/utility/sc_man.h b/source/common/utility/sc_man.h index 1f7cec576..610356ea2 100644 --- a/source/common/utility/sc_man.h +++ b/source/common/utility/sc_man.h @@ -74,6 +74,7 @@ public: int StringLen; int TokenType; int Number; + int64_t BigNumber; double Float; FName Name; int Line; diff --git a/source/duke3d/src/common_game.h b/source/duke3d/src/common_game.h index 3c91f93b0..c0ea71c4c 100644 --- a/source/duke3d/src/common_game.h +++ b/source/duke3d/src/common_game.h @@ -9,23 +9,13 @@ #include "collections.h" #include "grpscan.h" +#include "gamecontrol.h" #include "vfs.h" BEGIN_DUKE_NS -#define GAMEFLAG_DUKE 0x00000001 -#define GAMEFLAG_NAM 0x00000002 -#define GAMEFLAG_NAPALM 0x00000004 -#define GAMEFLAG_WW2GI 0x00000008 -#define GAMEFLAG_ADDON 0x00000010 -#define GAMEFLAG_SHAREWARE 0x00000020 -#define GAMEFLAG_DUKEBETA 0x00000060 // includes 0x20 since it's a shareware beta -#define GAMEFLAG_FURY 0x00000080 -#define GAMEFLAG_STANDALONE 0x00000100 -#define GAMEFLAGMASK 0x000000FF // flags allowed from grpinfo - extern struct grpfile_t const *g_selectedGrp; extern int32_t g_gameType; diff --git a/source/rr/src/common_game.h b/source/rr/src/common_game.h index 8585b6313..e943d8c16 100644 --- a/source/rr/src/common_game.h +++ b/source/rr/src/common_game.h @@ -9,23 +9,11 @@ #include "collections.h" #include "grpscan.h" +#include "gamecontrol.h" BEGIN_RR_NS -#define GAMEFLAG_DUKE 0x00000001 -#define GAMEFLAG_NAM 0x00000002 -#define GAMEFLAG_NAPALM 0x00000004 -//#define GAMEFLAG_WW2GI 0x00000008 -#define GAMEFLAG_ADDON 0x00000010 -#define GAMEFLAG_SHAREWARE 0x00000020 -#define GAMEFLAG_RR 0x00000040 -#define GAMEFLAG_RRRA 0x00000080 -//#define GAMEFLAG_DUKEBETA 0x00000060 // includes 0x20 since it's a shareware beta -//#define GAMEFLAG_IONMAIDEN 0x00000080 -//#define GAMEFLAG_STANDALONE 0x00000100 -#define GAMEFLAGMASK 0x000000FF // flags allowed from grpinfo - extern struct grpfile_t const *g_selectedGrp; extern int32_t g_gameType; diff --git a/wadsrc/static/demolition/demolition.grpinfo b/wadsrc/static/demolition/demolition.grpinfo new file mode 100644 index 000000000..4aec51a60 --- /dev/null +++ b/wadsrc/static/demolition/demolition.grpinfo @@ -0,0 +1,372 @@ +CRC +{ + DUKE13_CRC 0xBBC9CE44 + DUKEKR_CRC 0xAA4F6A40 + DUKE15_CRC 0xFD3DCFF1 + DUKEPP_CRC 0xF514A6AC + DUKEWT_CRC 0x982AFE4A + DUKE099_CRC 0x02F18900 + DUKE10_CRC 0xA28AA589 + DUKE11_CRC 0x912E1E8D + DUKESW_CRC 0x983AD923 + DUKEMD_CRC 0xC5F71561 + DUKEMD2_CRC 0x73A15EE7 + DUKEDC13_CRC 0xA9242158 + DUKEDCPP_CRC 0xB79D997F + DUKEDC_CRC 0xA8CF80DA + VACA13_CRC 0x4A2DBB62 + VACAPP_CRC 0x2F4FCCEE + VACA15_CRC 0xB62B42FD + DUKECB_CRC 0x18F01C5B + DUKENW_CRC 0xF1CAE8E4 + DZ2_13_CRC 0x82C1B47F + DZ2_PP_CRC 0x7FB6117C + NAM_CRC 0x75C1F07B + NAPALM_CRC 0x3DE1589A + WW2GI_CRC 0x907B82BF + PLATOONL_CRC 0xD1ED8C0C + RR_CRC 0x19D9BC79 + RRRA_CRC 0x958018C6 + BLOOD_CRC 0xA8FDDA84 +} + + + +//static const char *defaultconfilename = "GAME.CON"; +//static const char *defaultgamegrp[GAMECOUNT] = { "DUKE3D.GRP", "REDNECK.GRP", "REDNECK.GRP", "NAM.GRP", "NAPALM.GRP" }; +//static const char *defaultdeffilename[GAMECOUNT] = { "duke3d.def", "rr.def", "rrra.def", "nam.def", "napalm.grp" }; +//static const char *defaultgameconfilename[GAMECOUNT] = { "GAME.CON", "GAME.CON", "GAME.CON", "NAM.CON", "NAPALM.CON" }; + + +grpinfo +{ + name "Duke Nukem 3D" + size 26524524 + crc DUKE13_CRC + flags GAMEFLAG_DUKE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D (South Korean Censored)" + size 26385383 + crc DUKEKR_CRC + flags GAMEFLAG_DUKE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D: Atomic Edition" + size 44356548 + crc DUKE15_CRC + flags GAMEFLAG_DUKE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D: Atomic Edition (WT)" + size 44356548 + crc DUKEWT_CRC + flags GAMEFLAG_DUKE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D: Plutonium Pak" + size 44348015 + crc DUKEPP_CRC + flags GAMEFLAG_DUKE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D Shareware 0.99" + size 9690241 + crc DUKE099_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_DUKEBETA + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D Shareware 1.0" + size 10429258 + crc DUKE10_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D Shareware 1.1" + size 10442980 + crc DUKE11_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D Shareware 1.3D" + size 11035779 + crc DUKESW_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D Mac Demo" + size 10444391 + crc DUKEMD_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke Nukem 3D MacUser Demo" + size 10628573 + crc DUKEMD2_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE + gamefilter "Duke.Duke" + dependency 0 +} + +grpinfo +{ + name "Duke it out in D.C. (1.3D)" + size 7926624 + crc DUKEDC13_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE13_CRC + gamefilter "Duke.DukeDC" +} + +grpinfo +{ + name "Duke it out in D.C." + size 8225517 + crc DUKEDCPP_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.DukeDC" +} + +grpinfo +{ + name "Duke it out in D.C." + size 8410183 + crc DUKEDC_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.DukeDC" +} + +grpinfo +{ + name "Duke it out in D.C." + scriptname "DUKEDC.CON" + size 8410187 + crc 0x39A692BF + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.DukeDC" +} + + +// { "Duke Caribbean: Life's a Beach (1.3D)", VACA13_CRC, 23559381, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE13_CRC, NULL, process_vaca13}, +// { "Duke Caribbean: Life's a Beach (PPak)", VACAPP_CRC, 22551333, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKEPP_CRC, NULL, process_vacapp15}, +// { "Duke Caribbean: Life's a Beach", VACA15_CRC, 22521880, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, process_vacapp15}, + +grpinfo +{ + name "Duke Caribbean: Life's a Beach" + size 22213819 + crc DUKECB_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.Vacation" +} + +grpinfo +{ + name "Duke Caribbean: Life's a Beach" + scriptname "VACARION.CON" + size 22397273 + crc 0x65B5F690 + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.Vacation" +} + +grpinfo +{ + name "Duke: Nuclear Winter" + scriptname "NWINTER.CON" + size 16169365 + crc DUKENW_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.NWinter" +} + +grpinfo +{ + name "Duke: Nuclear Winter Demo" + scriptname "NWINTER.CON" + size 10965909 + crc 0xC7EFBFA9 + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.NWinter" +} + +grpinfo +{ + name "Duke!ZONE II (1.3D)" + scriptname "DZ-GAME.CON" + size 26135388 + crc 0xC7EFBFA9 + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE13_CRC + gamefilter "Duke.Zone" +} + +grpinfo +{ + name "Duke!ZONE II" + scriptname "DZ-GAME.CON" + size 44100411 + crc DZ2_PP_CRC + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE15_CRC + gamefilter "Duke.Zone" +} + +grpinfo +{ + name "Duke!ZONE II" + scriptname "DZ-GAME.CON" + size 3186656 + crc 0x1E9516F1 + flags GAMEFLAG_DUKE|GAMEFLAG_ADDON + dependency DUKE13_CRC + gamefilter "Duke.Zone" +} + +grpinfo +{ + name "NAM" + size 43448927 + crc NAM_CRC + flags GAMEFLAG_NAM + dependency 0 + gamefilter "Nam.Nam" +} + +grpinfo +{ + name "NAPALM" + size 44365728 + crc NAPALM_CRC + flags GAMEFLAG_NAM|GAMEFLAG_NAPALM + dependency 0 + gamefilter "Nam.Napalm" +} + +grpinfo +{ + name "WWII GI" + size 77939508 + crc WW2GI_CRC + flags GAMEFLAG_WW2GI + dependency 0 + gamefilter "WW2GI.WW2GI" +} + +grpinfo +{ + name "Platoon Leader" + scriptname "PLATOONL.CON" + size 37852572 + crc PLATOONL_CRC + flags GAMEFLAG_WW2GI|GAMEFLAG_ADDON + dependency WW2GI_CRC + gamefilter "WW2GI.Platoon" +} + +grpinfo +{ + name "Redneck Rampage" + size 141174222 + crc RR_CRC + flags GAMEFLAG_RR + dependency 0 + gamefilter "Redneck.Redneck" +} + +grpinfo +{ + name "Redneck Rampage Rides Again" + size 191798609 + crc RRRA_CRC + flags GAMEFLAG_RRRA + dependency 0 + gamefilter "Redneck.RidesAgain" +} + +grpinfo +{ + name "Redneck Rampage: Suckin' Grits on Route 66" + crc 0x30C49F26 // tests carnival.map + size 234898 + loaddirectory "CARNIVAL.MAP" + scriptname "GAME66.CON" + flags GAMEFLAG_RR|GAMEFLAG_ADDON + dependency RR_CRC + loaddirectory + loadart "TILESA66.ART", "TILESB66.ART" + gamefilter "Redneck.Route66" +} + +grpinfo +{ + name "BLOOD: One Unit Whole Blood" + size 9570681 + crc 0xA8FDDA84 + scriptname "BLOOD.INI" + flags GAMEFLAG_BLOOD + dependency 0 + loadgrp "SOUNDS.RFF", "GUI.RFF" + gamefilter "Blood.Blood" +} + +grpinfo +{ + name "BlOOD: Cryptic Passage" + crc 0x2144DF1C // tests CP01.MAP + size 327015 + loaddirectory "CP01.MAP" + scriptname "CRYPTIC.INI" + flags GAMEFLAG_BLOOD|GAMEFLAG_ADDON + dependency BLOOD_CRC + loadart "CPART07.AR_", "CPART15.AR_" + gamefilter "Blood.Cryptic" +} +