- tested and fixed game list loader.

This commit is contained in:
Christoph Oelckers 2019-10-31 00:41:56 +01:00
parent 2a7beeff69
commit c6753a3fec
10 changed files with 148 additions and 175 deletions

View file

@ -138,34 +138,6 @@ void G_ExtInit(void)
CommandPaths = s;
}
}
if (!access("user_profiles_enabled", F_OK))
{
char *homedir;
int32_t asperr;
if ((homedir = Bgethomedir()))
{
Bsnprintf(cwd,sizeof(cwd),"%s/"
#if defined(_WIN32)
APPNAME
#elif defined(GEKKO)
"apps/" APPBASENAME
#else
".config/" APPBASENAME
#endif
,homedir);
asperr = addsearchpath(cwd);
if (asperr == -2)
{
if (Bmkdir(cwd,S_IRWXU) == 0) asperr = addsearchpath(cwd);
else asperr = -1;
}
if (asperr == 0)
Bchdir(cwd);
Bfree(homedir);
}
}
}
static int32_t G_TryLoadingGrp(char const * const grpfile)

View file

@ -132,7 +132,7 @@ bool CDemo::Create(const char *pzFile)
for (int i = 0; i < 8 && !vc; i++)
{
G_ModDirSnprintf(buffer, BMAX_PATH, "%s0%02d.dem", BloodIniPre, i);
if (access(buffer, F_OK) != -1)
if (access(buffer, 0) != -1)
vc = 1;
}
if (vc == 1)

View file

@ -672,10 +672,6 @@ inline void Bexit(int a)
# define NULL ((void *)0)
#endif
#ifdef _MSC_VER
# define strtoll _strtoi64
#endif
#ifndef O_BINARY
# define O_BINARY 0
#endif
@ -687,34 +683,6 @@ inline void Bexit(int a)
# define F_OK 0
#endif
#ifdef GEKKO
# undef PRIdPTR
# define PRIdPTR "d"
# undef PRIxPTR
# define PRIxPTR "x"
# undef SCNx32
# define SCNx32 "x"
#endif
#if defined EDUKE32_OSX
# if !defined __x86_64__ && defined __GNUC__
// PK 20110617: is*() crashes for me in x86 code compiled from 64-bit, and gives link errors on ppc
// This hack patches all occurences.
# define isdigit(ch) ({ int32_t c__dontuse_=ch; c__dontuse_>='0' && c__dontuse_<='9'; })
# define isalpha(ch) ({ int32_t c__dontuse2_=ch; (c__dontuse2_>='A' && c__dontuse2_<='Z') || (c__dontuse2_>='a' && c__dontuse2_<='z'); })
# define isalnum(ch2) ({ int32_t c2__dontuse_=ch2; isalpha(c2__dontuse_) || isdigit(c2__dontuse_); })
# if defined __BIG_ENDIAN__
# define isspace(ch) ({ int32_t c__dontuse_=ch; (c__dontuse_==' ' || c__dontuse_=='\t' || c__dontuse_=='\n' || c__dontuse_=='\v' || c__dontuse_=='\f' || c__dontuse_=='\r'); })
# define isprint(ch) ({ int32_t c__dontuse_=ch; (c__dontuse_>=0x20 && c__dontuse_<0x7f); })
# endif
# endif
#endif
#ifdef __ANDROID__
void eduke32_exit_return(int) ATTRIBUTE((noreturn));
# define exit(x) eduke32_exit_return(x)
#endif
////////// Metaprogramming structs //////////

View file

