Skip to content

Commit

Permalink
vpkpp: add support for HROT's pak format variant
Browse files Browse the repository at this point in the history
  • Loading branch information
craftablescience committed Jan 1, 2025
1 parent 32c9f8d commit 255ba6f
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 11 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
</tr>
<tr><!-- empty row to disable github striped bg color --></tr>
<tr>
<td><a href="https://quakewiki.org/wiki/.pak">PAK</a> (Quake, WON Half-Life)</td>
<td>
<a href="https://quakewiki.org/wiki/.pak">PAK</a> (Quake, WON Half-Life)
<br> &bull; <a href="https://store.steampowered.com/app/824600/HROT">HROT</a> modifications
</td>
<td align="center">✅</td>
<td align="center">✅</td>
</tr>
Expand Down Expand Up @@ -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).
Expand Down
6 changes: 5 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
<td align="center">❌</td>
</tr>
<tr>
<td><a href="https://quakewiki.org/wiki/.pak">PAK</a> (Quake, WON Half-Life)</td>
<td>
<a href="https://quakewiki.org/wiki/.pak">PAK</a> (Quake, WON Half-Life)
<br> &bull; <a href="https://store.steampowered.com/app/824600/HROT">HROT</a> modifications
</td>
<td align="center">✅</td>
<td align="center">✅</td>
</tr>
Expand Down Expand Up @@ -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).
Expand Down
12 changes: 11 additions & 1 deletion include/vpkpp/format/PAK.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<PackFile> create(const std::string& path);
static std::unique_ptr<PackFile> create(const std::string& path, bool forHROT = false);

/// Open a PAK file
[[nodiscard]] static std::unique_ptr<PackFile> open(const std::string& path, const EntryCallback& callback = nullptr);
Expand All @@ -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<std::byte>& buffer, EntryOptions options) override;

bool isHROT_ = false;

private:
VPKPP_REGISTER_PACKFILE_OPEN(PAK_EXTENSION, &PAK::open);
};
Expand Down
7 changes: 7 additions & 0 deletions lang/c/include/vpkppc/format/PAK.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
31 changes: 31 additions & 0 deletions lang/c/src/vpkppc/format/PAK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <sourceppc/Convert.hpp>
#include <sourceppc/Helpers.h>
#include <vpkppc/Convert.hpp>

using namespace vpkpp;

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

Expand All @@ -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<PAK>()) {
return false;
}
return dynamic_cast<PAK*>(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<PAK>()) {
return;
}
return dynamic_cast<PAK*>(pak)->setHROT(hrot);
}
36 changes: 36 additions & 0 deletions lang/csharp/src/vpkpp/Format/PAK.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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));
}
}
}
}
}
28 changes: 20 additions & 8 deletions src/vpkpp/format/PAK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
using namespace sourcepp;
using namespace vpkpp;

std::unique_ptr<PackFile> PAK::create(const std::string& path) {
std::unique_ptr<PackFile> 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<uint32_t>(0)
.write<uint32_t>(0);
}
Expand All @@ -30,20 +30,24 @@ std::unique_ptr<PackFile> PAK::open(const std::string& path, const EntryCallback
FileStream reader{pak->fullFilePath};
reader.seek_in(0);

if (auto signature = reader.read<uint32_t>(); signature != PAK_SIGNATURE) {
if (auto signature = reader.read<uint32_t>(); 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<uint32_t>();
// Directory size / file entry size
auto fileCount = reader.read<uint32_t>() / 64;
auto fileCount = reader.read<uint32_t>() / (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<uint32_t>();
entry.length = reader.read<uint32_t>();
Expand Down Expand Up @@ -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<uint32_t>(entry->offset + DIRECTORY_INDEX + directorySize));
stream.write(static_cast<uint32_t>(entry->length));

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

0 comments on commit 255ba6f

Please sign in to comment.