From f9dc66e0b5d319f8a6b32499608b626292d63232 Mon Sep 17 00:00:00 2001 From: erysdren Date: Fri, 9 Oct 2026 15:08:12 -0500 Subject: [PATCH 1/4] vpkpp: add support for SiN Reloaded PAKs --- include/vpkpp/format/PAK.h | 3 + src/vpkpp/format/PAK.cpp | 139 ++++++++++++++++++++++++++++++------- 2 files changed, 115 insertions(+), 27 deletions(-) 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/src/vpkpp/format/PAK.cpp b/src/vpkpp/format/PAK.cpp index 955bccd10..ec612db3b 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) { + const auto reserved = reader.read(); + 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(namesOffset); + reader.read(namesBuffer, namesLength, false); - reader.seek_in(directoryOffset); - for (uint32_t i = 0; i < fileCount; i++) { - Entry entry = createNewEntry(); + // read directory + reader.seek_in(directoryOffset); + for (uint32_t i = 0; i < fileCount; i++) { + Entry entry = createNewEntry(); - auto entryPath = pak->cleanEntryPath(reader.read_string(pak->getFilenameLength())); + const auto filePos = reader.read(); + const auto fileLen = reader.read(); + const auto nameOfs = reader.read(); - entry.offset = reader.read(); - entry.length = reader.read(); + auto entryPath = pak->cleanEntryPath(namesBuffer.c_str() + nameOfs); - pak->entries.emplace(entryPath, entry); + entry.offset = filePos; + entry.length = fileLen; - if (callback) { - callback(entryPath, entry); + 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(); + + auto entryPath = pak->cleanEntryPath(reader.read_string(pak->getFilenameLength())); + + entry.offset = reader.read(); + entry.length = reader.read(); + + pak->entries.emplace(entryPath, entry); + + if (callback) { + callback(entryPath, entry); + } } } @@ -115,11 +154,16 @@ 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; for (auto& [path, entry] : entriesToBake) { if (auto binData = this->readEntry(path)) { entry->offset = fileData.size(); fileData.insert(fileData.end(), binData->begin(), binData->end()); + + size_t oldSize = nameData.size(); + nameData.resize(oldSize + path.size() + 1); + memcpy(&nameData[oldSize], path.data(), path.size()); } else { entry->offset = 0; entry->length = 0; @@ -133,24 +177,63 @@ 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())); + // Header + if (this->type == Type::SIN_RELOADED) { + // Reserved value + // Basically a version identifier, but not used...? + stream.write(0); - // File data - stream.write(fileData); + // Offset of directory + static constexpr auto HEADER_OFFSET = sizeof(uint32_t) * 3 + sizeof(uint64_t) * 2; + stream.write(HEADER_OFFSET + fileData.size()); - // Directory - for (const auto& [path, entry] : entriesToBake) { - stream - .write(path, true, this->getFilenameLength()) - .write(entry->offset + HEADER_OFFSET) - .write(entry->length); + // Offset of names + uint32_t numFiles = entriesToBake.size(); + uint64_t directorySize = (sizeof(uint64_t) + (sizeof(uint32_t) * 2)) * numFiles; + stream.write(HEADER_OFFSET + fileData.size() + directorySize); - if (callback) { - callback(path, *entry); + // Number of files + stream.write(numFiles); + + // Size of names + stream.write(nameData.size()); + + // File data + stream.write(fileData); + + // Directory + for (const auto& [path, entry] : entriesToBake) { + stream + .write(entry->offset + HEADER_OFFSET) + .write(entry->length); + + if (callback) { + callback(path, *entry); + } + } + + // Names + stream.write(nameData); + } else { + // 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); + + // 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 +263,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; } From 1e586acd33008f220cbe80c9b99703814eb3743a Mon Sep 17 00:00:00 2001 From: erysdren Date: Mon, 12 Oct 2026 07:19:19 -0500 Subject: [PATCH 2/4] vpkpp: hide this code behind type check --- src/vpkpp/format/PAK.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vpkpp/format/PAK.cpp b/src/vpkpp/format/PAK.cpp index ec612db3b..5f59c5b92 100644 --- a/src/vpkpp/format/PAK.cpp +++ b/src/vpkpp/format/PAK.cpp @@ -161,9 +161,11 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa fileData.insert(fileData.end(), binData->begin(), binData->end()); - size_t oldSize = nameData.size(); - nameData.resize(oldSize + path.size() + 1); - memcpy(&nameData[oldSize], path.data(), path.size()); + if (this->type == Type::SIN_RELOADED) { + size_t oldSize = nameData.size(); + nameData.resize(oldSize + path.size() + 1); + memcpy(&nameData[oldSize], path.data(), path.size()); + } } else { entry->offset = 0; entry->length = 0; From 4517615fbfa4f10b88cc81b6d0c0867d199099fb Mon Sep 17 00:00:00 2001 From: erysdren Date: Wed, 14 Oct 2026 13:39:28 -0500 Subject: [PATCH 3/4] vpkpp: add test for SiN Reloaded pak --- test/vpkpp.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/vpkpp.cpp b/test/vpkpp.cpp index 247009bd7..8685a2446 100644 --- a/test/vpkpp.cpp +++ b/test/vpkpp.cpp @@ -74,3 +74,11 @@ TEST(vpkpp, vpp_v3_big_read) { EXPECT_EQ(vpp->getEntryCount(), 20); EXPECT_TRUE(vpp->hasEntry("pretty.txt")); } + +TEST(vpkpp, pak_sin_read) { + const auto vpp = PackFile::open(ASSET_ROOT "vpkpp/sin/test.sin"); + ASSERT_TRUE(vpp); + VPKPP_PRINT_ALL_PATHS(vpp); + EXPECT_EQ(vpp->getEntryCount(), 3); + EXPECT_TRUE(vpp->hasEntry("vault/vault.json")); +} From b3e31b470b66dea1ebf0ec5adb95b7575b73d207 Mon Sep 17 00:00:00 2001 From: erysdren Date: Wed, 14 Oct 2026 13:44:57 -0500 Subject: [PATCH 4/4] vpkpp: oops, forgot to write name offset --- src/vpkpp/format/PAK.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vpkpp/format/PAK.cpp b/src/vpkpp/format/PAK.cpp index 5f59c5b92..80046c66e 100644 --- a/src/vpkpp/format/PAK.cpp +++ b/src/vpkpp/format/PAK.cpp @@ -155,6 +155,7 @@ 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(); @@ -165,6 +166,7 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa size_t oldSize = nameData.size(); nameData.resize(oldSize + path.size() + 1); memcpy(&nameData[oldSize], path.data(), path.size()); + nameOffsets.push_back(oldSize); } } else { entry->offset = 0; @@ -204,10 +206,12 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa stream.write(fileData); // Directory + size_t i = 0; for (const auto& [path, entry] : entriesToBake) { stream .write(entry->offset + HEADER_OFFSET) - .write(entry->length); + .write(entry->length) + .write(nameOffsets[i++]); if (callback) { callback(path, *entry);