| 3459 | } |
| 3460 | |
| 3461 | static void SyncLuaFiles() { |
| 3462 | if (!CloudStorage::IsCloudActive()) { |
| 3463 | LOG("[LuaSync] Cloud not active, skipping lua sync"); |
| 3464 | return; |
| 3465 | } |
| 3466 | uint32_t accountId = GetAccountId(); |
| 3467 | if (!accountId) { |
| 3468 | LOG("[LuaSync] No account ID yet, skipping lua sync"); |
| 3469 | return; |
| 3470 | } |
| 3471 | |
| 3472 | std::string luaDir = g_steamPath + "config\\stplug-in\\"; |
| 3473 | uint64_t now = NowUnix(); |
| 3474 | |
| 3475 | auto manifestData = CloudStorage::RetrieveBlob(accountId, LUA_SYNC_APPID, "LuaManifest.json"); |
| 3476 | auto cloudManifest = ParseManifest(manifestData); |
| 3477 | |
| 3478 | bool hasAlive = false; |
| 3479 | for (auto& [f, e] : cloudManifest) { if (!e.isDeleted()) { hasAlive = true; break; } } |
| 3480 | std::vector<uint8_t> archiveData; |
| 3481 | if (hasAlive) |
| 3482 | archiveData = CloudStorage::RetrieveBlob(accountId, LUA_SYNC_APPID, "LuaArchive.zip"); |
| 3483 | |
| 3484 | // Parse archive into a map |
| 3485 | std::unordered_map<std::string, std::vector<uint8_t>> cloudFiles; |
| 3486 | if (!archiveData.empty()) { |
| 3487 | mz_zip_archive zip{}; |
| 3488 | if (mz_zip_reader_init_mem(&zip, archiveData.data(), archiveData.size(), 0)) { |
| 3489 | mz_uint numFiles = mz_zip_reader_get_num_files(&zip); |
| 3490 | constexpr mz_uint MAX_ZIP_ENTRIES = 10000; |
| 3491 | if (numFiles > MAX_ZIP_ENTRIES) { |
| 3492 | LOG("[LuaSync] Archive has too many entries (%u), skipping", numFiles); |
| 3493 | mz_zip_reader_end(&zip); |
| 3494 | } else { |
| 3495 | size_t totalExtracted = 0; |
| 3496 | constexpr size_t MAX_TOTAL_SIZE = 100 * 1024 * 1024; // 100 MB aggregate |
| 3497 | for (mz_uint i = 0; i < numFiles; i++) { |
| 3498 | char fname[256]; |
| 3499 | // Probe true name size first; miniz's silent post-clamp truncation could chop "../" and slip past IsValidLuaFilename. |
| 3500 | mz_uint nameLenPlusNul = mz_zip_reader_get_filename(&zip, i, nullptr, 0); |
| 3501 | if (nameLenPlusNul == 0) { |
| 3502 | LOG("[LuaSync] Skipping zip entry %u: corrupt or missing CDH", i); |
| 3503 | continue; |
| 3504 | } |
| 3505 | if (nameLenPlusNul > sizeof(fname)) { |
| 3506 | LOG("[LuaSync] Skipping zip entry %u: overlength filename (%u bytes)", |
| 3507 | i, nameLenPlusNul); |
| 3508 | continue; |
| 3509 | } |
| 3510 | mz_zip_reader_get_filename(&zip, i, fname, sizeof(fname)); |
| 3511 | if (!IsValidLuaFilename(fname)) { |
| 3512 | LOG("[LuaSync] Skipping invalid zip entry: %s", fname); |
| 3513 | continue; |
| 3514 | } |
| 3515 | // Check declared size before allocating |
| 3516 | mz_zip_archive_file_stat fstat; |
| 3517 | if (!mz_zip_reader_file_stat(&zip, i, &fstat)) continue; |
| 3518 | constexpr size_t MAX_LUA_SIZE = 10 * 1024 * 1024; // 10 MB |
no test coverage detected