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