Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ The Python wrappers can be found on PyPI in the [sourcepp](https://pypi.org/proj
<td>
<a href="https://quakewiki.org/wiki/.pak" target="_blank" rel="noreferrer">PAK</a> (Quake, WON Half-Life)
<br> &bull; <a href="https://en.wikipedia.org/wiki/Sin_(video_game)" target="_blank" rel="noreferrer">SiN</a> modifications
<br> &bull; SiN Reloaded modifications
<br> &bull; <a href="https://store.steampowered.com/app/824600/HROT" target="_blank" rel="noreferrer">HROT</a> modifications
</td>
<td align="center">✅</td>
Expand Down Expand Up @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ <h2>Supported Formats</h2>
<a href="https://quakewiki.org/wiki/.pak" target="_blank" rel="noreferrer">PAK</a> (Quake, WON Half-Life)
<ul>
<li><a href="https://en.wikipedia.org/wiki/Sin_(video_game)" target="_blank" rel="noreferrer">SiN</a> modifications</li>
<li>SiN Reloaded modifications</li>
<li><a href="https://store.steampowered.com/app/824600/HROT" target="_blank" rel="noreferrer">HROT</a> modifications</li>
</ul>
</td>
Expand Down Expand Up @@ -901,6 +902,7 @@ <h2>Special Thanks</h2>
<li>OL parser is based on <a href="https://github.com/erysdren/scratch/blob/main/kaitai/worldcraft_ol.ksy" target="_blank" rel="noreferrer">reverse-engineering work</a> by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>ORE parser is based on <a href="https://github.com/erysdren/narbacular-drop-tools" target="_blank" rel="noreferrer">reverse-engineering work</a> by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>REZ parser is based on <a href="https://github.com/no-lith/RezExtract" target="_blank" rel="noreferrer">RezExtract</a> by <a href="https://github.com/no-lith" target="_blank" rel="noreferrer">@no-lith</a>.</li>
<li>SiN Reloaded parser was contributed by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>VPP parser was contributed by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>WAD3 parser/writer was contributed by <a href="https://github.com/ozxybox" target="_blank" rel="noreferrer">@ozxybox</a>.</li>
</ul>
Expand Down
3 changes: 3 additions & 0 deletions include/vpkpp/format/PAK.h
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -25,6 +27,7 @@ class PAK : public PackFile {
enum class Type {
PAK,
SIN,
SIN_RELOADED,
HROT,
};

Expand Down
17 changes: 11 additions & 6 deletions lang/c/include/vpkppc/format/PAK.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions lang/c/src/vpkppc/format/PAK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions lang/csharp/src/vpkpp/Format/PAK.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum PAKType
{
PAK,
SIN,
SIN_RELOADED,
HROT,
}

Expand Down
2 changes: 2 additions & 0 deletions lang/python/src/vpkpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -243,6 +244,7 @@ inline void register_python(py::module_& m) {
py::enum_<PAK::Type>(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
Expand Down
153 changes: 123 additions & 30 deletions src/vpkpp/format/PAK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ std::unique_ptr<PackFile> 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;
Expand Down Expand Up @@ -46,30 +49,66 @@ std::unique_ptr<PackFile> 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 {
// File is not a PAK
return nullptr;
}

const auto directoryOffset = reader.read<uint32_t>();
// Directory size / file entry size
const auto fileCount = reader.read<uint32_t>() / (sizeof(uint32_t) * 2 + pak->getFilenameLength());
if (pak->type == Type::SIN_RELOADED) {
reader.skip_in<int32_t>(); // reserved
const auto directoryOffset = reader.read<uint64_t>();
const auto namesOffset = reader.read<uint64_t>();
const auto fileCount = reader.read<uint32_t>();
const auto namesLength = reader.read<uint32_t>();

// 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<uint64_t>();
const auto fileLen = reader.read<uint32_t>();
const auto nameOfs = reader.read<uint32_t>();

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<uint32_t>();
// Directory size / file entry size
const auto fileCount = reader.read<uint32_t>() / (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<uint32_t>();
entry.length = reader.read<uint32_t>();
entry.offset = reader.read<uint32_t>();
entry.length = reader.read<uint32_t>();

pak->entries.emplace(entryPath, entry);
pak->entries.emplace(entryPath, entry);

if (callback) {
callback(entryPath, entry);
if (callback) {
callback(entryPath, entry);
}
}
}

Expand All @@ -95,14 +134,14 @@ std::optional<std::vector<std::byte>> PAK::readEntry(const std::string& path_) c
return stream.read_bytes(entry->length);
}

void PAK::addEntryInternal(Entry& entry, const std::string& path, std::vector<std::byte>& buffer, EntryOptions options) {
void PAK::addEntryInternal(Entry& entry, const std::string&, std::vector<std::byte>& 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();
Expand All @@ -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<std::byte> fileData;
std::vector<std::byte> nameData;
std::vector<uint32_t> 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;
Expand All @@ -133,24 +181,65 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
// Signature
stream.write<uint32_t>(this->getSignature());

// Offset and size of directory
static constexpr auto HEADER_OFFSET = sizeof(uint32_t) * 3;
stream
.write<uint32_t>(HEADER_OFFSET + fileData.size())
.write<uint32_t>(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<int32_t>(0);

// Offset of directory
static constexpr auto HEADER_OFFSET = sizeof(uint32_t) * 3 + sizeof(uint64_t) * 2;
stream.write<uint64_t>(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<uint64_t>(HEADER_OFFSET + fileData.size() + directorySize);

// Number of files
stream.write<uint32_t>(numFiles);

// Size of names
stream.write<uint32_t>(nameData.size());

// File data
stream.write(fileData);

// Directory
size_t i = 0;
for (const auto& [path, entry] : entriesToBake) {
stream
.write<uint64_t>(entry->offset + HEADER_OFFSET)
.write<uint32_t>(entry->length)
.write<uint32_t>(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<uint32_t>(entry->offset + HEADER_OFFSET)
.write<uint32_t>(entry->length);

if (callback) {
callback(path, *entry);
.write<uint32_t>(HEADER_OFFSET + fileData.size())
.write<uint32_t>(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<uint32_t>(entry->offset + HEADER_OFFSET)
.write<uint32_t>(entry->length);

if (callback) {
callback(path, *entry);
}
}
}
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
8 changes: 8 additions & 0 deletions test/vpkpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading