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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
<li><a href="https://quakewiki.org/wiki/.pak">PAK</a> (Quake, WON Half-Life)</li>
<li><a href="https://docs.godotengine.org/en/stable/tutorials/export/exporting_pcks.html">PCK</a> v1-2 (Godot Engine)</li>
<li><a href="https://developer.valvesoftware.com/wiki/VPK">VPK</a> v1-2</li>
<li>VPK (Vampire: The Masquerade - Bloodlines)</li>
<li>ZIP</li>
</ul>
</td>
Expand Down
1 change: 1 addition & 0 deletions include/vpkpp/PackFileType.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum class PackFileType {
PAK,
PCK,
VPK,
VPK_VTMB,
ZIP,
};

Expand Down
32 changes: 32 additions & 0 deletions include/vpkpp/format/VPK_VTMB.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <array>
#include <tuple>

#include "../PackFile.h"

namespace vpkpp {

constexpr std::string_view VPK_VTMB_EXTENSION = ".vpk";

class VPK_VTMB : public PackFile {
public:
/// Open Vampire: The Masquerade - Bloodlines VPK files
[[nodiscard]] static std::unique_ptr<PackFile> open(const std::string& path, PackFileOptions options = {}, const Callback& callback = nullptr);

[[nodiscard]] std::optional<std::vector<std::byte>> readEntry(const Entry& entry) const override;

bool bake(const std::string& outputDir_ /*= ""*/, const Callback& callback /*= nullptr*/) override;

[[nodiscard]] std::vector<Attribute> getSupportedEntryAttributes() const override;

protected:
VPK_VTMB(const std::string& fullFilePath_, PackFileOptions options_);

Entry& addEntryInternal(Entry& entry, const std::string& filename_, std::vector<std::byte>& buffer, EntryOptions options_) override;

private:
VPKPP_REGISTER_PACKFILE_OPEN(VPK_VTMB_EXTENSION, &VPK_VTMB::open);
};

} // namespace vpkpp
1 change: 1 addition & 0 deletions lang/c/include/vpkppc/PackFileType.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ typedef enum {
VPKPP_PACK_FILE_TYPE_PAK,
VPKPP_PACK_FILE_TYPE_PCK,
VPKPP_PACK_FILE_TYPE_VPK,
VPKPP_PACK_FILE_TYPE_VPK_VTMB,
VPKPP_PACK_FILE_TYPE_ZIP,
} vpkpp_pack_file_type_e;

Expand Down
9 changes: 9 additions & 0 deletions lang/c/include/vpkppc/format/VPK_VTMB.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include "../PackFile.h"

// REQUIRES MANUAL FREE: vpkpp_close
SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_vpk_vtmb_open(const char* path);

