@@ -146,6 +146,9 @@ std::error_code throw_err;
146146// Dependencies file in packages
147147#define PAK_DEPS_FILE " DEPS"
148148
149+ // Deleted file list in packages
150+ #define PAK_DELETED_FILE " DELETED"
151+
149152// Whether the search paths have been initialized yet. This can be used to delay
150153// writing log files until the filesystem is initialized.
151154static bool isInitialized = false ;
@@ -1052,11 +1055,91 @@ struct LoadedPakGuard {
10521055};
10531056static LoadedPakGuard loadedPaksGuard;
10541057
1058+ // std::unordered_set uses std::hash which does not
1059+ // hash pair of std::string.
1060+ struct stdStringPairHasher
1061+ {
1062+ std::size_t operator ()(const std::pair<std::string, std::string>& p) const
1063+ {
1064+ std::hash<std::string> string_hasher;
1065+ return string_hasher (p.first ) ^ string_hasher (p.second );
1066+ }
1067+ };
1068+
1069+ // List of deleted filenames per pak files.
1070+ // first string is pak name, second string is deleted file name
1071+ static std::unordered_set<std::pair<std::string, std::string>, stdStringPairHasher> deletedFileSet;
1072+
10551073// Map of filenames to pak files. The size_t is an offset into loadedPaks and
10561074// the offset_t is the position within the zip archive (unused for PAK_DIR).
10571075static std::unordered_map<std::string, std::pair<uint32_t , offset_t >> fileMap;
10581076
10591077#ifndef BUILD_VM
1078+ /* Parse the deleted file list file of a package.
1079+
1080+ Each line of the file is the pak basename a file must
1081+ be deleted from, followed by the deleted file name.
1082+
1083+ Example:
1084+ map-chasm chasm-level0.navMesh
1085+
1086+ This would prevent loading any chasm-level0.navMesh file
1087+ from any dependencies with map-chasm basename.
1088+
1089+ Current pak name is known as parent.name so it would be
1090+ possible to implement a format variant that assumes pak name
1091+ to be the same as the one containing the DELETED file. */
1092+ static void ParseDeleted (const PakInfo& parent, Str::StringRef deletedData)
1093+ {
1094+ auto lineStart = deletedData.begin ();
1095+ int line = 0 ;
1096+ while (lineStart != deletedData.end ()) {
1097+ // Get the end of the line or the end of the file.
1098+ line++;
1099+ auto lineEnd = std::find (lineStart, deletedData.end (), ' \n ' );
1100+
1101+ // Skip spaces.
1102+ while (lineStart != lineEnd && Str::cisspace (*lineStart)) {
1103+ lineStart++;
1104+ }
1105+
1106+ if (lineStart == lineEnd) {
1107+ lineStart = lineEnd == deletedData.end () ? lineEnd : lineEnd + 1 ;
1108+ continue ;
1109+ }
1110+
1111+ // Read the pak name.
1112+ std::string pakName;
1113+ while (lineStart != lineEnd && !Str::cisspace (*lineStart)) {
1114+ pakName.push_back (*lineStart++);
1115+ }
1116+
1117+ // Skip spaces.
1118+ while (lineStart != lineEnd && Str::cisspace (*lineStart)) {
1119+ lineStart++;
1120+ }
1121+
1122+ if (lineStart != lineEnd) {
1123+ // Read the file name.
1124+ std::string fileName;
1125+
1126+ while (lineStart != lineEnd) {
1127+ fileName.push_back (*lineStart++);
1128+ }
1129+
1130+ // If this is the end of the line, add the path to the deleted file list.
1131+ if (lineStart == lineEnd) {
1132+ fsLogs.Debug (" Deleted file %s from %s in %s" , fileName, pakName, parent.path );
1133+ deletedFileSet.emplace (std::pair<std::string, std::string>(pakName, fileName));
1134+ continue ;
1135+ }
1136+ }
1137+
1138+ fsLogs.Warn (" Invalid deleted file list specification on line %d in %s" , line, Path::Build (parent.path , PAK_DELETED_FILE));
1139+ lineStart = lineEnd == deletedData.end () ? lineEnd : lineEnd + 1 ;
1140+ }
1141+ }
1142+
10601143// Parse the dependencies file of a package
10611144// Each line of the dependencies file is a name followed by an optional version
10621145static void ParseDeps (const PakInfo& parent, Str::StringRef depsData, std::error_code& err)
@@ -1130,9 +1213,34 @@ static void ParseDeps(const PakInfo& parent, Str::StringRef depsData, std::error
11301213 }
11311214}
11321215
1216+ /* The code is expected to be only reliable for ignoring deleted files
1217+ in dependencies. For example if the unvanquished_0.52.2.dpk pak lists the
1218+ scripts/colors.shader file in the DELETED file and has unvanquished_0.52.1.dpk
1219+ in DEPS, the scripts/colors.shader file from unvanquished_0.52.1.dpk will
1220+ be ignored.
1221+
1222+ If the scripts/colors.shader file is still shipped in unvanquished_0.52.2.dpk
1223+ and listed in the DELETED file from unvanquished_0.52.2.dpk you cannot expect
1224+ the file to be ignored, it is expected the user deletes the file for real in
1225+ the parent pak. Making sure the DELETED file applies on files of the same pak
1226+ would increase code complexity while packager can just delete the file, in
1227+ some cases the file may be ignored (if DELETED file is read first) but you
1228+ must not rely on it and not expect it.
1229+
1230+ This feature means it's possible to only delete a file from a repository but
1231+ also to move a file from a pak repository to another pak repository and not
1232+ get the older version of the file being loaded from the old pak instead of
1233+ the new file from the new pak. */
1234+ static bool FileIsDeleted (const PakInfo& pak, Str::StringRef filename)
1235+ {
1236+ return deletedFileSet.find (std::pair<std::string, std::string>(pak.name , filename)) != deletedFileSet.end ();
1237+ }
1238+
11331239static void InternalLoadPak (const PakInfo& pak, Util::optional<uint32_t > expectedChecksum, Str::StringRef pathPrefix, std::error_code& err)
11341240{
11351241 Util::optional<uint32_t > realChecksum;
1242+ bool hasDeleted = false ;
1243+ offset_t deletedOffset = 0 ;
11361244 bool hasDeps = false ;
11371245 offset_t depsOffset = 0 ;
11381246 ZipArchive zipFile;
@@ -1177,10 +1285,19 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
11771285 if (err)
11781286 return ;
11791287 for (auto it = dirRange.begin (); it != dirRange.end ();) {
1180- if (!isLegacy && (*it == PAK_DEPS_FILE))
1288+ if (!isLegacy && *it == PAK_DELETED_FILE) {
1289+ hasDeleted = true ;
1290+ }
1291+ else if (!isLegacy && *it == PAK_DEPS_FILE) {
11811292 hasDeps = true ;
1293+ }
11821294 else if (!Str::IsSuffix (" /" , *it) && Str::IsPrefix (pathPrefix, *it)) {
1183- fileMap.emplace (*it, std::pair<uint32_t , offset_t >(loadedPaks.size () - 1 , 0 ));
1295+ if (FileIsDeleted (pak, *it)) {
1296+ Log::Debug (" Ignoring deleted file %s from %s" , *it, pak.path );
1297+ }
1298+ else {
1299+ fileMap.emplace (*it, std::pair<uint32_t , offset_t >(loadedPaks.size () - 1 , 0 ));
1300+ }
11841301 }
11851302 it.increment (err);
11861303 if (err)
@@ -1201,9 +1318,11 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
12011318
12021319 // Get the file list and calculate the checksum of the package (checksum of all file checksums)
12031320 realChecksum = crc32 (0 , Z_NULL, 0 );
1204- zipFile.ForEachFile ([&pak, &realChecksum, &pathPrefix, &hasDeps, &depsOffset, &isLegacy](Str::StringRef filename, offset_t offset, uint32_t crc) {
1321+ zipFile.ForEachFile ([&pak, &realChecksum, &pathPrefix, &hasDeps, &hasDeleted, & depsOffset, &deletedOffset , &isLegacy](Str::StringRef filename, offset_t offset, uint32_t crc) {
12051322 // Note that 'return' is effectively 'continue' since we are in a lambda
1206- if (!Str::IsPrefix (pathPrefix, filename) && filename != PAK_DEPS_FILE)
1323+ if (!Str::IsPrefix (pathPrefix, filename)
1324+ && filename != PAK_DELETED_FILE
1325+ && filename != PAK_DEPS_FILE)
12071326 return ;
12081327 if (Str::IsSuffix (" /" , filename))
12091328 return ;
@@ -1217,12 +1336,23 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
12171336 realChecksum = crc32 (*realChecksum, reinterpret_cast <const Bytef*>(&crc), sizeof (crc));
12181337 }
12191338
1220- if (!isLegacy && (filename == PAK_DEPS_FILE)) {
1339+ if (!isLegacy && filename == PAK_DELETED_FILE) {
1340+ hasDeleted = true ;
1341+ deletedOffset = offset;
1342+ return ;
1343+ }
1344+ else if (!isLegacy && filename == PAK_DEPS_FILE) {
12211345 hasDeps = true ;
12221346 depsOffset = offset;
12231347 return ;
12241348 }
1225- fileMap.emplace (filename, std::pair<uint32_t , offset_t >(loadedPaks.size () - 1 , offset));
1349+
1350+ if (FileIsDeleted (pak, filename)) {
1351+ Log::Debug (" Ignoring deleted file %s from %s" , filename, pak.path );
1352+ }
1353+ else {
1354+ fileMap.emplace (filename, std::pair<uint32_t , offset_t >(loadedPaks.size () - 1 , offset));
1355+ }
12261356 }, err);
12271357 if (err)
12281358 return ;
@@ -1258,33 +1388,64 @@ static void InternalLoadPak(const PakInfo& pak, Util::optional<uint32_t> expecte
12581388 fsLogs.Warn (" Pak checksum doesn't match filename: %s" , pak.path );
12591389 }
12601390
1261- // Load dependencies, but not if a checksum was specified
1262- // Do not look for dependencies if it's a legacy pak (pk3)
1263- if (!isLegacy && hasDeps && !expectedChecksum) {
1264- std::string depsData;
1265- if (pak.type == pakType_t::PAK_DIR) {
1266- File depsFile = RawPath::OpenRead (Path::Build (pak.path , PAK_DEPS_FILE), err);
1267- if (err)
1268- return ;
1269- depsData = depsFile.ReadAll (err);
1270- if (err)
1271- return ;
1272- } else if (pak.type == pakType_t::PAK_ZIP) {
1273- zipFile.OpenFile (depsOffset, err);
1274- if (err)
1275- return ;
1276- offset_t length = zipFile.FileLength (err);
1277- if (err)
1278- return ;
1279- depsData.resize (length);
1280- auto read = zipFile.ReadFile (&depsData[0 ], length, err);
1281- depsData.resize (read);
1282- if (err)
1283- return ;
1284- } else {
1285- ASSERT_UNREACHABLE ();
1391+ // Load deleted file list
1392+ // Do not look for deleted file list if it's a legacy pak (pk3)
1393+ if (!isLegacy) {
1394+ if (hasDeleted) {
1395+ std::string deletedData;
1396+ if (pak.type == pakType_t::PAK_DIR) {
1397+ File depsFile = RawPath::OpenRead (Path::Build (pak.path , PAK_DELETED_FILE), err);
1398+ if (err)
1399+ return ;
1400+ deletedData = depsFile.ReadAll (err);
1401+ if (err)
1402+ return ;
1403+ } else if (pak.type == pakType_t::PAK_ZIP) {
1404+ zipFile.OpenFile (depsOffset, err);
1405+ if (err)
1406+ return ;
1407+ offset_t length = zipFile.FileLength (err);
1408+ if (err)
1409+ return ;
1410+ deletedData.resize (length);
1411+ auto read = zipFile.ReadFile (&deletedData[0 ], length, err);
1412+ deletedData.resize (read);
1413+ if (err)
1414+ return ;
1415+ } else {
1416+ ASSERT_UNREACHABLE ();
1417+ }
1418+ ParseDeleted (pak, deletedData);
1419+ }
1420+
1421+ // Load dependencies, but not if a checksum was specified
1422+ // Do not look for dependencies if it's a legacy pak (pk3)
1423+ if (hasDeps && !expectedChecksum) {
1424+ std::string depsData;
1425+ if (pak.type == pakType_t::PAK_DIR) {
1426+ File depsFile = RawPath::OpenRead (Path::Build (pak.path , PAK_DEPS_FILE), err);
1427+ if (err)
1428+ return ;
1429+ depsData = depsFile.ReadAll (err);
1430+ if (err)
1431+ return ;
1432+ } else if (pak.type == pakType_t::PAK_ZIP) {
1433+ zipFile.OpenFile (depsOffset, err);
1434+ if (err)
1435+ return ;
1436+ offset_t length = zipFile.FileLength (err);
1437+ if (err)
1438+ return ;
1439+ depsData.resize (length);
1440+ auto read = zipFile.ReadFile (&depsData[0 ], length, err);
1441+ depsData.resize (read);
1442+ if (err)
1443+ return ;
1444+ } else {
1445+ ASSERT_UNREACHABLE ();
1446+ }
1447+ ParseDeps (pak, depsData, err);
12861448 }
1287- ParseDeps (pak, depsData, err);
12881449 }
12891450}
12901451
@@ -1306,6 +1467,7 @@ void LoadPakExplicit(const PakInfo& pak, uint32_t expectedChecksum, std::error_c
13061467void ClearPaks ()
13071468{
13081469 fsLogs.Verbose (" ^5Unloading all paks" );
1470+ deletedFileSet.clear ();
13091471 fileMap.clear ();
13101472 for (LoadedPakInfo& x: loadedPaks) {
13111473 if (x.fd != -1 )
0 commit comments