From d889d872b38beb8b64c28e1b8765c268fc4c3bdc Mon Sep 17 00:00:00 2001 From: ASpoonPlaysGames <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:14:16 +0000 Subject: [PATCH 1/5] extract extractFile into a lambda --- primedev/mods/autodownload/moddownloader.cpp | 219 ++++++++++--------- 1 file changed, 114 insertions(+), 105 deletions(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index 2b79fcbd3..26d53a3ca 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -451,134 +451,142 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif modState.total = GetModArchiveSize(file, gi); modState.progress = 0; - // We don't know how to extract mods from unknown platforms - if (platform == VerifiedModPlatform::Unknown) - { - spdlog::error("Failed extracting mod from unknown platform."); - modState.state = UNKNOWN_PLATFORM; - return; - } - - for (int i = 0; i < gi.number_entry; i++) + auto extractFile = + [&](fs::path fileDestination, char* zipFilename) -> bool { - char zipFilename[256]; - unz_file_info64 fileInfo; - status = unzGetCurrentFileInfo64(file, &fileInfo, zipFilename, sizeof(zipFilename), NULL, 0, NULL, 0); + std::error_code ec; + spdlog::info("=> {}", fileDestination.generic_string()); - // Extract file + // Create parent directory if needed + if (!std::filesystem::exists(fileDestination.parent_path())) { - std::error_code ec; - fs::path fileDestination = destinationPath / zipFilename; - spdlog::info("=> {}", fileDestination.generic_string()); - - // Create parent directory if needed - if (!std::filesystem::exists(fileDestination.parent_path())) + spdlog::info("Parent directory does not exist, creating it.", fileDestination.generic_string()); + if (!std::filesystem::create_directories(fileDestination.parent_path(), ec) && ec.value() != 0) { - spdlog::info("Parent directory does not exist, creating it.", fileDestination.generic_string()); - if (!std::filesystem::create_directories(fileDestination.parent_path(), ec) && ec.value() != 0) - { - spdlog::error("Parent directory ({}) creation failed.", fileDestination.parent_path().generic_string()); - modState.state = FAILED_WRITING_TO_DISK; - return; - } + spdlog::error("Parent directory ({}) creation failed.", fileDestination.parent_path().generic_string()); + modState.state = FAILED_WRITING_TO_DISK; + return false; } + } - // If current file is a directory, create directory... - if (fileDestination.generic_string().back() == '/') + // If current file is a directory, create directory... + if (fileDestination.generic_string().back() == '/') + { + // Create directory + if (!std::filesystem::create_directory(fileDestination, ec) && ec.value() != 0) { - // Create directory - if (!std::filesystem::create_directory(fileDestination, ec) && ec.value() != 0) - { - spdlog::error("Directory creation failed: {}", ec.message()); - modState.state = FAILED_WRITING_TO_DISK; - return; - } + spdlog::error("Directory creation failed: {}", ec.message()); + modState.state = FAILED_WRITING_TO_DISK; + return false; } - // ...else create file - else + } + // ...else create file + else + { + // Ensure file is in zip archive + if (unzLocateFile(file, zipFilename, 0) != UNZ_OK) { - // Ensure file is in zip archive - if (unzLocateFile(file, zipFilename, 0) != UNZ_OK) - { - spdlog::error("File \"{}\" was not found in archive.", zipFilename); - modState.state = FAILED_READING_ARCHIVE; - return; - } + spdlog::error("File \"{}\" was not found in archive.", zipFilename); + modState.state = FAILED_READING_ARCHIVE; + return false; + } - // Create file - const int bufferSize = 8192; - void* buffer; - int err = UNZ_OK; - FILE* fout = NULL; + // Create file + const int bufferSize = 8192; + void* buffer; + int err = UNZ_OK; + FILE* fout = NULL; - // Open zip file to prepare its extraction - status = unzOpenCurrentFile(file); - if (status != UNZ_OK) - { - spdlog::error("Could not open file {} from archive.", zipFilename); - modState.state = FAILED_READING_ARCHIVE; - return; - } + // Open zip file to prepare its extraction + status = unzOpenCurrentFile(file); + if (status != UNZ_OK) + { + spdlog::error("Could not open file {} from archive.", zipFilename); + modState.state = FAILED_READING_ARCHIVE; + return false; + } - // Create destination file - fout = fopen(fileDestination.generic_string().c_str(), "wb"); - if (fout == NULL) - { - spdlog::error("Failed creating destination file."); - modState.state = FAILED_WRITING_TO_DISK; - return; - } + // Create destination file + fout = fopen(fileDestination.generic_string().c_str(), "wb"); + if (fout == NULL) + { + spdlog::error("Failed creating destination file."); + modState.state = FAILED_WRITING_TO_DISK; + return false; + } + + // Allocate memory for buffer + buffer = (void*)malloc(bufferSize); + if (buffer == NULL) + { + spdlog::error("Error while allocating memory."); + modState.state = FAILED_WRITING_TO_DISK; + return false; + } - // Allocate memory for buffer - buffer = (void*)malloc(bufferSize); - if (buffer == NULL) + // Extract file to destination + do + { + err = unzReadCurrentFile(file, buffer, bufferSize); + if (err < 0) { - spdlog::error("Error while allocating memory."); - modState.state = FAILED_WRITING_TO_DISK; - return; + spdlog::error("error {} with zipfile in unzReadCurrentFile", err); + break; } - - // Extract file to destination - do + if (err > 0) { - err = unzReadCurrentFile(file, buffer, bufferSize); - if (err < 0) + if (fwrite(buffer, (unsigned)err, 1, fout) != 1) { - spdlog::error("error {} with zipfile in unzReadCurrentFile", err); + spdlog::error("error in writing extracted file\n"); + err = UNZ_ERRNO; break; } - if (err > 0) - { - if (fwrite(buffer, (unsigned)err, 1, fout) != 1) - { - spdlog::error("error in writing extracted file\n"); - err = UNZ_ERRNO; - break; - } - } - - // Update extraction stats - modState.progress += bufferSize; - modState.ratio = roundf(static_cast(modState.progress) / modState.total * 100); - } while (err > 0); - - if (err != UNZ_OK) - { - spdlog::error("An error occurred during file extraction (code: {})", err); - modState.state = FAILED_WRITING_TO_DISK; - return; - } - err = unzCloseCurrentFile(file); - if (err != UNZ_OK) - { - spdlog::error("error {} with zipfile in unzCloseCurrentFile", err); } - // Cleanup - if (fout) - fclose(fout); + // Update extraction stats + modState.progress += bufferSize; + modState.ratio = roundf(static_cast(modState.progress) / modState.total * 100); + } while (err > 0); + + if (err != UNZ_OK) + { + spdlog::error("An error occurred during file extraction (code: {})", err); + modState.state = FAILED_WRITING_TO_DISK; + return false; + } + err = unzCloseCurrentFile(file); + if (err != UNZ_OK) + { + spdlog::error("error {} with zipfile in unzCloseCurrentFile", err); } + + // Cleanup + if (fout) + fclose(fout); + + return true; } + }; + + // We don't know how to extract mods from unknown platforms + if (platform == VerifiedModPlatform::Unknown) + { + spdlog::error("Failed extracting mod from unknown platform."); + modState.state = UNKNOWN_PLATFORM; + return; + } + + for (int i = 0; i < gi.number_entry; i++) + { + char zipFilename[256]; + unz_file_info64 fileInfo; + status = unzGetCurrentFileInfo64(file, &fileInfo, zipFilename, sizeof(zipFilename), NULL, 0, NULL, 0); + + // Extract file + fs::path fileDestination = destinationPath / zipFilename; + + if (!extractFile(fileDestination, zipFilename)) + return; // Abort mod extraction if needed if (modState.state == ABORTED) @@ -599,6 +607,7 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif } } + // Mod extraction went fine modState.state = DONE; } From daeb1375ba8d67eb10b80b36e18c1bca711e2746 Mon Sep 17 00:00:00 2001 From: ASpoonPlaysGames <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:14:49 +0000 Subject: [PATCH 2/5] use wider int type to avoid potential overflow --- primedev/mods/autodownload/moddownloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index 26d53a3ca..189d3623b 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -576,7 +576,7 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif return; } - for (int i = 0; i < gi.number_entry; i++) + for (uint64_t i = 0; i < gi.number_entry; i++) { char zipFilename[256]; unz_file_info64 fileInfo; From 034b4a761d5524489bb53e72a34252c78952157a Mon Sep 17 00:00:00 2001 From: ASpoonPlaysGames <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 3 Jan 2026 02:23:18 +0000 Subject: [PATCH 3/5] make MWS downloads more robust --- primedev/mods/autodownload/moddownloader.cpp | 76 ++++++++++++++++---- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index 189d3623b..1e7a18d53 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -451,8 +451,8 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif modState.total = GetModArchiveSize(file, gi); modState.progress = 0; - auto extractFile = - [&](fs::path fileDestination, char* zipFilename) -> bool + // extracts the file in the archive at zipFilename to fileDestination on disk + auto extractFile = [&](fs::path fileDestination, char* zipFilename) -> bool { std::error_code ec; spdlog::info("=> {}", fileDestination.generic_string()); @@ -564,10 +564,14 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif if (fout) fclose(fout); - return true; } + + return true; }; + // the folder that contains the mod.json. all other files are considered relative to this + fs::path rootDir = ""; + // We don't know how to extract mods from unknown platforms if (platform == VerifiedModPlatform::Unknown) { @@ -575,6 +579,43 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif modState.state = UNKNOWN_PLATFORM; return; } + else if (platform == VerifiedModPlatform::ModWorkshop) + { + // find the mod.json and store the folder that it's in as the root directory + for (uint64_t i = 0; i < gi.number_entry; ++i) + { + char zipFilename[256]; + unz_file_info64 fileInfo; + status = unzGetCurrentFileInfo64(file, &fileInfo, zipFilename, sizeof(zipFilename), NULL, 0, NULL, 0); + fs::path filePath = zipFilename; + + if (filePath.has_filename() && filePath.filename() == "mod.json") + { + fs::path parentPath = modPath.filename(); + if (filePath.has_parent_path()) + rootDir = filePath.parent_path() / ""; + + break; + } + + if ((i + 1) < gi.number_entry) + { + status = unzGoToNextFile(file); + if (status != UNZ_OK) + { + spdlog::error("Error while browsing archive files (error code: {}).", status); + return; + } + } + } + + status = unzGoToFirstFile(file); + if (status != UNZ_OK) + { + spdlog::error("Error while browsing archive files (error code: {}).", status); + return; + } + } for (uint64_t i = 0; i < gi.number_entry; i++) { @@ -582,17 +623,24 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif unz_file_info64 fileInfo; status = unzGetCurrentFileInfo64(file, &fileInfo, zipFilename, sizeof(zipFilename), NULL, 0, NULL, 0); - // Extract file - fs::path fileDestination = destinationPath / zipFilename; + // Get the destination path, correcting for rootDir + fs::path zipFilePath = zipFilename; + fs::path relativePath = zipFilePath.lexically_relative(rootDir); + // don't try to do anything with our root directory + if (zipFilePath.compare(rootDir)) + { + fs::path fileDestination = destinationPath / relativePath; - if (!extractFile(fileDestination, zipFilename)) - return; + // Extract file + if (!extractFile(fileDestination, zipFilename)) + return; - // Abort mod extraction if needed - if (modState.state == ABORTED) - { - spdlog::info("User cancelled mod installation, aborting mod extraction."); - return; + // Abort mod extraction if needed + if (modState.state == ABORTED) + { + spdlog::info("User cancelled mod installation, aborting mod extraction."); + return; + } } // Go to next file @@ -607,7 +655,6 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif } } - // Mod extraction went fine modState.state = DONE; } @@ -690,7 +737,8 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) /// Don't use archive name as destination with ModWorkshop if (fullVersion.platform == VerifiedModPlatform::ModWorkshop) { - modDirectory = GetRemoteModFolderPath(); + name = archiveLocation.stem().string(); + modDirectory = GetRemoteModFolderPath() / std::format("{}-{}", name, modVersion); } else /// Removes the ".zip" fom the archive name and use it as parent directory From 2ecff97004f27603c3e3e5e18190843ea192a0ca Mon Sep 17 00:00:00 2001 From: ASpoonPlaysGames <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 3 Jan 2026 02:25:33 +0000 Subject: [PATCH 4/5] more aggressively move back to the first file --- primedev/mods/autodownload/moddownloader.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index 1e7a18d53..f5a013a81 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -582,6 +582,7 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif else if (platform == VerifiedModPlatform::ModWorkshop) { // find the mod.json and store the folder that it's in as the root directory + unzGoToFirstFile(file); for (uint64_t i = 0; i < gi.number_entry; ++i) { char zipFilename[256]; @@ -608,15 +609,9 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif } } } - - status = unzGoToFirstFile(file); - if (status != UNZ_OK) - { - spdlog::error("Error while browsing archive files (error code: {}).", status); - return; - } } + unzGoToFirstFile(file); for (uint64_t i = 0; i < gi.number_entry; i++) { char zipFilename[256]; From 663398a3e2d6fb2253aa60de08ac458899be5249 Mon Sep 17 00:00:00 2001 From: ASpoonPlaysGames <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sun, 4 Jan 2026 17:24:53 +0000 Subject: [PATCH 5/5] formatting --- primedev/mods/autodownload/moddownloader.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index f5a013a81..ed3ae7c83 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -563,7 +563,6 @@ void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, Verif // Cleanup if (fout) fclose(fout); - } return true;