SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
SDAT.cpp
Go to the documentation of this file.
1// ReSharper disable CppRedundantQualifier
2
3#include <vpkpp/format/SDAT.h>
4
5#include <filesystem>
6
7#include <FileStream.h>
8
9using namespace sourcepp;
10using namespace vpkpp;
11
12std::unique_ptr<PackFile> SDAT::create(const std::string& path) {
13 {
14 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
15 stream
16 .write(SDAT_SIGNATURE)
17 .write<uint32_t>(0)
18 .write<uint32_t>(0);
19 }
20 return SDAT::open(path);
21}
22
23std::unique_ptr<PackFile> SDAT::open(const std::string& path, const EntryCallback& callback) {
24 if (!std::filesystem::exists(path)) {
25 // File does not exist
26 return nullptr;
27 }
28
29 auto* sdat = new SDAT{path};
30 auto packFile = std::unique_ptr<PackFile>(sdat);
31
32 FileStream reader{sdat->fullFilePath};
33 reader.seek_in(0);
34
35 if (reader.read<uint32_t>() != SDAT_SIGNATURE) {
36 // File is not an SDAT
37 return nullptr;
38 }
39 reader.set_big_endian(true);
40
41 // dirSize does not include the signature or itself
42 reader.skip_in<uint32_t>(); //const auto dirSize = reader.read<uint32_t>();
43
44 const auto entryCount = reader.read<uint32_t>();
45
46 for (uint32_t i = 0; i < entryCount; i++) {
47 Entry entry = createNewEntry();
48
49 entry.offset = reader.read<uint32_t>();
50 entry.length = reader.read<uint32_t>();
51
52 const auto entryPathOffset = reader.read<uint32_t>();
53 const auto oldPos = reader.tell_in();
54 const auto entryPath = sdat->cleanEntryPath(reader.seek_in_u(entryPathOffset + sizeof(uint32_t) * 2).read_string());
55 reader.seek_in_u(oldPos);
56
57 sdat->entries.emplace(entryPath, entry);
58
59 if (callback) {
60 callback(entryPath, entry);
61 }
62 }
63
64 return packFile;
65}
66
67std::optional<std::vector<std::byte>> SDAT::readEntry(const std::string& path_) const {
68 const auto path = this->cleanEntryPath(path_);
69 const auto entry = this->findEntry(path);
70 if (!entry) {
71 return std::nullopt;
72 }
73 if (entry->unbaked) {
74 return readUnbakedEntry(*entry);
75 }
76
77 // It's baked into the file on disk
78 FileStream stream{this->fullFilePath};
79 if (!stream) {
80 return std::nullopt;
81 }
82 stream.seek_in_u(entry->offset);
83 return stream.read_bytes(entry->length);
84}
85
86void SDAT::addEntryInternal(Entry& entry, const std::string&, std::vector<std::byte>& buffer, EntryOptions) {
87 entry.length = buffer.size();
88
89 // Offset will be reset when it's baked
90 entry.offset = 0;
91}
92
93bool SDAT::bake(const std::string& outputDir_, BakeOptions, const EntryCallback& callback) {
94 // Get the proper file output folder
95 const std::string outputDir = this->getBakeOutputDir(outputDir_);
96 const std::string outputPath = outputDir + '/' + this->getFilename();
97
98 // Reconstruct data for ease of access
99 uint32_t pathBlockSize = 0;
100 std::vector<std::pair<std::string, Entry*>> entriesToBake;
101 this->runForAllEntriesInternal([&pathBlockSize, &entriesToBake](const std::string& path, Entry& entry) {
102 pathBlockSize += path.size() + 1;
103 entriesToBake.emplace_back(path, &entry);
104 });
105
106 // Read data before overwriting, we don't know if we're writing to ourself
107 std::vector<std::byte> fileData;
108 for (auto& [path, entry] : entriesToBake) {
109 if (auto binData = this->readEntry(path)) {
110 entry->offset = fileData.size();
111
112 fileData.insert(fileData.end(), binData->begin(), binData->end());
113 } else {
114 entry->offset = 0;
115 entry->length = 0;
116 }
117 }
118
119 {
120 FileStream stream{outputPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
121 stream.seek_out(0);
122
123 const auto dirSize = sizeof(uint32_t) * 3 * (entriesToBake.size() + 1) + pathBlockSize;
124
125 // Signature + dir size + entry count
126 stream
127 .write(SDAT_SIGNATURE)
128 .set_big_endian(true)
129 .write<uint32_t>(dirSize - 8)
130 .write<uint32_t>(entriesToBake.size())
131 .pad(dirSize - sizeof(uint32_t))
132 .seek_out(sizeof(uint32_t) * 3);
133
134 // Directory
135 auto currentPathOffset = dirSize - pathBlockSize;
136 for (const auto& [path, entry] : entriesToBake) {
137 stream
138 .write<uint32_t>(entry->offset + dirSize)
139 .write<uint32_t>(entry->length)
140 .write<uint32_t>(currentPathOffset - sizeof(uint32_t) * 2);
141
142 const auto oldPos = stream.tell_out();
143 stream.seek_out_u(currentPathOffset).write(path);
144 currentPathOffset = stream.tell_out();
145 stream.seek_out_u(oldPos);
146
147 if (callback) {
148 callback(path, *entry);
149 }
150 }
151
152 // File data
153 stream.seek_out_u(dirSize).write(fileData);
154 }
155
156 // Clean up
157 this->mergeUnbakedEntries();
158 PackFile::setFullFilePath(outputDir);
159 return true;
160}
161
163 using enum Attribute;
164 return LENGTH;
165}
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 length
Length in bytes (in formats with compression, this is the uncompressed length).
Definition Entry.h:26
EntryCallbackBase< void > EntryCallback
Definition PackFile.h:38
void mergeUnbakedEntries()
Definition PackFile.cpp:685
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
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
Definition PackFile.cpp:571
bool bake()
If output folder is an empty string, it will overwrite the original.
Definition PackFile.cpp:369
std::string getFilename() const
/home/user/pak01_dir.vpk -> pak01_dir.vpk
Definition PackFile.cpp:621
std::string getBakeOutputDir(const std::string &outputDir) const
Definition PackFile.cpp:670
void setFullFilePath(const std::string &outputDir)
Definition PackFile.cpp:701
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
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open an SDAT file.
Definition SDAT.cpp:23
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
Definition SDAT.cpp:67
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
Definition SDAT.cpp:86
static std::unique_ptr< PackFile > create(const std::string &path)
Create an SDAT file.
Definition SDAT.cpp:12
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition SDAT.cpp:162
Attribute
Definition Attribute.h:7
constexpr auto SDAT_SIGNATURE
Definition SDAT.h:11