resourcemanager.cpp

main 1149 lines · 38.4 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "resourcemanager.h"
24#include "filestream.h"
25#include "resource.h"
26
27#include <framework/core/application.h>
28#include <framework/luaengine/luainterface.h>
29#include <framework/platform/platform.h>
30#include <framework/util/crypt.h>
31#include <framework/http/http.h>
32#include <queue>
33#include <regex>
34
35#if not(defined(ANDROID) || defined(FREE_VERSION))
36#include <boost/process.hpp>
37#endif
38#include <locale>
39#include <zlib.h>
40
41#define PHYSFS_DEPRECATED
42#include <physfs.h>
43#ifndef __EMSCRIPTEN__
44#include <zip.h>
45#include <zlib.h>
46#endif
47
48ResourceManager g_resources;
49static const std::string INIT_FILENAME = "init.lua";
50
51void ResourceManager::init(const char *argv0)
52{
53#if defined(WIN32)
54 char fileName[255];
55 GetModuleFileNameA(NULL, fileName, sizeof(fileName));
56 m_binaryPath = std::filesystem::absolute(fileName);
57#elif defined(ANDROID)
58 // nothing
59#else
60 m_binaryPath = std::filesystem::absolute(argv0);
61#endif
62 PHYSFS_init(argv0);
63 PHYSFS_permitSymbolicLinks(1);
64}
65
66void ResourceManager::terminate()
67{
68 PHYSFS_deinit();
69}
70
71bool ResourceManager::launchCorrect(const std::string& product, const std::string& app) { // curently works only on windows
72#if not(defined(ANDROID) || defined(FREE_VERSION))
73 auto init_path = m_binaryPath.parent_path();
74 init_path /= INIT_FILENAME;
75 if (std::filesystem::exists(init_path)) // debug version
76 return false;
77
78 const char* localDir = PHYSFS_getPrefDir(product.c_str(), app.c_str());
79 if (!localDir)
80 return false;
81
82 auto fileName2 = m_binaryPath.stem().string();
83 fileName2 = stdext::split(fileName2, "-")[0];
84 stdext::tolower(fileName2);
85
86 std::filesystem::path path(std::filesystem::u8path(localDir));
87 std::error_code ec;
88 auto lastWrite = std::filesystem::last_write_time(m_binaryPath, ec);
89 std::filesystem::path binary = m_binaryPath;
90 for (auto& entry : std::filesystem::directory_iterator(path)) {
91 if (std::filesystem::is_directory(entry.path()))
92 continue;
93
94 auto fileName1 = entry.path().stem().string();
95 fileName1 = stdext::split(fileName1, "-")[0];
96 stdext::tolower(fileName1);
97 if (fileName1 != fileName2)
98 continue;
99
100 if (entry.path().extension() == m_binaryPath.extension()) {
101 std::error_code ec;
102 auto writeTime = std::filesystem::last_write_time(entry.path(), ec);
103 if (!ec && writeTime > lastWrite) {
104 lastWrite = writeTime;
105 binary = entry.path();
106 }
107 }
108 }
109
110 for (auto& entry : std::filesystem::directory_iterator(path)) { // remove old
111 if (std::filesystem::is_directory(entry.path()))
112 continue;
113
114 auto fileName1 = entry.path().stem().string();
115 fileName1 = stdext::split(fileName1, "-")[0];
116 stdext::tolower(fileName1);
117 if (fileName1 != fileName2)
118 continue;
119
120 if (entry.path().extension() == m_binaryPath.extension()) {
121 if (binary == entry.path())
122 continue;
123 std::error_code ec;
124 std::filesystem::remove(entry.path(), ec);
125 }
126 }
127
128 if (binary == m_binaryPath)
129 return false;
130
131 boost::process::child c(binary.string());
132 std::error_code ec2;
133 if (c.wait_for(std::chrono::seconds(5), ec2)) {
134 return c.exit_code() == 0;
135 }
136
137 c.detach();
138 return true;
139#else
140 return false;
141#endif
142}
143
144bool ResourceManager::setupWriteDir(const std::string& product, const std::string& app) {
145#ifdef ANDROID
146 const char* localDir = g_androidState->activity->internalDataPath;
147#else
148 const char* localDir = PHYSFS_getPrefDir(product.c_str(), app.c_str());
149#endif
150
151 if (!localDir) {
152 g_logger.fatal(stdext::format("Unable to get local dir, error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
153 return false;
154 }
155
156 if (!PHYSFS_mount(localDir, NULL, 0)) {
157 g_logger.fatal(stdext::format("Unable to mount local directory '%s': %s", localDir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
158 return false;
159 }
160
161 if (!PHYSFS_setWriteDir(localDir)) {
162 g_logger.fatal(stdext::format("Unable to set write dir '%s': %s", localDir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
163 return false;
164 }
165
166#ifndef ANDROID
167 m_writeDir = std::filesystem::path(std::filesystem::u8path(localDir));
168#endif
169 return true;
170}
171
172bool ResourceManager::setup()
173{
174 std::shared_ptr<std::vector<uint8_t>> data = nullptr;
175#ifdef ANDROID
176 PHYSFS_File* file = PHYSFS_openRead("data.zip");
177 if (file) {
178 auto data = std::make_shared<std::vector<uint8_t>>(PHYSFS_fileLength(file));
179 PHYSFS_readBytes(file, data->data(), data->size());
180 PHYSFS_close(file);
181 if (mountMemoryData(data))
182 return true;
183 }
184#else
185 std::string localDir(PHYSFS_getWriteDir());
186 std::vector<std::string> possiblePaths = { localDir, g_platform.getCurrentDir() };
187 const char* baseDir = PHYSFS_getBaseDir();
188 if (baseDir)
189 possiblePaths.push_back(baseDir);
190
191 for (const std::string& dir : possiblePaths) {
192 if (dir == localDir || !PHYSFS_mount(dir.c_str(), NULL, 0))
193 continue;
194
195 if(PHYSFS_exists(INIT_FILENAME.c_str())) {
196 g_logger.info(stdext::format("Found work dir at '%s'", dir));
197 return true;
198 }
199
200 PHYSFS_unmount(dir.c_str());
201 }
202
203 for(const std::string& dir : possiblePaths) {
204 if (dir != localDir && !PHYSFS_mount(dir.c_str(), NULL, 0)) {
205 continue;
206 }
207
208 if (!PHYSFS_exists("data.zip")) {
209 if(dir != localDir)
210 PHYSFS_unmount(dir.c_str());
211 continue;
212 }
213
214 PHYSFS_File* file = PHYSFS_openRead("data.zip");
215 if (!file) {
216 if (dir != localDir)
217 PHYSFS_unmount(dir.c_str());
218 continue;
219 }
220
221 auto data = std::make_shared<std::vector<uint8_t>>(PHYSFS_fileLength(file));
222 PHYSFS_readBytes(file, data->data(), data->size());
223 PHYSFS_close(file);
224 if (dir != localDir)
225 PHYSFS_unmount(dir.c_str());
226
227 g_logger.info(stdext::format("Found work dir at '%s'", dir));
228 if (mountMemoryData(data))
229 return true;
230 }
231#endif
232 if (loadDataFromSelf()) {
233 g_logger.info(stdext::format("Found work dir inside binary"));
234 return true;
235 }
236
237 g_logger.fatal("Unable to find working directory (or data.zip)");
238 return false;
239}
240
241std::string ResourceManager::getCompactName() {
242 std::string fileData;
243 if (loadDataFromSelf()) {
244 try {
245 fileData = readFileContents(INIT_FILENAME);
246 } catch (...) {
247 fileData = "";
248 }
249 unmountMemoryData();
250 }
251
252#ifndef ANDROID
253 std::vector<std::string> possiblePaths = { g_platform.getCurrentDir() };
254 const char* baseDir = PHYSFS_getBaseDir();
255 if (baseDir)
256 possiblePaths.push_back(baseDir);
257
258 if (fileData.empty()) {
259 try {
260 for (const std::string& dir : possiblePaths) {
261 if (!PHYSFS_mount(dir.c_str(), NULL, 0))
262 continue;
263
264 if (PHYSFS_exists(INIT_FILENAME.c_str())) {
265 fileData = readFileContents(INIT_FILENAME);
266 PHYSFS_unmount(dir.c_str());
267 break;
268 }
269 PHYSFS_unmount(dir.c_str());
270 }
271 } catch (...) {
272 fileData = "";
273 }
274 }
275
276 if (fileData.empty()) {
277 try {
278 for (const std::string& dir : possiblePaths) {
279 std::string path = dir + "/data.zip";
280 if (!PHYSFS_mount(path.c_str(), NULL, 0))
281 continue;
282
283 if (PHYSFS_exists(INIT_FILENAME.c_str())) {
284 fileData = readFileContents(INIT_FILENAME);
285 PHYSFS_unmount(path.c_str());
286 break;
287 }
288 PHYSFS_unmount(path.c_str());
289 }
290 } catch (...) {}
291 }
292#endif
293
294 std::smatch regex_match;
295 if (std::regex_search(fileData, regex_match, std::regex("APP_NAME[^\"]+\"([^\"]+)"))) {
296 if (regex_match.size() == 2 && regex_match[1].str().length() > 0 && regex_match[1].str().length() < 30) {
297 return regex_match[1].str();
298 }
299 }
300 return "otclientv8";
301}
302
303bool ResourceManager::loadDataFromSelf(bool unmountIfMounted) {
304 std::shared_ptr<std::vector<uint8_t>> data = nullptr;
305#ifdef ANDROID
306 AAsset* file = AAssetManager_open(g_androidState->activity->assetManager, "data.zip", AASSET_MODE_BUFFER);
307 if (!file)
308 g_logger.fatal("Can't open data.zip from assets");
309 data = std::make_shared<std::vector<uint8_t>>(AAsset_getLength(file));
310 AAsset_read(file, data->data(), data->size());
311 AAsset_close(file);
312#else
313 std::ifstream file(m_binaryPath.string(), std::ios::binary);
314 if (!file.is_open())
315 return false;
316 file.seekg(0, std::ios_base::end);
317 std::size_t size = file.tellg();
318 file.seekg(0, std::ios_base::beg);
319 if (size < 1024 || size > 1024 * 1024 * 128) {
320 file.close();
321 return false;
322 }
323
324 std::vector<uint8_t> v(1 + size);
325 file.read((char*)&v[0], size);
326 file.close();
327 for (size_t i = 0, end = size - 128; i < end; ++i) {
328 if (v[i] == 0x50 && v[i + 1] == 0x4b && v[i + 2] == 0x03 && v[i + 3] == 0x04 && v[i + 4] == 0x14) {
329 uint32_t compSize = *(uint32_t*)&v[i + 18];
330 uint32_t decompSize = *(uint32_t*)&v[i + 22];
331 if (compSize < 1024 * 1024 * 512 && decompSize < 1024 * 1024 * 512) {
332 data = std::make_shared<std::vector<uint8_t>>(&v[i], &v[v.size() - 1]);
333 break;
334 }
335 }
336 }
337 v.clear();
338
339#endif
340
341 if (unmountIfMounted)
342 unmountMemoryData();
343
344 if (mountMemoryData(data)) {
345 m_loadedFromMemory = true;
346 return true;
347 }
348
349 return false;
350}
351
352bool ResourceManager::fileExists(const std::string& fileName)
353{
354 if (fileName.find("/downloads") != std::string::npos)
355 return g_http.getFile(fileName.substr(10)) != nullptr;
356 return (PHYSFS_exists(resolvePath(fileName).c_str()) && !PHYSFS_isDirectory(resolvePath(fileName).c_str()));
357}
358
359bool ResourceManager::directoryExists(const std::string& directoryName)
360{
361 if (directoryName == "/downloads")
362 return true;
363 return (PHYSFS_isDirectory(resolvePath(directoryName).c_str()));
364}
365
366void ResourceManager::readFileStream(const std::string& fileName, std::iostream& out)
367{
368 std::string buffer(readFileContents(fileName));
369 if(buffer.length() == 0) {
370 out.clear(std::ios::eofbit);
371 return;
372 }
373 out.clear(std::ios::goodbit);
374 out.write(&buffer[0], buffer.length());
375 out.seekg(0, std::ios::beg);
376}
377
378std::string ResourceManager::readFileContents(const std::string& fileName, bool safe)
379{
380 std::string fullPath = resolvePath(fileName);
381
382 if (fullPath.find("/downloads") != std::string::npos) {
383 auto dfile = g_http.getFile(fullPath.substr(10));
384 if (dfile)
385 return std::string(dfile->response.begin(), dfile->response.end());
386 }
387
388 PHYSFS_File* file = PHYSFS_openRead(fullPath.c_str());
389 if(!file)
390 stdext::throw_exception(stdext::format("unable to open file '%s': %s", fullPath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
391
392 int fileSize = PHYSFS_fileLength(file);
393 std::string buffer(fileSize, 0);
394 PHYSFS_readBytes(file, (void*)&buffer[0], fileSize);
395 PHYSFS_close(file);
396
397 if (safe) {
398 return buffer;
399 }
400
401 // skip decryption for bot configs
402 if (fullPath.find("/bot/") != std::string::npos) {
403 return buffer;
404 }
405
406 static std::string unencryptedExtensions[] = { ".otml", ".otmm", ".dmp", ".log", ".txt", ".dll", ".exe", ".zip" };
407
408 if (!decryptBuffer(buffer)) {
409 bool ignore = (m_customEncryption == 0);
410 for (auto& it : unencryptedExtensions) {
411 if (fileName.find(it) == fileName.size() - it.size()) {
412 ignore = true;
413 }
414 }
415 if(!ignore)
416 g_logger.fatal(stdext::format("unable to decrypt file: %s", fullPath));
417 }
418
419 return buffer;
420}
421
422bool ResourceManager::isFileEncryptedOrCompressed(const std::string& fileName)
423{
424 std::string fullPath = resolvePath(fileName);
425 std::string fileContent;
426
427 if (fullPath.find("/downloads") != std::string::npos) {
428 auto dfile = g_http.getFile(fullPath.substr(10));
429 if (dfile) {
430 if (dfile->response.size() < 10)
431 return false;
432 fileContent = std::string(dfile->response.begin(), dfile->response.begin() + 10);
433 }
434 }
435
436 if (!fileContent.empty()) {
437 PHYSFS_File* file = PHYSFS_openRead(fullPath.c_str());
438 if (!file)
439 stdext::throw_exception(stdext::format("unable to open file '%s': %s", fullPath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
440
441 int fileSize = std::min<int>(10, PHYSFS_fileLength(file));
442 fileContent.resize(fileSize);
443 PHYSFS_readBytes(file, (void*)&fileContent[0], fileSize);
444 PHYSFS_close(file);
445 }
446
447 if (fileContent.size() < 10)
448 return false;
449
450 if (fileContent.substr(0, 4).compare("ENC3") == 0)
451 return true;
452
453 if ((uint8_t)fileContent[0] != 0x1f || (uint8_t)fileContent[1] != 0x8b || (uint8_t)fileContent[2] != 0x08) {
454 return false;
455 }
456
457 return true;
458}
459
460bool ResourceManager::writeFileBuffer(const std::string& fileName, const uchar* data, uint size)
461{
462 PHYSFS_file* file = PHYSFS_openWrite(fileName.c_str());
463 if(!file) {
464 g_logger.error(stdext::format("unable to open file for writing '%s': %s", fileName, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
465 return false;
466 }
467
468 PHYSFS_writeBytes(file, (void*)data, size);
469 PHYSFS_close(file);
470 return true;
471}
472
473bool ResourceManager::writeFileStream(const std::string& fileName, std::iostream& in)
474{
475 std::streampos oldPos = in.tellg();
476 in.seekg(0, std::ios::end);
477 std::streampos size = in.tellg();
478 in.seekg(0, std::ios::beg);
479 std::vector<char> buffer(size);
480 in.read(&buffer[0], size);
481 bool ret = writeFileBuffer(fileName, (const uchar*)&buffer[0], size);
482 in.seekg(oldPos, std::ios::beg);
483 return ret;
484}
485
486bool ResourceManager::writeFileContents(const std::string& fileName, const std::string& data)
487{
488 return writeFileBuffer(fileName, (const uchar*)data.c_str(), data.size());
489}
490
491FileStreamPtr ResourceManager::openFile(const std::string& fileName, bool dontCache)
492{
493 std::string fullPath = resolvePath(fileName);
494 if (isFileEncryptedOrCompressed(fullPath) || !dontCache) {
495 return FileStreamPtr(new FileStream(fullPath, readFileContents(fullPath)));
496 }
497 PHYSFS_File* file = PHYSFS_openRead(fullPath.c_str());
498 if (!file)
499 stdext::throw_exception(stdext::format("unable to open file '%s': %s", fullPath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
500 return FileStreamPtr(new FileStream(fullPath, file, false));
501}
502
503FileStreamPtr ResourceManager::appendFile(const std::string& fileName)
504{
505 PHYSFS_File* file = PHYSFS_openAppend(fileName.c_str());
506 if(!file)
507 stdext::throw_exception(stdext::format("failed to append file '%s': %s", fileName, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
508 return FileStreamPtr(new FileStream(fileName, file, true));
509}
510
511FileStreamPtr ResourceManager::createFile(const std::string& fileName)
512{
513 PHYSFS_File* file = PHYSFS_openWrite(fileName.c_str());
514 if(!file)
515 stdext::throw_exception(stdext::format("failed to create file '%s': %s", fileName, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
516 return FileStreamPtr(new FileStream(fileName, file, true));
517}
518
519bool ResourceManager::deleteFile(const std::string& fileName)
520{
521 return PHYSFS_delete(resolvePath(fileName).c_str()) != 0;
522}
523
524bool ResourceManager::makeDir(const std::string directory)
525{
526 return PHYSFS_mkdir(directory.c_str());
527}
528
529std::list<std::string> ResourceManager::listDirectoryFiles(const std::string& directoryPath, bool fullPath /* = false */, bool raw /*= false*/)
530{
531 std::list<std::string> files;
532 auto path = raw ? directoryPath : resolvePath(directoryPath);
533 auto rc = PHYSFS_enumerateFiles(path.c_str());
534
535 if (!rc)
536 return files;
537
538 for (int i = 0; rc[i] != NULL; i++) {
539 if(fullPath)
540 files.push_back(path + "/" + rc[i]);
541 else
542 files.push_back(rc[i]);
543 }
544
545 PHYSFS_freeList(rc);
546 files.sort();
547 return files;
548}
549
550std::string ResourceManager::resolvePath(std::string path)
551{
552 if(!stdext::starts_with(path, "/")) {
553 std::string scriptPath = "/" + g_lua.getCurrentSourcePath();
554 if(!scriptPath.empty())
555 path = scriptPath + "/" + path;
556 else
557 g_logger.traceWarning(stdext::format("the following file path is not fully resolved: %s", path));
558 }
559 stdext::replace_all(path, "//", "/");
560 if(!PHYSFS_exists(path.c_str())) {
561 static const std::string layouts_prefix = "/layouts/";
562 if (!m_layout.empty()) {
563 if (PHYSFS_exists((layouts_prefix + m_layout + path).c_str())) {
564 return layouts_prefix + m_layout + path;
565 }
566 }
567 static const std::string extra_check[] = { "/mods", "/data", "/modules" };
568 for (auto extra : extra_check) {
569 if (PHYSFS_exists((extra + path).c_str())) {
570 return extra + path;
571 }
572 }
573 }
574 return path;
575}
576
577std::string ResourceManager::guessFilePath(const std::string& filename, const std::string& type)
578{
579 if(isFileType(filename, type))
580 return filename;
581 return filename + "." + type;
582}
583
584bool ResourceManager::isFileType(const std::string& filename, const std::string& type)
585{
586 if(stdext::ends_with(filename, std::string(".") + type))
587 return true;
588 return false;
589}
590
591std::string ResourceManager::fileChecksum(const std::string& path) {
592 static std::map<std::string, std::string> cache;
593
594 auto it = cache.find(path);
595 if (it != cache.end())
596 return it->second;
597
598 PHYSFS_File* file = PHYSFS_openRead(path.c_str());
599 if(!file)
600 return "";
601
602 int fileSize = PHYSFS_fileLength(file);
603 std::string buffer(fileSize, 0);
604 PHYSFS_readBytes(file, (void*)&buffer[0], fileSize);
605 PHYSFS_close(file);
606
607 auto checksum = g_crypt.crc32(buffer, false);
608 cache[path] = checksum;
609
610 return checksum;
611}
612
613std::map<std::string, std::string> ResourceManager::filesChecksums()
614{
615 std::map<std::string, std::string> ret;
616#ifndef __EMSCRIPTEN__
617 if (!m_memoryData)
618 return ret;
619
620 zip_source_t* src;
621 zip_t* za;
622 zip_stat_t file_stat;
623 zip_error_t error;
624 zip_error_init(&error);
625 zip_stat_init(&file_stat);
626
627 if ((src = zip_source_buffer_create(m_memoryData->data(), m_memoryData->size(), 0, &error)) == NULL)
628 g_logger.fatal(stdext::format("can't create source: %s", zip_error_strerror(&error)));
629
630 if ((za = zip_open_from_source(src, ZIP_RDONLY, &error)) == NULL)
631 g_logger.fatal(stdext::format("can't open zip from source: %s", zip_error_strerror(&error)));
632
633 zip_int64_t entries = zip_get_num_entries(za, 0);
634 for (zip_int64_t entry_idx = 0; entry_idx < entries; entry_idx++) {
635 if (zip_stat_index(za, entry_idx, 0, &file_stat)) {
636 g_logger.fatal(stdext::format("error stat-ing file at index %i: %s",
637 (int)(entry_idx), zip_strerror(za)));
638 }
639 if (!(file_stat.valid & ZIP_STAT_NAME)) {
640 g_logger.warning(stdext::format("warning: skipping entry at index %i with invalid name.",
641 (int)entry_idx));
642 continue;
643 }
644 std::string name(file_stat.name);
645 if (name.empty()) continue;
646 if (name[0] != '/')
647 name = std::string("/") + name;
648 if (name.back() == '/' || file_stat.size == 0) // dir
649 continue;
650 stdext::replace_all(name, "\\", "/");
651 ret[name] = stdext::dec_to_hex(file_stat.crc);
652 }
653
654 if (zip_close(za) < 0)
655 g_logger.fatal(stdext::format("can't close zip archive: %s", zip_strerror(za)));
656 zip_error_fini(&error);
657#endif
658 return ret;
659}
660
661std::string ResourceManager::selfChecksum() {
662#ifdef ANDROID
663 return "";
664#else
665 static std::string checksum;
666 if (!checksum.empty())
667 return checksum;
668
669 std::ifstream file(m_binaryPath.string(), std::ios::binary);
670 if (!file.is_open())
671 return "";
672
673 std::string buffer(std::istreambuf_iterator<char>(file), {});
674 file.close();
675
676 checksum = g_crypt.crc32(buffer, false);
677 return checksum;
678#endif
679}
680
681void ResourceManager::updateData(const std::set<std::string>& files, bool reMount) {
682#if not(defined(__EMSCRIPTEN__) || defined(FREE_VERSION))
683 if (!m_loadedFromArchive)
684 g_logger.fatal("Client can be updated only when running from zip archive");
685
686 g_logger.info(stdext::format("Updating client, %i files", files.size()));
687
688 zip_source_t *src;
689 zip_t *za;
690 zip_error_t error;
691 zip_error_init(&error);
692
693 if ((src = zip_source_buffer_create(0, 0, 0, &error)) == NULL)
694 return g_logger.fatal(stdext::format("can't create source: %s", zip_error_strerror(&error)));
695 zip_source_keep(src);
696
697 if ((za = zip_open_from_source(src, ZIP_TRUNCATE, &error)) == NULL)
698 return g_logger.fatal(stdext::format("can't open zip from source: %s", zip_error_strerror(&error)));
699
700 zip_error_fini(&error);
701
702 for (auto fileName : files) {
703 if (fileName.empty())
704 continue;
705 if (fileName.size() > 1 && fileName[0] == '/')
706 fileName = fileName.substr(1);
707 zip_source_t* s;
708 auto dFile = g_http.getFile(fileName);
709 if (dFile) {
710 if ((s = zip_source_buffer(za, dFile->response.data(), dFile->response.size(), 0)) == NULL)
711 return g_logger.fatal(stdext::format("can't create source buffer: %s", zip_strerror(za)));
712 } else {
713 PHYSFS_File* file = PHYSFS_openRead((std::string("/") + fileName).c_str());
714 if (!file)
715 g_logger.fatal(stdext::format("unable to open file '%s': %s", fileName, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
716
717 int fileSize = PHYSFS_fileLength(file);
718 void* buffer = malloc(fileSize);
719 PHYSFS_readBytes(file, buffer, fileSize);
720 PHYSFS_close(file);
721 if ((s = zip_source_buffer(za, buffer, fileSize, 1)) == NULL)
722 return g_logger.fatal(stdext::format("can't create source buffer: %s", zip_strerror(za)));
723 }
724
725 int fileIndex = zip_file_add(za, fileName.c_str(), s, ZIP_FL_OVERWRITE);
726 if(fileIndex < 0)
727 return g_logger.fatal(stdext::format("can't add file %s to zip archive: %s", fileName, zip_strerror(za)));
728 if (zip_set_file_compression(za, fileIndex, ZIP_CM_DEFLATE, 1) != 0)
729 return g_logger.fatal("Can't set file compression level");
730 }
731
732 if (zip_close(za) < 0)
733 return g_logger.fatal(stdext::format("can't close zip archive: %s", zip_strerror(za)));
734
735 zip_stat_t zst;
736 if (zip_source_stat(src, &zst) < 0)
737 return g_logger.fatal(stdext::format("can't stat source: %s", zip_error_strerror(zip_source_error(src))));
738
739 size_t zipSize = zst.size;
740
741 if (zip_source_open(src) < 0)
742 return g_logger.fatal(stdext::format("can't open source: %s", zip_error_strerror(zip_source_error(src))));
743
744 PHYSFS_file* file = PHYSFS_openWrite("data.zip");
745 if (!file)
746 return g_logger.fatal(stdext::format("can't open data.zip for writing: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
747
748 static const size_t CHUNK_SIZE = 1024 * 1024;
749 std::vector<char> chunk(CHUNK_SIZE);
750 while (zipSize > 0) {
751 size_t currentChunk = std::min<size_t>(zipSize, CHUNK_SIZE);
752 if ((zip_uint64_t)zip_source_read(src, chunk.data(), currentChunk) < currentChunk)
753 return g_logger.fatal(stdext::format("can't read data from source: %s", zip_error_strerror(zip_source_error(src))));
754 PHYSFS_writeBytes(file, chunk.data(), currentChunk);
755 zipSize -= currentChunk;
756 }
757
758 PHYSFS_close(file);
759 zip_source_close(src);
760 zip_source_free(src);
761
762 if (reMount) {
763 unmountMemoryData();
764 file = PHYSFS_openRead("data.zip");
765 if (!file)
766 g_logger.fatal(stdext::format("Can't open new data.zip"));
767
768 int size = PHYSFS_fileLength(file);
769 if (size < 1024)
770 g_logger.fatal(stdext::format("New data.zip is invalid"));
771
772 auto data = std::make_shared<std::vector<uint8_t>>(size);
773 PHYSFS_readBytes(file, data->data(), data->size());
774 PHYSFS_close(file);
775 if (!mountMemoryData(data)) {
776 g_logger.fatal("Error while mounting new data.zip");
777 }
778 }
779#else
780 g_logger.fatal("updateData is unsupported");
781#endif
782}
783
784void ResourceManager::updateExecutable(std::string fileName)
785{
786#if defined(ANDROID) || defined(FREE_VERSION)
787 g_logger.fatal("Executable cannot be updated on android or in free version");
788#else
789 if (fileName.size() <= 2) {
790 g_logger.fatal("Invalid executable name");
791 }
792
793 if (fileName[0] == '/')
794 fileName = fileName.substr(1);
795
796 auto dFile = g_http.getFile(fileName);
797 if (!dFile)
798 g_logger.fatal(stdext::format("Cannot find executable: %s in downloads", fileName));
799
800 std::filesystem::path path(m_binaryPath);
801 auto newBinary = path.stem().string() + "-" + std::to_string(time(nullptr)) + path.extension().string();
802 g_logger.info(stdext::format("Updating binary file: %s", newBinary));
803 PHYSFS_file* file = PHYSFS_openWrite(newBinary.c_str());
804 if (!file)
805 return g_logger.fatal(stdext::format("can't open %s for writing: %s", newBinary, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
806 PHYSFS_writeBytes(file, dFile->response.data(), dFile->response.size());
807 PHYSFS_close(file);
808
809 std::filesystem::path newBinaryPath(std::filesystem::u8path(PHYSFS_getWriteDir()));
810#if defined(WIN32) && not(defined(FREE_VERSION))
811 installDlls(newBinaryPath);
812#endif
813#endif
814}
815
816std::string ResourceManager::createArchive(const std::map<std::string, std::string>& files)
817{
818#ifdef __EMSCRIPTEN__
819 return "";
820#else
821 if (files.empty()) return "";
822
823 zip_source_t* src;
824 zip_t* za;
825 zip_error_t error;
826 zip_error_init(&error);
827
828 if ((src = zip_source_buffer_create(0, 0, 0, &error)) == NULL)
829 stdext::throw_exception(stdext::format("can't create source: %s", zip_error_strerror(&error)));
830 zip_source_keep(src);
831
832 if ((za = zip_open_from_source(src, ZIP_TRUNCATE, &error)) == NULL)
833 stdext::throw_exception(stdext::format("can't open zip from source: %s", zip_error_strerror(&error)));
834
835 zip_error_fini(&error);
836
837 for (auto& file : files) {
838 if (file.first.empty() || file.second.empty())
839 continue;
840
841 zip_source_t* s;
842 if ((s = zip_source_buffer(za, file.second.data(), file.second.size(), 0)) == NULL)
843 stdext::throw_exception(stdext::format("can't create source buffer: %s", zip_strerror(za)));
844
845 std::string fileName = file.first;
846 if (fileName.size() > 1 && fileName[0] == '/')
847 fileName = fileName.substr(1);
848
849 int fileIndex = zip_file_add(za, fileName.c_str(), s, ZIP_FL_OVERWRITE);
850 if (fileIndex < 0)
851 stdext::throw_exception(stdext::format("can't add file %s to zip archive: %s", fileName, zip_strerror(za)));
852// if (zip_set_file_compression(za, fileIndex, ZIP_CM_DEFLATE, 1) != 0)
853// stdext::throw_exception("Can't set file compression level");
854 }
855
856 if (zip_close(za) < 0)
857 stdext::throw_exception(stdext::format("can't close zip archive: %s", zip_strerror(za)));
858
859 zip_stat_t zst;
860 if (zip_source_stat(src, &zst) < 0)
861 stdext::throw_exception(stdext::format("can't stat source: %s", zip_error_strerror(zip_source_error(src))));
862
863 size_t zipSize = zst.size;
864
865 if (zip_source_open(src) < 0)
866 stdext::throw_exception(stdext::format("can't open source: %s", zip_error_strerror(zip_source_error(src))));
867
868 std::string data(zipSize, '\0');
869 if ((zip_uint64_t)zip_source_read(src, data.data(), data.size()) != data.size())
870 stdext::throw_exception(stdext::format("can't read data from source: %s", zip_error_strerror(zip_source_error(src))));
871
872 zip_source_close(src);
873 zip_source_free(src);
874
875 return data;
876#endif
877}
878
879std::map<std::string, std::string> ResourceManager::decompressArchive(std::string dataOrPath)
880{
881 std::map<std::string, std::string> ret;
882#ifdef __EMSCRIPTEN__
883 return ret;
884#else
885 if (dataOrPath.size() < 64) {
886 dataOrPath = readFileContents(dataOrPath);
887 }
888
889 zip_source_t* src;
890 zip_t* za;
891 zip_stat_t file_stat;
892 zip_error_t error;
893 zip_error_init(&error);
894 zip_stat_init(&file_stat);
895
896 if ((src = zip_source_buffer_create(dataOrPath.c_str(), dataOrPath.size(), 0, &error)) == NULL)
897 stdext::throw_exception(stdext::format("unpackArchive: can't create source: %s", zip_error_strerror(&error)));
898
899 if ((za = zip_open_from_source(src, ZIP_RDONLY, &error)) == NULL)
900 stdext::throw_exception(stdext::format("unpackArchive: can't open zip from source: %s", zip_error_strerror(&error)));
901
902 zip_int64_t entries = zip_get_num_entries(za, 0);
903 for (zip_int64_t entry_idx = 0; entry_idx < entries; entry_idx++) {
904 if (zip_stat_index(za, entry_idx, 0, &file_stat)) {
905 stdext::throw_exception(stdext::format("unpackArchive: error stat-ing file at index %i: %s",
906 (int)(entry_idx), zip_strerror(za)));
907 }
908 if (!(file_stat.valid & ZIP_STAT_NAME)) {
909 g_logger.warning(stdext::format("warning: skipping entry at index %i with invalid name.",
910 (int)entry_idx));
911 continue;
912 }
913 std::string name(file_stat.name);
914 if (name.empty()) continue;
915 if (name[0] != '/')
916 name = std::string("/") + name;
917 if (name.back() == '/' || file_stat.size == 0) // dir
918 continue;
919 stdext::replace_all(name, "\\", "/");
920
921 zip_file_t* file = zip_fopen_index(za, entry_idx, 0);
922 if(!file)
923 stdext::throw_exception(stdext::format("can't open file from zip archive: %s - %s", name, zip_strerror(za)));
924 std::string buffer(file_stat.size, '\0');
925 zip_fread(file, buffer.data(), buffer.size());
926 zip_fclose(file);
927 ret[name] = std::move(buffer);
928 }
929
930 if (zip_close(za) < 0)
931 stdext::throw_exception(stdext::format("can't close zip archive: %s", zip_strerror(za)));
932 zip_error_fini(&error);
933 return ret; // success
934#endif
935}
936
937#if defined(WIN32) && not(defined(FREE_VERSION))
938void ResourceManager::installDlls(std::filesystem::path dest)
939{
940 static std::list<std::string> dlls = {
941 {"libEGL.dll"},
942 {"libGLESv2.dll"},
943 {"d3dcompiler_46.dll"},
944 {"d3dcompiler_47.dll"}
945 };
946
947 int added_dlls = 0;
948 for (auto& dll : dlls) {
949 auto dll_path = m_binaryPath.parent_path();
950 dll_path /= dll;
951 if (!std::filesystem::exists(dll_path)) {
952 continue;
953 }
954 auto out_path = dest;
955 out_path /= dll;
956 if (std::filesystem::exists(out_path)) {
957 continue;
958 }
959 std::filesystem::copy_file(dll_path, out_path);
960 }
961}
962#endif
963
964#if defined(WITH_ENCRYPTION) && !defined(ANDROID)
965void ResourceManager::encrypt(const std::string& seed) {
966 const std::string dirsToCheck[] = { "data", "modules", "mods", "layouts" };
967 const std::string luaExtension = ".lua";
968
969 g_logger.setLogFile("encryption.log");
970 g_logger.info("----------------------");
971
972 std::queue<std::filesystem::path> toEncrypt;
973 // you can add custom files here
974 toEncrypt.push(std::filesystem::path(INIT_FILENAME));
975
976 for (auto& dir : dirsToCheck) {
977 if (!std::filesystem::exists(dir))
978 continue;
979 for(auto&& entry : std::filesystem::recursive_directory_iterator(std::filesystem::path(dir))) {
980 if (!std::filesystem::is_regular_file(entry.path()))
981 continue;
982 std::string str(entry.path().string());
983 // skip encryption for bot configs
984 if (str.find("game_bot") != std::string::npos && str.find("default_config") != std::string::npos) {
985 continue;
986 }
987 toEncrypt.push(entry.path());
988 }
989 }
990
991 bool encryptForAndroid = seed.find("android") != std::string::npos;
992 uint32_t uintseed = seed.empty() ? 0 : stdext::adler32((const uint8_t*)seed.c_str(), seed.size());
993
994 while (!toEncrypt.empty()) {
995 auto it = toEncrypt.front();
996 toEncrypt.pop();
997 std::ifstream in_file(it, std::ios::binary);
998 if (!in_file.is_open())
999 continue;
1000 std::string buffer(std::istreambuf_iterator<char>(in_file), {});
1001 in_file.close();
1002 if (buffer.size() >= 4 && buffer.substr(0, 4).compare("ENC3") == 0)
1003 continue; // already encrypted
1004
1005 if (!encryptForAndroid && it.extension().string() == luaExtension && it.filename().string() != INIT_FILENAME) {
1006 std::string bytecode = g_lua.generateByteCode(buffer, it.string());
1007 if (bytecode.length() > 10) {
1008 buffer = bytecode;
1009 g_logger.info(stdext::format("%s - lua bytecode encrypted", it.string()));
1010 } else {
1011 g_logger.info(stdext::format("%s - lua but not bytecode encrypted", it.string()));
1012 }
1013 }
1014
1015 if (!encryptBuffer(buffer, uintseed)) { // already encrypted
1016 g_logger.info(stdext::format("%s - already encrypted", it.string()));
1017 continue;
1018 }
1019
1020 std::ofstream out_file(it, std::ios::binary);
1021 if (!out_file.is_open())
1022 continue;
1023 out_file.write(buffer.data(), buffer.size());
1024 out_file.close();
1025 g_logger.info(stdext::format("%s - encrypted", it.string()));
1026 }
1027}
1028#endif
1029
1030bool ResourceManager::decryptBuffer(std::string& buffer) {
1031#ifdef FREE_VERSION
1032 return false;
1033#else
1034 if (buffer.size() < 5)
1035 return true;
1036
1037 if (buffer.substr(0, 4).compare("ENC3") != 0) {
1038 return false;
1039 }
1040
1041 uint64_t key = *(uint64_t*)&buffer[4];
1042 uint32_t compressed_size = *(uint32_t*)&buffer[12];
1043 uint32_t size = *(uint32_t*)&buffer[16];
1044 uint32_t adler = *(uint32_t*)&buffer[20];
1045
1046 if (compressed_size < buffer.size() - 24)
1047 return false;
1048
1049 g_crypt.bdecrypt((uint8_t*)&buffer[24], compressed_size, key);
1050 std::string new_buffer;
1051 new_buffer.resize(size);
1052 unsigned long new_buffer_size = new_buffer.size();
1053 if (uncompress((uint8_t*)new_buffer.data(), &new_buffer_size, (uint8_t*)&buffer[24], compressed_size) != Z_OK)
1054 return false;
1055
1056 uint32_t addlerCheck = stdext::adler32((const uint8_t*)&new_buffer[0], size);
1057 if (adler != addlerCheck) {
1058 uint32_t cseed = adler ^ addlerCheck;
1059 if (m_customEncryption == 0) {
1060 m_customEncryption = cseed;
1061 }
1062 if ((addlerCheck ^ m_customEncryption) != adler) {
1063 return false;
1064 }
1065 }
1066
1067 buffer = new_buffer;
1068 return true;
1069#endif
1070}
1071
1072#ifdef WITH_ENCRYPTION
1073bool ResourceManager::encryptBuffer(std::string& buffer, uint32_t seed) {
1074 if (buffer.size() >= 4 && buffer.substr(0, 4).compare("ENC3") == 0)
1075 return false; // already encrypted
1076
1077 // not random beacause it would require to update to new files each time
1078 int64_t key = stdext::adler32((const uint8_t*)&buffer[0], buffer.size());
1079 key <<= 32;
1080 key += stdext::adler32((const uint8_t*)&buffer[0], buffer.size() / 2);
1081
1082 std::string new_buffer(24 + buffer.size() * 2, '0');
1083 new_buffer[0] = 'E';
1084 new_buffer[1] = 'N';
1085 new_buffer[2] = 'C';
1086 new_buffer[3] = '3';
1087
1088 unsigned long dstLen = new_buffer.size() - 24;
1089 if (compress((uint8_t*)&new_buffer[24], &dstLen, (const uint8_t*)buffer.data(), buffer.size()) != Z_OK) {
1090 g_logger.error("Error while compressing");
1091 return false;
1092 }
1093 new_buffer.resize(24 + dstLen);
1094
1095 *(int64_t*)&new_buffer[4] = key;
1096 *(uint32_t*)&new_buffer[12] = (uint32_t)dstLen;
1097 *(uint32_t*)&new_buffer[16] = (uint32_t)buffer.size();
1098 *(uint32_t*)&new_buffer[20] = ((uint32_t)stdext::adler32((const uint8_t*)&buffer[0], buffer.size())) ^ seed;
1099
1100 g_crypt.bencrypt((uint8_t*)&new_buffer[0] + 24, new_buffer.size() - 24, key);
1101 buffer = new_buffer;
1102 return true;
1103}
1104#endif
1105
1106void ResourceManager::setLayout(std::string layout)
1107{
1108 stdext::tolower(layout);
1109 stdext::replace_all(layout, "/", "");
1110 if (layout == "default") {
1111 layout = "";
1112 }
1113 if (!layout.empty() && !PHYSFS_exists((std::string("/layouts/") + layout).c_str())) {
1114 g_logger.error(stdext::format("Layour %s doesn't exist, using default", layout));
1115 return;
1116 }
1117 m_layout = layout;
1118}
1119
1120bool ResourceManager::mountMemoryData(const std::shared_ptr<std::vector<uint8_t>>& data)
1121{
1122 if (!data || data->size() < 1024)
1123 return false;
1124
1125 if (PHYSFS_mountMemory(data->data(), data->size(), nullptr,
1126 "memory_data.zip", "/", 0)) {
1127 if (PHYSFS_exists(INIT_FILENAME.c_str())) {
1128 m_loadedFromArchive = true;
1129 m_memoryData = data;
1130 return true;
1131 }
1132 PHYSFS_unmount("memory_data.zip");
1133 }
1134 return false;
1135}
1136
1137void ResourceManager::unmountMemoryData()
1138{
1139 if (!m_memoryData)
1140 return;
1141
1142 if (!PHYSFS_unmount("memory_data.zip")) {
1143 g_logger.fatal(stdext::format("Unable to unmount memory data", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
1144 }
1145 m_memoryData = nullptr;
1146 m_loadedFromMemory = false;
1147 m_loadedFromArchive = false;
1148}
1149
Top