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; +}