mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-10 19:01:53 +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"]
|
||||
path = thirdparty/xxHash
|
||||
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/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)
|
||||
|
|
|
|||
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