mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-11 03:12:15 +00:00
Added miniz and implemented mod manifest loading from zip
This commit is contained in:
parent
0a53855333
commit
950c889235
5 changed files with 309 additions and 0 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +1,6 @@
|
||||||
[submodule "thirdparty/xxHash"]
|
[submodule "thirdparty/xxHash"]
|
||||||
path = thirdparty/xxHash
|
path = thirdparty/xxHash
|
||||||
url = https://github.com/Cyan4973/xxHash.git
|
url = https://github.com/Cyan4973/xxHash.git
|
||||||
|
[submodule "thirdparty/miniz"]
|
||||||
|
path = thirdparty/miniz
|
||||||
|
url = https://github.com/richgel999/miniz
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ add_library(librecomp STATIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/files.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/files.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/flash.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/flash.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.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/overlays.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp"
|
||||||
|
|
@ -46,4 +48,7 @@ if (WIN32)
|
||||||
add_compile_definitions(NOMINMAX)
|
add_compile_definitions(NOMINMAX)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(${PROJECT_SOURCE_DIR}/../thirdparty/miniz ${CMAKE_BINARY_DIR}/miniz)
|
||||||
|
|
||||||
target_link_libraries(librecomp PRIVATE ultramodern)
|
target_link_libraries(librecomp PRIVATE ultramodern)
|
||||||
|
target_link_libraries(librecomp PUBLIC miniz)
|
||||||
|
|
|
||||||
65
librecomp/include/librecomp/mods.hpp
Normal file
65
librecomp/include/librecomp/mods.hpp
Normal 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
|
||||||
235
librecomp/src/mod_manifest.cpp
Normal file
235
librecomp/src/mod_manifest.cpp
Normal 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
1
thirdparty/miniz
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 8573fd7cd6f49b262a0ccc447f3c6acfc415e556
|
||||||
Loading…
Add table
Reference in a new issue