// REQUIRES MANUAL FREE: vpkpp_close
SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_vpk_vtmb_open_with_options(const char* path, vpkpp_pack_file_options_t options);
2 changes: 2 additions & 0 deletions lang/c/src/vpkppc/_vpkppc.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_pretty_parser(vpkpp C
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/PAK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/PCK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/VPK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/VPK_VTMB.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/format/ZIP.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/Attribute.h"
"${CMAKE_CURRENT_SOURCE_DIR}/lang/c/include/vpkppc/Entry.h"
Expand All @@ -25,6 +26,7 @@ add_pretty_parser(vpkpp C
"${CMAKE_CURRENT_LIST_DIR}/format/PAK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/PCK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK_VTMB.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/ZIP.cpp"
"${CMAKE_CURRENT_LIST_DIR}/Convert.cpp"
"${CMAKE_CURRENT_LIST_DIR}/Entry.cpp"
Expand Down
28 changes: 28 additions & 0 deletions lang/c/src/vpkppc/format/VPK_VTMB.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <vpkppc/format/VPK_VTMB.h>

#include <vpkpp/format/VPK_VTMB.h>

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

using namespace vpkpp;

SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_vpk_vtmb_open(const char* path) {
SOURCEPP_EARLY_RETURN_VAL(path, nullptr);

auto packFile = VPK_VTMB::open(path);
if (!packFile) {
return nullptr;
}
return packFile.release();
}

SOURCEPP_API vpkpp_pack_file_handle_t vpkpp_vpk_vtmb_open_with_options(const char* path, vpkpp_pack_file_options_t options) {
SOURCEPP_EARLY_RETURN_VAL(path, nullptr);

auto packFile = VPK_VTMB::open(path, Convert::optionsFromC(options));
if (!packFile) {
return nullptr;
}
return packFile.release();
}
36 changes: 36 additions & 0 deletions lang/csharp/src/vpkpp/Format/VPK_VTMB.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Runtime.InteropServices;

namespace vpkpp.Format
{
internal static unsafe partial class Extern
{
[DllImport("vpkppc")]
public static extern void* vpkpp_vpk_vtmb_open([MarshalAs(UnmanagedType.LPStr)] string path);

[DllImport("vpkppc")]
public static extern void* vpkpp_vpk_vtmb_open_with_options([MarshalAs(UnmanagedType.LPStr)] string path, PackFileOptions options);
}

public class VPK_VTMB : PackFile
{
private protected unsafe VPK_VTMB(void* handle) : base(handle) {}

public new static VPK_VTMB? Open(string path)
{
unsafe
{
var handle = Extern.vpkpp_vpk_vtmb_open(path);
return handle == null ? null : new VPK_VTMB(handle);
}
}

public new static VPK_VTMB? Open(string path, PackFileOptions options)
{
unsafe
{
var handle = Extern.vpkpp_vpk_vtmb_open_with_options(path, options);
return handle == null ? null : new VPK_VTMB(handle);
}
}
}
}
1 change: 1 addition & 0 deletions lang/csharp/src/vpkpp/PackFileType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum PackFileType
PAK,
PCK,
VPK,
VPK_VTMB,
ZIP,
}
}
2 changes: 2 additions & 0 deletions src/vpkpp/PackFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <vpkpp/format/PAK.h>
#include <vpkpp/format/PCK.h>
#include <vpkpp/format/VPK.h>
#include <vpkpp/format/VPK_VTMB.h>
#include <vpkpp/format/ZIP.h>