@ -61,7 +61,7 @@ static inline int64_t buildvfs_flength(FILE * f)
return buildvfs_length(fileno(f));
#endif
}
#define buildvfs_exists(fn) (access((fn), F_OK) == 0)
#define buildvfs_exists(fn) (access((fn), 0) == 0)
static inline int buildvfs_isdir(char const *path)
{
struct Bstat st;

View file

@ -17,7 +17,6 @@
InputState inputState;
void SetClipshapes();
TArray<FString> CollectSearchPaths();
struct GameFuncNameDesc
{
@ -308,11 +307,11 @@ void CONFIG_Init()
G_LoadConfig();
// Startup dialog must be presented here so that everything can be set up before reading the keybinds.
TArray<FString> paths = CollectSearchPaths();
for (auto& path : paths)
auto groups = GrpScan();
for (auto& grp : groups)
{
OutputDebugStringA(path);
OutputDebugStringA("\r\n");
FStringf grpinfo("%s: %s, %s, %s, %s\r\n", grp.FileInfo.name.GetChars(), grp.FileName.GetChars(), grp.FileInfo.scriptname.GetChars(), grp.FileInfo.rtsname.GetChars(), grp.FileInfo.defname.GetChars());
OutputDebugStringA(grpinfo);
}
LumpFilter = currentGame;
if (LumpFilter.Compare("Redneck") == 0) LumpFilter = "Redneck.Redneck";

View file

@ -132,4 +132,32 @@ enum
GAMEFLAG_STANDALONE = 0x00000800,
GAMEFLAGMASK = 0x000007FF, // flags allowed from grpinfo
};
};
struct GrpInfo
{
FString name;
FString scriptname;
FString dirname;
FString defname;
FString rtsname;
FString gamefilter;
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;
};
TArray<GrpEntry> GrpScan();

View file

