2019-11-01 21:17:15 +00:00
|
|
|
/*
|
|
|
|
** initfs.cpp
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
** Copyright 1999-2016 Randy Heit
|
|
|
|
** Copyright 2002-2019 Christoph Oelckers
|
|
|
|
** All rights reserved.
|
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
2020-04-11 21:54:33 +00:00
|
|
|
#include "filesystem.h"
|
2019-11-01 21:17:15 +00:00
|
|
|
#include "cmdlib.h"
|
|
|
|
#include "zstring.h"
|
|
|
|
#include "gamecontrol.h"
|
|
|
|
#include "gameconfigfile.h"
|
|
|
|
#include "printf.h"
|
|
|
|
#include "m_argv.h"
|
2019-12-26 13:04:53 +00:00
|
|
|
#include "version.h"
|
2020-01-11 16:05:25 +00:00
|
|
|
#include "sc_man.h"
|
|
|
|
#include "v_video.h"
|
|
|
|
#include "v_text.h"
|
2020-04-11 21:50:43 +00:00
|
|
|
#include "findfile.h"
|
2020-04-11 22:04:02 +00:00
|
|
|
#include "palutil.h"
|
2020-04-23 19:18:40 +00:00
|
|
|
#include "startupinfo.h"
|
2019-11-01 21:17:15 +00:00
|
|
|
|
|
|
|
#ifndef PATH_MAX
|
|
|
|
#define PATH_MAX 260
|
|
|
|
#endif
|
|
|
|
|
2020-04-11 22:07:52 +00:00
|
|
|
static const char* validexts[] = { "*.grp", "*.zip", "*.pk3", "*.pk4", "*.7z", "*.pk7", "*.dat", "*.rff" };
|
2019-11-01 21:17:15 +00:00
|
|
|
|
2020-01-11 16:05:25 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static FString ParseGameInfo(TArray<FString>& pwads, const char* fn, const char* data, int size)
|
|
|
|
{
|
|
|
|
FScanner sc;
|
|
|
|
FString iwad;
|
|
|
|
int pos = 0;
|
|
|
|
|
|
|
|
const char* lastSlash = strrchr(fn, '/');
|
|
|
|
|
|
|
|
sc.OpenMem("GAMEINFO", data, size);
|
|
|
|
sc.SetCMode(true);
|
|
|
|
while (sc.GetToken())
|
|
|
|
{
|
|
|
|
sc.TokenMustBe(TK_Identifier);
|
|
|
|
FString nextKey = sc.String;
|
|
|
|
sc.MustGetToken('=');
|
|
|
|
if (!nextKey.CompareNoCase("GAME"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
iwad = sc.String;
|
|
|
|
}
|
|
|
|
else if (!nextKey.CompareNoCase("LOAD"))
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
|
|
|
|
// Try looking for the wad in the same directory as the .wad
|
|
|
|
// before looking for it in the current directory.
|
|
|
|
|
|
|
|
FString checkpath;
|
|
|
|
if (lastSlash != NULL)
|
|
|
|
{
|
|
|
|
checkpath = FString(fn, (lastSlash - fn) + 1);
|
|
|
|
checkpath += sc.String;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
checkpath = sc.String;
|
|
|
|
}
|
|
|
|
if (!FileExists(checkpath))
|
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
pos += D_AddFile(pwads, sc.String, true, pos, GameConfig);
|
2020-01-11 16:05:25 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
pos += D_AddFile(pwads, checkpath, true, pos, GameConfig);
|
2020-01-11 16:05:25 +00:00
|
|
|
}
|
|
|
|
} while (sc.CheckToken(','));
|
|
|
|
}
|
|
|
|
else if (!nextKey.CompareNoCase("STARTUPTITLE"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
2020-04-23 19:18:40 +00:00
|
|
|
GameStartupInfo.Name = sc.String;
|
2020-01-11 16:05:25 +00:00
|
|
|
}
|
|
|
|
else if (!nextKey.CompareNoCase("STARTUPCOLORS"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
2020-04-23 19:18:40 +00:00
|
|
|
GameStartupInfo.FgColor = V_GetColor(NULL, sc);
|
2020-01-11 16:05:25 +00:00
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetString();
|
2020-04-23 19:18:40 +00:00
|
|
|
GameStartupInfo.BkColor = V_GetColor(NULL, sc);
|
2020-01-11 16:05:25 +00:00
|
|
|
}
|
2020-03-07 19:14:03 +00:00
|
|
|
else if (!nextKey.CompareNoCase("MODERN"))
|
|
|
|
{
|
|
|
|
sc.MustGetNumber();
|
2020-04-23 19:18:40 +00:00
|
|
|
GameStartupInfo.modern = sc.Number ? 1 : -1;
|
2020-03-07 19:14:03 +00:00
|
|
|
}
|
2020-01-11 16:05:25 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Silently ignore unknown properties
|
|
|
|
do
|
|
|
|
{
|
|
|
|
sc.MustGetAnyToken();
|
|
|
|
} while (sc.CheckToken(','));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return iwad;
|
|
|
|
}
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static FString CheckGameInfo(TArray<FString>& pwads)
|
|
|
|
{
|
|
|
|
// scan the list of WADs backwards to find the last one that contains a GAMEINFO lump
|
|
|
|
for (int i = pwads.Size() - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
bool isdir = false;
|
|
|
|
FResourceFile* resfile;
|
|
|
|
const char* filename = pwads[i];
|
|
|
|
|
|
|
|
// Does this exist? If so, is it a directory?
|
|
|
|
if (!DirEntryExists(pwads[i], &isdir))
|
|
|
|
{
|
|
|
|
Printf(TEXTCOLOR_RED "Could not find %s\n", filename);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isdir)
|
|
|
|
{
|
|
|
|
FileReader fr;
|
|
|
|
if (!fr.OpenFile(filename))
|
|
|
|
{
|
|
|
|
// Didn't find file
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
resfile = FResourceFile::OpenResourceFile(filename, fr, true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
resfile = FResourceFile::OpenDirectory(filename, true);
|
|
|
|
|
|
|
|
FName gameinfo = "GAMEINFO.TXT";
|
|
|
|
if (resfile != NULL)
|
|
|
|
{
|
|
|
|
uint32_t cnt = resfile->LumpCount();
|
|
|
|
for (int i = cnt - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
FResourceLump* lmp = resfile->GetLump(i);
|
|
|
|
|
2020-04-11 21:54:33 +00:00
|
|
|
if (FName(lmp->getName(), true) == gameinfo)
|
2020-01-11 16:05:25 +00:00
|
|
|
{
|
|
|
|
// Found one!
|
|
|
|
FString iwad = ParseGameInfo(pwads, resfile->FileName, (const char*)lmp->Lock(), lmp->LumpSize);
|
|
|
|
delete resfile;
|
|
|
|
return iwad;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete resfile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FString GetGameFronUserFiles()
|
|
|
|
{
|
|
|
|
TArray<FString> Files;
|
|
|
|
|
|
|
|
if (userConfig.AddFilesPre) for (auto& file : *userConfig.AddFilesPre)
|
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, file, true, -1, GameConfig);
|
2020-01-11 16:05:25 +00:00
|
|
|
}
|
|
|
|
if (userConfig.AddFiles)
|
|
|
|
{
|
|
|
|
for (auto& file : *userConfig.AddFiles)
|
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, file, true, -1, GameConfig);
|
2020-01-11 16:05:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, if the last entry in the chain is a directory, it's being considered the mod directory, and all GRPs inside need to be loaded, too.
|
|
|
|
if (userConfig.AddFiles->NumArgs() > 0)
|
|
|
|
{
|
|
|
|
auto fn = (*userConfig.AddFiles)[userConfig.AddFiles->NumArgs() - 1];
|
|
|
|
bool isdir = false;
|
|
|
|
if (DirEntryExists(fn, &isdir) && isdir)
|
|
|
|
{
|
|
|
|
// Insert the GRPs before this entry itself.
|
|
|
|
FString lastfn;
|
|
|
|
Files.Pop(lastfn);
|
2020-04-11 22:07:52 +00:00
|
|
|
for (auto ext : validexts)
|
|
|
|
{
|
|
|
|
D_AddDirectory(Files, fn, ext, GameConfig);
|
|
|
|
}
|
2020-01-11 16:05:25 +00:00
|
|
|
Files.Push(lastfn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return CheckGameInfo(Files);
|
|
|
|
}
|
|
|
|
|
2020-04-11 21:54:33 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Deletes unwanted content from the main game files
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void DeleteStuff(FileSystem &fileSystem, const TArray<FString>& deletelumps, int numgamefiles)
|
|
|
|
{
|
|
|
|
// This must account for the game directory being inserted at index 2.
|
|
|
|
// Deletion may only occur in the main game file, the directory and the add-on, there are no secondary dependencies, i.e. more than two game files.
|
|
|
|
numgamefiles++;
|
|
|
|
for (auto str : deletelumps)
|
|
|
|
{
|
|
|
|
FString renameTo;
|
|
|
|
auto ndx = str.IndexOf("*");
|
|
|
|
if (ndx >= 0)
|
|
|
|
{
|
|
|
|
renameTo = FName(str.Mid(ndx + 1)).GetChars();
|
|
|
|
str.Truncate(ndx);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < fileSystem.GetNumEntries(); i++)
|
|
|
|
{
|
|
|
|
int cf = fileSystem.GetFileContainer(i);
|
|
|
|
auto fname = fileSystem.GetFileFullName(i, false);
|
|
|
|
if (cf >= 1 && cf <= numgamefiles && !str.CompareNoCase(fname))
|
|
|
|
{
|
|
|
|
fileSystem.RenameFile(i, renameTo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-11 16:05:25 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
2019-11-01 21:17:15 +00:00
|
|
|
|
|
|
|
void InitFileSystem(TArray<GrpEntry>& groups)
|
|
|
|
{
|
|
|
|
TArray<int> dependencies;
|
|
|
|
TArray<FString> Files;
|
|
|
|
|
|
|
|
// First comes the engine's own stuff.
|
2019-12-26 13:04:53 +00:00
|
|
|
FString baseres = progdir + ENGINERES_FILE;
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, baseres, true, -1, GameConfig);
|
2019-11-01 21:17:15 +00:00
|
|
|
|
|
|
|
bool insertdirectoriesafter = Args->CheckParm("-insertdirafter");
|
|
|
|
|
|
|
|
int i = groups.Size()-1;
|
2020-02-01 20:12:09 +00:00
|
|
|
FString fn;
|
2019-11-01 21:17:15 +00:00
|
|
|
for (auto &grp : groups)
|
|
|
|
{
|
|
|
|
// Add all dependencies, plus the directory of the base dependency.
|
|
|
|
// Directories of addon content are not added if they differ from the main directory.
|
|
|
|
// Also, the directory is inserted after the base dependency, allowing the addons to override directory content.
|
|
|
|
// This can be overridden via command line switch if needed.
|
2020-02-01 20:12:09 +00:00
|
|
|
if (!grp.FileInfo.loaddirectory && grp.FileName.IsNotEmpty())
|
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, grp.FileName, true, -1, GameConfig);
|
2020-02-01 20:12:09 +00:00
|
|
|
fn = ExtractFilePath(grp.FileName);
|
|
|
|
if (fn.Len() > 0 && fn.Back() != '/') fn += '/';
|
|
|
|
}
|
2019-11-01 21:17:15 +00:00
|
|
|
|
|
|
|
for (auto& fname : grp.FileInfo.loadfiles)
|
|
|
|
{
|
|
|
|
FString newname = fn + fname;
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, newname, true, -1, GameConfig);
|
2019-11-01 21:17:15 +00:00
|
|
|
}
|
2019-11-02 17:46:11 +00:00
|
|
|
bool insert = (!insertdirectoriesafter && &grp == &groups[0]) || (insertdirectoriesafter && &grp == &groups.Last());
|
2019-11-01 21:17:15 +00:00
|
|
|
|
|
|
|
// Add the game's main directory in the proper spot.
|
2019-11-02 17:46:11 +00:00
|
|
|
if (insert)
|
2019-11-01 21:17:15 +00:00
|
|
|
{
|
|
|
|
// Build's original 'file system' loads all GRPs before the first external directory.
|
|
|
|
// Do this only if explicitly requested because this severely limits the usability of GRP files.
|
2019-12-17 19:08:59 +00:00
|
|
|
if (insertdirectoriesafter && userConfig.AddFilesPre) for (auto& file : *userConfig.AddFilesPre)
|
2019-11-01 21:17:15 +00:00
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, file, true, -1, GameConfig);
|
2019-11-01 21:17:15 +00:00
|
|
|
}
|
|
|
|
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, fn, true, -1, GameConfig);
|
2019-11-01 21:17:15 +00:00
|
|
|
}
|
|
|
|
i--;
|
|
|
|
}
|
2020-06-07 11:35:23 +00:00
|
|
|
fileSystem.SetMaxIwadNum(Files.Size() - 1);
|
2019-11-01 21:17:15 +00:00
|
|
|
|
2020-05-21 23:24:40 +00:00
|
|
|
D_AddConfigFiles(Files, "Global.Autoload", "*.grp", GameConfig);
|
|
|
|
|
|
|
|
long len;
|
|
|
|
int lastpos = 0;
|
|
|
|
|
|
|
|
while (lastpos < LumpFilter.Len() && (len = strcspn(LumpFilter.GetChars() + lastpos, ".")) > 0)
|
2020-01-11 16:05:25 +00:00
|
|
|
{
|
2020-05-21 23:24:40 +00:00
|
|
|
auto file = LumpFilter.Left(len + lastpos) + ".Autoload";
|
|
|
|
D_AddConfigFiles(Files, file, "*.grp", GameConfig);
|
|
|
|
lastpos += len + 1;
|
2020-01-11 16:05:25 +00:00
|
|
|
}
|
|
|
|
|
2019-12-17 19:08:59 +00:00
|
|
|
if (!insertdirectoriesafter && userConfig.AddFilesPre) for (auto& file : *userConfig.AddFilesPre)
|
2019-11-01 21:17:15 +00:00
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, file, true, -1, GameConfig);
|
2019-11-01 21:17:15 +00:00
|
|
|
}
|
2019-12-17 19:08:59 +00:00
|
|
|
if (userConfig.AddFiles)
|
2019-11-01 21:17:15 +00:00
|
|
|
{
|
2019-12-17 19:08:59 +00:00
|
|
|
for (auto& file : *userConfig.AddFiles)
|
|
|
|
{
|
2020-04-11 22:07:52 +00:00
|
|
|
D_AddFile(Files, file, true, -1, GameConfig);
|
2019-12-17 19:08:59 +00:00
|
|
|
}
|
2019-11-01 21:17:15 +00:00
|
|
|
|
2019-12-17 19:08:59 +00:00
|
|
|
// Finally, if the last entry in the chain is a directory, it's being considered the mod directory, and all GRPs inside need to be loaded, too.
|
|
|
|
if (userConfig.AddFiles->NumArgs() > 0)
|
2019-11-01 21:17:15 +00:00
|
|
|
{
|
2019-12-17 19:08:59 +00:00
|
|
|
auto fn = (*userConfig.AddFiles)[userConfig.AddFiles->NumArgs() - 1];
|
|
|
|
bool isdir = false;
|
|
|
|
if (DirEntryExists(fn, &isdir) && isdir)
|
|
|
|
{
|
|
|
|
// Insert the GRPs before this entry itself.
|
|
|
|
FString lastfn;
|
|
|
|
Files.Pop(lastfn);
|
2020-04-11 22:07:52 +00:00
|
|
|
for (auto ext : validexts)
|
|
|
|
{
|
|
|
|
D_AddDirectory(Files, fn, ext, GameConfig);
|
|
|
|
}
|
2019-12-17 19:08:59 +00:00
|
|
|
Files.Push(lastfn);
|
|
|
|
}
|
2019-11-01 21:17:15 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-11 16:05:25 +00:00
|
|
|
|
2019-11-01 21:17:15 +00:00
|
|
|
|
|
|
|
TArray<FString> todelete;
|
2019-12-26 09:47:10 +00:00
|
|
|
for (auto& g : groups)
|
|
|
|
{
|
|
|
|
todelete.Append(g.FileInfo.tobedeleted);
|
|
|
|
}
|
2020-01-10 20:36:46 +00:00
|
|
|
todelete.Append(userConfig.toBeDeleted);
|
2020-04-11 21:54:33 +00:00
|
|
|
LumpFilterInfo lfi;
|
2019-11-01 21:17:15 +00:00
|
|
|
|
2020-04-11 21:54:33 +00:00
|
|
|
lfi.dotFilter = LumpFilter;
|
|
|
|
lfi.postprocessFunc = [&]()
|
|
|
|
{
|
|
|
|
DeleteStuff(fileSystem, todelete, groups.Size());
|
|
|
|
};
|
|
|
|
fileSystem.InitMultipleFiles(Files, false, &lfi);
|
2020-01-10 20:54:18 +00:00
|
|
|
if (Args->CheckParm("-dumpfs"))
|
2019-11-01 21:17:15 +00:00
|
|
|
{
|
2020-01-10 20:54:18 +00:00
|
|
|
FILE* f = fopen("filesystem.dir", "wb");
|
|
|
|
for (int i = 0; i < fileSystem.GetNumEntries(); i++)
|
|
|
|
{
|
|
|
|
auto fd = fileSystem.GetFileAt(i);
|
2020-04-11 21:54:33 +00:00
|
|
|
fprintf(f, "%.50s %60s %d\n", fd->getName(), fileSystem.GetResourceFileFullName(fileSystem.GetFileContainer(i)), fd->Size());
|
2020-01-10 20:54:18 +00:00
|
|
|
}
|
|
|
|
fclose(f);
|
2019-11-01 21:17:15 +00:00
|
|
|
}
|
|
|
|
}
|