- WIP on game data search.

This commit is contained in:
Christoph Oelckers 2019-10-30 18:09:00 +01:00
parent 5e76fd659a
commit 7f250fc3e8
8 changed files with 749 additions and 45 deletions

View File

@ -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/.+")

View File

@ -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
};

View File

@ -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 <filesystem>
#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<FString> loadfiles;
TArray<FString> 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<FileEntry> CollectAllFilesInSearchPath()
{
TArray<FileEntry> filelist;
auto paths = CollectSearchPaths();
int index = 0;
for(auto &path : paths)
{
auto fpath = fs::u8path(path.GetChars());
@ -604,11 +636,13 @@ TArray<FileEntry> 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<FileEntry> CollectAllFilesInSearchPath()
//
//==========================================================================
static TArray<FileEntry> LoadGroupsCache(void)
static TArray<FileEntry> LoadCRCCache(void)
{
auto cachepath = M_GetAppDataPath(false) + "/grpcrccache.txt";
FScanner sc;
@ -639,7 +673,7 @@ static TArray<FileEntry> 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<FileEntry> LoadGroupsCache(void)
//
//==========================================================================
void ParseAllGrpInfos(TArray<FileEntry>& filelist, FResourceFile *baseres)
static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr)
{
auto basegrp = baseres->FindLump("demolition/demolition.grpinfo");
if (basegrp)
TArray<GrpInfo> groups;
TMap<FString, uint32_t> CRCMap;
TMap<FString, int> 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<GrpInfo> ParseAllGrpInfos(TArray<FileEntry>& filelist)
{
TArray<GrpInfo> groups;
extern FString progdir;
// This opens the base resource only for reading the grpinfo from it.
std::unique_ptr<FResourceFile> 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<FileEntry>& 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<FileEntry> &CRCCache)
{
std::unique_ptr<FResourceFile> 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<uint8_t> 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<GrpInfo *> &groups)
{
for (auto g : groups)
{
if (entry->FileLength == g->size && entry->CRCValue == g->CRC)
return g;
}
return nullptr;
}
//==========================================================================
//
//
//
//==========================================================================
TArray<GrpEntry> GrpScan()
{
TArray<GrpEntry> foundGames;
TArray<FileEntry *> sortedFileList;
TArray<GrpInfo *> 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;
}

View File

@ -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;
}

View File

@ -74,6 +74,7 @@ public:
int StringLen;
int TokenType;
int Number;
int64_t BigNumber;
double Float;
FName Name;
int Line;

View File

@ -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;

View File

@ -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;

View File

@ -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"
}