SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
OO7.cpp
Go to the documentation of this file.
1#include <vpkpp/format/OO7.h>
2
3#include <filesystem>
4#include <format>
5
6#include <FileStream.h>
7#include <miniz.h>
9
10using namespace sourcepp;
11using namespace vpkpp;
12
13std::unique_ptr<PackFile> OO7::open(const std::string& path, const EntryCallback& callback) {
14 if (!std::filesystem::exists(path)) {
15 // File does not exist
16 return nullptr;
17 }
18
19 auto* oo7 = new OO7{path};
20 auto packFile = std::unique_ptr<PackFile>(oo7);
21
22 FileStream reader{oo7->fullFilePath};
23 reader.seek_in(0);
24
25 reader >> oo7->majorVersion >> oo7->minorVersion;
26 if (oo7->majorVersion != 1 || (oo7->minorVersion != 1 && oo7->minorVersion != 3)) {
27 // We only support v1.1 and v1.3
28 return nullptr;
29 }
30
31 std::vector<std::string> v3EntriesFixup;
32
33 // Read the file tree recursively
34 std::function<void(const std::string&, bool)> readDir;
35 readDir = [&callback, &oo7, &reader, &v3EntriesFixup, &readDir](const std::string& parentPath, bool root) {
36 auto currentPath = parentPath + '/' + reader.read_string(reader.read<uint32_t>(), false);
37 if (root) {
38 currentPath = ""; // The root folder gets ignored
39 }
40 auto subDirCount = reader.read<uint32_t>();
41 if (oo7->minorVersion == 3) {
42 reader.skip_in<uint32_t>(); // File count
43 }
44 while (1) {
45 auto filenameSize = reader.read<uint32_t>();
46 if (!filenameSize) {
47 break;
48 }
49
50 Entry entry = createNewEntry();
51
52 auto entryPath = oo7->cleanEntryPath(currentPath + '/' + reader.read_string(filenameSize, false));
53
54 bool compressed = reader.read<uint8_t>();
55 entry.length = reader.read<uint32_t>();
56 if (compressed) {
57 entry.compressedLength = reader.read<uint32_t>();
58 } else {
59 reader.skip_in<uint32_t>();
60 }
61
62 if (oo7->minorVersion == 1) {
63 entry.offset = reader.tell_in();
64
65 if (compressed) {
66 reader.skip_in(entry.compressedLength);
67 } else {
68 reader.skip_in(entry.length);
69 }
70 } else {
71 v3EntriesFixup.push_back(entryPath);
72 }
73
74 oo7->entries.emplace(entryPath, entry);
75
76 if (callback) {
77 callback(entryPath, entry);
78 }
79 }
80 for (uint32_t i = 0; i < subDirCount; i++) {
81 readDir(currentPath, false);
82 }
83 };
84 readDir("", true);
85
86 for (const auto& entryPath : v3EntriesFixup) {
87 auto& entry = oo7->entries.at(entryPath);
88 entry.offset = reader.tell_in();
89 if (!entry.compressedLength) {
90 reader.skip_in(entry.length);
91 } else {
92 reader.skip_in(entry.compressedLength);
93 }
94 }
95
96 // Absolutely no idea what the demo checksum is calculated from, and if it's
97 // the file tree minus file data that is insane. It technically has one, but
98 // I'm not going to advertise that to users of this library.
99 if (oo7->minorVersion == 3 && reader.read<uint32_t>() == 16) {
100 oo7->hasChecksum = true;
101 reader >> oo7->checksum;
102 }
103
104 return packFile;
105}
106
108 return this->hasChecksum;
109}
110
112 if (!this->hasChecksum) {
113 return true;
114 }
115
116 // Looping here is faster than parsing the tree again
117 // We're assuming the first entry on disk is directly after the tree
118 if (this->entries.empty()) {
119 return true;
120 }
121 uint32_t minOffset = UINT32_MAX;
122 this->runForAllEntries([&minOffset](const std::string&, const Entry& entry) {
123 minOffset = (entry.offset < minOffset) ? entry.offset : minOffset;
124 });
125 auto data = fs::readFileBuffer(this->fullFilePath, sizeof(uint32_t) * 2);
126 data.resize(minOffset - (sizeof(uint32_t) * 2));
127 return crypto::computeMD5(data) == this->checksum;
128}
129
130std::optional<std::vector<std::byte>> OO7::readEntry(const std::string& path_) const {
131 auto path = this->cleanEntryPath(path_);
132 auto entry = this->findEntry(path);
133 if (!entry) {
134 return std::nullopt;
135 }
136 if (entry->unbaked) {
137 return readUnbakedEntry(*entry);
138 }
139
140 // It's baked into the file on disk
141 FileStream stream{this->fullFilePath};
142 if (!stream) {
143 return std::nullopt;
144 }
145 stream.seek_in_u(entry->offset);
146 if (!entry->compressedLength) {
147 return stream.read_bytes(entry->length);
148 }
149
150 // Decompress
151 auto compressedData = stream.read_bytes(entry->compressedLength);
152 mz_ulong uncompressedLength = entry->length;
153 std::vector<std::byte> uncompressedData(uncompressedLength);
154 if (mz_uncompress(reinterpret_cast<unsigned char*>(uncompressedData.data()), &uncompressedLength, reinterpret_cast<const unsigned char*>(compressedData.data()), entry->compressedLength) != MZ_OK) {
155 return std::nullopt;
156 }
157 return uncompressedData;
158}
159
161 using enum Attribute;
162 return LENGTH;
163}
164
165OO7::operator std::string() const {
166 return PackFileReadOnly::operator std::string() + std::format(" | Version v{}.{}", this->majorVersion, this->minorVersion);
167}
This class represents the metadata that a file has inside a PackFile.
Definition Entry.h:14
uint64_t offset
Offset, format-specific meaning - 0 if unused, or if the offset genuinely is 0.
Definition Entry.h:33
uint64_t compressedLength
If the format supports compression, this is the compressed length.
Definition Entry.h:30
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length).
Definition Entry.h:26
uint32_t minorVersion
Definition OO7.h:36
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition OO7.cpp:160
uint32_t majorVersion
Definition OO7.h:35
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a 007 file.
Definition OO7.cpp:13
bool hasChecksum
Definition OO7.h:37
bool verifyPackFileChecksum() const override
Verify the checksum of the entire file, returns true on success Will return true if there is no check...
Definition OO7.cpp:111
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
Definition OO7.cpp:130
bool hasPackFileChecksum() const override
Returns true if the entire file has a checksum.
Definition OO7.cpp:107
std::array< std::byte, 16 > checksum
Definition OO7.h:38
EntryCallbackBase< void > EntryCallback
Definition PackFile.h:38
std::optional< Entry > findEntry(const std::string &path_, bool includeUnbaked=true) const
Try to find an entry given the file path.
Definition PackFile.cpp:172
std::string fullFilePath
Definition PackFile.h:231
EntryTrie entries
Definition PackFile.h:232
void runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
Definition PackFile.cpp:529
std::string cleanEntryPath(const std::string &path) const
Definition PackFile.cpp:706
static Entry createNewEntry()
Definition PackFile.cpp:715
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
Definition PackFile.cpp:719
std::array< std::byte, 16 > computeMD5(std::span< const std::byte > buffer)
Definition MD5.cpp:8
std::vector< std::byte > readFileBuffer(const std::filesystem::path &filepath, std::size_t startOffset=0)
Definition FS.cpp:7
Attribute
Definition Attribute.h:7