Added miniz and implemented mod manifest loading from zip

This commit is contained in:
Mr-Wiseguy 2024-07-04 21:51:48 -04:00
parent 0a53855333
commit 950c889235
5 changed files with 309 additions and 0 deletions

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "thirdparty/xxHash"]
path = thirdparty/xxHash
url = https://github.com/Cyan4973/xxHash.git
[submodule "thirdparty/miniz"]
path = thirdparty/miniz
url = https://github.com/richgel999/miniz

View file

@ -15,6 +15,8 @@ add_library(librecomp STATIC
"${CMAKE_CURRENT_SOURCE_DIR}/src/files.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/flash.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/mods.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp"
@ -46,4 +48,7 @@ if (WIN32)
add_compile_definitions(NOMINMAX)
endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/../thirdparty/miniz ${CMAKE_BINARY_DIR}/miniz)
target_link_libraries(librecomp PRIVATE ultramodern)
target_link_libraries(librecomp PUBLIC miniz)

View file

@ -0,0 +1,65 @@
#ifndef __RECOMP_MODS_HPP__
#define __RECOMP_MODS_HPP__
#include <filesystem>
#include <string>
#include <fstream>
#include <cstdio>
#include <vector>
#include <memory>
#define MINIZ_NO_DEFLATE_APIS
#define MINIZ_NO_ARCHIVE_WRITING_APIS
#include "miniz.h"
#include "miniz_zip.h"
namespace recomp {
namespace mods {
enum class ModLoadError {
Good,
DoesNotExist,
NotAFile,
FileError,
InvalidZip,
NoManifest,
InvalidManifest,
};
struct ZipModHandle {
FILE* file_handle = nullptr;
std::unique_ptr<mz_zip_archive> archive;
ZipModHandle() = default;
ZipModHandle(const std::filesystem::path& mod_path, ModLoadError& error);
ZipModHandle(const ZipModHandle& rhs) = delete;
ZipModHandle& operator=(const ZipModHandle& rhs) = delete;
ZipModHandle(ZipModHandle&& rhs);
ZipModHandle& operator=(ZipModHandle&& rhs);
~ZipModHandle();
std::vector<char> read_file(const std::string& filename, bool& exists);
};
struct ModManifest {
std::filesystem::path mod_root_path;
std::string mod_id;
int major_version;
int minor_version;
int patch_version;
// These are all relative to the base path for loose mods or inside the zip for zipped mods.
std::string binary_path;
std::string binary_syms_path;
std::string rom_patch_path;
std::string rom_patch_syms_path;
ZipModHandle mod_handle;
};
ModManifest load_mod(const std::filesystem::path& mod_path, ModLoadError& error);
}
};
#endif

View file

