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
556 changes: 278 additions & 278 deletions CMakeLists.txt

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ 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 rowspan="23"><code>vpkpp</code></td>
<td rowspan="25"><code>vpkpp</code></td>
<td>007 v1.1, v1.3 (007 - Nightfire)</td>
<td align="center">✅</td>
<td align="center">❌</td>
<td rowspan="23" align="center">C<br>C#</td>
<td rowspan="25" align="center">C<br>C#</td>
</tr>
<tr><!-- empty row to disable github striped bg color --></tr>
<tr>
Expand Down Expand Up @@ -225,6 +225,12 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
<td align="center">✅</td>
</tr>
<tr><!-- empty row to disable github striped bg color --></tr>
<tr>
<td>VPP (Red Faction)</td>
<td align="center">✅</td>
<td align="center">❌</td>
</tr>
<tr><!-- empty row to disable github striped bg color --></tr>
<tr>
<td>WAD v3</td>
<td align="center">✅</td>
Expand Down Expand Up @@ -369,6 +375,7 @@ found on PyPI in the [sourcepp](https://pypi.org/project/sourcepp) package.
- `vpkpp`'s GCF parser was contributed by [@bt](https://github.com/caatge) and [@ymgve](https://github.com/ymgve).
- `vpkpp`'s 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).
- `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 VPP parser was contributed by [@erysdren](https://github.com/erysdren).
- `vpkpp`'s WAD3 parser/writer was contributed by [@ozxybox](https://github.com/ozxybox).
- `vtfpp`'s SHT parser/writer was contributed by [@Trico Everfire](https://github.com/Trico-Everfire).
- `vtfpp`'s VTF write support is loosely based on work by [@Trico Everfire](https://github.com/Trico-Everfire).
Expand Down
10 changes: 8 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
<td align="center">❌</td>
</tr>
<tr>
<td rowspan="12"><code>vpkpp</code></td>
<td rowspan="13"><code>vpkpp</code></td>
<td>007 v1.1, v1.3 (007 - Nightfire)</td>
<td align="center">✅</td>
<td align="center">❌</td>
<td rowspan="12" align="center">C<br>C#</td>
<td rowspan="13" align="center">C<br>C#</td>
</tr>
<tr>
<td>FPX v10 (Tactical Intervention)</td>
Expand Down Expand Up @@ -198,6 +198,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
<td align="center">✅</td>
<td align="center">✅</td>
</tr>
<tr>
<td>VPP (Red Faction)</td>
<td align="center">✅</td>
<td align="center">❌</td>
</tr>
<tr>
<td>WAD v3</td>
<td align="center">✅</td>
Expand Down Expand Up @@ -326,6 +331,7 @@ found on PyPI in the [sourcepp](https://pypi.org/project/sourcepp) package.
- `vpkpp`'s GCF parser was contributed by [@bt](https://github.com/caatge) and [@ymgve](https://github.com/ymgve).
- `vpkpp`'s 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).
- `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 VPP parser was contributed by [@erysdren](https://github.com/erysdren).
- `vpkpp`'s WAD3 parser/writer was contributed by [@ozxybox](https://github.com/ozxybox).
- `vtfpp`'s SHT parser/writer was contributed by [@Trico Everfire](https://github.com/Trico-Everfire).
- `vtfpp`'s VTF write support is loosely based on work by [@Trico Everfire](https://github.com/Trico-Everfire).
Expand Down
38 changes: 38 additions & 0 deletions include/vpkpp/format/VPP.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include "../PackFile.h"

namespace vpkpp {

constexpr uint32_t VPP_SIGNATURE_LIL = 0x51890ACE;
constexpr uint32_t VPP_SIGNATURE_BIG = 0xCE0A8951;
constexpr uint32_t VPP_ALIGNMENT = 2048;
constexpr std::string_view VPP_EXTENSION = ".vpp";

class VPP : public PackFileReadOnly {
public:
/// Open a VPP file
[[nodiscard]] static std::unique_ptr<PackFile> open(const std::string& path, const EntryCallback& callback = nullptr);

static constexpr inline std::string_view GUID = "C47C5C4D88AE4032967F8CDC93D6D2E1";

[[nodiscard]] constexpr std::string_view getGUID() const override {
return VPP::GUID;
}

[[nodiscard]] constexpr bool isCaseSensitive() const override {
return true;
}

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

[[nodiscard]] Attribute getSupportedEntryAttributes() const override;

protected:
using PackFileReadOnly::PackFileReadOnly;

private:
VPKPP_REGISTER_PACKFILE_OPEN(VPP_EXTENSION, &VPP::open);
};

} // namespace vpkpp
1 change: 1 addition & 0 deletions include/vpkpp/vpkpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "format/PCK.h"
#include "format/VPK.h"
#include "format/VPK_VTMB.h"
#include "format/VPP.h"
#include "format/WAD3.h"
#include "format/ZIP.h"
#include "Attribute.h"
Expand Down
80 changes: 41 additions & 39 deletions src/vpkpp/_vpkpp.cmake
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
add_pretty_parser(vpkpp
DEPS libzstd_static miniz MINIZIP::minizip sourcepp_crypto sourcepp_parser sourcepp::kvpp
DEPS_PUBLIC tsl::hat_trie
PRECOMPILED_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/FPX.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/GCF.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/GMA.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/OL.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/OO7.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/ORE.h"
"${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/WAD3.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"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/Options.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/PackFile.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/vpkpp.h"
SOURCES
"${CMAKE_CURRENT_LIST_DIR}/format/FPX.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/GCF.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/GMA.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/OL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/OO7.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/ORE.cpp"
"${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/WAD3.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/ZIP.cpp"
"${CMAKE_CURRENT_LIST_DIR}/PackFile.cpp")

if(SOURCEPP_VPKPP_SUPPORT_VPK_V54)
target_compile_definitions(sourcepp_vpkpp PRIVATE VPKPP_SUPPORT_VPK_V54)
endif()
add_pretty_parser(vpkpp
DEPS libzstd_static miniz MINIZIP::minizip sourcepp_crypto sourcepp_parser sourcepp::kvpp
DEPS_PUBLIC tsl::hat_trie
PRECOMPILED_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/FPX.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/GCF.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/GMA.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/OL.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/OO7.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/ORE.h"
"${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/VPP.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/WAD3.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"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/Options.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/PackFile.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/vpkpp.h"
SOURCES
"${CMAKE_CURRENT_LIST_DIR}/format/FPX.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/GCF.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/GMA.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/OL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/OO7.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/ORE.cpp"
"${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/VPP.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/WAD3.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/ZIP.cpp"
"${CMAKE_CURRENT_LIST_DIR}/PackFile.cpp")

if(SOURCEPP_VPKPP_SUPPORT_VPK_V54)
target_compile_definitions(sourcepp_vpkpp PRIVATE VPKPP_SUPPORT_VPK_V54)
endif()
96 changes: 96 additions & 0 deletions src/vpkpp/format/VPP.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include <vpkpp/format/VPP.h>

#include <filesystem>

#include <FileStream.h>

using namespace sourcepp;
using namespace vpkpp;

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

auto* vpp = new VPP{path};
auto packFile = std::unique_ptr<PackFile>(vpp);

FileStream reader{vpp->fullFilePath};
reader.seek_in(0);

// Verify signature
if (const auto signature = reader.read<uint32_t>(); signature == VPP_SIGNATURE_BIG) {
reader.set_big_endian(true);
} else if (signature != VPP_SIGNATURE_LIL) {
return nullptr;
}

// Create entries
if (const auto version = reader.read<uint32_t>(); version == 1) {
// Get header fields
const auto entryCount = reader.read<uint32_t>();

// Verify file size
if (reader.read<uint32_t>() != std::filesystem::file_size(path)) {
return nullptr;
}

// Seek past header to 2048 byte boundary
reader.seek_in(VPP_ALIGNMENT);

// Get first file offset
// 64 is the byte size of each file directory entry
uint32_t entryOffset = VPP_ALIGNMENT + sourcepp::math::paddingForAlignment(VPP_ALIGNMENT, 64 * entryCount);

// Read file entries
for (uint32_t i = 0; i < entryCount; i++) {
Entry entry = createNewEntry();

// Get file path
const auto entryPath = vpp->cleanEntryPath(reader.read_string(60));

// Get file size
entry.length = reader.read<uint32_t>();

// Calculate file offset
entry.offset = entryOffset;
entryOffset += entry.length + sourcepp::math::paddingForAlignment(VPP_ALIGNMENT, entry.length);

// Put it in
vpp->entries.emplace(entryPath, entry);

if (callback) {
callback(entryPath, entry);
}
}
} else {
return nullptr;
}

return packFile;
}

std::optional<std::vector<std::byte>> VPP::readEntry(const std::string& path_) const {
auto path = this->cleanEntryPath(path_);
auto entry = this->findEntry(path);
if (!entry) {
return std::nullopt;
}
if (entry->unbaked) {
return 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);
}

Attribute VPP::getSupportedEntryAttributes() const {
using enum Attribute;
return LENGTH;
}
12 changes: 12 additions & 0 deletions test/vpkpp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <gtest/gtest.h>

#include <vpkpp/vpkpp.h>

using namespace sourcepp;
using namespace vpkpp;

TEST(vpkpp, vpp_read) {
const auto vpp = PackFile::open(ASSET_ROOT "vpkpp/vpp/v1.vpp");
ASSERT_TRUE(vpp);
EXPECT_EQ(vpp->getEntryCount(), 29);
}