@ -39,32 +39,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
// 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;
@ -578,7 +552,6 @@ TArray<FileEntry> CollectAllFilesInSearchPath()
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);
}
}
}
@ -604,19 +577,20 @@ static TArray<FileEntry> LoadCRCCache(void)
while (sc.GetString())
{
crclist.Reserve(1);
auto flentry = crclist.Last();
auto &flentry = crclist.Last();
flentry.FileName = sc.String;
sc.MustGetString();
flentry.FileLength = strtoull(sc.String, nullptr, 0); // Cannot use sc.Number because that's only 32 bit.
sc.MustGetString();
flentry.FileTime = strtoull(sc.String, nullptr, 0); // Cannot use sc.Number because that's only 32 bit.
sc.MustGetString();
flentry.CRCValue = strtoull(sc.String, nullptr, 0); // Cannot use sc.Number because that's only 32 bit.
sc.MustGetNumber();
flentry.FileLength = sc.BigNumber;
sc.MustGetNumber();
flentry.FileTime = sc.BigNumber;
sc.MustGetNumber();
flentry.CRCValue = (unsigned)sc.BigNumber;
}
}
catch (std::runtime_error &)
catch (std::runtime_error &err)
{
// If there's a parsing error, return what we got and discard the rest.
OutputDebugStringA(err.what());
}
return crclist;
}
@ -627,10 +601,31 @@ static TArray<FileEntry> LoadCRCCache(void)
//
//==========================================================================
static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr)
void SaveCRCs(TArray<FileEntry>& crclist)
{
auto cachepath = M_GetAppDataPath(true) + "/grpcrccache.txt";
FileWriter* fw = FileWriter::Open(cachepath);
if (fw)
{
for (auto& crc : crclist)
{
FStringf line("\"%s\" %llu %llu %u\n", crc.FileName.GetChars(), (long long)crc.FileLength, (long long)crc.FileTime, crc.CRCValue);
fw->Write(line.GetChars(), line.Len());
}
delete fw;
}
}
//==========================================================================
//
//
//
//==========================================================================
static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr, TMap<FString, uint32_t> &CRCMap)
{
TArray<GrpInfo> groups;
TMap<FString, uint32_t> CRCMap;
TMap<FString, int> FlagMap;
FlagMap.Insert("GAMEFLAG_DUKE", GAMEFLAG_DUKE);
@ -667,10 +662,10 @@ static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr)
CRCMap.Insert(key, (uint32_t)sc.BigNumber);
}
}
if (sc.Compare("grpinfo"))
else if (sc.Compare("grpinfo"))
{
groups.Reserve(1);
auto grp = groups.Last();
auto& grp = groups.Last();
sc.MustGetToken('{');
while (!sc.CheckToken('}'))
{
@ -700,6 +695,11 @@ static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr)
sc.MustGetToken(TK_StringConst);
grp.rtsname = sc.String;
}
else if (sc.Compare("gamefilter"))
{
sc.MustGetToken(TK_StringConst);
grp.gamefilter = sc.String;
}
else if (sc.Compare("crc"))
{
sc.MustGetAnyToken();
@ -707,6 +707,11 @@ static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr)
{
grp.CRC = (uint32_t)sc.BigNumber;
}
else if (sc.TokenType == '-')
{
sc.MustGetToken(TK_IntConst);
grp.CRC = (uint32_t)-sc.BigNumber;
}
else if (sc.TokenType == TK_Identifier)
{
auto ip = CRCMap.CheckKey(sc.String);
@ -722,6 +727,11 @@ static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr)
{
grp.dependencyCRC = (uint32_t)sc.BigNumber;
}
else if (sc.TokenType == '-')
{
sc.MustGetToken(TK_IntConst);
grp.dependencyCRC = (uint32_t)-sc.BigNumber;
}
else if (sc.TokenType == TK_Identifier)
{
auto ip = CRCMap.CheckKey(sc.String);
@ -789,6 +799,7 @@ static TArray<GrpInfo> ParseGrpInfo(const char *fn, FileReader &fr)
TArray<GrpInfo> ParseAllGrpInfos(TArray<FileEntry>& filelist)
{
TArray<GrpInfo> groups;
TMap<FString, uint32_t> CRCMap;
extern FString progdir;
// This opens the base resource only for reading the grpinfo from it.
std::unique_ptr<FResourceFile> engine_res;
@ -802,7 +813,7 @@ TArray<GrpInfo> ParseAllGrpInfos(TArray<FileEntry>& filelist)
auto fr = basegrp->NewReader();
if (fr.isOpen())
{
groups = ParseGrpInfo("demolition/demolition.grpinfo", fr);
groups = ParseGrpInfo("demolition/demolition.grpinfo", fr, CRCMap);
}
}
}
@ -818,7 +829,7 @@ TArray<GrpInfo> ParseAllGrpInfos(TArray<FileEntry>& filelist)
FileReader fr;
if (fr.OpenFile(entry.FileName))
{
auto g = ParseGrpInfo(entry.FileName, fr);
auto g = ParseGrpInfo(entry.FileName, fr, CRCMap);
groups.Append(g);
}
}
@ -838,7 +849,7 @@ void GetCRC(FileEntry *entry, TArray<FileEntry> &CRCCache)
for (auto &ce : CRCCache)
{
// 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))
if (entry->FileLength == ce.FileLength && entry->FileTime == ce.FileTime && entry->FileName.Compare(ce.FileName) == 0)
{
entry->CRCValue = ce.CRCValue;
return;
@ -880,28 +891,29 @@ GrpInfo *IdentifyGroup(FileEntry *entry, TArray<GrpInfo *> &groups)
TArray<GrpEntry> GrpScan()
{
TArray<GrpEntry> foundGames;
TArray<FileEntry *> sortedFileList;
TArray<GrpInfo *> sortedGroupList;
TArray<FileEntry*> sortedFileList;
TArray<GrpInfo*> sortedGroupList;
auto allFiles = CollectAllFilesInSearchPath();
auto allGroups = ParseAllGrpInfos(allFiles);
auto cachedCRCs = LoadCRCCache();
auto numCRCs = cachedCRCs.Size();
// 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);
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();
int findex = sortedFileList.Size() - 1;
int gindex = sortedGroupList.Size() - 1;
while (findex > 0 || gindex > 0)
while (findex > 0 && gindex > 0)
{
if (sortedFileList[findex]->FileLength > sortedGroupList[gindex]->size)
{
@ -915,12 +927,19 @@ TArray<GrpEntry> GrpScan()
}
else
{
findex--;
gindex--;
// 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--;
while (findex > 0 && sortedFileList[findex]->FileLength == sortedFileList[findex + 1]->FileLength) findex--;
while (gindex > 0 && sortedGroupList[gindex]->size == sortedGroupList[gindex + 1]->size) gindex--;
}
}
sortedFileList.Delete(0, findex + 1);
sortedGroupList.Delete(0, gindex + 1);
if (sortedGroupList.Size() == 0 || sortedFileList.Size() == 0)
return foundGames;
for (auto entry : sortedFileList)
{
GetCRC(entry, cachedCRCs);
@ -928,21 +947,21 @@ TArray<GrpEntry> GrpScan()
if (grp)
{
foundGames.Reserve(1);
auto fg = foundGames.Last();
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--)
// 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)
for (auto& fg : foundGames)
{
if (fg.FileInfo.CRC == crc)
{
@ -953,6 +972,31 @@ TArray<GrpEntry> GrpScan()
if (!found) foundGames.Delete(i); // Dependent add-on without dependency cannot be played.
}
}
// return everything to its proper order (using qsort because sorting an array of complex structs with std::sort is a messy affair.)
qsort(foundGames.Data(), foundGames.Size(), sizeof(foundGames[0]), [](const void* a, const void* b)->int
{
auto A = (const GrpEntry*)a;
auto B = (const GrpEntry*)b;
return (int)A->FileIndex - (int)B->FileIndex;
});
// Finally, scan the list for duplicstes. Only the first occurence should count.
for (unsigned i = 0; i < foundGames.Size(); i++)
{
for (unsigned j = foundGames.Size(); j > i; j--)
{
if (foundGames[i].FileInfo.CRC == foundGames[j].FileInfo.CRC)
foundGames.Delete(j);
}
}
// new CRCs got added so save the list.
if (cachedCRCs.Size() > numCRCs)
{
SaveCRCs(cachedCRCs);
}
// Do we have anything left? If not, error out
if (foundGames.Size() == 0)

View file

@ -167,8 +167,8 @@ void FScanner::Open (const char *name)
void FScanner::OpenFile (const char *name)
{
Close ();
auto fr = fopenFileReader(name, 0);
if (!fr.isOpen()) return;
FileReader fr;
if (!fr.OpenFile(name)) return;
auto data = fr.ReadPadded(1);
ScriptBuffer = data;
ScriptName = name; // This is used for error messages so the full file name is preferable
@ -631,7 +631,8 @@ bool FScanner::GetNumber ()
}
else
{
Number = strtol (String, &stopper, 0);
BigNumber = strtoll(String, &stopper, 0);
Number = (int)clamp(BigNumber, INT_MIN, INT_MAX);
if (*stopper != 0)
{
ScriptError ("SC_GetNumber: Bad numeric constant \"%s\".", String);
@ -682,11 +683,13 @@ bool FScanner::CheckNumber ()
}
else if (strcmp (String, "MAXINT") == 0)
{
BigNumber = INT64_MAX;
Number = INT_MAX;
}
else
{
Number = strtol (String, &stopper, 0);
BigNumber = strtoll (String, &stopper, 0);
Number = (int)clamp(BigNumber, INT_MIN, INT_MAX);
if (*stopper != 0)
{
UnGet();

View file

@ -247,46 +247,6 @@ void G_ExtInit(void)
CommandPaths = s;
}
}
if (!access("user_profiles_enabled", F_OK))
{
char *homedir;
int32_t asperr;
if ((homedir = Bgethomedir()))
{
Bsnprintf(cwd,sizeof(cwd),"%s/"
#if defined(_WIN32)
APPNAME
#elif defined(GEKKO)
"apps/" APPBASENAME
#else
".config/" APPBASENAME
#endif
,homedir);
asperr = addsearchpath(cwd);
if (asperr == -2)
{
if (Bmkdir(cwd,S_IRWXU) == 0) asperr = addsearchpath(cwd);
else asperr = -1;
}
if (asperr == 0)
Bchdir(cwd);
Bfree(homedir);
}
}
// JBF 20031220: Because it's annoying renaming GRP files whenever I want to test different game data
if (g_grpNamePtr == NULL)
{
const char *cp = getenv("DUKE3DGRP");
if (cp)
{
clearGrpNamePtr();
g_grpNamePtr = dup_filename(cp);
initprintf("Using \"%s\" as main GRP file\n", g_grpNamePtr);
}
}
}
void G_ScanGroups(void)

View file

@ -340,7 +340,6 @@ grpinfo
scriptname "GAME66.CON"
flags GAMEFLAG_RR|GAMEFLAG_ADDON
dependency RR_CRC
loaddirectory
loadart "TILESA66.ART", "TILESB66.ART"
gamefilter "Redneck.Route66"
}
@ -359,7 +358,7 @@ grpinfo
grpinfo
{
name "BlOOD: Cryptic Passage"
name "BLOOD: Cryptic Passage"
crc 0x2144DF1C // tests CP01.MAP
size 327015
loaddirectory "CP01.MAP"