#include "extractor.hpp" #include #include #include #ifdef __SWITCH__ #include #endif static constexpr size_t READ_BUF_SIZE = 8192; int PatchExtractor::mkdirs(const char* path) { char tmp[512]; snprintf(tmp, sizeof(tmp), "%s", path); size_t len = strlen(tmp); if (len == 0) return 0; if (tmp[len - 1] == '/') tmp[len - 1] = '\0'; for (char* p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; mkdir(tmp, 0755); *p = '/'; } } return mkdir(tmp, 0755); } bool PatchExtractor::open() { zip = unzOpen(PATCHES_ZIP); if (!zip) return false; unz_global_info gi{}; if (unzGetGlobalInfo(zip, &gi) != UNZ_OK) { close(); return false; } total = gi.number_entry; extracted = 0; skipped = 0; finished = false; err = unzGoToFirstFile(zip); return true; } void PatchExtractor::close() { if (zip) { unzClose(zip); zip = nullptr; } } int PatchExtractor::getProgressPercent() const { if (total == 0) return 100; return static_cast((extracted * 100) / total); } bool PatchExtractor::step() { if (!zip || finished) return false; if (err != UNZ_OK) { finished = true; return false; } char filename[512]; char fullpath[1024]; unz_file_info fi{}; unzGetCurrentFileInfo(zip, &fi, filename, sizeof(filename), nullptr, 0, nullptr, 0); snprintf(fullpath, sizeof(fullpath), "%s%s", EXTRACT_DIR, filename); currentFile = filename; size_t flen = strlen(filename); if (flen > 0 && filename[flen - 1] == '/') { mkdirs(fullpath); } else { char dirpart[1024]; snprintf(dirpart, sizeof(dirpart), "%s", fullpath); char* last_slash = strrchr(dirpart, '/'); if (last_slash) { *last_slash = '\0'; mkdirs(dirpart); } if (unzOpenCurrentFile(zip) == UNZ_OK) { FILE* out = fopen(fullpath, "wb"); if (out) { unsigned char buf[READ_BUF_SIZE]; int bytes; while ((bytes = unzReadCurrentFile(zip, buf, READ_BUF_SIZE)) > 0) { fwrite(buf, 1, static_cast(bytes), out); } fclose(out); } else { skipped++; } unzCloseCurrentFile(zip); } else { skipped++; } } extracted++; err = unzGoToNextFile(zip); if (err != UNZ_OK) finished = true; return !finished; } bool PatchExtractor::cleanup() { cleanupOk_ = true; if (remove(PATCHES_ZIP) != 0) cleanupOk_ = false; // Cannot delete our own NRO while the app is still running; try anyway for edge cases. remove(SELF_NRO); remove("sdmc:/switch/.PatchExtractor.nro.star"); remove("sdmc:/switch/.packages/boot_package.ini"); return cleanupOk_; } void PatchExtractor::tryDeleteSelfOnExit() { #ifdef __SWITCH__ // Embedded ROMFS keeps the .nro open on SD; unmount before unlink (console build had no ROMFS). romfsExit(); #endif remove(SELF_NRO); }