diff --git a/README.md b/README.md
index 961029a8d..a2813f094 100644
--- a/README.md
+++ b/README.md
@@ -167,7 +167,10 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
- PAK (Quake, WON Half-Life) |
+
+ PAK (Quake, WON Half-Life)
+ • HROT modifications
+ |
✅ |
✅ |
@@ -322,6 +325,7 @@ found on PyPI in the [sourcepp](https://pypi.org/project/sourcepp) package.
## Special Thanks
- `steampp` is based on the [SteamAppPathProvider](https://github.com/Trico-Everfire/SteamAppPathProvider) library by [@Trico Everfire](https://github.com/Trico-Everfire) and [Momentum Mod](https://momentum-mod.org) contributors.
+- `vpkpp`'s 007 parser is based on [reverse-engineering work](https://raw.githubusercontent.com/SmileyAG/dumpster/refs/heads/src_jb007nightfirepc_alurazoe/file_format_analysis.txt) by Alhexx.
- `vpkpp`'s GCF parser was contributed by [@bt](https://github.com/caatge) and [@ymgve](https://github.com/ymgve).
- `vpkpp`'s ORE parser is based on [reverse-engineering work](https://github.com/erysdren/narbacular-drop-tools) by [@erysdren](https://github.com/erysdren).
- `vpkpp`'s WAD3 parser/writer was contributed by [@ozxybox](https://github.com/ozxybox).
diff --git a/docs/index.md b/docs/index.md
index ae22705b0..1f747d126 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -147,7 +147,10 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
❌ |
- PAK (Quake, WON Half-Life) |
+
+ PAK (Quake, WON Half-Life)
+ • HROT modifications
+ |
✅ |
✅ |
@@ -282,6 +285,7 @@ found on PyPI in the [sourcepp](https://pypi.org/project/sourcepp) package.
## Special Thanks
- `steampp` is based on the [SteamAppPathProvider](https://github.com/Trico-Everfire/SteamAppPathProvider) library by [@Trico Everfire](https://github.com/Trico-Everfire) and [Momentum Mod](https://momentum-mod.org) contributors.
+- `vpkpp`'s 007 parser is based on [reverse-engineering work](https://raw.githubusercontent.com/SmileyAG/dumpster/refs/heads/src_jb007nightfirepc_alurazoe/file_format_analysis.txt) by Alhexx.
- `vpkpp`'s GCF parser was contributed by [@bt](https://github.com/caatge) and [@ymgve](https://github.com/ymgve).
- `vpkpp`'s ORE parser is based on [reverse-engineering work](https://github.com/erysdren/narbacular-drop-tools) by [@erysdren](https://github.com/erysdren).
- `vpkpp`'s WAD3 parser/writer was contributed by [@ozxybox](https://github.com/ozxybox).
diff --git a/include/vpkpp/format/PAK.h b/include/vpkpp/format/PAK.h
index 01cb191a5..10f96986d 100644
--- a/include/vpkpp/format/PAK.h
+++ b/include/vpkpp/format/PAK.h
@@ -8,12 +8,16 @@ namespace vpkpp {
constexpr int8_t PAK_FILENAME_MAX_SIZE = 56;
constexpr auto PAK_SIGNATURE = sourcepp::parser::binary::makeFourCC("PACK");
+
+constexpr int8_t PAK_HROT_FILENAME_MAX_SIZE = 120;
+constexpr auto PAK_HROT_SIGNATURE = sourcepp::parser::binary::makeFourCC("HROT");
+
constexpr std::string_view PAK_EXTENSION = ".pak";
class PAK : public PackFile {
public:
/// Create a PAK file
- static std::unique_ptr create(const std::string& path);
+ static std::unique_ptr create(const std::string& path, bool forHROT = false);
/// Open a PAK file
[[nodiscard]] static std::unique_ptr open(const std::string& path, const EntryCallback& callback = nullptr);
@@ -30,11 +34,17 @@ class PAK : public PackFile {
[[nodiscard]] Attribute getSupportedEntryAttributes() const override;
+ [[nodiscard]] bool isHROT() const;
+
+ void setHROT(bool hrot);
+
protected:
using PackFile::PackFile;
void addEntryInternal(Entry& entry, const std::string& path, std::vector& buffer, EntryOptions options) override;
+ bool isHROT_ = false;
+
private:
VPKPP_REGISTER_PACKFILE_OPEN(PAK_EXTENSION, &PAK::open);
};
diff --git a/lang/c/include/vpkppc/format/PAK.h b/lang/c/include/vpkppc/format/PAK.h
index f9fd8f72c..81ae9c2e7 100644
--- a/lang/c/include/vpkppc/format/PAK.h
+++ b/lang/c/include/vpkppc/format/PAK.h
@@ -5,8 +5,15 @@
// REQUIRES MANUAL FREE: vpkpp_close
SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_pak_create(const char* path);
+// REQUIRES MANUAL FREE: vpkpp_close
+SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_pak_create_with_options(const char* path, int hrot);
+
// REQUIRES MANUAL FREE: vpkpp_close
SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_pak_open(const char* path, vpkpp_entry_callback_t callback);
// REQUIRES MANUAL FREE: sourcepp_string_free
SOURCEPP_API sourcepp_string_t vpkpp_pak_guid(vpkpp_pack_file_handle_t handle);
+
+SOURCEPP_API int vpkpp_pak_is_hrot(vpkpp_pack_file_handle_t handle);
+
+SOURCEPP_API void vpkpp_pak_set_hrot(vpkpp_pack_file_handle_t handle, int hrot);
diff --git a/lang/c/src/vpkppc/format/PAK.cpp b/lang/c/src/vpkppc/format/PAK.cpp
index 2898cede8..c86a697ee 100644
--- a/lang/c/src/vpkppc/format/PAK.cpp
+++ b/lang/c/src/vpkppc/format/PAK.cpp
@@ -4,6 +4,7 @@
#include
#include
+#include
using namespace vpkpp;
@@ -17,6 +18,16 @@ SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_pak_create(const char* path) {
return packFile.release();
}
+SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_pak_create_with_options(const char* path, int hrot) {
+ SOURCEPP_EARLY_RETURN_VAL(path, nullptr);
+
+ auto packFile = PAK::create(path, hrot);
+ if (!packFile) {
+ return nullptr;
+ }
+ return packFile.release();
+}
+
SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_pak_open(const char* path, vpkpp_entry_callback_t callback) {
SOURCEPP_EARLY_RETURN_VAL(path, nullptr);
@@ -35,3 +46,23 @@ SOURCEPP_API sourcepp_string_t vpkpp_pak_guid(vpkpp_pack_file_handle_t handle) {
return Convert::toString(PAK::GUID);
}
+
+SOURCEPP_API int vpkpp_pak_is_hrot(vpkpp_pack_file_handle_t handle) {
+ SOURCEPP_EARLY_RETURN_VAL(handle, false);
+
+ auto* pak = Convert::packFile(handle);
+ if (!pak->isInstanceOf()) {
+ return false;
+ }
+ return dynamic_cast(pak)->isHROT();
+}
+
+SOURCEPP_API void vpkpp_pak_set_hrot(vpkpp_pack_file_handle_t handle, int hrot) {
+ SOURCEPP_EARLY_RETURN(handle);
+
+ auto* pak = Convert::packFile(handle);
+ if (!pak->isInstanceOf()) {
+ return;
+ }
+ return dynamic_cast(pak)->setHROT(hrot);
+}
diff --git a/lang/csharp/src/vpkpp/Format/PAK.cs b/lang/csharp/src/vpkpp/Format/PAK.cs
index 5104e2cfb..de80ce995 100644
--- a/lang/csharp/src/vpkpp/Format/PAK.cs
+++ b/lang/csharp/src/vpkpp/Format/PAK.cs
@@ -12,11 +12,20 @@ internal static unsafe partial class PAK
[LibraryImport("vpkppc", EntryPoint = "vpkpp_pak_create")]
public static partial void* Create([MarshalAs(UnmanagedType.LPStr)] string path);
+ [LibraryImport("vpkppc", EntryPoint = "vpkpp_pak_create_with_options")]
+ public static partial void* Create([MarshalAs(UnmanagedType.LPStr)] string path, int hrot);
+
[LibraryImport("vpkppc", EntryPoint = "vpkpp_pak_open")]
public static partial void* Open([MarshalAs(UnmanagedType.LPStr)] string path, IntPtr callback);
[LibraryImport("vpkppc", EntryPoint = "vpkpp_pak_guid")]
public static partial sourcepp.String GUID();
+
+ [LibraryImport("vpkppc", EntryPoint = "vpkpp_pak_is_hrot")]
+ public static partial int IsHROT(void* handle);
+
+ [LibraryImport("vpkppc", EntryPoint = "vpkpp_pak_set_hrot")]
+ public static partial void SetHROT(void* handle, int hrot);
}
}
@@ -33,6 +42,15 @@ private protected unsafe PAK(void* handle) : base(handle) {}
}
}
+ public static PAK? Create(string path, bool hrot)
+ {
+ unsafe
+ {
+ var handle = Extern.PAK.Create(path, Convert.ToInt32(hrot));
+ return handle == null ? null : new PAK(handle);
+ }
+ }
+
public new static PAK? Open(string path)
{
unsafe
@@ -66,5 +84,23 @@ public static string GUID
}
}
}
+
+ public bool HROT
+ {
+ get
+ {
+ unsafe
+ {
+ return Convert.ToBoolean(Extern.PAK.IsHROT(Handle));
+ }
+ }
+ set
+ {
+ unsafe
+ {
+ Extern.PAK.SetHROT(Handle, Convert.ToInt32(value));
+ }
+ }
+ }
}
}
diff --git a/src/vpkpp/format/PAK.cpp b/src/vpkpp/format/PAK.cpp
index b50789d46..571e35c5d 100644
--- a/src/vpkpp/format/PAK.cpp
+++ b/src/vpkpp/format/PAK.cpp
@@ -7,11 +7,11 @@
using namespace sourcepp;
using namespace vpkpp;
-std::unique_ptr PAK::create(const std::string& path) {
+std::unique_ptr PAK::create(const std::string& path, bool hrot) {
{
FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
stream
- .write(PAK_SIGNATURE)
+ .write(hrot ? PAK_HROT_SIGNATURE : PAK_SIGNATURE)
.write(0)
.write(0);
}
@@ -30,20 +30,24 @@ std::unique_ptr PAK::open(const std::string& path, const EntryCallback
FileStream reader{pak->fullFilePath};
reader.seek_in(0);
- if (auto signature = reader.read(); signature != PAK_SIGNATURE) {
+ if (auto signature = reader.read(); signature == PAK_SIGNATURE) {
+ pak->isHROT_ = false;
+ } else if (signature == PAK_HROT_SIGNATURE) {
+ pak->isHROT_ = true;
+ } else {
// File is not a PAK
return nullptr;
}
auto directoryOffset = reader.read();
// Directory size / file entry size
- auto fileCount = reader.read() / 64;
+ auto fileCount = reader.read() / (pak->isHROT() ? 128 : 64);
reader.seek_in(directoryOffset);
for (uint32_t i = 0; i < fileCount; i++) {
Entry entry = createNewEntry();
- auto entryPath = pak->cleanEntryPath(reader.read_string(PAK_FILENAME_MAX_SIZE));
+ auto entryPath = pak->cleanEntryPath(reader.read_string(pak->isHROT() ? PAK_HROT_FILENAME_MAX_SIZE : PAK_FILENAME_MAX_SIZE));
entry.offset = reader.read();
entry.length = reader.read();
@@ -113,17 +117,17 @@ bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
stream.seek_out(0);
// Signature
- stream.write(PAK_SIGNATURE);
+ stream.write(this->isHROT() ? PAK_HROT_SIGNATURE : PAK_SIGNATURE);
// Index and size of directory
static constexpr uint32_t DIRECTORY_INDEX = sizeof(PAK_SIGNATURE) + sizeof(uint32_t) * 2;
stream.write(DIRECTORY_INDEX);
- const uint32_t directorySize = entriesToBake.size() * 64;
+ const uint32_t directorySize = entriesToBake.size() * (this->isHROT() ? 128 : 64);
stream.write(directorySize);
// Directory
for (const auto& [path, entry] : entriesToBake) {
- stream.write(path, false, PAK_FILENAME_MAX_SIZE);
+ stream.write(path, false, this->isHROT() ? PAK_HROT_FILENAME_MAX_SIZE : PAK_FILENAME_MAX_SIZE);
stream.write(static_cast(entry->offset + DIRECTORY_INDEX + directorySize));
stream.write(static_cast(entry->length));
@@ -146,3 +150,11 @@ Attribute PAK::getSupportedEntryAttributes() const {
using enum Attribute;
return LENGTH;
}
+
+bool PAK::isHROT() const {
+ return this->isHROT_;
+}
+
+void PAK::setHROT(bool hrot) {
+ this->isHROT_ = hrot;
+}