@ -0,0 +1,235 @@
#include <unordered_map>
#include "json/json.hpp"
#include "librecomp/mods.hpp"
recomp::mods::ZipModHandle::~ZipModHandle() {
if (file_handle) {
fclose(file_handle);
file_handle = nullptr;
}
if (archive) {
mz_zip_reader_end(archive.get());
}
archive = {};
}
recomp::mods::ZipModHandle::ZipModHandle(ZipModHandle&& rhs) {
*this = std::move(rhs);
}
recomp::mods::ZipModHandle& recomp::mods::ZipModHandle::operator=(ZipModHandle&& rhs) {
if (file_handle) {
fclose(file_handle);
}
file_handle = rhs.file_handle;
rhs.file_handle = nullptr;
mz_zip_reader_end(archive.get());
archive = std::move(rhs.archive);
return *this;
}
recomp::mods::ZipModHandle::ZipModHandle(const std::filesystem::path& mod_path, ModLoadError& error) {
#ifdef _WIN32
if (_wfopen_s(&file_handle, mod_path.c_str(), L"rb") != 0) {
error = ModLoadError::FileError;
return;
}
#else
file_handle = fopen(mod_path.c_str(), L"rb");
if (!file_handle) {
error = ModLoadError::FileError;
return;
}
#endif
archive = std::make_unique<mz_zip_archive>();
if (!mz_zip_reader_init_cfile(archive.get(), file_handle, 0, 0)) {
error = ModLoadError::InvalidZip;
return;
}
error = ModLoadError::Good;
}
std::vector<char> recomp::mods::ZipModHandle::read_file(const std::string& filename, bool& exists) {
std::vector<char> ret{};
mz_uint32 file_index;
if (!mz_zip_reader_locate_file_v2(archive.get(), filename.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE, &file_index)) {
exists = false;
return ret;
}
mz_zip_archive_file_stat stat;
if (!mz_zip_reader_file_stat(archive.get(), file_index, &stat)) {
exists = false;
return ret;
}
ret.resize(stat.m_uncomp_size);
if (!mz_zip_reader_extract_to_mem(archive.get(), file_index, ret.data(), ret.size(), 0)) {
exists = false;
return {};
}
exists = true;
return ret;
}
enum class ManifestField {
Id,
MajorVersion,
MinorVersion,
PatchVersion,
BinaryPath,
BinarySymsPath,
RomPatchPath,
RomPatchSymsPath,
Invalid,
};
std::unordered_map<std::string, ManifestField> field_map {
{ "id", ManifestField::Id },
{ "major_version", ManifestField::MajorVersion },
{ "minor_version", ManifestField::MinorVersion },
{ "patch_version", ManifestField::PatchVersion },
{ "binary", ManifestField::BinaryPath },
{ "binary_syms", ManifestField::BinarySymsPath },
{ "rom_patch", ManifestField::RomPatchPath },
{ "rom_patch_syms", ManifestField::RomPatchSymsPath },
};
template <typename T1, typename T2>
bool get_to(const nlohmann::json& val, T2& out) {
const T1* ptr = val.get_ptr<const T1*>();
if (ptr == nullptr) {
return false;
}
out = *ptr;
return true;
}
bool parse_manifest(recomp::mods::ModManifest& ret, const std::vector<char>& manifest_data) {
using json = nlohmann::json;
json manifest_json = json::parse(manifest_data.begin(), manifest_data.end(), false);
if (manifest_json.is_discarded()) {
// Failed to parse
return false;
}
if (!manifest_json.is_object()) {
// Invalid manifest
return false;
}
for (const auto& [key, val] : manifest_json.items()) {
const auto find_key_it = field_map.find(key);
if (find_key_it == field_map.end()) {
// Unrecognized field
return false;
}
ManifestField field = find_key_it->second;
switch (field) {
case ManifestField::Id:
if (!get_to<json::string_t>(val, ret.mod_id)) {
// Invalid type
return false;
}
break;
case ManifestField::MajorVersion:
if (!get_to<json::number_unsigned_t>(val, ret.major_version)) {
// Invalid type
return false;
}
break;
case ManifestField::MinorVersion:
if (!get_to<json::number_unsigned_t>(val, ret.minor_version)) {
// Invalid type
return false;
}
break;
case ManifestField::PatchVersion:
if (!get_to<json::number_unsigned_t>(val, ret.patch_version)) {
// Invalid type
return false;
}
break;
case ManifestField::BinaryPath:
if (!get_to<json::string_t>(val, ret.binary_path)) {
// Invalid type
return false;
}
break;
case ManifestField::BinarySymsPath:
if (!get_to<json::string_t>(val, ret.binary_syms_path)) {
// Invalid type
return false;
}
break;
case ManifestField::RomPatchPath:
if (!get_to<json::string_t>(val, ret.rom_patch_path)) {
// Invalid type
return false;
}
break;
case ManifestField::RomPatchSymsPath:
if (!get_to<json::string_t>(val, ret.rom_patch_syms_path)) {
// Invalid type
return false;
}
break;
}
}
return true;
}
recomp::mods::ModManifest recomp::mods::load_mod(const std::filesystem::path& mod_path, ModLoadError& error) {
ModManifest ret{};
std::error_code ec;
if (!std::filesystem::exists(mod_path, ec) || ec) {
error = ModLoadError::DoesNotExist;
return {};
}
// TODO support symlinks?
if (!std::filesystem::is_regular_file(mod_path, ec) || ec) {
error = ModLoadError::NotAFile;
return {};
}
// Load the zip file.
ModLoadError zip_error;
ret.mod_handle = recomp::mods::ZipModHandle(mod_path, zip_error);
if (zip_error != ModLoadError::Good) {
error = zip_error;
return {};
}
{
bool exists;
std::vector<char> manifest_data = ret.mod_handle.read_file("manifest.json", exists);
if (!exists) {
error = ModLoadError::NoManifest;
return {};
}
if (!parse_manifest(ret, manifest_data)) {
error = ModLoadError::InvalidManifest;
return {};
}
}
// Return the loaded mod manifest
error = ModLoadError::Good;
return ret;
}

1
thirdparty/miniz vendored Submodule

@ -0,0 +1 @@
Subproject commit 8573fd7cd6f49b262a0ccc447f3c6acfc415e556