| @@ -0,0 +1,1148 @@ | |||
| 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 | + | ||
| 48 | + | ResourceManager g_resources; | |
| 49 | + | static const std::string INIT_FILENAME = "init.lua"; | |
| 50 | + | ||
| 51 | + | void 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 | + | ||
| 66 | + | void ResourceManager::terminate() | |
| 67 | + | { | |
| 68 | + | PHYSFS_deinit(); | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | bool 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 | + | ||
| 144 | + | bool 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 | + | ||
| 172 | + | bool 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 | + | ||
| 241 | + | std::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 | + | ||
| 303 | + | bool 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 | + | ||
| 352 | + | bool 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 | + | ||
| 359 | + | bool ResourceManager::directoryExists(const std::string& directoryName) | |
| 360 | + | { | |
| 361 | + | if (directoryName == "/downloads") | |
| 362 | + | return true; | |
| 363 | + | return (PHYSFS_isDirectory(resolvePath(directoryName).c_str())); | |
| 364 | + | } | |
| 365 | + | ||
| 366 | + | void 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 | + | ||
| 378 | + | std::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 | + | ||
| 422 | + | bool 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 | + | ||
| 460 | + | bool 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 | + | ||
| 473 | + | bool 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 | + | ||
| 486 | + | bool ResourceManager::writeFileContents(const std::string& fileName, const std::string& data) | |
| 487 | + | { | |
| 488 | + | return writeFileBuffer(fileName, (const uchar*)data.c_str(), data.size()); | |
| 489 | + | } | |
| 490 | + | ||
| 491 | + | FileStreamPtr 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 | + | ||
| 503 | + | FileStreamPtr 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 | + | ||
| 511 | + | FileStreamPtr 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 | + | ||
| 519 | + | bool ResourceManager::deleteFile(const std::string& fileName) | |
| 520 | + | { | |
| 521 | + | return PHYSFS_delete(resolvePath(fileName).c_str()) != 0; | |
| 522 | + | } | |
| 523 | + | ||
| 524 | + | bool ResourceManager::makeDir(const std::string directory) | |
| 525 | + | { | |
| 526 | + | return PHYSFS_mkdir(directory.c_str()); | |
| 527 | + | } | |
| 528 | + | ||
| 529 | + | std::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 | + | ||
| 550 | + | std::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 | + | ||
| 577 | + | std::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 | + | ||
| 584 | + | bool 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 | + | ||
| 591 | + | std::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 | + | ||
| 613 | + | std::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 | + | ||
| 661 | + | std::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 | + | ||
| 681 | + | void 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 | + | ||
| 784 | + | void 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 | + | ||
| 816 | + | std::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 | + | ||
| 879 | + | std::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)) | |
| 938 | + | void 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) | |
| 965 | + | void 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 | + | ||
| 1030 | + | bool 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 | |
| 1073 | + | bool 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 | + | ||
| 1106 | + | void 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 | + | ||
| 1120 | + | bool 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 | + | ||
| 1137 | + | void 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 | + | } |
| @@ -0,0 +1,127 @@ | |||
| 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 | + | #ifndef RESOURCES_H | |
| 24 | + | #define RESOURCES_H | |
| 25 | + | ||
| 26 | + | #include "declarations.h" | |
| 27 | + | ||
| 28 | + | // @bindsingleton g_resources | |
| 29 | + | class ResourceManager | |
| 30 | + | { | |
| 31 | + | public: | |
| 32 | + | // @dontbind | |
| 33 | + | void init(const char *argv0); | |
| 34 | + | // @dontbind | |
| 35 | + | void terminate(); | |
| 36 | + | ||
| 37 | + | bool launchCorrect(const std::string& product, const std::string& app); | |
| 38 | + | bool setupWriteDir(const std::string& product, const std::string& app); | |
| 39 | + | bool setup(); | |
| 40 | + | ||
| 41 | + | std::string getCompactName(); | |
| 42 | + | bool loadDataFromSelf(bool unmountIfMounted = false); | |
| 43 | + | ||
| 44 | + | bool fileExists(const std::string& fileName); | |
| 45 | + | bool directoryExists(const std::string& directoryName); | |
| 46 | + | ||
| 47 | + | // @dontbind | |
| 48 | + | void readFileStream(const std::string& fileName, std::iostream& out); | |
| 49 | + | std::string readFileContents(const std::string& fileName, bool safe = false); | |
| 50 | + | std::string readFileContentsSafe(const std::string& fileName) { return readFileContents(fileName, true); } | |
| 51 | + | bool isFileEncryptedOrCompressed(const std::string& fileName); | |
| 52 | + | // @dontbind | |
| 53 | + | bool writeFileBuffer(const std::string& fileName, const uchar* data, uint size); | |
| 54 | + | bool writeFileContents(const std::string& fileName, const std::string& data); | |
| 55 | + | // @dontbind | |
| 56 | + | bool writeFileStream(const std::string& fileName, std::iostream& in); | |
| 57 | + | ||
| 58 | + | FileStreamPtr openFile(const std::string& fileName, bool dontCache = false); | |
| 59 | + | FileStreamPtr appendFile(const std::string& fileName); | |
| 60 | + | FileStreamPtr createFile(const std::string& fileName); | |
| 61 | + | bool deleteFile(const std::string& fileName); | |
| 62 | + | ||
| 63 | + | bool makeDir(const std::string directory); | |
| 64 | + | std::list<std::string> listDirectoryFiles(const std::string & directoryPath = "", bool fullPath = false, bool raw = false); | |
| 65 | + | ||
| 66 | + | std::string resolvePath(std::string path); | |
| 67 | + | std::string getWorkDir() { return "/"; } | |
| 68 | + | #ifdef ANDROID | |
| 69 | + | std::string getWriteDir() { return "/"; } | |
| 70 | + | std::string getBinaryName() { return "otclientv8.apk"; } | |
| 71 | + | #else | |
| 72 | + | std::string getWriteDir() { return m_writeDir.string(); } | |
| 73 | + | std::string getBinaryName() { return m_binaryPath.filename().string(); } | |
| 74 | + | #endif | |
| 75 | + | ||
| 76 | + | std::string guessFilePath(const std::string& filename, const std::string& type); | |
| 77 | + | bool isFileType(const std::string& filename, const std::string& type); | |
| 78 | + | ||
| 79 | + | bool isLoadedFromArchive() { return m_loadedFromArchive; } | |
| 80 | + | bool isLoadedFromMemory() { return m_loadedFromMemory; } | |
| 81 | + | ||
| 82 | + | std::string fileChecksum(const std::string& path); | |
| 83 | + | ||
| 84 | + | std::map<std::string, std::string> filesChecksums(); | |
| 85 | + | std::string selfChecksum(); | |
| 86 | + | ||
| 87 | + | std::string readCrashLog(bool txt); | |
| 88 | + | void deleteCrashLog(); | |
| 89 | + | ||
| 90 | + | void updateData(const std::set<std::string>& files, bool reMount); | |
| 91 | + | void updateExecutable(std::string fileName); | |
| 92 | + | ||
| 93 | + | std::string createArchive(const std::map<std::string, std::string>& files); | |
| 94 | + | std::map<std::string, std::string> decompressArchive(std::string dataOrPath); | |
| 95 | + | ||
| 96 | + | #ifdef WITH_ENCRYPTION | |
| 97 | + | void encrypt(const std::string& seed = ""); | |
| 98 | + | bool encryptBuffer(std::string & buffer, uint32_t seed = 0); | |
| 99 | + | #endif | |
| 100 | + | bool decryptBuffer(std::string & buffer); | |
| 101 | + | #if defined(WIN32) && not(defined(FREE_VERSION)) | |
| 102 | + | void installDlls(std::filesystem::path dest); | |
| 103 | + | #endif | |
| 104 | + | ||
| 105 | + | void setLayout(std::string layout); | |
| 106 | + | std::string getLayout() | |
| 107 | + | { | |
| 108 | + | return m_layout; | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | private: | |
| 112 | + | bool mountMemoryData(const std::shared_ptr<std::vector<uint8_t>>& data); | |
| 113 | + | void unmountMemoryData(); | |
| 114 | + | ||
| 115 | + | #ifndef ANDROID | |
| 116 | + | std::filesystem::path m_binaryPath, m_writeDir; | |
| 117 | + | #endif | |
| 118 | + | bool m_loadedFromMemory = false; | |
| 119 | + | bool m_loadedFromArchive = false; | |
| 120 | + | std::shared_ptr<std::vector<uint8_t>> m_memoryData; | |
| 121 | + | uint32_t m_customEncryption = 0; | |
| 122 | + | std::string m_layout; | |
| 123 | + | }; | |
| 124 | + | ||
| 125 | + | extern ResourceManager g_resources; | |
| 126 | + | ||
| 127 | + | #endif |
| @@ -0,0 +1,57 @@ | |||
| 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 "scheduledevent.h" | |
| 24 | + | ||
| 25 | + | ScheduledEvent::ScheduledEvent(const std::string& function, const std::function<void()>& callback, int delay, int maxCycles, bool botSafe) : Event(function, callback, botSafe) | |
| 26 | + | { | |
| 27 | + | m_ticks = g_clock.millis() + delay; | |
| 28 | + | m_delay = delay; | |
| 29 | + | m_maxCycles = maxCycles; | |
| 30 | + | m_cyclesExecuted = 0; | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | void ScheduledEvent::execute() | |
| 34 | + | { | |
| 35 | + | if(!m_canceled && m_callback && (m_maxCycles == 0 || m_cyclesExecuted < m_maxCycles)) { | |
| 36 | + | m_callback(); | |
| 37 | + | m_executed = true; | |
| 38 | + | // callback may be used in the next cycle | |
| 39 | + | } else { | |
| 40 | + | // reset callback to free object refs | |
| 41 | + | m_callback = nullptr; | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | m_cyclesExecuted++; | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | bool ScheduledEvent::nextCycle() | |
| 48 | + | { | |
| 49 | + | if(m_callback && !m_canceled && (m_maxCycles == 0 || m_cyclesExecuted < m_maxCycles)) { | |
| 50 | + | m_ticks += m_delay; | |
| 51 | + | return true; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | // reset callback to free object refs | |
| 55 | + | m_callback = nullptr; | |
| 56 | + | return false; | |
| 57 | + | } |
| @@ -0,0 +1,57 @@ | |||
| 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 | + | #ifndef SCHEDULEDEVENT_H | |
| 24 | + | #define SCHEDULEDEVENT_H | |
| 25 | + | ||
| 26 | + | #include <framework/global.h> | |
| 27 | + | #include "event.h" | |
| 28 | + | #include "clock.h" | |
| 29 | + | ||
| 30 | + | // @bindclass | |
| 31 | + | class ScheduledEvent : public Event | |
| 32 | + | { | |
| 33 | + | public: | |
| 34 | + | ScheduledEvent(const std::string& function, const std::function<void()>& callback, int delay, int maxCycles, bool botSafe = false); | |
| 35 | + | void execute(); | |
| 36 | + | bool nextCycle(); | |
| 37 | + | ||
| 38 | + | int ticks() { return m_ticks; } | |
| 39 | + | int remainingTicks() { return m_ticks - g_clock.millis(); } | |
| 40 | + | int delay() { return m_delay; } | |
| 41 | + | int cyclesExecuted() { return m_cyclesExecuted; } | |
| 42 | + | int maxCycles() { return m_maxCycles; } | |
| 43 | + | ||
| 44 | + | private: | |
| 45 | + | ticks_t m_ticks; | |
| 46 | + | int m_delay; | |
| 47 | + | int m_maxCycles; | |
| 48 | + | int m_cyclesExecuted; | |
| 49 | + | }; | |
| 50 | + | ||
| 51 | + | struct lessScheduledEvent { | |
| 52 | + | bool operator()(const ScheduledEventPtr& a, const ScheduledEventPtr& b) { | |
| 53 | + | return b->ticks() < a->ticks(); | |
| 54 | + | } | |
| 55 | + | }; | |
| 56 | + | ||
| 57 | + | #endif |
| @@ -0,0 +1,36 @@ | |||
| 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 | + | ||
| 24 | + | #include "timer.h" | |
| 25 | + | #include "clock.h" | |
| 26 | + | ||
| 27 | + | void Timer::restart() | |
| 28 | + | { | |
| 29 | + | m_startTicks = g_clock.millis(); | |
| 30 | + | m_stopped = false; | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | ticks_t Timer::ticksElapsed() | |
| 34 | + | { | |
| 35 | + | return g_clock.millis() - m_startTicks; | |
| 36 | + | } |
| @@ -0,0 +1,48 @@ | |||
| 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 | + | #ifndef TIMER_H | |
| 24 | + | #define TIMER_H | |
| 25 | + | ||
| 26 | + | #include <framework/global.h> | |
| 27 | + | ||
| 28 | + | class Timer | |
| 29 | + | { | |
| 30 | + | public: | |
| 31 | + | Timer() { restart(); } | |
| 32 | + | ||
| 33 | + | void restart(); | |
| 34 | + | void stop() { m_stopped = true; } | |
| 35 | + | void adjust(ticks_t value) { m_startTicks += value; } | |
| 36 | + | ||
| 37 | + | ticks_t startTicks() { return m_startTicks; } | |
| 38 | + | ticks_t ticksElapsed(); | |
| 39 | + | float timeElapsed() { return ticksElapsed()/1000.0f; } | |
| 40 | + | ||
| 41 | + | bool running() { return !m_stopped; } | |
| 42 | + | ||
| 43 | + | private: | |
| 44 | + | ticks_t m_startTicks; | |
| 45 | + | stdext::boolean<false> m_stopped; | |
| 46 | + | }; | |
| 47 | + | ||
| 48 | + | #endif |
| @@ -0,0 +1,56 @@ | |||
| 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 | + | #ifndef FRAMEWORK_GLOBAL_H | |
| 24 | + | #define FRAMEWORK_GLOBAL_H | |
| 25 | + | ||
| 26 | + | #include "stdext/compiler.h" | |
| 27 | + | ||
| 28 | + | // common C/C++ headers | |
| 29 | + | #include "pch.h" | |
| 30 | + | ||
| 31 | + | // error handling | |
| 32 | + | #if defined(NDEBUG) | |
| 33 | + | #define VALIDATE(expression) ((void)0) | |
| 34 | + | #else | |
| 35 | + | extern void fatalError(const char* error, const char* file, int line); | |
| 36 | + | #define VALIDATE(expression) { if(!(expression)) fatalError(#expression, __FILE__, __LINE__); }; | |
| 37 | + | #endif | |
| 38 | + | ||
| 39 | + | ||
| 40 | + | // global constants | |
| 41 | + | #include "const.h" | |
| 42 | + | ||
| 43 | + | // stdext which includes additional C++ algorithms | |
| 44 | + | #include "stdext/stdext.h" | |
| 45 | + | ||
| 46 | + | // additional utilities | |
| 47 | + | #include "util/point.h" | |
| 48 | + | #include "util/color.h" | |
| 49 | + | #include "util/rect.h" | |
| 50 | + | #include "util/size.h" | |
| 51 | + | #include "util/matrix.h" | |
| 52 | + | ||
| 53 | + | // logger | |
| 54 | + | #include "core/logger.h" | |
| 55 | + | ||
| 56 | + | #endif |
| @@ -0,0 +1,80 @@ | |||
| 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 "animatedtexture.h" | |
| 24 | + | #include "graphics.h" | |
| 25 | + | ||
| 26 | + | #include <framework/core/eventdispatcher.h> | |
| 27 | + | ||
| 28 | + | AnimatedTexture::AnimatedTexture(const Size& size, std::vector<ImagePtr> frames, std::vector<int> framesDelay, bool buildMipmaps, bool compress) : | |
| 29 | + | Texture(size) | |
| 30 | + | { | |
| 31 | + | for(uint i=0;i<frames.size();++i) { | |
| 32 | + | m_frames.push_back(new Texture(frames[i], buildMipmaps, compress)); | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | m_framesDelay = framesDelay; | |
| 36 | + | m_hasMipmaps = buildMipmaps; | |
| 37 | + | m_uniqueId = 0; | |
| 38 | + | m_currentFrame = 0; | |
| 39 | + | m_animTimer.restart(); | |
| 40 | + | setupTranformMatrix(); | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | AnimatedTexture::~AnimatedTexture() | |
| 44 | + | { | |
| 45 | + | ||
| 46 | + | } | |
| 47 | + | ||
| 48 | + | bool AnimatedTexture::buildHardwareMipmaps() | |
| 49 | + | { | |
| 50 | + | for(const TexturePtr& frame : m_frames) | |
| 51 | + | frame->buildHardwareMipmaps(); | |
| 52 | + | m_hasMipmaps = true; | |
| 53 | + | return true; | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | void AnimatedTexture::setSmooth(bool smooth) | |
| 57 | + | { | |
| 58 | + | for(const TexturePtr& frame : m_frames) | |
| 59 | + | frame->setSmooth(smooth); | |
| 60 | + | m_smooth = smooth; | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | void AnimatedTexture::setRepeat(bool repeat) | |
| 64 | + | { | |
| 65 | + | for(const TexturePtr& frame : m_frames) | |
| 66 | + | frame->setRepeat(repeat); | |
| 67 | + | m_repeat = repeat; | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | void AnimatedTexture::update() | |
| 71 | + | { | |
| 72 | + | if (m_animTimer.ticksElapsed() >= m_framesDelay[m_currentFrame]) { | |
| 73 | + | m_animTimer.restart(); | |
| 74 | + | m_currentFrame = (m_currentFrame + 1) % m_frames.size(); | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | m_frames[m_currentFrame]->update(); | |
| 78 | + | m_id = m_frames[m_currentFrame]->getId(); | |
| 79 | + | m_uniqueId = m_frames[m_currentFrame]->getUniqueId(); | |
| 80 | + | } |
| @@ -0,0 +1,53 @@ | |||
| 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 | + | #ifndef ANIMATEDTEXTURE_H | |
| 24 | + | #define ANIMATEDTEXTURE_H | |
| 25 | + | ||
| 26 | + | #include "texture.h" | |
| 27 | + | #include <framework/core/timer.h> | |
| 28 | + | ||
| 29 | + | class AnimatedTexture : public Texture | |
| 30 | + | { | |
| 31 | + | public: | |
| 32 | + | AnimatedTexture(const Size& size, std::vector<ImagePtr> frames, std::vector<int> framesDelay, bool buildMipmaps = false, bool compress = false); | |
| 33 | + | virtual ~AnimatedTexture(); | |
| 34 | + | ||
| 35 | + | void replace(const ImagePtr& image) { } | |
| 36 | + | void update(); | |
| 37 | + | ||
| 38 | + | virtual bool isAnimatedTexture() { return true; } | |
| 39 | + | ||
| 40 | + | protected: | |
| 41 | + | virtual bool buildHardwareMipmaps(); | |
| 42 | + | ||
| 43 | + | virtual void setSmooth(bool smooth); | |
| 44 | + | virtual void setRepeat(bool repeat); | |
| 45 | + | ||
| 46 | + | private: | |
| 47 | + | std::vector<TexturePtr> m_frames; | |
| 48 | + | std::vector<int> m_framesDelay; | |
| 49 | + | uint m_currentFrame; | |
| 50 | + | Timer m_animTimer; | |
| 51 | + | }; | |
| 52 | + | ||
| 53 | + | #endif |
| @@ -0,0 +1,1142 @@ | |||
| 1 | + | /* | |
| 2 | + | * Based on APNG Disassembler 2.3 | |
| 3 | + | * | |
| 4 | + | * Copyright (c) 2009 Max Stepin | |
| 5 | + | * maxst at users.sourceforge.net | |
| 6 | + | * | |
| 7 | + | * GNU LGPL information | |
| 8 | + | * -------------------- | |
| 9 | + | * | |
| 10 | + | * This library is free software; you can redistribute it and/or | |
| 11 | + | * modify it under the terms of the GNU Lesser General Public | |
| 12 | + | * License as published by the Free Software Foundation; either | |
| 13 | + | * version 2.1 of the License, or (at your option) any later version. | |
| 14 | + | * | |
| 15 | + | * This library is distributed in the hope that it will be useful, | |
| 16 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | + | * Lesser General Public License for more details. | |
| 19 | + | * | |
| 20 | + | * You should have received a copy of the GNU Lesser General Public | |
| 21 | + | * License along with this library; if not, write to the Free Software | |
| 22 | + | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| 23 | + | * | |
| 24 | + | */ | |
| 25 | + | ||
| 26 | + | #include <stdio.h> | |
| 27 | + | #include <stdlib.h> | |
| 28 | + | #include <memory.h> | |
| 29 | + | #include <zlib.h> | |
| 30 | + | #include "apngloader.h" | |
| 31 | + | #include <iostream> | |
| 32 | + | #include <sstream> | |
| 33 | + | #include <fstream> | |
| 34 | + | ||
| 35 | + | #if defined(_MSC_VER) && _MSC_VER >= 1300 | |
| 36 | + | #define swap16(data) _byteswap_ushort(data) | |
| 37 | + | #define swap32(data) _byteswap_ulong(data) | |
| 38 | + | #elif __linux__ | |
| 39 | + | #include <byteswap.h> | |
| 40 | + | #define swap16(data) bswap_16(data) | |
| 41 | + | #define swap32(data) bswap_32(data) | |
| 42 | + | #else | |
| 43 | + | #define swap16(data) (((data >> 8) & 255) | ((data & 255) << 8)) | |
| 44 | + | #define swap32(data) ((swap16(data) << 16) | swap16(data >> 16)) | |
| 45 | + | #endif | |
| 46 | + | ||
| 47 | + | #define PNG_ZBUF_SIZE 32768 | |
| 48 | + | ||
| 49 | + | #define PNG_DISPOSE_OP_NONE 0x00 | |
| 50 | + | #define PNG_DISPOSE_OP_BACKGROUND 0x01 | |
| 51 | + | #define PNG_DISPOSE_OP_PREVIOUS 0x02 | |
| 52 | + | ||
| 53 | + | #define PNG_BLEND_OP_SOURCE 0x00 | |
| 54 | + | #define PNG_BLEND_OP_OVER 0x01 | |
| 55 | + | ||
| 56 | + | #define notabc(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97)) | |
| 57 | + | ||
| 58 | + | #define ROWBYTES(pixel_bits, width) \ | |
| 59 | + | ((pixel_bits) >= 8 ? \ | |
| 60 | + | ((width) * (((unsigned int)(pixel_bits)) >> 3)) : \ | |
| 61 | + | (( ((width) * ((unsigned int)(pixel_bits))) + 7) >> 3) ) | |
| 62 | + | ||
| 63 | + | unsigned char png_sign[8] = {137, 80, 78, 71, 13, 10, 26, 10}; | |
| 64 | + | ||
| 65 | + | int mask4[2]={240,15}; | |
| 66 | + | int shift4[2]={4,0}; | |
| 67 | + | ||
| 68 | + | int mask2[4]={192,48,12,3}; | |
| 69 | + | int shift2[4]={6,4,2,0}; | |
| 70 | + | ||
| 71 | + | int mask1[8]={128,64,32,16,8,4,2,1}; | |
| 72 | + | int shift1[8]={7,6,5,4,3,2,1,0}; | |
| 73 | + | ||
| 74 | + | unsigned int keep_original = 1; | |
| 75 | + | unsigned char pal[256][3]; | |
| 76 | + | unsigned char trns[256]; | |
| 77 | + | unsigned int palsize, trnssize; | |
| 78 | + | unsigned int hasTRNS; | |
| 79 | + | unsigned short trns1, trns2, trns3; | |
| 80 | + | ||
| 81 | + | unsigned int read32(std::istream& f1) | |
| 82 | + | { | |
| 83 | + | unsigned char a, b, c, d; | |
| 84 | + | f1.read((char*)&a, 1); | |
| 85 | + | f1.read((char*)&b, 1); | |
| 86 | + | f1.read((char*)&c, 1); | |
| 87 | + | f1.read((char*)&d, 1); | |
| 88 | + | return ((unsigned int)a<<24)+((unsigned int)b<<16)+((unsigned int)c<<8)+(unsigned int)d; | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | unsigned short read16(std::istream& f1) | |
| 92 | + | { | |
| 93 | + | unsigned char a, b; | |
| 94 | + | f1.read((char*)&a, 1); | |
| 95 | + | f1.read((char*)&b, 1); | |
| 96 | + | return ((unsigned short)a<<8)+(unsigned short)b; | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | unsigned short readshort(unsigned char * p) | |
| 100 | + | { | |
| 101 | + | return ((unsigned short)(*p)<<8)+(unsigned short)(*(p+1)); | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | void read_sub_row(unsigned char * row, unsigned int rowbytes, unsigned int bpp) | |
| 105 | + | { | |
| 106 | + | unsigned int i; | |
| 107 | + | ||
| 108 | + | for (i=bpp; i<rowbytes; i++) | |
| 109 | + | row[i] += row[i-bpp]; | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | void read_up_row(unsigned char * row, unsigned char * prev_row, unsigned int rowbytes, unsigned int bpp) | |
| 113 | + | { | |
| 114 | + | unsigned int i; | |
| 115 | + | ||
| 116 | + | if (prev_row) | |
| 117 | + | for (i=0; i<rowbytes; i++) | |
| 118 | + | row[i] += prev_row[i]; | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | void read_average_row(unsigned char * row, unsigned char * prev_row, unsigned int rowbytes, unsigned int bpp) | |
| 122 | + | { | |
| 123 | + | unsigned int i; | |
| 124 | + | ||
| 125 | + | if (prev_row) | |
| 126 | + | { | |
| 127 | + | for (i=0; i<bpp; i++) | |
| 128 | + | row[i] += prev_row[i]>>1; | |
| 129 | + | for (i=bpp; i<rowbytes; i++) | |
| 130 | + | row[i] += (prev_row[i] + row[i-bpp])>>1; | |
| 131 | + | } | |
| 132 | + | else | |
| 133 | + | { | |
| 134 | + | for (i=bpp; i<rowbytes; i++) | |
| 135 | + | row[i] += row[i-bpp]>>1; | |
| 136 | + | } | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | void read_paeth_row(unsigned char * row, unsigned char * prev_row, unsigned int rowbytes, unsigned int bpp) | |
| 140 | + | { | |
| 141 | + | unsigned int i; | |
| 142 | + | int a, b, c, pa, pb, pc, p; | |
| 143 | + | ||
| 144 | + | if (prev_row) | |
| 145 | + | { | |
| 146 | + | for (i=0; i<bpp; i++) | |
| 147 | + | row[i] += prev_row[i]; | |
| 148 | + | for (i=bpp; i<rowbytes; i++) | |
| 149 | + | { | |
| 150 | + | a = row[i-bpp]; | |
| 151 | + | b = prev_row[i]; | |
| 152 | + | c = prev_row[i-bpp]; | |
| 153 | + | p = b - c; | |
| 154 | + | pc = a - c; | |
| 155 | + | pa = abs(p); | |
| 156 | + | pb = abs(pc); | |
| 157 | + | pc = abs(p + pc); | |
| 158 | + | row[i] += ((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c); | |
| 159 | + | } | |
| 160 | + | } | |
| 161 | + | else | |
| 162 | + | { | |
| 163 | + | for (i=bpp; i<rowbytes; i++) | |
| 164 | + | row[i] += row[i-bpp]; | |
| 165 | + | } | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | void unpack(z_stream& zstream, unsigned char * dst, unsigned int dst_size, unsigned char * src, unsigned int src_size, unsigned int h, unsigned int rowbytes, unsigned char bpp) | |
| 169 | + | { | |
| 170 | + | unsigned int j; | |
| 171 | + | unsigned char * row = dst; | |
| 172 | + | unsigned char * prev_row = NULL; | |
| 173 | + | ||
| 174 | + | zstream.next_out = dst; | |
| 175 | + | zstream.avail_out = dst_size; | |
| 176 | + | zstream.next_in = src; | |
| 177 | + | zstream.avail_in = src_size; | |
| 178 | + | inflate(&zstream, Z_FINISH); | |
| 179 | + | inflateReset(&zstream); | |
| 180 | + | ||
| 181 | + | for (j=0; j<h; j++) | |
| 182 | + | { | |
| 183 | + | switch (*row++) | |
| 184 | + | { | |
| 185 | + | case 0: break; | |
| 186 | + | case 1: read_sub_row(row, rowbytes, bpp); break; | |
| 187 | + | case 2: read_up_row(row, prev_row, rowbytes, bpp); break; | |
| 188 | + | case 3: read_average_row(row, prev_row, rowbytes, bpp); break; | |
| 189 | + | case 4: read_paeth_row(row, prev_row, rowbytes, bpp); break; | |
| 190 | + | } | |
| 191 | + | prev_row = row; | |
| 192 | + | row += rowbytes; | |
| 193 | + | } | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | void compose0(unsigned char * dst1, unsigned int dstbytes1, unsigned char * dst2, unsigned int dstbytes2, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) | |
| 197 | + | { | |
| 198 | + | unsigned int i, j, g, a; | |
| 199 | + | unsigned char * sp; | |
| 200 | + | unsigned char * dp1; | |
| 201 | + | unsigned int * dp2; | |
| 202 | + | ||
| 203 | + | for (j=0; j<h; j++) | |
| 204 | + | { | |
| 205 | + | sp = src+1; | |
| 206 | + | dp1 = dst1; | |
| 207 | + | dp2 = (unsigned int*)dst2; | |
| 208 | + | ||
| 209 | + | if (bop == PNG_BLEND_OP_SOURCE) | |
| 210 | + | { | |
| 211 | + | switch (depth) | |
| 212 | + | { | |
| 213 | + | case 16: for (i=0; i<w; i++) { a = 0xFF; if (hasTRNS && readshort(sp)==trns1) a = 0; *dp1++ = *sp; *dp2++ = (a << 24) + (*sp << 16) + (*sp << 8) + *sp; sp+=2; } break; | |
| 214 | + | case 8: for (i=0; i<w; i++) { a = 0xFF; if (hasTRNS && *sp==trns1) a = 0; *dp1++ = *sp; *dp2++ = (a << 24) + (*sp << 16) + (*sp << 8) + *sp; sp++; } break; | |
| 215 | + | case 4: for (i=0; i<w; i++) { g = (sp[i>>1] & mask4[i&1]) >> shift4[i&1]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0x11; *dp2++ = (a<<24) + g*0x111111; } break; | |
| 216 | + | case 2: for (i=0; i<w; i++) { g = (sp[i>>2] & mask2[i&3]) >> shift2[i&3]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0x55; *dp2++ = (a<<24) + g*0x555555; } break; | |
| 217 | + | case 1: for (i=0; i<w; i++) { g = (sp[i>>3] & mask1[i&7]) >> shift1[i&7]; a = 0xFF; if (hasTRNS && g==trns1) a = 0; *dp1++ = g*0xFF; *dp2++ = (a<<24) + g*0xFFFFFF; } break; | |
| 218 | + | } | |
| 219 | + | } | |
| 220 | + | else /* PNG_BLEND_OP_OVER */ | |
| 221 | + | { | |
| 222 | + | switch (depth) | |
| 223 | + | { | |
| 224 | + | case 16: for (i=0; i<w; i++, dp1++, dp2++) { if (readshort(sp) != trns1) { *dp1 = *sp; *dp2 = 0xFF000000 + (*sp << 16) + (*sp << 8) + *sp; } sp+=2; } break; | |
| 225 | + | case 8: for (i=0; i<w; i++, dp1++, dp2++) { if (*sp != trns1) { *dp1 = *sp; *dp2 = 0xFF000000 + (*sp << 16) + (*sp << 8) + *sp; } sp++; } break; | |
| 226 | + | case 4: for (i=0; i<w; i++, dp1++, dp2++) { g = (sp[i>>1] & mask4[i&1]) >> shift4[i&1]; if (g != trns1) { *dp1 = g*0x11; *dp2 = 0xFF000000+g*0x111111; } } break; | |
| 227 | + | case 2: for (i=0; i<w; i++, dp1++, dp2++) { g = (sp[i>>2] & mask2[i&3]) >> shift2[i&3]; if (g != trns1) { *dp1 = g*0x55; *dp2 = 0xFF000000+g*0x555555; } } break; | |
| 228 | + | case 1: for (i=0; i<w; i++, dp1++, dp2++) { g = (sp[i>>3] & mask1[i&7]) >> shift1[i&7]; if (g != trns1) { *dp1 = g*0xFF; *dp2 = 0xFF000000+g*0xFFFFFF; } } break; | |
| 229 | + | } | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | src += srcbytes; | |
| 233 | + | dst1 += dstbytes1; | |
| 234 | + | dst2 += dstbytes2; | |
| 235 | + | } | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | void compose2(unsigned char * dst1, unsigned int dstbytes1, unsigned char * dst2, unsigned int dstbytes2, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) | |
| 239 | + | { | |
| 240 | + | unsigned int i, j; | |
| 241 | + | unsigned int r, g, b, a; | |
| 242 | + | unsigned char * sp; | |
| 243 | + | unsigned char * dp1; | |
| 244 | + | unsigned int * dp2; | |
| 245 | + | ||
| 246 | + | for (j=0; j<h; j++) | |
| 247 | + | { | |
| 248 | + | sp = src+1; | |
| 249 | + | dp1 = dst1; | |
| 250 | + | dp2 = (unsigned int*)dst2; | |
| 251 | + | ||
| 252 | + | if (bop == PNG_BLEND_OP_SOURCE) | |
| 253 | + | { | |
| 254 | + | if (depth == 8) | |
| 255 | + | { | |
| 256 | + | for (i=0; i<w; i++) | |
| 257 | + | { | |
| 258 | + | b = *sp++; | |
| 259 | + | g = *sp++; | |
| 260 | + | r = *sp++; | |
| 261 | + | a = 0xFF; | |
| 262 | + | if (hasTRNS && b==trns1 && g==trns2 && r==trns3) | |
| 263 | + | a = 0; | |
| 264 | + | *dp1++ = b; *dp1++ = g; *dp1++ = r; | |
| 265 | + | *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 266 | + | } | |
| 267 | + | } | |
| 268 | + | else | |
| 269 | + | { | |
| 270 | + | for (i=0; i<w; i++, sp+=6) | |
| 271 | + | { | |
| 272 | + | b = *sp; | |
| 273 | + | g = *(sp+2); | |
| 274 | + | r = *(sp+4); | |
| 275 | + | a = 0xFF; | |
| 276 | + | if (hasTRNS && readshort(sp)==trns1 && readshort(sp+2)==trns2 && readshort(sp+4)==trns3) | |
| 277 | + | a = 0; | |
| 278 | + | *dp1++ = b; *dp1++ = g; *dp1++ = r; | |
| 279 | + | *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 280 | + | } | |
| 281 | + | } | |
| 282 | + | } | |
| 283 | + | else /* PNG_BLEND_OP_OVER */ | |
| 284 | + | { | |
| 285 | + | if (depth == 8) | |
| 286 | + | { | |
| 287 | + | for (i=0; i<w; i++, sp+=3, dp1+=3, dp2++) | |
| 288 | + | if ((*sp != trns1) || (*(sp+1) != trns2) || (*(sp+2) != trns3)) | |
| 289 | + | { | |
| 290 | + | *dp1 = *sp; *(dp1+1) = *(sp+1); *(dp1+2) = *(sp+2); | |
| 291 | + | *dp2 = 0xFF000000 + (*(sp+2) << 16) + (*(sp+1) << 8) + *sp; | |
| 292 | + | } | |
| 293 | + | } | |
| 294 | + | else | |
| 295 | + | { | |
| 296 | + | for (i=0; i<w; i++, sp+=6, dp1+=3, dp2++) | |
| 297 | + | if ((readshort(sp) != trns1) || (readshort(sp+2) != trns2) || (readshort(sp+4) != trns3)) | |
| 298 | + | { | |
| 299 | + | *dp1 = *sp; *(dp1+1) = *(sp+2); *(dp1+2) = *(sp+4); | |
| 300 | + | *dp2 = 0xFF000000 + (*(sp+4) << 16) + (*(sp+2) << 8) + *sp; | |
| 301 | + | } | |
| 302 | + | } | |
| 303 | + | } | |
| 304 | + | src += srcbytes; | |
| 305 | + | dst1 += dstbytes1; | |
| 306 | + | dst2 += dstbytes2; | |
| 307 | + | } | |
| 308 | + | } | |
| 309 | + | ||
| 310 | + | void compose3(unsigned char * dst1, unsigned int dstbytes1, unsigned char * dst2, unsigned int dstbytes2, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) | |
| 311 | + | { | |
| 312 | + | unsigned int i, j; | |
| 313 | + | unsigned int r, g, b, a; | |
| 314 | + | unsigned int r2, g2, b2, a2; | |
| 315 | + | int u, v, al; | |
| 316 | + | unsigned char col = 0; | |
| 317 | + | unsigned char * sp; | |
| 318 | + | unsigned char * dp1; | |
| 319 | + | unsigned int * dp2; | |
| 320 | + | ||
| 321 | + | for (j=0; j<h; j++) | |
| 322 | + | { | |
| 323 | + | sp = src+1; | |
| 324 | + | dp1 = dst1; | |
| 325 | + | dp2 = (unsigned int*)dst2; | |
| 326 | + | ||
| 327 | + | for (i=0; i<w; i++) | |
| 328 | + | { | |
| 329 | + | switch (depth) | |
| 330 | + | { | |
| 331 | + | case 8: col = sp[i]; break; | |
| 332 | + | case 4: col = (sp[i>>1] & mask4[i&1]) >> shift4[i&1]; break; | |
| 333 | + | case 2: col = (sp[i>>2] & mask2[i&3]) >> shift2[i&3]; break; | |
| 334 | + | case 1: col = (sp[i>>3] & mask1[i&7]) >> shift1[i&7]; break; | |
| 335 | + | } | |
| 336 | + | ||
| 337 | + | b = pal[col][0]; | |
| 338 | + | g = pal[col][1]; | |
| 339 | + | r = pal[col][2]; | |
| 340 | + | a = trns[col]; | |
| 341 | + | ||
| 342 | + | if (bop == PNG_BLEND_OP_SOURCE) | |
| 343 | + | { | |
| 344 | + | *dp1++ = col; | |
| 345 | + | *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 346 | + | } | |
| 347 | + | else /* PNG_BLEND_OP_OVER */ | |
| 348 | + | { | |
| 349 | + | if (a == 255) | |
| 350 | + | { | |
| 351 | + | *dp1++ = col; | |
| 352 | + | *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 353 | + | } | |
| 354 | + | else | |
| 355 | + | if (a != 0) | |
| 356 | + | { | |
| 357 | + | if ((a2 = (*dp2)>>24)) | |
| 358 | + | { | |
| 359 | + | keep_original = 0; | |
| 360 | + | u = a*255; | |
| 361 | + | v = (255-a)*a2; | |
| 362 | + | al = 255*255-(255-a)*(255-a2); | |
| 363 | + | b2 = ((*dp2)&255); | |
| 364 | + | g2 = (((*dp2)>>8)&255); | |
| 365 | + | r2 = (((*dp2)>>16)&255); | |
| 366 | + | b = (b*u + b2*v)/al; | |
| 367 | + | g = (g*u + g2*v)/al; | |
| 368 | + | r = (r*u + r2*v)/al; | |
| 369 | + | a = al/255; | |
| 370 | + | } | |
| 371 | + | *dp1++ = col; | |
| 372 | + | *dp2++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 373 | + | } | |
| 374 | + | else | |
| 375 | + | { | |
| 376 | + | dp1++; | |
| 377 | + | dp2++; | |
| 378 | + | } | |
| 379 | + | } | |
| 380 | + | } | |
| 381 | + | src += srcbytes; | |
| 382 | + | dst1 += dstbytes1; | |
| 383 | + | dst2 += dstbytes2; | |
| 384 | + | } | |
| 385 | + | } | |
| 386 | + | ||
| 387 | + | void compose4(unsigned char * dst, unsigned int dstbytes, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) | |
| 388 | + | { | |
| 389 | + | unsigned int i, j, step; | |
| 390 | + | unsigned int g, a, g2, a2; | |
| 391 | + | int u, v, al; | |
| 392 | + | unsigned char * sp; | |
| 393 | + | unsigned char * dp; | |
| 394 | + | ||
| 395 | + | step = (depth+7)/8; | |
| 396 | + | ||
| 397 | + | for (j=0; j<h; j++) | |
| 398 | + | { | |
| 399 | + | sp = src+1; | |
| 400 | + | dp = dst; | |
| 401 | + | ||
| 402 | + | if (bop == PNG_BLEND_OP_SOURCE) | |
| 403 | + | { | |
| 404 | + | for (i=0; i<w; i++) | |
| 405 | + | { | |
| 406 | + | g = *sp; sp += step; | |
| 407 | + | a = *sp; sp += step; | |
| 408 | + | *dp++ = g; | |
| 409 | + | *dp++ = a; | |
| 410 | + | } | |
| 411 | + | } | |
| 412 | + | else /* PNG_BLEND_OP_OVER */ | |
| 413 | + | { | |
| 414 | + | for (i=0; i<w; i++) | |
| 415 | + | { | |
| 416 | + | g = *sp; sp += step; | |
| 417 | + | a = *sp; sp += step; | |
| 418 | + | if (a == 255) | |
| 419 | + | { | |
| 420 | + | *dp++ = g; | |
| 421 | + | *dp++ = a; | |
| 422 | + | } | |
| 423 | + | else | |
| 424 | + | if (a != 0) | |
| 425 | + | { | |
| 426 | + | if ((a2 = *(dp+1))) | |
| 427 | + | { | |
| 428 | + | u = a*255; | |
| 429 | + | v = (255-a)*a2; | |
| 430 | + | al = 255*255-(255-a)*(255-a2); | |
| 431 | + | g2 = ((*dp)&255); | |
| 432 | + | g = (g*u + g2*v)/al; | |
| 433 | + | a = al/255; | |
| 434 | + | } | |
| 435 | + | *dp++ = g; | |
| 436 | + | *dp++ = a; | |
| 437 | + | } | |
| 438 | + | else | |
| 439 | + | dp+=2; | |
| 440 | + | } | |
| 441 | + | } | |
| 442 | + | src += srcbytes; | |
| 443 | + | dst += dstbytes; | |
| 444 | + | } | |
| 445 | + | } | |
| 446 | + | ||
| 447 | + | void compose6(unsigned char * dst, unsigned int dstbytes, unsigned char * src, unsigned int srcbytes, unsigned int w, unsigned int h, unsigned int bop, unsigned char depth) | |
| 448 | + | { | |
| 449 | + | unsigned int i, j, step; | |
| 450 | + | unsigned int r, g, b, a; | |
| 451 | + | unsigned int r2, g2, b2, a2; | |
| 452 | + | int u, v, al; | |
| 453 | + | unsigned char * sp; | |
| 454 | + | unsigned int * dp; | |
| 455 | + | ||
| 456 | + | step = (depth+7)/8; | |
| 457 | + | ||
| 458 | + | for (j=0; j<h; j++) | |
| 459 | + | { | |
| 460 | + | sp = src+1; | |
| 461 | + | dp = (unsigned int*)dst; | |
| 462 | + | ||
| 463 | + | if (bop == PNG_BLEND_OP_SOURCE) | |
| 464 | + | { | |
| 465 | + | for (i=0; i<w; i++) | |
| 466 | + | { | |
| 467 | + | b = *sp; sp += step; | |
| 468 | + | g = *sp; sp += step; | |
| 469 | + | r = *sp; sp += step; | |
| 470 | + | a = *sp; sp += step; | |
| 471 | + | *dp++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 472 | + | } | |
| 473 | + | } | |
| 474 | + | else /* PNG_BLEND_OP_OVER */ | |
| 475 | + | { | |
| 476 | + | for (i=0; i<w; i++) | |
| 477 | + | { | |
| 478 | + | b = *sp; sp += step; | |
| 479 | + | g = *sp; sp += step; | |
| 480 | + | r = *sp; sp += step; | |
| 481 | + | a = *sp; sp += step; | |
| 482 | + | if (a == 255) | |
| 483 | + | *dp++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 484 | + | else | |
| 485 | + | if (a != 0) | |
| 486 | + | { | |
| 487 | + | if ((a2 = (*dp)>>24)) | |
| 488 | + | { | |
| 489 | + | u = a*255; | |
| 490 | + | v = (255-a)*a2; | |
| 491 | + | al = 255*255-(255-a)*(255-a2); | |
| 492 | + | b2 = ((*dp)&255); | |
| 493 | + | g2 = (((*dp)>>8)&255); | |
| 494 | + | r2 = (((*dp)>>16)&255); | |
| 495 | + | b = (b*u + b2*v)/al; | |
| 496 | + | g = (g*u + g2*v)/al; | |
| 497 | + | r = (r*u + r2*v)/al; | |
| 498 | + | a = al/255; | |
| 499 | + | } | |
| 500 | + | *dp++ = (a << 24) + (r << 16) + (g << 8) + b; | |
| 501 | + | } | |
| 502 | + | else | |
| 503 | + | dp++; | |
| 504 | + | } | |
| 505 | + | } | |
| 506 | + | src += srcbytes; | |
| 507 | + | dst += dstbytes; | |
| 508 | + | } | |
| 509 | + | } | |
| 510 | + | ||
| 511 | + | int load_apng(std::stringstream& file, struct apng_data *apng) | |
| 512 | + | { | |
| 513 | + | unsigned int i, j; | |
| 514 | + | unsigned int rowbytes; | |
| 515 | + | int imagesize, zbuf_size, zsize, trns_idx; | |
| 516 | + | unsigned int len, chunk/*, crc, seq*/; | |
| 517 | + | unsigned int w, h, w0, h0, x0, y0; | |
| 518 | + | unsigned int frames, loops, first_frame, cur_frame; | |
| 519 | + | unsigned int outrow1, outrow2, outimg1, outimg2; | |
| 520 | + | unsigned short d1, d2; | |
| 521 | + | unsigned char c, dop, bop; | |
| 522 | + | unsigned char channels, depth, pixeldepth, bpp; | |
| 523 | + | unsigned char coltype, compr, filter, interl; | |
| 524 | + | z_stream zstream; | |
| 525 | + | memset(apng, 0, sizeof(struct apng_data)); | |
| 526 | + | ||
| 527 | + | for (i=0; i<256; i++) | |
| 528 | + | { | |
| 529 | + | pal[i][0] = i; | |
| 530 | + | pal[i][1] = i; | |
| 531 | + | pal[i][2] = i; | |
| 532 | + | trns[i] = 255; | |
| 533 | + | } | |
| 534 | + | ||
| 535 | + | zstream.zalloc = Z_NULL; | |
| 536 | + | zstream.zfree = Z_NULL; | |
| 537 | + | zstream.opaque = Z_NULL; | |
| 538 | + | inflateInit(&zstream); | |
| 539 | + | ||
| 540 | + | frames = 1; | |
| 541 | + | first_frame = 0; | |
| 542 | + | cur_frame = 0; | |
| 543 | + | zsize = 0; | |
| 544 | + | hasTRNS = 0; | |
| 545 | + | trns_idx = -1; | |
| 546 | + | x0 = 0; | |
| 547 | + | y0 = 0; | |
| 548 | + | loops = 0; | |
| 549 | + | bop = PNG_BLEND_OP_SOURCE; | |
| 550 | + | ||
| 551 | + | unsigned char sig[8]; | |
| 552 | + | unsigned char * pOut1; | |
| 553 | + | unsigned char * pOut2; | |
| 554 | + | unsigned char * pTemp; | |
| 555 | + | unsigned char * pData; | |
| 556 | + | unsigned char * pImg1; | |
| 557 | + | unsigned char * pImg2; | |
| 558 | + | unsigned char * pDst1; | |
| 559 | + | unsigned char * pDst2; | |
| 560 | + | unsigned short* frames_delay; | |
| 561 | + | ||
| 562 | + | file.read((char*)sig, 8); | |
| 563 | + | if(!file.eof() && memcmp(sig, png_sign, 8) == 0) { | |
| 564 | + | len = read32(file); | |
| 565 | + | chunk = read32(file); | |
| 566 | + | ||
| 567 | + | if ((len == 13) && (chunk == 0x49484452)) /* IHDR */ | |
| 568 | + | { | |
| 569 | + | w = w0 = read32(file); | |
| 570 | + | h = h0 = read32(file); | |
| 571 | + | file.read((char*)&depth, 1); | |
| 572 | + | file.read((char*)&coltype, 1); | |
| 573 | + | file.read((char*)&compr, 1); | |
| 574 | + | file.read((char*)&filter, 1); | |
| 575 | + | file.read((char*)&interl, 1); | |
| 576 | + | /*crc = */read32(file); | |
| 577 | + | ||
| 578 | + | channels = 1; | |
| 579 | + | if (coltype == 2) | |
| 580 | + | channels = 3; | |
| 581 | + | else if (coltype == 4) | |
| 582 | + | channels = 2; | |
| 583 | + | else if (coltype == 6) | |
| 584 | + | channels = 4; | |
| 585 | + | ||
| 586 | + | pixeldepth = depth*channels; | |
| 587 | + | bpp = (pixeldepth + 7) >> 3; | |
| 588 | + | rowbytes = ROWBYTES(pixeldepth, w); | |
| 589 | + | ||
| 590 | + | imagesize = (rowbytes + 1) * h; | |
| 591 | + | zbuf_size = imagesize + ((imagesize + 7) >> 3) + ((imagesize + 63) >> 6) + 11; | |
| 592 | + | ||
| 593 | + | /* | |
| 594 | + | * We'll render into 2 output buffers, first in original coltype, | |
| 595 | + | * second in RGBA. | |
| 596 | + | * | |
| 597 | + | * It's better to try to keep the original coltype, but if dispose/blend | |
| 598 | + | * operations will make it impossible, then we'll save RGBA version instead. | |
| 599 | + | */ | |
| 600 | + | ||
| 601 | + | outrow1 = w*channels; /* output coltype = input coltype */ | |
| 602 | + | outrow2 = w*4; /* output coltype = RGBA */ | |
| 603 | + | outimg1 = h*outrow1; | |
| 604 | + | outimg2 = h*outrow2; | |
| 605 | + | ||
| 606 | + | pOut1=(unsigned char *)malloc(outimg1); | |
| 607 | + | pOut2=(unsigned char *)malloc(outimg2); | |
| 608 | + | pTemp=(unsigned char *)malloc(imagesize); | |
| 609 | + | pData=(unsigned char *)malloc(zbuf_size); | |
| 610 | + | pImg1=pOut1; | |
| 611 | + | pImg2=pOut2; | |
| 612 | + | frames_delay = NULL; | |
| 613 | + | ||
| 614 | + | /* apng decoding - begin */ | |
| 615 | + | memset(pOut1, 0, outimg1); | |
| 616 | + | memset(pOut2, 0, outimg2); | |
| 617 | + | ||
| 618 | + | while (!file.eof()) | |
| 619 | + | { | |
| 620 | + | len = read32(file); | |
| 621 | + | chunk = read32(file); | |
| 622 | + | ||
| 623 | + | if (chunk == 0x504C5445) /* PLTE */ | |
| 624 | + | { | |
| 625 | + | unsigned int col; | |
| 626 | + | for (i=0; i<len; i++) | |
| 627 | + | { | |
| 628 | + | file.read((char*)&c, 1); | |
| 629 | + | col = i/3; | |
| 630 | + | if (col<256) | |
| 631 | + | { | |
| 632 | + | pal[col][i%3] = c; | |
| 633 | + | palsize = col+1; | |
| 634 | + | } | |
| 635 | + | } | |
| 636 | + | /*crc = */read32(file); | |
| 637 | + | } | |
| 638 | + | else if (chunk == 0x74524E53) /* tRNS */ | |
| 639 | + | { | |
| 640 | + | hasTRNS = 1; | |
| 641 | + | for (i=0; i<len; i++) | |
| 642 | + | { | |
| 643 | + | file.read((char*)&c, 1); | |
| 644 | + | if (i<256) | |
| 645 | + | { | |
| 646 | + | trns[i] = c; | |
| 647 | + | trnssize = i+1; | |
| 648 | + | if (c == 0 && coltype == 3 && trns_idx == -1) | |
| 649 | + | trns_idx = i; | |
| 650 | + | } | |
| 651 | + | } | |
| 652 | + | if (coltype == 0) | |
| 653 | + | { | |
| 654 | + | trns1 = readshort(&trns[0]); | |
| 655 | + | if (depth == 16) | |
| 656 | + | { | |
| 657 | + | trns[1] = trns[0]; trns[0] = 0; | |
| 658 | + | } | |
| 659 | + | } | |
| 660 | + | else | |
| 661 | + | if (coltype == 2) | |
| 662 | + | { | |
| 663 | + | trns1 = readshort(&trns[0]); | |
| 664 | + | trns2 = readshort(&trns[2]); | |
| 665 | + | trns3 = readshort(&trns[4]); | |
| 666 | + | if (depth == 16) | |
| 667 | + | { | |
| 668 | + | trns[1] = trns[0]; trns[0] = 0; | |
| 669 | + | trns[3] = trns[2]; trns[2] = 0; | |
| 670 | + | trns[5] = trns[4]; trns[4] = 0; | |
| 671 | + | } | |
| 672 | + | } | |
| 673 | + | /*crc = */read32(file); | |
| 674 | + | } | |
| 675 | + | else if (chunk == 0x6163544C) /* acTL */ | |
| 676 | + | { | |
| 677 | + | frames = read32(file); | |
| 678 | + | if(frames_delay) | |
| 679 | + | free(frames_delay); | |
| 680 | + | frames_delay = (unsigned short*)malloc(frames*sizeof(int)); | |
| 681 | + | loops = read32(file); | |
| 682 | + | /*crc = */read32(file); | |
| 683 | + | if (pOut1) | |
| 684 | + | free(pOut1); | |
| 685 | + | if (pOut2) | |
| 686 | + | free(pOut2); | |
| 687 | + | pOut1 = (unsigned char *)malloc((frames+1)*outimg1); | |
| 688 | + | pOut2 = (unsigned char *)malloc((frames+1)*outimg2); | |
| 689 | + | pImg1 = pOut1; | |
| 690 | + | pImg2 = pOut2; | |
| 691 | + | memset(pOut1, 0, outimg1); | |
| 692 | + | memset(pOut2, 0, outimg2); | |
| 693 | + | } | |
| 694 | + | else if (chunk == 0x6663544C) /* fcTL */ | |
| 695 | + | { | |
| 696 | + | if (zsize == 0) | |
| 697 | + | first_frame = 1; | |
| 698 | + | else | |
| 699 | + | { | |
| 700 | + | if (dop == PNG_DISPOSE_OP_PREVIOUS) | |
| 701 | + | { | |
| 702 | + | if (coltype != 6) | |
| 703 | + | memcpy(pImg1 + outimg1, pImg1, outimg1); | |
| 704 | + | if (coltype != 4) | |
| 705 | + | memcpy(pImg2 + outimg2, pImg2, outimg2); | |
| 706 | + | } | |
| 707 | + | ||
| 708 | + | pDst1 = pImg1 + y0*outrow1 + x0*channels; | |
| 709 | + | pDst2 = pImg2 + y0*outrow2 + x0*4; | |
| 710 | + | unpack(zstream, pTemp, imagesize, pData, zsize, h0, rowbytes, bpp); | |
| 711 | + | switch (coltype) | |
| 712 | + | { | |
| 713 | + | case 0: compose0(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 714 | + | case 2: compose2(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 715 | + | case 3: compose3(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 716 | + | case 4: compose4(pDst1, outrow1, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 717 | + | case 6: compose6( pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 718 | + | } | |
| 719 | + | zsize = 0; | |
| 720 | + | ||
| 721 | + | if (dop != PNG_DISPOSE_OP_PREVIOUS) | |
| 722 | + | { | |
| 723 | + | if (coltype != 6) | |
| 724 | + | memcpy(pImg1 + outimg1, pImg1, outimg1); | |
| 725 | + | if (coltype != 4) | |
| 726 | + | memcpy(pImg2 + outimg2, pImg2, outimg2); | |
| 727 | + | ||
| 728 | + | if (dop == PNG_DISPOSE_OP_BACKGROUND) | |
| 729 | + | { | |
| 730 | + | pDst1 += outimg1; | |
| 731 | + | pDst2 += outimg2; | |
| 732 | + | ||
| 733 | + | for (j=0; j<h0; j++) | |
| 734 | + | { | |
| 735 | + | switch (coltype) | |
| 736 | + | { | |
| 737 | + | case 0: memset(pDst2, 0, w0*4); if (hasTRNS) memset(pDst1, trns[1], w0); else keep_original = 0; break; | |
| 738 | + | case 2: memset(pDst2, 0, w0*4); if (hasTRNS) for (i=0; i<w0; i++) { pDst1[i*3] = trns[1]; pDst1[i*3+1] = trns[3]; pDst1[i*3+2] = trns[5]; } else keep_original = 0; break; | |
| 739 | + | case 3: memset(pDst2, 0, w0*4); if (trns_idx >= 0) memset(pDst1, trns_idx, w0); else keep_original = 0; break; | |
| 740 | + | case 4: memset(pDst1, 0, w0*2); break; | |
| 741 | + | case 6: memset(pDst2, 0, w0*4); break; | |
| 742 | + | } | |
| 743 | + | pDst1 += outrow1; | |
| 744 | + | pDst2 += outrow2; | |
| 745 | + | } | |
| 746 | + | } | |
| 747 | + | } | |
| 748 | + | } | |
| 749 | + | ||
| 750 | + | /*seq = */read32(file); | |
| 751 | + | w0 = read32(file); | |
| 752 | + | h0 = read32(file); | |
| 753 | + | x0 = read32(file); | |
| 754 | + | y0 = read32(file); | |
| 755 | + | d1 = read16(file); | |
| 756 | + | d2 = read16(file); | |
| 757 | + | file.read((char*)&dop, 1); | |
| 758 | + | file.read((char*)&bop, 1); | |
| 759 | + | /*crc = */read32(file); | |
| 760 | + | ||
| 761 | + | if(d2 == 0) | |
| 762 | + | d2 = 100; | |
| 763 | + | frames_delay[cur_frame] = (d1 * 1000)/d2; | |
| 764 | + | ||
| 765 | + | if (cur_frame == 0) | |
| 766 | + | { | |
| 767 | + | bop = PNG_BLEND_OP_SOURCE; | |
| 768 | + | if (dop == PNG_DISPOSE_OP_PREVIOUS) | |
| 769 | + | dop = PNG_DISPOSE_OP_BACKGROUND; | |
| 770 | + | } | |
| 771 | + | ||
| 772 | + | if (!(coltype & 4) && !(hasTRNS)) | |
| 773 | + | bop = PNG_BLEND_OP_SOURCE; | |
| 774 | + | ||
| 775 | + | rowbytes = ROWBYTES(pixeldepth, w0); | |
| 776 | + | cur_frame++; | |
| 777 | + | pImg1 += outimg1; | |
| 778 | + | pImg2 += outimg2; | |
| 779 | + | } | |
| 780 | + | else if (chunk == 0x49444154) /* IDAT */ | |
| 781 | + | { | |
| 782 | + | file.read((char*)(pData + zsize), len); | |
| 783 | + | zsize += len; | |
| 784 | + | /*crc = */read32(file); | |
| 785 | + | } | |
| 786 | + | else if (chunk == 0x66644154) /* fdAT */ | |
| 787 | + | { | |
| 788 | + | /*seq = */read32(file); | |
| 789 | + | len -= 4; | |
| 790 | + | file.read((char*)(pData + zsize), len); | |
| 791 | + | zsize += len; | |
| 792 | + | /*crc = */read32(file); | |
| 793 | + | } | |
| 794 | + | else if (chunk == 0x49454E44) /* IEND */ | |
| 795 | + | { | |
| 796 | + | pDst1 = pImg1 + y0*outrow1 + x0*channels; | |
| 797 | + | pDst2 = pImg2 + y0*outrow2 + x0*4; | |
| 798 | + | unpack(zstream, pTemp, imagesize, pData, zsize, h0, rowbytes, bpp); | |
| 799 | + | switch (coltype) | |
| 800 | + | { | |
| 801 | + | case 0: compose0(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 802 | + | case 2: compose2(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 803 | + | case 3: compose3(pDst1, outrow1, pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 804 | + | case 4: compose4(pDst1, outrow1, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 805 | + | case 6: compose6( pDst2, outrow2, pTemp, rowbytes+1, w0, h0, bop, depth); break; | |
| 806 | + | } | |
| 807 | + | break; | |
| 808 | + | } | |
| 809 | + | else | |
| 810 | + | { | |
| 811 | + | c = (unsigned char)(chunk>>24); | |
| 812 | + | if (notabc(c)) break; | |
| 813 | + | c = (unsigned char)((chunk>>16) & 0xFF); | |
| 814 | + | if (notabc(c)) break; | |
| 815 | + | c = (unsigned char)((chunk>>8) & 0xFF); | |
| 816 | + | if (notabc(c)) break; | |
| 817 | + | c = (unsigned char)(chunk & 0xFF); | |
| 818 | + | if (notabc(c)) break; | |
| 819 | + | ||
| 820 | + | file.seekg(len, std::ios_base::cur); | |
| 821 | + | /*crc = */read32(file); | |
| 822 | + | } | |
| 823 | + | } | |
| 824 | + | /* apng decoding - end */ | |
| 825 | + | ||
| 826 | + | if (coltype == 0) | |
| 827 | + | { | |
| 828 | + | switch (depth) | |
| 829 | + | { | |
| 830 | + | case 4: trns[1] *= 0x11; break; | |
| 831 | + | case 2: trns[1] *= 0x55; break; | |
| 832 | + | case 1: trns[1] *= 0xFF; break; | |
| 833 | + | } | |
| 834 | + | } | |
| 835 | + | ||
| 836 | + | inflateEnd(&zstream); | |
| 837 | + | ||
| 838 | + | apng->bpp = channels; | |
| 839 | + | apng->coltype = coltype; | |
| 840 | + | apng->last_frame = cur_frame; | |
| 841 | + | apng->first_frame = first_frame; | |
| 842 | + | apng->height = h; | |
| 843 | + | apng->width = w; | |
| 844 | + | apng->num_frames = frames; | |
| 845 | + | apng->num_plays = loops; | |
| 846 | + | apng->frames_delay = frames_delay; | |
| 847 | + | apng->pdata = pOut2; | |
| 848 | + | apng->bpp = 4; | |
| 849 | + | apng->coltype = 6; | |
| 850 | + | ||
| 851 | + | if(pData) | |
| 852 | + | free(pData); | |
| 853 | + | if(pTemp) | |
| 854 | + | free(pTemp); | |
| 855 | + | if(pOut1) | |
| 856 | + | free(pOut1); | |
| 857 | + | } else | |
| 858 | + | return -1; | |
| 859 | + | } else | |
| 860 | + | return -1; | |
| 861 | + | ||
| 862 | + | return 0; | |
| 863 | + | } | |
| 864 | + | ||
| 865 | + | void write_chunk(std::ostream& f, const char* name, unsigned char* data, unsigned int length) | |
| 866 | + | { | |
| 867 | + | unsigned int crc = crc32(0, Z_NULL, 0); | |
| 868 | + | unsigned int len = swap32(length); | |
| 869 | + | ||
| 870 | + | f.write((char*)&len, 4); | |
| 871 | + | f.write(name, 4); | |
| 872 | + | crc = crc32(crc, (const Bytef*)name, 4); | |
| 873 | + | ||
| 874 | + | if(data != NULL && length > 0) { | |
| 875 | + | f.write((char*)data, length); | |
| 876 | + | crc = crc32(crc, data, length); | |
| 877 | + | } | |
| 878 | + | ||
| 879 | + | crc = swap32(crc); | |
| 880 | + | f.write((char*)&crc, 4); | |
| 881 | + | } | |
| 882 | + | ||
| 883 | + | void write_IDATs(std::ostream& f, unsigned char* data, unsigned int length, unsigned int idat_size) | |
| 884 | + | { | |
| 885 | + | unsigned int z_cmf = data[0]; | |
| 886 | + | ||
| 887 | + | if((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) { | |
| 888 | + | if(length >= 2) { | |
| 889 | + | unsigned int z_cinfo = z_cmf >> 4; | |
| 890 | + | unsigned int half_z_window_size = 1 << (z_cinfo + 7); | |
| 891 | + | ||
| 892 | + | while(idat_size <= half_z_window_size && half_z_window_size >= 256) { | |
| 893 | + | z_cinfo--; | |
| 894 | + | half_z_window_size >>= 1; | |
| 895 | + | } | |
| 896 | + | ||
| 897 | + | z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); | |
| 898 | + | ||
| 899 | + | if(data[0] != (unsigned char)z_cmf) { | |
| 900 | + | data[0] = (unsigned char)z_cmf; | |
| 901 | + | data[1] &= 0xe0; | |
| 902 | + | data[1] += (unsigned char)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f); | |
| 903 | + | } | |
| 904 | + | } | |
| 905 | + | } | |
| 906 | + | ||
| 907 | + | while(length > 0) { | |
| 908 | + | unsigned int ds = length; | |
| 909 | + | ||
| 910 | + | if(ds > PNG_ZBUF_SIZE) | |
| 911 | + | ds = PNG_ZBUF_SIZE; | |
| 912 | + | ||
| 913 | + | write_chunk(f, "IDAT", data, ds); | |
| 914 | + | ||
| 915 | + | data += ds; | |
| 916 | + | length -= ds; | |
| 917 | + | } | |
| 918 | + | } | |
| 919 | + | ||
| 920 | + | void save_png(std::stringstream& f, unsigned int width, unsigned int height, int channels, unsigned char *pixels) | |
| 921 | + | { | |
| 922 | + | unsigned int bpp = 4; | |
| 923 | + | unsigned char coltype = 0; | |
| 924 | + | ||
| 925 | + | if(channels == 3) | |
| 926 | + | coltype = 2; | |
| 927 | + | else if (channels == 2) | |
| 928 | + | coltype = 4; | |
| 929 | + | else if (channels == 4) | |
| 930 | + | coltype = 6; | |
| 931 | + | ||
| 932 | + | struct IHDR { | |
| 933 | + | unsigned int mWidth; | |
| 934 | + | unsigned int mHeight; | |
| 935 | + | unsigned char mDepth; | |
| 936 | + | unsigned char mColorType; | |
| 937 | + | unsigned char mCompression; | |
| 938 | + | unsigned char mFilterMethod; | |
| 939 | + | unsigned char mInterlaceMethod; | |
| 940 | + | } ihdr = { swap32(width), swap32(height), 8, coltype, 0, 0, 0 }; | |
| 941 | + | ||
| 942 | + | z_stream zstream1; | |
| 943 | + | z_stream zstream2; | |
| 944 | + | unsigned int i, j; | |
| 945 | + | ||
| 946 | + | unsigned int rowbytes = width * bpp; | |
| 947 | + | unsigned int idat_size = (rowbytes + 1) * height; | |
| 948 | + | unsigned int zbuf_size = idat_size + ((idat_size + 7) >> 3) + ((idat_size + 63) >> 6) + 11; | |
| 949 | + | ||
| 950 | + | unsigned char* row_buf = (unsigned char*)malloc(rowbytes + 1); | |
| 951 | + | unsigned char* sub_row = (unsigned char*)malloc(rowbytes + 1); | |
| 952 | + | unsigned char* up_row = (unsigned char*)malloc(rowbytes + 1); | |
| 953 | + | unsigned char* avg_row = (unsigned char*)malloc(rowbytes + 1); | |
| 954 | + | unsigned char* paeth_row = (unsigned char*)malloc(rowbytes + 1); | |
| 955 | + | unsigned char* zbuf1 = (unsigned char*)malloc(zbuf_size); | |
| 956 | + | unsigned char* zbuf2 = (unsigned char*)malloc(zbuf_size); | |
| 957 | + | ||
| 958 | + | if(!row_buf || !sub_row || !up_row || !avg_row || !paeth_row || !zbuf1 || !zbuf2) | |
| 959 | + | return; | |
| 960 | + | ||
| 961 | + | row_buf[0] = 0; | |
| 962 | + | sub_row[0] = 1; | |
| 963 | + | up_row[0] = 2; | |
| 964 | + | avg_row[0] = 3; | |
| 965 | + | paeth_row[0] = 4; | |
| 966 | + | ||
| 967 | + | zstream1.data_type = Z_BINARY; | |
| 968 | + | zstream1.zalloc = Z_NULL; | |
| 969 | + | zstream1.zfree = Z_NULL; | |
| 970 | + | zstream1.opaque = Z_NULL; | |
| 971 | + | deflateInit2(&zstream1, 3, 8, 15, 8, Z_DEFAULT_STRATEGY); | |
| 972 | + | ||
| 973 | + | zstream2.data_type = Z_BINARY; | |
| 974 | + | zstream2.zalloc = Z_NULL; | |
| 975 | + | zstream2.zfree = Z_NULL; | |
| 976 | + | zstream2.opaque = Z_NULL; | |
| 977 | + | deflateInit2(&zstream2, 3, 8, 15, 8, Z_FILTERED); | |
| 978 | + | ||
| 979 | + | int a, b, c, pa, pb, pc, p, v; | |
| 980 | + | unsigned char* prev; | |
| 981 | + | unsigned char* row; | |
| 982 | + | ||
| 983 | + | f.write((char*)png_sign, 8); | |
| 984 | + | write_chunk(f, "IHDR", (unsigned char*)(&ihdr), 13); | |
| 985 | + | ||
| 986 | + | if(palsize > 0) | |
| 987 | + | write_chunk(f, "PLTE", (unsigned char*)(&pal), palsize * 3); | |
| 988 | + | ||
| 989 | + | zstream1.next_out = zbuf1; | |
| 990 | + | zstream1.avail_out = zbuf_size; | |
| 991 | + | zstream2.next_out = zbuf2; | |
| 992 | + | zstream2.avail_out = zbuf_size; | |
| 993 | + | ||
| 994 | + | prev = NULL; | |
| 995 | + | row = pixels; | |
| 996 | + | ||
| 997 | + | for(j = 0; j < (unsigned int)height; j++) { | |
| 998 | + | unsigned char* out; | |
| 999 | + | unsigned int sum = 0; | |
| 1000 | + | unsigned char* best_row = row_buf; | |
| 1001 | + | unsigned int mins = ((unsigned int)(-1)) >> 1; | |
| 1002 | + | ||
| 1003 | + | out = row_buf + 1; | |
| 1004 | + | ||
| 1005 | + | for(i = 0; i < rowbytes; i++) { | |
| 1006 | + | v = out[i] = row[i]; | |
| 1007 | + | sum += (v < 128) ? v : 256 - v; | |
| 1008 | + | } | |
| 1009 | + | ||
| 1010 | + | mins = sum; | |
| 1011 | + | ||
| 1012 | + | sum = 0; | |
| 1013 | + | out = sub_row + 1; | |
| 1014 | + | ||
| 1015 | + | for(i = 0; i < bpp; i++) { | |
| 1016 | + | v = out[i] = row[i]; | |
| 1017 | + | sum += (v < 128) ? v : 256 - v; | |
| 1018 | + | } | |
| 1019 | + | ||
| 1020 | + | for(i = bpp; i < rowbytes; i++) { | |
| 1021 | + | v = out[i] = row[i] - row[i - bpp]; | |
| 1022 | + | sum += (v < 128) ? v : 256 - v; | |
| 1023 | + | ||
| 1024 | + | if(sum > mins) break; | |
| 1025 | + | } | |
| 1026 | + | ||
| 1027 | + | if(sum < mins) { | |
| 1028 | + | mins = sum; | |
| 1029 | + | best_row = sub_row; | |
| 1030 | + | } | |
| 1031 | + | ||
| 1032 | + | if(prev) { | |
| 1033 | + | sum = 0; | |
| 1034 | + | out = up_row + 1; | |
| 1035 | + | ||
| 1036 | + | for(i = 0; i < rowbytes; i++) { | |
| 1037 | + | v = out[i] = row[i] - prev[i]; | |
| 1038 | + | sum += (v < 128) ? v : 256 - v; | |
| 1039 | + | ||
| 1040 | + | if(sum > mins) break; | |
| 1041 | + | } | |
| 1042 | + | ||
| 1043 | + | if(sum < mins) { | |
| 1044 | + | mins = sum; | |
| 1045 | + | best_row = up_row; | |
| 1046 | + | } | |
| 1047 | + | ||
| 1048 | + | sum = 0; | |
| 1049 | + | out = avg_row + 1; | |
| 1050 | + | ||
| 1051 | + | for(i = 0; i < bpp; i++) { | |
| 1052 | + | v = out[i] = row[i] - prev[i] / 2; | |
| 1053 | + | sum += (v < 128) ? v : 256 - v; | |
| 1054 | + | } | |
| 1055 | + | ||
| 1056 | + | for(i = bpp; i < rowbytes; i++) { | |
| 1057 | + | v = out[i] = row[i] - (prev[i] + row[i - bpp]) / 2; | |
| 1058 | + | sum += (v < 128) ? v : 256 - v; | |
| 1059 | + | ||
| 1060 | + | if(sum > mins) break; | |
| 1061 | + | } | |
| 1062 | + | ||
| 1063 | + | if(sum < mins) { | |
| 1064 | + | mins = sum; | |
| 1065 | + | best_row = avg_row; | |
| 1066 | + | } | |
| 1067 | + | ||
| 1068 | + | sum = 0; | |
| 1069 | + | out = paeth_row + 1; | |
| 1070 | + | ||
| 1071 | + | for(i = 0; i < bpp; i++) { | |
| 1072 | + | v = out[i] = row[i] - prev[i]; | |
| 1073 | + | sum += (v < 128) ? v : 256 - v; | |
| 1074 | + | } | |
| 1075 | + | ||
| 1076 | + | for(i = bpp; i < rowbytes; i++) { | |
| 1077 | + | a = row[i - bpp]; | |
| 1078 | + | b = prev[i]; | |
| 1079 | + | c = prev[i - bpp]; | |
| 1080 | + | p = b - c; | |
| 1081 | + | pc = a - c; | |
| 1082 | + | pa = abs(p); | |
| 1083 | + | pb = abs(pc); | |
| 1084 | + | pc = abs(p + pc); | |
| 1085 | + | p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; | |
| 1086 | + | v = out[i] = row[i] - p; | |
| 1087 | + | sum += (v < 128) ? v : 256 - v; | |
| 1088 | + | ||
| 1089 | + | if(sum > mins) break; | |
| 1090 | + | } | |
| 1091 | + | ||
| 1092 | + | if(sum < mins) { | |
| 1093 | + | best_row = paeth_row; | |
| 1094 | + | } | |
| 1095 | + | } | |
| 1096 | + | ||
| 1097 | + | zstream1.next_in = row_buf; | |
| 1098 | + | zstream1.avail_in = rowbytes + 1; | |
| 1099 | + | deflate(&zstream1, Z_NO_FLUSH); | |
| 1100 | + | ||
| 1101 | + | zstream2.next_in = best_row; | |
| 1102 | + | zstream2.avail_in = rowbytes + 1; | |
| 1103 | + | deflate(&zstream2, Z_NO_FLUSH); | |
| 1104 | + | ||
| 1105 | + | prev = row; | |
| 1106 | + | row += rowbytes; | |
| 1107 | + | } | |
| 1108 | + | ||
| 1109 | + | deflate(&zstream1, Z_FINISH); | |
| 1110 | + | deflate(&zstream2, Z_FINISH); | |
| 1111 | + | ||
| 1112 | + | if(zstream1.total_out <= zstream2.total_out) | |
| 1113 | + | write_IDATs(f, zbuf1, zstream1.total_out, idat_size); | |
| 1114 | + | else | |
| 1115 | + | write_IDATs(f, zbuf2, zstream2.total_out, idat_size); | |
| 1116 | + | ||
| 1117 | + | deflateReset(&zstream1); | |
| 1118 | + | zstream1.data_type = Z_BINARY; | |
| 1119 | + | deflateReset(&zstream2); | |
| 1120 | + | zstream2.data_type = Z_BINARY; | |
| 1121 | + | ||
| 1122 | + | write_chunk(f, "IEND", 0, 0); | |
| 1123 | + | ||
| 1124 | + | deflateEnd(&zstream1); | |
| 1125 | + | deflateEnd(&zstream2); | |
| 1126 | + | free(zbuf1); | |
| 1127 | + | free(zbuf2); | |
| 1128 | + | free(row_buf); | |
| 1129 | + | free(sub_row); | |
| 1130 | + | free(up_row); | |
| 1131 | + | free(avg_row); | |
| 1132 | + | free(paeth_row); | |
| 1133 | + | } | |
| 1134 | + | ||
| 1135 | + | void free_apng(struct apng_data *apng) | |
| 1136 | + | { | |
| 1137 | + | if(apng->pdata) | |
| 1138 | + | free(apng->pdata); | |
| 1139 | + | if(apng->frames_delay) | |
| 1140 | + | free(apng->frames_delay); | |
| 1141 | + | } | |
| 1142 | + |
| @@ -0,0 +1,46 @@ | |||
| 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 | + | #ifndef APNGLOADER_H | |
| 24 | + | #define APNGLOADER_H | |
| 25 | + | ||
| 26 | + | #include <sstream> | |
| 27 | + | ||
| 28 | + | struct apng_data { | |
| 29 | + | unsigned char *pdata; | |
| 30 | + | unsigned int width; | |
| 31 | + | unsigned int height; | |
| 32 | + | unsigned int first_frame; | |
| 33 | + | unsigned int last_frame; | |
| 34 | + | unsigned char bpp; | |
| 35 | + | unsigned char coltype; | |
| 36 | + | unsigned int num_frames; | |
| 37 | + | unsigned int num_plays; | |
| 38 | + | unsigned short *frames_delay; // each frame delay in ms | |
| 39 | + | }; | |
| 40 | + | ||
| 41 | + | // returns -1 on error, 0 on success | |
| 42 | + | int load_apng(std::stringstream& file, struct apng_data *apng); | |
| 43 | + | void save_png(std::stringstream& file, unsigned int width, unsigned int height, int channels, unsigned char *pixels); | |
| 44 | + | void free_apng(struct apng_data *apng); | |
| 45 | + | ||
| 46 | + | #endif |