VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
VirtualFileSystem.cpp
2#include <algorithm>
3#include <cstring>
4#include <sstream>
5#include "components/ErrorUtils.hpp"
6#include <zstd.h>
7
8namespace vex {
9
10std::string VirtualFileSystem::s_vpak_key = "FALLBACK_VPAK_KEY_0000";
11
12void VirtualFileSystem::SetVpakKey(const std::string& key) {
13 s_vpak_key = key;
14}
15
17 return s_vpak_key;
18}
19
21 : m_use_packed_assets(false) {
22}
23
24VirtualFileSystem::~VirtualFileSystem() {
25}
26
27bool VirtualFileSystem::initialize(const std::string& base_path) {
28 m_base_path = base_path;
29
30#if DEBUG
31 m_use_packed_assets = false;
32 return true;
33#else
34 m_use_packed_assets = true;
35 std::string vpk_path = base_path + "/Assets/assets.vpk";
36
37 if (fs::exists(vpk_path)) {
38 return load_vpk_file(vpk_path);
39 } else {
40 m_use_packed_assets = false;
41 log(LogLevel::WARNING, "VPK file [ path: %s ] not found, falling back to loose files", vpk_path.c_str());
42 return true;
43 }
44#endif
45}
46
47bool VirtualFileSystem::load_vpk_file(const std::string& vpk_path) {
48 if (s_vpak_key == "FALLBACK_VPAK_KEY_0000") {
49 log(LogLevel::ERROR, "VirtualFileSystem: Using fallback VPAK key - assets may not decrypt correctly!");
50 }
51
52 m_loaded_vpk = std::make_unique<LoadedVPK>();
53 m_loaded_vpk->file_path = vpk_path;
54
55 m_loaded_vpk->file_stream.open(vpk_path, std::ios::binary);
56 if (!m_loaded_vpk->file_stream) {
57 log(LogLevel::ERROR, "Failed to open VPK file: %s", vpk_path.c_str());
58 throw_error("Failed to open VPK file.");
59 m_loaded_vpk.reset();
60 return false;
61 }
62
63 m_loaded_vpk->file_stream.read(
64 reinterpret_cast<char*>(&m_loaded_vpk->header),
65 sizeof(VPKHeader)
66 );
67
68 if (std::strncmp(m_loaded_vpk->header.magic, "VPAK", 4) != 0) {
69 throw_error("Invalid VPK file: bad magic");
70 m_loaded_vpk.reset();
71 return false;
72 }
73
74 if (m_loaded_vpk->header.version != 3) {
75 log(LogLevel::WARNING, "VPK version mismatch: expected 3, got %u", m_loaded_vpk->header.version);
76 }
77
78 m_loaded_vpk->entries.resize(m_loaded_vpk->header.file_count);
79 m_loaded_vpk->file_stream.seekg(sizeof(VPKHeader));
80 m_loaded_vpk->file_stream.read(
81 reinterpret_cast<char*>(m_loaded_vpk->entries.data()),
82 m_loaded_vpk->header.file_count * sizeof(VPKFileEntry)
83 );
84
85 m_loaded_vpk->file_stream.seekg(m_loaded_vpk->header.names_offset);
86 for (uint32_t i = 0; i < m_loaded_vpk->header.file_count; ++i) {
87 std::string name;
88 std::getline(m_loaded_vpk->file_stream, name, '\0');
89 m_loaded_vpk->file_names.push_back(name);
90 }
91
92 std::string key = GetVpakKey();
93
94 std::vector<uint8_t> compressed_buffer(m_loaded_vpk->header.solid_compressed_size);
95
96 {
97 std::lock_guard<std::mutex> lock(stream_mutex);
98 m_loaded_vpk->file_stream.seekg(m_loaded_vpk->header.data_offset);
99 m_loaded_vpk->file_stream.read(
100 reinterpret_cast<char*>(compressed_buffer.data()),
101 m_loaded_vpk->header.solid_compressed_size
102 );
103 }
104
105 /*for (size_t j = 0; j < compressed_buffer.size(); ++j) {
106 compressed_buffer[j] ^= key[j % key.length()];
107 }*/
108
109 size_t key_len = key.length();
110 size_t key_idx = 0;
111 for (size_t j = 0; j < compressed_buffer.size(); ++j) {
112 compressed_buffer[j] ^= key[key_idx];
113 key_idx++;
114 if (key_idx == key_len) {
115 key_idx = 0;
116 }
117 }
118
119 m_loaded_vpk->solid_data.resize(m_loaded_vpk->header.solid_uncompressed_size);
120 std::memset(m_loaded_vpk->solid_data.data(), 0, m_loaded_vpk->header.solid_uncompressed_size);
121
122 size_t result = ZSTD_decompress(
123 m_loaded_vpk->solid_data.data(), m_loaded_vpk->header.solid_uncompressed_size,
124 compressed_buffer.data(), m_loaded_vpk->header.solid_compressed_size
125 );
126
127 if (ZSTD_isError(result)) {
128 log(LogLevel::ERROR, "Zstd decompression failed: %s", ZSTD_getErrorName(result));
129 m_loaded_vpk.reset();
130 return false;
131 }
132
133 log("Loaded VPK with %u files", m_loaded_vpk->header.file_count);
134
135 return true;
136}
137
139 if (!m_loaded_vpk) return nullptr;
140
141 std::string cleanVirtualPath = clean_path(virtual_path);
142 auto it = std::find(
143 m_loaded_vpk->file_names.begin(),
144 m_loaded_vpk->file_names.end(),
145 cleanVirtualPath
146 );
147
148 if (it == m_loaded_vpk->file_names.end()) {
149 return nullptr;
150 }
151
152 size_t index = std::distance(m_loaded_vpk->file_names.begin(), it);
153 return &m_loaded_vpk->entries[index];
154}
155
156std::unique_ptr<VirtualFileSystem::FileData> VirtualFileSystem::load_file(const std::string& virtual_path) {
157 std::string cleanVirtualPath = clean_path(virtual_path);
158
159 if (m_use_packed_assets && m_loaded_vpk) {
160 const auto* entry = find_file_entry(cleanVirtualPath);
161 if (!entry) {
162 return nullptr;
163 }
164
165 auto fileData = std::make_unique<FileData>();
166 fileData->size = entry->uncompressed_size;
167 fileData->data.resize(entry->uncompressed_size);
168
169 std::memcpy(fileData->data.data(),
170 m_loaded_vpk->solid_data.data() + entry->data_offset,
171 entry->uncompressed_size);
172
173 return fileData;
174 } else {
175 std::string fullPath;
176
177 #ifdef _WIN32
178 fullPath = cleanVirtualPath;
179 #else
180 fullPath = "/" + cleanVirtualPath;
181 #endif
182
183 if (!fs::exists(fullPath)) {
184 return nullptr;
185 }
186
187 std::ifstream file(fullPath, std::ios::binary);
188 if (!file) {
189 return nullptr;
190 }
191
192 file.seekg(0, std::ios::end);
193 size_t size = file.tellg();
194 file.seekg(0, std::ios::beg);
195
196 auto fileData = std::make_unique<FileData>();
197 fileData->data.resize(size);
198 fileData->size = size;
199
200 file.read(reinterpret_cast<char*>(fileData->data.data()), size);
201 return fileData;
202 }
203}
204
205std::unique_ptr<std::istream> VirtualFileSystem::open_file_stream(const std::string& virtual_path) {
206 std::string cleanVirtualPath = clean_path(virtual_path);
207
208 if (m_use_packed_assets && m_loaded_vpk) {
209 const auto* entry = find_file_entry(cleanVirtualPath);
210 if (!entry) {
211 return nullptr;
212 }
213
214 return std::make_unique<VPKStream>(
215 reinterpret_cast<const char*>(m_loaded_vpk->solid_data.data() + entry->data_offset),
216 entry->uncompressed_size
217 );
218 } else {
219 std::string fullPath;
220
221 #ifdef _WIN32
222 fullPath = cleanVirtualPath;
223 #else
224 fullPath = "/" + cleanVirtualPath;
225 #endif
226 if (!fs::exists(fullPath)) {
227 return nullptr;
228 }
229 return std::make_unique<std::ifstream>(fullPath, std::ios::binary);
230 }
231}
232
233bool VirtualFileSystem::file_exists(const std::string& virtual_path) {
234 std::string cleanVirtualPath = clean_path(virtual_path);
235
236 if (m_use_packed_assets && m_loaded_vpk) {
237 return find_file_entry(cleanVirtualPath) != nullptr;
238 } else {
239 std::string fullPath;
240
241 #ifdef _WIN32
242 fullPath = cleanVirtualPath;
243 #else
244 fullPath = "/" + cleanVirtualPath;
245 #endif
246 return fs::exists(fullPath);
247 }
248}
249
250size_t VirtualFileSystem::get_file_size(const std::string& virtual_path) {
251 std::string cleanVirtualPath = clean_path(virtual_path);
252
253 if (m_use_packed_assets && m_loaded_vpk) {
254 const auto* entry = find_file_entry(cleanVirtualPath);
255 if (!entry) {
256 return 0;
257 }
258 return entry->uncompressed_size;
259 } else {
260 std::string fullPath;
261
262 #ifdef _WIN32
263 fullPath = cleanVirtualPath;
264 #else
265 fullPath = "/" + cleanVirtualPath;
266 #endif
267 if (fs::exists(fullPath)) {
268 return fs::file_size(fullPath);
269 }
270 return 0;
271 }
272}
273
274std::vector<std::string> VirtualFileSystem::list_files(const std::string& virtual_dir) {
275 std::vector<std::string> result;
276 std::string cleanDir = clean_path(virtual_dir);
277
278 if (m_use_packed_assets && m_loaded_vpk) {
279 for (const auto& name : m_loaded_vpk->file_names) {
280 if (cleanDir.empty() || name.find(cleanDir) == 0) {
281 result.push_back(name);
282 }
283 }
284 } else {
285 std::string fullDir = m_base_path + "/Assets/" + cleanDir;
286 if (fs::exists(fullDir) && fs::is_directory(fullDir)) {
287 for (const auto& entry : fs::recursive_directory_iterator(fullDir)) {
288 if (entry.is_regular_file()) {
289 std::string relativePath = fs::relative(entry.path(), m_base_path + "/Assets").generic_string();
290 result.push_back(relativePath);
291 }
292 }
293 }
294 }
295
296 return result;
297}
298
299std::string VirtualFileSystem::clean_path(const std::string& path) {
300 std::string result = path;
301
302 std::replace(result.begin(), result.end(), '\\', '/');
303
304 if (result.find("./") == 0) {
305 result = result.substr(2);
306 }
307
308 if (!result.empty() && result[0] == '/') {
309 result = result.substr(1);
310 }
311
312 if (result.find("Assets/") == 0 && m_use_packed_assets) {
313 result = result.substr(7);
314 }
315
316 return result;
317}
318
319std::string VirtualFileSystem::resolve_relative_path(const std::string& base_path, const std::string& relative_path) {
320 std::filesystem::path base(base_path);
321 std::filesystem::path relative(relative_path);
322
323 if (relative.is_absolute()) {
324 return clean_path(relative.string());
325 }
326
327 base.remove_filename();
328 std::filesystem::path resolved = base / relative;
329 return clean_path(resolved.lexically_normal().string());
330}
331
332}
This file defines VirtualFileSystem and VPKStream classes.
std::vector< std::string > list_files(const std::string &virtual_dir="")
Lists all files contained within a specific directory.
std::unique_ptr< FileData > load_file(const std::string &virtual_path)
Loads a file into memory from the specified path.
VirtualFileSystem()
Constructor for VirtualFileSystem.
std::string clean_path(const std::string &path)
cleans path from unwanted string eg "Assets/"
size_t get_file_size(const std::string &virtual_path)
Gets the size of a file in bytes.
std::string resolve_relative_path(const std::string &base_path, const std::string &relative_path)
Resolves a relative path to a normalized, cleaner format.
const VPKFileEntry * find_file_entry(const std::string &virtual_path)
Method to find a file entry by its virtual path.
std::unique_ptr< std::istream > open_file_stream(const std::string &virtual_path)
Opens a file stream for reading from a specified path.
bool initialize(const std::string &base_path)
Initializes the virtual file system.
bool load_vpk_file(const std::string &vpk_path)
Method to load a VPK file.
static std::string GetVpakKey()
Gets the current VPAK decryption key.
static void SetVpakKey(const std::string &key)
Sets the VPAK decryption key.
bool file_exists(const std::string &virtual_path)
Checks if a file exists in the currently active file system mode.
void VEX_EXPORT throw_error(const std::string &msg)
Throws an error or terminates the application.
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
struct defining virtual file "header", used to identify files in the virtual file system.
struct defining vpak file header data, used to validate file integrity.