mkxp-sunshine/src/modloader.cpp
2026-07-14 12:43:43 -04:00

221 lines
6.7 KiB
C++

//https://terminalroot.com/how-to-generate-sha256-hash-with-cpp-and-openssl/
//https://stackoverflow.com/questions/15347123/how-to-construct-a-stdstring-from-a-stdvectorstring
//https://stackoverflow.com/questions/54260184/how-to-sha256-hash-a-text-file-in-chunks-with-openssl-sha-h
//https://stackoverflow.com/questions/1673445/how-to-convert-unsigned-char-to-stdstring-in-c
//https://www.geeksforgeeks.org/cpp/cpp-program-to-read-and-print-all-files-from-a-zip-file/
#include "modloader.h"
#include "debugwriter.h"
#include "meow.h"
#include "config.h"
#include <cctype>
#include <string>
#include <iostream>
#include <filesystem>
#include <vector>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <openssl/sha.h>
#include <SDL3/SDL_stdinc.h>
#include <zip.h>
#include <locale>
#include <filesystem>
#include <string>
#include <vector>
#include <algorithm>
#include <system_error>
#include <SDL3/SDL_system.h>
namespace fs = std::filesystem;
// Get directory for storing cached builds
//TODO: support for other OS and platforms
std::string getCacheDir(){
#ifdef _WIN32
return SDL_getenv("Temp");
#elif __linux__
return std::string(SDL_getenv("HOME")) + "/.cache";
#elif BSD
return std::string(SDL_getenv("HOME")) + "/.cache";
#elif __APPLE__
return "~/Library/Caches";
#elif __ANDROID__
return std::string str(SDL_GetAndroidCachePath());
#else
return "idk";
#endif
}
//helper
bool ensure_parent_dir(const std::filesystem::path& p){
if (p.has_parent_path()){
std::error_code ec;
std::filesystem::create_directories(p.parent_path(), ec);
return !ec;
}
return true;
}
std::string sha512(const std::string str){
unsigned char hash[SHA512_DIGEST_LENGTH];
SHA512_CTX sha512;
SHA512_Init(&sha512);
SHA512_Update(&sha512, str.c_str(), str.size());
SHA512_Final(hash, &sha512);
std::stringstream ss;
for(int i = 0; i < SHA512_DIGEST_LENGTH; i++){
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>( hash[i] );
}
return ss.str();
}
std::string sha256_file(const std::string &fn) {
FILE *file = fopen(fn.c_str(), "rb");
if (!file) {
crash(Exception::ModLoaderError, "Failed to load mod, filesystem error.");
}
unsigned char buf[1024];
unsigned char hash[SHA256_DIGEST_LENGTH];
size_t len;
SHA256_CTX ctx;
SHA256_Init(&ctx);
while ((len = fread(buf, 1, sizeof buf, file)) != 0){
SHA256_Update(&ctx, buf, len);
}
fclose(file);
SHA256_Final(hash, &ctx);
std::string out;
out.reserve(SHA256_DIGEST_LENGTH * 2);
char hex[3] = {0};
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
SDL_snprintf(hex, sizeof hex, "%02x", hash[i]);
out += hex;
}
return out;
}
#ifdef __EMSCRIPTEN__
std::string ModLoader(Config conf){ return ""; }
#else
std::string ModLoader(Config conf){
std::string path = conf.Modloader.ModsDirPath;
if (!fs::exists(path) || !std::filesystem::is_directory(path)) {
Debug() << "[MODLOADER] Mods directory not found, skip.";
return "";
}
if (fs::is_empty(path)){
Debug() << "[MODLOADER] Mods directory empty, skip.";
return "";
}
//buildID - unique ID of a certain combination of mods
std::string buildID = "";
std::string buildID_tmp = "";
std::vector<std::string> mod_list = {};
try{
//1.check if any zip(mod) file, 2. calculate sha256 hash of zip(mod) files
for (const auto &entry : std::filesystem::directory_iterator(path, std::filesystem::directory_options::skip_permission_denied)) {
std::error_code ec;
auto p = entry.path();
if (!std::filesystem::is_regular_file(p, ec) || ec) continue;
auto ext = p.extension().string();
if (ext != ".zip") continue;
std::string full = p.string();
Debug() << "[MODLOADER] " << full;
mod_list.push_back(full);
mods_count++;
auto h = sha256_file(full);
buildID_tmp.append(h);
}
buildID = sha512(buildID_tmp);
Debug() << "[MODLOADER] BuildID: " << buildID;
std::string path3 = getCacheDir() + "/sunshine-" + buildID;
int err = 0;
modloader_is_enabled = true;
if(!std::filesystem::exists(path3)){
std::error_code ec;
fs::create_directory(path3, ec);
if (ec) {
crash(Exception::ModLoaderError, "Failed to create destination: %s", ec.message().c_str());
}
fs::copy(conf.gameFolder, path3, fs::copy_options::recursive | fs::copy_options::overwrite_existing, ec);
if (ec) {
crash(Exception::ModLoaderError, "Copy error: %s", ec.message().c_str());
}
//Extracting zipsodpsofspo idk
for (size_t i = 0; i < mod_list.size(); ++i){
zip_t* za = zip_open(mod_list[i].c_str(), ZIP_RDONLY, &err);
if (!za){
crash(Exception::ModLoaderError, "Failed to open zip");
}
zip_int64_t n = zip_get_num_entries(za, 0);
for (zip_uint64_t i2 = 0; i2 < static_cast<zip_uint64_t>(n); ++i2) {
zip_stat_t st;
if (zip_stat_index(za, i2, 0, &st) != 0) {
crash(Exception::ModLoaderError, "zip_stat_index failed");
}
std::string name = st.name;
std::filesystem::path target = path3 / std::filesystem::path(name);
// If entry name ends with '/', treat as directory
if (!name.empty() && name.back() == '/') {
std::error_code ec;
std::filesystem::create_directories(target, ec);
if (ec) crash(Exception::MEOW, "Failed to create dir");
continue;
}
if (!ensure_parent_dir(target)){
crash(Exception::ModLoaderError, "Failed to create parent dirs");
}
zip_file_t* zf = zip_fopen_index(za, i2, 0);
if (!zf) {
crash(Exception::ModLoaderError, "zip_fopen_index failed");
}
std::ofstream out(target, std::ios::binary);
if (!out) {
crash(Exception::ModLoaderError, "Failed to open output file %s", target.c_str());
zip_fclose(zf);
}
const zip_uint64_t bufsize = 4096;
std::vector<char> buf(bufsize);
zip_int64_t bytes_read;
zip_uint64_t remaining = st.size;
while (remaining > 0) {
zip_uint64_t to_read = std::min<zip_uint64_t>(bufsize, remaining);
bytes_read = zip_fread(zf, buf.data(), to_read);
if (bytes_read < 0) {
crash(Exception::ModLoaderError, "zip_fread error for %s", name.c_str());
break;
}
out.write(buf.data(), bytes_read);
remaining -= bytes_read;
}
zip_fclose(zf);
}
zip_close(za);
}
return path3;
}else{
return path3;
}
}catch(const std::exception& e){
crash(Exception::ModLoaderError, "Something is wrong, Exception: %s ", e.what());
}
}
#endif