Mod download improvements (#162)

This PR adds multiple features relating to mod downloads.
1. With this PR the launcher will convert old style mods (without hash)
to new style mods (with hash). This is so people don't have to
re-download all of their mods after joining a server which was
previously on a version below 3.6.0
2. This PR will save the last used date of mods in a JSON file in the
caching directory. This will allow for smart deletion of cached mods

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
This commit is contained in:
Tixx 2025-01-18 22:56:33 +01:00 committed by GitHub
commit 8373a70c4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -382,7 +382,7 @@ struct ModInfo {
}
} catch (const std::exception& e) {
debug(std::string("Failed to receive mod list: ") + e.what());
warn("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
debug("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
}
return std::make_pair(success, modInfos);
}
@ -392,6 +392,52 @@ struct ModInfo {
std::string HashAlgorithm;
};
nlohmann::json modUsage = {};
void UpdateModUsage(const std::string& fileName) {
try {
fs::path usageFile = fs::path(CachingDirectory) / "mods.json";
if (!fs::exists(usageFile)) {
if (std::ofstream file(usageFile); !file.is_open()) {
error("Failed to create mods.json");
return;
} else {
file.close();
}
}
std::fstream file(usageFile, std::ios::in | std::ios::out);
if (!file.is_open()) {
error("Failed to open or create mods.json");
return;
}
if (modUsage.empty()) {
auto Size = fs::file_size(fs::path(CachingDirectory) / "mods.json");
std::string modsJson(Size, 0);
file.read(&modsJson[0], Size);
if (!modsJson.empty()) {
auto parsedModJson = nlohmann::json::parse(modsJson, nullptr, false);
if (parsedModJson.is_object())
modUsage = parsedModJson;
}
}
modUsage[fileName] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
file.clear();
file.seekp(0, std::ios::beg);
file << modUsage.dump();
file.close();
} catch (std::exception& e) {
error("Failed to update mods.json: " + std::string(e.what()));
}
}
void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<ModInfo> ModInfos) {
if (ModInfos.empty()) {
CoreSend("L");
@ -451,6 +497,42 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
UpdateModUsage(FileName);
} catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
continue;
}
WaitForConfirm();
continue;
} else if (auto OldCachedPath = fs::path(CachingDirectory) / std::filesystem::path(ModInfoIter->FileName).filename();
fs::exists(OldCachedPath) && GetSha256HashReallyFast(OldCachedPath.string()) == ModInfoIter->Hash) {
debug("Mod '" + FileName + "' found in old cache, copying it to the new cache");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
try {
fs::copy_file(OldCachedPath, PathToSaveTo, fs::copy_options::overwrite_existing);
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
auto modname = ModInfoIter->FileName;
#if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) {
c = ::tolower(c);
}
#endif
debug("Mod name: " + modname);
auto name = std::filesystem::path(GetGamePath()) / "mods/multiplayer" / modname;
std::string tmp_name = name.string();
tmp_name += ".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
UpdateModUsage(FileName);
} catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
@ -505,6 +587,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
#endif
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
UpdateModUsage(FName);
}
WaitForConfirm();
++ModNo;
@ -606,6 +689,7 @@ void SyncResources(SOCKET Sock) {
auto tmp_name = name + ".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
UpdateModUsage(modname);
} catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
@ -661,6 +745,7 @@ void SyncResources(SOCKET Sock) {
#endif
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
UpdateModUsage(FN->substr(pos));
}
WaitForConfirm();
}