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