SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
WAD3.cpp
Go to the documentation of this file.
1//
2// Header: (size=12)
3// fourcc identity
4// int32 lump_count // Number of lump infos in the array
5// int32 lump_offset // Offset to the start of the lump info array
6//
7// NOTE: Are the compression fields actually used in WAD3? Answer seems to be no
8// Lump Infos: (size=32)
9// int32 offset
10// int32 size
11// int32 size_uncompressed
12// int8 type
13// int8 compression
14// int16 padding
15// char name[16]
16
17#include <vpkpp/format/WAD3.h>
18
19#include <filesystem>
20
21#include <FileStream.h>
22#include <sourcepp/String.h>
23
24using namespace sourcepp;
25using namespace vpkpp;
26
28{
30
38
40};
41
42constexpr std::string_view k_FileTypeNames[] = {
43 "palette",
44 "colormap",
45 "qpic",
46 "mip", // miptex
47 "raw",
48 "colormap2",
49 "font",
50};
51
52std::unique_ptr<PackFile> WAD3::create(const std::string& path) {
53 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
54 stream
55 .write(WAD3_SIGNATURE)
56 .write<uint32_t>(0)
57 .write<uint32_t>(0);
58 return WAD3::open(path);
59}
60
61std::unique_ptr<PackFile> WAD3::open(const std::string& path, const EntryCallback& callback) {
62 if (!std::filesystem::exists(path)) {
63 // File does not exist
64 return nullptr;
65 }
66
67 auto* wad = new WAD3{path};
68 std::unique_ptr<PackFile> packFile{wad};
69
70 FileStream reader{wad->fullFilePath};
71 reader.seek_in(0);
72
73 // Verify the identity
74 if (auto signature = reader.read<uint32_t>(); signature != WAD3_SIGNATURE) {
75 return nullptr;
76 }
77
78 // Treating counts as unsigned to simplify some logic... No reason to really have a negative count here anyways
79 auto lumpCount = reader.read<uint32_t>();
80 auto lumpOffset = reader.read<uint32_t>();
81
82 // Read in all lump entries
83 reader.seek_in(lumpOffset);
84 for (uint32_t i = 0; i < lumpCount; i++) {
85 // Read all entry data
86 auto offset = reader.read<int32_t>();
87 auto size = reader.read<int32_t>();
88 reader.skip_in<int32_t>(); // size_uncompressed
89 auto type = reader.read<int8_t>();
90 reader.skip_in<int8_t>(); // compression
91 reader.skip_in<int16_t>(); // padding
92 auto name = reader.read_string(WAD3_FILENAME_MAX_SIZE);
93
94 // We'll append the type onto the name as an extension
95 if(type >= WFT_FIRST && type < WFT_COUNT) {
96 name += ".";
97 name += k_FileTypeNames[type - WFT_FIRST];
98 }
99
100 // Create the entry
101 Entry entry = createNewEntry();
102 entry.offset = offset;
103 entry.length = size;
104 entry.unbaked = false;
105
106 auto entryPath = wad->cleanEntryPath(name);
107 wad->entries.emplace(entryPath, entry);
108
109 if (callback) {
110 callback(entryPath, entry);
111 }
112 }
113
114 return packFile;
115}
116
117// Read and return entry file data from disk or from memory
118std::optional<std::vector<std::byte>> WAD3::readEntry(const std::string& path_) const {
119 auto path = this->cleanEntryPath(path_);
120 auto entry = this->findEntry(path);
121 if (!entry) {
122 return std::nullopt;
123 }
124
125 // We have it in memory, no need to read from disk
126 if (entry->unbaked) {
127 return readUnbakedEntry(*entry);
128 }
129
130 // It's baked into the file on disk. Open it, read it, send it back
131 FileStream stream{this->fullFilePath};
132 if (!stream) {
133 return std::nullopt;
134 }
135 stream.seek_in_u(entry->offset);
136 return stream.read_bytes(entry->length);
137}
138
139void WAD3::addEntryInternal(Entry& entry, const std::string& path, std::vector<std::byte>& buffer, EntryOptions options) {
140 entry.length = buffer.size();
141
142 // Offset will be reset when it's baked
143 entry.offset = 0;
144}
145
146bool WAD3::bake(const std::string& outputDir_, BakeOptions options, const EntryCallback& callback) {
147 // Get the proper file output folder
148 std::string outputDir = this->getBakeOutputDir(outputDir_);
149 std::string outputPath = outputDir + '/' + this->getFilename();
150
151 // File output stream
152 FileStream stream{outputPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
153 stream.seek_out(0);
154
155 // File data to be appended at the end
156 std::vector<std::byte> fileData;
157
158 // We'll quickly tally up and preallocate our vector so we don't need to do extra allocations at run time
159 uint32_t totalDataLength = 0;
160 this->runForAllEntries([&totalDataLength](const std::string&, const Entry& entry) {
161 totalDataLength += entry.length;
162 });
163 fileData.reserve(totalDataLength);
164
165 // Counter to keep track of where we're writing data to, starts after the header
166 uint32_t currentOffset = sizeof(uint32_t) * 3;
167
168 // Write out the header
169 stream.write(WAD3_SIGNATURE);
170 stream.write<uint32_t>(this->getEntryCount());
171 stream.write<uint32_t>(currentOffset);
172
173 // Push forward to end of lump info array
174 currentOffset += this->getEntryCount() * 32;
175
176 this->runForAllEntriesInternal([this, &callback, &stream, &fileData, currentOffset](std::string path, Entry& entry) {
177 // Append the lump data to the end of the data vector
178 if (auto binData = this->readEntry(path)) {
179 entry.offset = currentOffset + fileData.size();
180 fileData.insert(fileData.end(), binData->begin(), binData->end());
181 }
182 else {
183 entry.offset = 0;
184 entry.length = 0;
185 }
186
187 // Convert the extension back into the type
188 int type = 0;
189 const std::size_t pos = path.find_last_of('.');
190 if (pos > 0) {
191 const std::string_view ext = path.c_str() + pos + 1;
192 for (int i = WFT_FIRST; i < WFT_COUNT; i++) {
194 type = i;
195 break;
196 }
197 }
198 // Chop off the extension
199 path = path.substr(0, pos);
200 }
201
202 // Write the lump header
203 stream.write<uint32_t>(entry.offset);
204 stream.write<uint32_t>(0);
205 stream.write<uint32_t>(entry.length);
206 stream.write<uint8_t>(type); // type
207 stream.write<uint8_t>(0); // compression
208 stream.write<uint16_t>(0); // padding
209 stream.write(path, false, WAD3_FILENAME_MAX_SIZE);
210
211 if (callback) {
212 callback(path, entry);
213 }
214 });
215
216 // File data
217 stream.write(fileData);
218
219 // Clean up
220 this->mergeUnbakedEntries();
221 PackFile::setFullFilePath(outputDir);
222 return true;
223}
224
226 using enum Attribute;
227 return LENGTH;
228}
WAD3FileTypes
Definition: WAD3.cpp:28
@ WFT_COLORMAP2
Definition: WAD3.cpp:36
@ WFT_COLORMAP
Definition: WAD3.cpp:32
@ WFT_RAW
Definition: WAD3.cpp:35
@ WFT_PALETTE
Definition: WAD3.cpp:31
@ WFT_FIRST
Definition: WAD3.cpp:29
@ WFT_MIPTEX
Definition: WAD3.cpp:34
@ WFT_QPIC
Definition: WAD3.cpp:33
@ WFT_COUNT
Definition: WAD3.cpp:39
@ WFT_FONT
Definition: WAD3.cpp:37
constexpr std::string_view k_FileTypeNames[]
Definition: WAD3.cpp:42
This class represents the metadata that a file has inside a PackFile.
Definition: Entry.h:14
bool unbaked
Used to check if entry is saved to disk.
Definition: Entry.h:43
uint64_t offset
Offset, format-specific meaning - 0 if unused, or if the offset genuinely is 0.
Definition: Entry.h:33
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length)
Definition: Entry.h:26
EntryCallbackBase< void > EntryCallback
Definition: PackFile.h:30
void mergeUnbakedEntries()
Definition: PackFile.cpp:656
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
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
Definition: PackFile.cpp:541
std::string getFilename() const
/home/user/pak01_dir.vpk -> pak01_dir.vpk
Definition: PackFile.cpp:591
std::string getBakeOutputDir(const std::string &outputDir) const
Definition: PackFile.cpp:640
void runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
Definition: PackFile.cpp:499
void setFullFilePath(const std::string &outputDir)
Definition: PackFile.cpp:672
std::string cleanEntryPath(const std::string &path) const
Definition: PackFile.cpp:677
static Entry createNewEntry()
Definition: PackFile.cpp:686
std::size_t getEntryCount(bool includeUnbaked=true) const
Get the number of entries in the pack file.
Definition: PackFile.cpp:490
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
Definition: PackFile.cpp:690
Valve GoldSrc WAD3 file.
Definition: WAD3.h:16
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a WAD3 file.
Definition: WAD3.cpp:61
static std::unique_ptr< PackFile > create(const std::string &path)
Create a WAD3 file.
Definition: WAD3.cpp:52
bool bake(const std::string &outputDir_, BakeOptions options, const EntryCallback &callback) override
If output folder is an empty string, it will overwrite the original.
Definition: WAD3.cpp:146
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
Definition: WAD3.cpp:118
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition: WAD3.cpp:225
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
Definition: WAD3.cpp:139
bool iequals(std::string_view s1, std::string_view s2)
Definition: String.cpp:61
Definition: LZMA.h:11
Definition: Attribute.h:5
constexpr int8_t WAD3_FILENAME_MAX_SIZE
Definition: WAD3.h:9
constexpr auto WAD3_SIGNATURE
Definition: WAD3.h:10
Attribute
Definition: Attribute.h:7