Skip to content
Draft
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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ The Python wrappers can be found on PyPI in the [sourcepp](https://pypi.org/proj
</tr>
<tr><!-- empty row to disable GitHub striped bg color --></tr>
<tr>
<td rowspan="35"><code>vpkpp</code></td>
<td rowspan="37"><code>vpkpp</code></td>
<td>007 v1.1, v1.3 (007 - Nightfire)</td>
<td align="center">✅</td>
<td align="center">❌</td>
<td rowspan="35" align="center">C<br>C#<br>Python</td>
<td rowspan="37" align="center">C<br>C#<br>Python</td>
</tr>
<tr><!-- empty row to disable GitHub striped bg color --></tr>
<tr>
Expand Down Expand Up @@ -259,6 +259,12 @@ The Python wrappers can be found on PyPI in the [sourcepp](https://pypi.org/proj
<td align="center">✅</td>
</tr>
<tr><!-- empty row to disable GitHub striped bg color --></tr>
<tr>
<td>REZ v1 (LithTech Engine)</td>
<td align="center">✅</td>
<td align="center">❌</td>
</tr>
<tr><!-- empty row to disable GitHub striped bg color --></tr>
<tr>
<td>TAB v3 (Avalanche Engine)</td>
<td align="center">✅</td>
Expand Down Expand Up @@ -521,6 +527,7 @@ These are only the tools and games using `sourcepp` that I know of. If you would
- HOG parser was contributed by [@erysdren](https://github.com/erysdren).
- 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).
- ORE parser is based on [reverse-engineering work](https://github.com/erysdren/narbacular-drop-tools) by [@erysdren](https://github.com/erysdren).
- REZ parser is based on [RezExtract](https://github.com/no-lith/RezExtract) by [@no-lith](https://github.com/no-lith).
- VPP parser was contributed by [@erysdren](https://github.com/erysdren).
- WAD3 parser/writer was contributed by [@ozxybox](https://github.com/ozxybox).
- `vtfpp`
Expand Down
8 changes: 7 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ <h2>Supported Formats</h2>
<td>✅</td>
</tr>
<tr class="support-table-no-offset">
<td rowspan="18">
<td rowspan="19">
vpkpp
<ul>
<li>C</li>
Expand Down Expand Up @@ -671,6 +671,11 @@ <h2>Supported Formats</h2>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>REZ v1 (LithTech Engine)</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>TAB v3 (Avalanche Engine)</td>
<td>✅</td>
Expand Down Expand Up @@ -880,6 +885,7 @@ <h2>Special Thanks</h2>
<li>HOG parser was contributed by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>OL parser is based on <a href="https://github.com/erysdren/scratch/blob/main/kaitai/worldcraft_ol.ksy" target="_blank" rel="noreferrer">reverse-engineering work</a> by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>ORE parser is based on <a href="https://github.com/erysdren/narbacular-drop-tools" target="_blank" rel="noreferrer">reverse-engineering work</a> by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>REZ parser is based on <a href="https://github.com/no-lith/RezExtract" target="_blank" rel="noreferrer">RezExtract</a> by <a href="https://github.com/no-lith" target="_blank" rel="noreferrer">@no-lith</a>.</li>
<li>VPP parser was contributed by <a href="https://github.com/erysdren" target="_blank" rel="noreferrer">@erysdren</a>.</li>
<li>WAD3 parser/writer was contributed by <a href="https://github.com/ozxybox" target="_blank" rel="noreferrer">@ozxybox</a>.</li>
</ul>
Expand Down
31 changes: 31 additions & 0 deletions include/vpkpp/format/REZ.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// ReSharper disable CppRedundantQualifier

#pragma once

#include "../PackFile.h"

namespace vpkpp {

constexpr std::string_view REZ_EXTENSION = ".rez";

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

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

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

protected:
using PackFileReadOnly::PackFileReadOnly;

std::string fileType;
std::string userTitle;
uint32_t version;

private:
VPKPP_REGISTER_PACKFILE_OPEN(REZ_EXTENSION, &REZ::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 @@ -16,6 +16,7 @@
#include "format/ORE.h"
#include "format/PAK.h"
#include "format/PCK.h"
#include "format/REZ.h"
#include "format/TAB.h"
#include "format/VPK.h"
#include "format/VPK_VTMB.h"
Expand Down
2 changes: 2 additions & 0 deletions src/vpkpp/_vpkpp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_pretty_parser(vpkpp
"${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/REZ.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/TAB.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/VPK_VTMB.h"
Expand All @@ -37,6 +38,7 @@ add_pretty_parser(vpkpp
"${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/REZ.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/TAB.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/format/VPK_VTMB.cpp"
Expand Down
157 changes: 157 additions & 0 deletions src/vpkpp/format/REZ.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#include <vpkpp/format/REZ.h>

#include <filesystem>

#include <FileStream.h>

using namespace sourcepp;
using namespace vpkpp;

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

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

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

if (const auto c = reader.read<uint8_t>(); c != '\r' && c != '&') {
return nullptr;
}
if (const auto c = reader.read<uint8_t>(); c != '\n' && c != '#') {
return nullptr;
}
reader.read(rez->fileType, 60, false);
string::rtrim(rez->fileType);

if (const auto c = reader.read<uint8_t>(); c != '\r' && c != '!') {
return nullptr;
}
if (const auto c = reader.read<uint8_t>(); c != '\n' && c != '\"') {
return nullptr;
}
reader.read(rez->userTitle, 60, false);
string::rtrim(rez->userTitle);

if (const auto c = reader.read<uint8_t>(); c != '\r' && c != '%') {
return nullptr;
}
if (const auto c = reader.read<uint8_t>(); c != '\n' && c != '\'') {
return nullptr;
}
rez->version = reader.read<uint8_t>();

switch (rez->version) {
case 0x1A: // EOF
reader.read(rez->version);
if (rez->version != 1) {
return nullptr;
}
break;
default:
return nullptr;
}

const auto rootDirOffset = reader.read<uint32_t>();
const auto rootDirSize = reader.read<uint32_t>();
reader.skip_in<uint32_t>(); // rootDirTime
reader.skip_in<uint32_t>(); // nextWriteOffset
reader.skip_in<uint32_t>(); // time
reader.skip_in<uint32_t>(); // largestKeyArray
reader.skip_in<uint32_t>(); // largestDirNameSize
reader.skip_in<uint32_t>(); // largestFileNameSize
reader.skip_in<uint32_t>(); // largestCommentSize
reader.skip_in<uint8_t>(); // isSorted

// Read the file tree recursively
std::function<bool(const std::string&, std::span<const std::byte>)> readBlock;
readBlock = [&callback, &rez, &reader, &readBlock](const std::string& parentPath, std::span<const std::byte> blockData) {
BufferStreamReadOnly blockStream{blockData.data(), blockData.size()};

while (blockStream.tell() != blockStream.size()) {
const auto blockType = blockStream.read<uint32_t>();
const auto blockOffset = blockStream.read<uint32_t>();
const auto blockLength = blockStream.read<uint32_t>();
blockStream.skip<uint32_t>(); // blockTime

if (blockType == 0) {
// File
Entry entry = createNewEntry();

const auto resourceID = blockStream.read<uint32_t>();
auto resourceExtension = blockStream.read_string(4);
const auto resourceKeyCount = blockStream.read<uint32_t>();
const auto resourceName = blockStream.read_string();
const auto resourceDesc = blockStream.read_string();
blockStream.skip<uint32_t>(resourceKeyCount);

auto entryPath = parentPath;
entryPath += '/' + resourceName;
if (!resourceExtension.empty()) {
std::ranges::reverse(resourceExtension);
entryPath += '.' + resourceExtension;
}
entryPath = rez->cleanEntryPath(entryPath);

entry.length = blockLength;
entry.offset = blockOffset;

rez->entries.emplace(entryPath, entry);

if (callback) {
callback(entryPath, entry);
}
} else if (blockType == 1) {
// Directory
const auto dirName = blockStream.read_string();
if (blockLength) {
std::string newParentPath;
if (!parentPath.empty()) {
newParentPath = parentPath;
newParentPath += '/' + dirName;
} else {
newParentPath = dirName;
}
readBlock(newParentPath, reader.seek_in(blockOffset).read_bytes(blockLength));
}
} else {
// Invalid
return false;
}
}
return true;
};
if (!readBlock("", reader.seek_in(rootDirOffset).read_bytes(rootDirSize))) {
return nullptr;
}

return packFile;
}

std::optional<std::vector<std::byte>> REZ::readEntry(const std::string& path_) const {
const auto path = this->cleanEntryPath(path_);
const 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 REZ::getSupportedEntryAttributes() const {
using enum Attribute;
return LENGTH;
}
8 changes: 8 additions & 0 deletions test/vpkpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ TEST(vpkpp, ore_read) {
EXPECT_TRUE(ore->hasEntry("startup.cfg"));
}

TEST(vpkpp, rez_v1_read) {
const auto rez = PackFile::open(ASSET_ROOT "vpkpp/rez/v1.rez");
ASSERT_TRUE(rez);
VPKPP_PRINT_ALL_PATHS(rez);
EXPECT_EQ(rez->getEntryCount(), 1);
EXPECT_TRUE(rez->hasEntry("patch.txt"));
}

TEST(vpkpp, vpp_v1_read) {
const auto vpp = PackFile::open(ASSET_ROOT "vpkpp/vpp/v1.vpp");
ASSERT_TRUE(vpp);
Expand Down
Loading