using namespace sourcepp;
Expand Down Expand Up @@ -418,6 +419,7 @@ bool PackFile::extractAll(const std::string& outputDir, const std::function<bool
std::vector<std::string> rootDirList;
{
std::vector<std::vector<std::string>> pathSplits;
pathSplits.reserve(saveEntries.size());
for (const auto& entry : saveEntries) {
pathSplits.push_back(::splitPath(entry.path));
}
Expand Down
2 changes: 2 additions & 0 deletions src/vpkpp/_vpkpp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_pretty_parser(vpkpp
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/PAK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/PCK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK_VTMB.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/ZIP.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/Attribute.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/Entry.h"
Expand All @@ -25,6 +26,7 @@ add_pretty_parser(vpkpp
"${CMAKE_CURRENT_LIST_DIR}/format/PAK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/PCK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK_VTMB.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/ZIP.cpp"
"${CMAKE_CURRENT_LIST_DIR}/Entry.cpp"
"${CMAKE_CURRENT_LIST_DIR}/PackFile.cpp")
2 changes: 1 addition & 1 deletion src/vpkpp/format/PAK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ std::unique_ptr<PackFile> PAK::open(const std::string& path, PackFileOptions opt
auto fileCount = reader.read<uint32_t>() / 64;

reader.seek_in(directoryOffset);
for (int i = 0; i < fileCount; i++) {
for (uint32_t i = 0; i < fileCount; i++) {
Entry entry = createNewEntry();

reader.read(entry.path, PAK_FILENAME_MAX_SIZE);
Expand Down
170 changes: 170 additions & 0 deletions src/vpkpp/format/VPK_VTMB.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#include <vpkpp/format/VPK_VTMB.h>

#include <filesystem>

#include <FileStream.h>

#include <sourcepp/parser/Text.h>
#include <sourcepp/FS.h>
#include <sourcepp/String.h>

using namespace sourcepp;
using namespace vpkpp;

VPK_VTMB::VPK_VTMB(const std::string& fullFilePath_, PackFileOptions options_)
: PackFile(fullFilePath_, options_) {
this->type = PackFileType::VPK_VTMB;
}

std::unique_ptr<PackFile> VPK_VTMB::open(const std::string& path, PackFileOptions options, const Callback& callback) {
if (!std::filesystem::exists(path)) {
// File does not exist
return nullptr;
}

// Extra check to make sure this is a VTMB VPK path
auto stem = std::filesystem::path{path}.stem().string();
if (stem.length() != 7 || !stem.starts_with("pack") || !parser::text::isNumber(stem.substr(4))) {
return nullptr;
}

auto* vpkVTMB = new VPK_VTMB{path, options};
auto packFile = std::unique_ptr<PackFile>(vpkVTMB);

FileStream reader{vpkVTMB->fullFilePath};
reader.seek_in(-static_cast<int64_t>(sizeof(uint32_t) * 2 + sizeof(uint8_t)), std::ios::end);

auto fileCount = reader.read<uint32_t>();
auto dirOffset = reader.read<uint32_t>();

// Make 100% sure
auto version = reader.read<uint8_t>();
if (version != 0) {
return nullptr;
}

// Ok now let's load this thing
reader.seek_in(dirOffset);
for (uint32_t i = 0; i < fileCount; i++) {
Entry entry = createNewEntry();

entry.path = reader.read_string(reader.read<uint32_t>());
string::normalizeSlashes(entry.path, true);
if (!vpkVTMB->isCaseSensitive()) {
string::toLower(entry.path);
}

entry.offset = reader.read<uint32_t>();
entry.length = reader.read<uint32_t>();

auto parentDir = std::filesystem::path{entry.path}.parent_path().string();
if (!vpkVTMB->entries.contains(parentDir)) {
vpkVTMB->entries[parentDir] = {};
}
vpkVTMB->entries[parentDir].push_back(entry);

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

return packFile;
}

std::optional<std::vector<std::byte>> VPK_VTMB::readEntry(const Entry& entry) const {
if (entry.unbaked) {
return this->readUnbakedEntry(entry);
}

// It's baked into the file on disk
FileStream stream{this->fullFilePath};
if (!stream) {
return std::nullopt;
}
stream.seek_in_u(entry.offset);
return stream.read_bytes(entry.length);
}

Entry& VPK_VTMB::addEntryInternal(Entry& entry, const std::string& filename_, std::vector<std::byte>& buffer, EntryOptions options_) {
auto filename = filename_;
if (!this->isCaseSensitive()) {
string::toLower(filename);
}
auto [dir, name] = splitFilenameAndParentDir(filename);

entry.path = filename;
entry.length = buffer.size();

// Offset will be reset when it's baked
entry.offset = 0;

if (!this->unbakedEntries.contains(dir)) {
this->unbakedEntries[dir] = {};
}
this->unbakedEntries.at(dir).push_back(entry);
return this->unbakedEntries.at(dir).back();
}

bool VPK_VTMB::bake(const std::string& outputDir_, const Callback& callback) {
// Get the proper file output folder
std::string outputDir = this->getBakeOutputDir(outputDir_);
std::string outputPath = outputDir + '/' + this->getFilename();

// Reconstruct data for ease of access
std::vector<Entry*> entriesToBake;
for (auto& [entryDir, entryList] : this->entries) {
for (auto& entry : entryList) {
entriesToBake.push_back(&entry);
}
}
for (auto& [entryDir, entryList] : this->unbakedEntries) {
for (auto& entry : entryList) {
entriesToBake.push_back(&entry);
}
}

{
FileStream stream{outputPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
stream.seek_out(0);

// File data
for (auto* entry : entriesToBake) {
if (auto binData = this->readEntry(*entry)) {
entry->offset = stream.tell_out();

stream.write(*binData);
} else {
entry->offset = 0;
entry->length = 0;
}
}

// Directory
auto dirOffset = stream.tell_out();
for (auto entry : entriesToBake) {
stream.write<uint32_t>(entry->path.length());
stream.write(entry->path, false);
stream.write<uint32_t>(entry->offset);
stream.write<uint32_t>(entry->length);

if (callback) {
callback(entry->getParentPath(), *entry);
}
}

// Footer
stream.write<uint32_t>(this->entries.size() + this->unbakedEntries.size());
stream.write<uint32_t>(dirOffset);
stream.write<uint8_t>(0);
}

// Clean up
this->mergeUnbakedEntries();
PackFile::setFullFilePath(outputDir);
return true;
}

std::vector<Attribute> VPK_VTMB::getSupportedEntryAttributes() const {
using enum Attribute;
return {LENGTH};
}