diff --git a/README.md b/README.md
index 77512ca03..fc46a8b64 100644
--- a/README.md
+++ b/README.md
@@ -253,6 +253,7 @@ The Python wrappers can be found on PyPI in the [sourcepp](https://pypi.org/proj
PAK (Quake, WON Half-Life)
• SiN modifications
+ • SiN Reloaded modifications
• HROT modifications
|
✅ |
@@ -546,6 +547,7 @@ These are only the tools and games using `sourcepp` that I know of. If you would
- OL parser is based on [reverse-engineering work](https://github.com/erysdren/scratch/blob/main/kaitai/worldcraft_ol.ksy) by [@erysdren](https://github.com/erysdren).
- ORE parser is based on [reverse-engineering work](https://github.com/erysdren/narbacular-drop-tools) by [@erysdren](https://github.com/erysdren).
- REZ parser is based on [RezExtract](https://github.com/no-lith/RezExtract) by [@no-lith](https://github.com/no-lith).
+ - SiN Reloaded parser was contributed by [@erysdren](https://github.com/erysdren).
- VPP parser was contributed by [@erysdren](https://github.com/erysdren).
- WAD3 parser/writer was contributed by [@ozxybox](https://github.com/ozxybox).
- `vtfpp`
diff --git a/docs/index.html b/docs/index.html
index e8727a27c..e770f182a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -665,6 +665,7 @@ Supported Formats
PAK (Quake, WON Half-Life)
- SiN modifications
+ - SiN Reloaded modifications
- HROT modifications
@@ -901,6 +902,7 @@ Special Thanks
OL parser is based on reverse-engineering work by @erysdren.
ORE parser is based on reverse-engineering work by @erysdren.
REZ parser is based on RezExtract by @no-lith.
+ SiN Reloaded parser was contributed by @erysdren.
VPP parser was contributed by @erysdren.
WAD3 parser/writer was contributed by @ozxybox.
diff --git a/include/vpkpp/format/PAK.h b/include/vpkpp/format/PAK.h
index 057b208c9..cae1e41cd 100644
--- a/include/vpkpp/format/PAK.h
+++ b/include/vpkpp/format/PAK.h
@@ -14,6 +14,8 @@ constexpr auto PAK_SIGNATURE = sourcepp::parser::binary::makeFourCC("PACK");
constexpr uint8_t PAK_SIN_FILENAME_MAX_SIZE = 120;
constexpr auto PAK_SIN_SIGNATURE = sourcepp::parser::binary::makeFourCC("SPAK");
+constexpr auto PAK_SIN_RELOADED_SIGNATURE = sourcepp::parser::binary::makeFourCC("SRPK");
+
constexpr uint8_t PAK_HROT_FILENAME_MAX_SIZE = 120;
constexpr auto PAK_HROT_SIGNATURE = sourcepp::parser::binary::makeFourCC("HROT");
@@ -25,6 +27,7 @@ class PAK : public PackFile {
enum class Type {
PAK,
SIN,
+ SIN_RELOADED,
HROT,
};
diff --git a/lang/c/include/vpkppc/format/PAK.h b/lang/c/include/vpkppc/format/PAK.h
index c4db3da4d..02e52e370 100644
--- a/lang/c/include/vpkppc/format/PAK.h
+++ b/lang/c/include/vpkppc/format/PAK.h
@@ -8,6 +8,8 @@ VPKPP_EXTERNVAR const uint32_t VPKPP_PAK_SIGNATURE;
VPKPP_EXTERNVAR const uint8_t VPKPP_PAK_SIN_FILENAME_MAX_SIZE;
VPKPP_EXTERNVAR const uint32_t VPKPP_PAK_SIN_SIGNATURE;
+VPKPP_EXTERNVAR const uint32_t VPKPP_PAK_SIN_RELOADED_SIGNATURE;
+
VPKPP_EXTERNVAR const uint8_t VPKPP_PAK_HROT_FILENAME_MAX_SIZE;
VPKPP_EXTERNVAR const uint32_t VPKPP_PAK_HROT_SIGNATURE;
@@ -17,6 +19,7 @@ VPKPP_EXTERNVAR const char* VPKPP_SIN_EXTENSION;
VPKPP_EXTERN typedef enum {
VPKPP_PAK_TYPE_PAK,
VPKPP_PAK_TYPE_SIN,
+ VPKPP_PAK_TYPE_SIN_RELOADED,
VPKPP_PAK_TYPE_HROT,
} vpkpp_pak_type_e;
@@ -35,18 +38,20 @@ namespace sourceppc::convert {
inline vpkpp::PAK::Type cast(vpkpp_pak_type_e value) {
switch (value) {
- case VPKPP_PAK_TYPE_PAK: return vpkpp::PAK::Type::PAK;
- case VPKPP_PAK_TYPE_SIN: return vpkpp::PAK::Type::SIN;
- case VPKPP_PAK_TYPE_HROT: return vpkpp::PAK::Type::HROT;
+ case VPKPP_PAK_TYPE_PAK: return vpkpp::PAK::Type::PAK;
+ case VPKPP_PAK_TYPE_SIN: return vpkpp::PAK::Type::SIN;
+ case VPKPP_PAK_TYPE_SIN_RELOADED: return vpkpp::PAK::Type::SIN_RELOADED;
+ case VPKPP_PAK_TYPE_HROT: return vpkpp::PAK::Type::HROT;
}
return vpkpp::PAK::Type::PAK;
}
inline vpkpp_pak_type_e cast(vpkpp::PAK::Type value) {
switch (value) {
- case vpkpp::PAK::Type::PAK: return VPKPP_PAK_TYPE_PAK;
- case vpkpp::PAK::Type::SIN: return VPKPP_PAK_TYPE_SIN;
- case vpkpp::PAK::Type::HROT: return VPKPP_PAK_TYPE_HROT;
+ case vpkpp::PAK::Type::PAK: return VPKPP_PAK_TYPE_PAK;
+ case vpkpp::PAK::Type::SIN: return VPKPP_PAK_TYPE_SIN;
+ case vpkpp::PAK::Type::SIN_RELOADED: return VPKPP_PAK_TYPE_SIN_RELOADED;
+ case vpkpp::PAK::Type::HROT: return VPKPP_PAK_TYPE_HROT;
}
return VPKPP_PAK_TYPE_PAK;
}
diff --git a/lang/c/src/vpkppc/format/PAK.cpp b/lang/c/src/vpkppc/format/PAK.cpp
index d37553658..5067c8571 100644
--- a/lang/c/src/vpkppc/format/PAK.cpp
+++ b/lang/c/src/vpkppc/format/PAK.cpp
@@ -11,6 +11,8 @@ const uint32_t VPKPP_PAK_SIGNATURE = PAK_SIGNATURE;
const uint8_t VPKPP_PAK_SIN_FILENAME_MAX_SIZE = PAK_SIN_FILENAME_MAX_SIZE;
const uint32_t VPKPP_PAK_SIN_SIGNATURE = PAK_SIN_SIGNATURE;
+const uint32_t VPKPP_PAK_SIN_RELOADED_SIGNATURE = PAK_SIN_RELOADED_SIGNATURE;
+
const uint8_t VPKPP_PAK_HROT_FILENAME_MAX_SIZE = PAK_HROT_FILENAME_MAX_SIZE;
const uint32_t VPKPP_PAK_HROT_SIGNATURE = PAK_HROT_SIGNATURE;
diff --git a/lang/csharp/src/vpkpp/Format/PAK.cs b/lang/csharp/src/vpkpp/Format/PAK.cs
index 8c8ae56b9..d7e5a279f 100644
--- a/lang/csharp/src/vpkpp/Format/PAK.cs
+++ b/lang/csharp/src/vpkpp/Format/PAK.cs
@@ -11,6 +11,7 @@ public enum PAKType
{
PAK,
SIN,
+ SIN_RELOADED,
HROT,
}
diff --git a/lang/python/src/vpkpp.h b/lang/python/src/vpkpp.h
index 8fd86ae95..814c9aa04 100644
--- a/lang/python/src/vpkpp.h
+++ b/lang/python/src/vpkpp.h
@@ -233,6 +233,7 @@ inline void register_python(py::module_& m) {
vpkpp.attr("PAK_SIGNATURE") = PAK_SIGNATURE;
vpkpp.attr("PAK_SIN_FILENAME_MAX_SIZE") = PAK_SIN_FILENAME_MAX_SIZE;
vpkpp.attr("PAK_SIN_SIGNATURE") = PAK_SIN_SIGNATURE;
+ vpkpp.attr("PAK_SIN_RELOADED_SIGNATURE") = PAK_SIN_RELOADED_SIGNATURE;
vpkpp.attr("PAK_HROT_FILENAME_MAX_SIZE") = PAK_HROT_FILENAME_MAX_SIZE;
vpkpp.attr("PAK_HROT_SIGNATURE") = PAK_HROT_SIGNATURE;
vpkpp.attr("PAK_EXTENSION") = PAK_EXTENSION;
@@ -243,6 +244,7 @@ inline void register_python(py::module_& m) {
py::enum_(cPAK, "Type")
.value("PAK", PAK::Type::PAK)
.value("SIN", PAK::Type::SIN)
+ .value("SIN_RELOADED", PAK::Type::SIN_RELOADED)
.value("HROT", PAK::Type::HROT);
cPAK
diff --git a/src/vpkpp/format/PAK.cpp b/src/vpkpp/format/PAK.cpp
index 955bccd10..f709f78fa 100644
--- a/src/vpkpp/format/PAK.cpp
+++ b/src/vpkpp/format/PAK.cpp
@@ -19,6 +19,9 @@ std::unique_ptr PAK::create(const std::string& path, Type type) {
case Type::SIN:
stream << PAK_SIN_SIGNATURE;
break;
+ case Type::SIN_RELOADED:
+ stream << PAK_SIN_RELOADED_SIGNATURE;
+ break;
case Type::HROT:
stream << PAK_HROT_SIGNATURE;
break;
@@ -46,6 +49,8 @@ std::unique_ptr PAK::open(const std::string& path, const EntryCallback
pak->type = Type::PAK;
} else if (signature == PAK_SIN_SIGNATURE) {
pak->type = Type::SIN;
+ } else if (signature == PAK_SIN_RELOADED_SIGNATURE) {
+ pak->type = Type::SIN_RELOADED;
} else if (signature == PAK_HROT_SIGNATURE) {
pak->type = Type::HROT;
} else {
@@ -53,23 +58,57 @@ std::unique_ptr PAK::open(const std::string& path, const EntryCallback
return nullptr;
}
- const auto directoryOffset = reader.read();
- // Directory size / file entry size
- const auto fileCount = reader.read() / (sizeof(uint32_t) * 2 + pak->getFilenameLength());
+ if (pak->type == Type::SIN_RELOADED) {
+ reader.skip_in(); // reserved
+ const auto directoryOffset = reader.read();
+ const auto namesOffset = reader.read();
+ const auto fileCount = reader.read();
+ const auto namesLength = reader.read();
+
+ // read names buffer
+ std::string namesBuffer;
+ reader.seek_in_u(namesOffset);
+ reader.read(namesBuffer, namesLength, false);
+
+ // read directory
+ reader.seek_in_u(directoryOffset);
+ for (uint32_t i = 0; i < fileCount; i++) {
+ Entry entry = createNewEntry();
+
+ const auto filePos = reader.read();
+ const auto fileLen = reader.read();
+ const auto nameOfs = reader.read();
+
+ auto entryPath = pak->cleanEntryPath(namesBuffer.c_str() + nameOfs);
+
+ entry.offset = filePos;
+ entry.length = fileLen;
+
+ pak->entries.emplace(entryPath, entry);
+
+ if (callback) {
+ callback(entryPath, entry);
+ }
+ }
+ } else {
+ const auto directoryOffset = reader.read();
+ // Directory size / file entry size
+ const auto fileCount = reader.read() / (sizeof(uint32_t) * 2 + pak->getFilenameLength());
- reader.seek_in(directoryOffset);
- for (uint32_t i = 0; i < fileCount; i++) {
- Entry entry = createNewEntry();
+ reader.seek_in(directoryOffset);
+ for (uint32_t i = 0; i < fileCount; i++) {
+ Entry entry = createNewEntry();
- auto entryPath = pak->cleanEntryPath(reader.read_string(pak->getFilenameLength()));
+ auto entryPath = pak->cleanEntryPath(reader.read_string(pak->getFilenameLength()));
- entry.offset = reader.read();
- entry.length = reader.read();
+ entry.offset = reader.read();
+ entry.length = reader.read();
- pak->entries.emplace(entryPath, entry);
+ pak->entries.emplace(entryPath, entry);
- if (callback) {
- callback(entryPath, entry);
+ if (callback) {
+ callback(entryPath, entry);
+ }
}
}
@@ -95,14 +134,14 @@ std::optional> PAK::readEntry(const std::string& path_) c
return stream.read_bytes(entry->length);
}
-void PAK::addEntryInternal(Entry& entry, const std::string& path, std::vector& buffer, EntryOptions options) {
+void PAK::addEntryInternal(Entry& entry, const std::string&, std::vector& buffer, EntryOptions) {
entry.length = buffer.size();
// Offset will be reset when it's baked
entry.offset = 0;
}
-bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCallback& callback) {
+bool PAK::bake(const std::string& outputDir_, BakeOptions, const EntryCallback& callback) {
// Get the proper file output folder
const std::string outputDir = this->getBakeOutputDir(outputDir_);
const std::string outputPath = outputDir + '/' + this->getFilename();
@@ -115,11 +154,20 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
// Read data before overwriting, we don't know if we're writing to ourself
std::vector fileData;
+ std::vector nameData;
+ std::vector nameOffsets;
for (auto& [path, entry] : entriesToBake) {
if (auto binData = this->readEntry(path)) {
entry->offset = fileData.size();
fileData.insert(fileData.end(), binData->begin(), binData->end());
+
+ if (this->type == Type::SIN_RELOADED) {
+ const auto oldSize = nameData.size();
+ nameData.resize(oldSize + path.size() + 1);
+ memcpy(&nameData[oldSize], path.data(), path.size());
+ nameOffsets.push_back(oldSize);
+ }
} else {
entry->offset = 0;
entry->length = 0;
@@ -133,24 +181,65 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
// Signature
stream.write(this->getSignature());
- // Offset and size of directory
- static constexpr auto HEADER_OFFSET = sizeof(uint32_t) * 3;
- stream
- .write(HEADER_OFFSET + fileData.size())
- .write(entriesToBake.size() * (sizeof(uint32_t) * 2 + this->getFilenameLength()));
-
- // File data
- stream.write(fileData);
+ // Header
+ if (this->type == Type::SIN_RELOADED) {
+ // Reserved value
+ // Basically a version identifier, but not used...?
+ stream.write(0);
+
+ // Offset of directory
+ static constexpr auto HEADER_OFFSET = sizeof(uint32_t) * 3 + sizeof(uint64_t) * 2;
+ stream.write(HEADER_OFFSET + fileData.size());
+
+ // Offset of names
+ const auto numFiles = entriesToBake.size();
+ const auto directorySize = (sizeof(uint64_t) + sizeof(uint32_t) * 2) * numFiles;
+ stream.write(HEADER_OFFSET + fileData.size() + directorySize);
+
+ // Number of files
+ stream.write(numFiles);
+
+ // Size of names
+ stream.write(nameData.size());
+
+ // File data
+ stream.write(fileData);
+
+ // Directory
+ size_t i = 0;
+ for (const auto& [path, entry] : entriesToBake) {
+ stream
+ .write(entry->offset + HEADER_OFFSET)
+ .write(entry->length)
+ .write(nameOffsets[i++]);
+
+ if (callback) {
+ callback(path, *entry);
+ }
+ }
- // Directory
- for (const auto& [path, entry] : entriesToBake) {
+ // Names
+ stream.write(nameData);
+ } else {
+ // Offset and size of directory
+ static constexpr auto HEADER_OFFSET = sizeof(uint32_t) * 3;
stream
- .write(path, true, this->getFilenameLength())
- .write(entry->offset + HEADER_OFFSET)
- .write(entry->length);
-
- if (callback) {
- callback(path, *entry);
+ .write(HEADER_OFFSET + fileData.size())
+ .write(entriesToBake.size() * (sizeof(uint32_t) * 2 + this->getFilenameLength()));
+
+ // File data
+ stream.write(fileData);
+
+ // Directory
+ for (const auto& [path, entry] : entriesToBake) {
+ stream
+ .write(path, true, this->getFilenameLength())
+ .write(entry->offset + HEADER_OFFSET)
+ .write(entry->length);
+
+ if (callback) {
+ callback(path, *entry);
+ }
}
}
}
@@ -180,6 +269,8 @@ uint32_t PAK::getSignature() const {
return PAK_SIGNATURE;
case Type::SIN:
return PAK_SIN_SIGNATURE;
+ case Type::SIN_RELOADED:
+ return PAK_SIN_RELOADED_SIGNATURE;
case Type::HROT:
return PAK_HROT_SIGNATURE;
}
@@ -192,6 +283,8 @@ uint8_t PAK::getFilenameLength() const {
return PAK_FILENAME_MAX_SIZE;
case Type::SIN:
return PAK_SIN_FILENAME_MAX_SIZE;
+ case Type::SIN_RELOADED:
+ return 0; // variable
case Type::HROT:
return PAK_HROT_FILENAME_MAX_SIZE;
}
diff --git a/test/vpkpp.cpp b/test/vpkpp.cpp
index 247009bd7..f7a2e0935 100644
--- a/test/vpkpp.cpp
+++ b/test/vpkpp.cpp
@@ -27,6 +27,14 @@ TEST(vpkpp, ore_read) {
EXPECT_TRUE(ore->hasEntry("startup.cfg"));
}
+TEST(vpkpp, pak_sin_read) {
+ const auto sin = PackFile::open(ASSET_ROOT "vpkpp/sin/test.sin");
+ ASSERT_TRUE(sin);
+ VPKPP_PRINT_ALL_PATHS(sin);
+ EXPECT_EQ(sin->getEntryCount(), 3);
+ EXPECT_TRUE(sin->hasEntry("vault/vault.json"));
+}
+
TEST(vpkpp, rez_v1_read) {
const auto rez = PackFile::open(ASSET_ROOT "vpkpp/rez/v1.rez");
ASSERT_TRUE(rez);