From 52b365f42cb899a14a6e289e72cad9b71b81e6a2 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Fri, 5 Jul 2024 13:09:58 -0400 Subject: [PATCH] Added validation for mod contents and mod load error strings --- librecomp/include/librecomp/mods.hpp | 13 ++- librecomp/src/mod_manifest.cpp | 130 ++++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 13 deletions(-) diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 6326c48..ef89c3e 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -26,11 +26,14 @@ namespace recomp { InvalidManifestSchema, UnrecognizedManifestField, IncorrectManifestFieldType, + MissingManifestField, + InnerFileDoesNotExist }; struct ModHandle { virtual ~ModHandle() = default; - virtual std::vector read_file(const std::string& filepath, bool& exists) = 0; + virtual std::vector read_file(const std::string& filepath, bool& exists) const = 0; + virtual bool file_exists(const std::string& filepath) const = 0; }; struct ZipModHandle : public ModHandle { @@ -41,7 +44,8 @@ namespace recomp { ZipModHandle(const std::filesystem::path& mod_path, ModLoadError& error); ~ZipModHandle() final; - std::vector read_file(const std::string& filepath, bool& exists) final; + std::vector read_file(const std::string& filepath, bool& exists) const final; + bool file_exists(const std::string& filepath) const final; }; struct LooseModHandle : public ModHandle { @@ -51,7 +55,8 @@ namespace recomp { LooseModHandle(const std::filesystem::path& mod_path, ModLoadError& error); ~LooseModHandle() final; - std::vector read_file(const std::string& filepath, bool& exists) final; + std::vector read_file(const std::string& filepath, bool& exists) const final; + bool file_exists(const std::string& filepath) const final; }; struct ModManifest { @@ -74,6 +79,8 @@ namespace recomp { ModManifest load_mod(const std::filesystem::path& mod_path, ModLoadError& error, std::string& error_string); bool load_mod_(uint8_t* rdram, int32_t target_vram, const std::filesystem::path& symbol_file, const std::filesystem::path& binary_file); + + std::string error_to_string(ModLoadError); } }; diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 06135e2..39a67b1 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -38,7 +38,7 @@ recomp::mods::ZipModHandle::ZipModHandle(const std::filesystem::path& mod_path, error = ModLoadError::Good; } -std::vector recomp::mods::ZipModHandle::read_file(const std::string& filepath, bool& exists) { +std::vector recomp::mods::ZipModHandle::read_file(const std::string& filepath, bool& exists) const { std::vector ret{}; mz_uint32 file_index; @@ -63,6 +63,15 @@ std::vector recomp::mods::ZipModHandle::read_file(const std::string& filep return ret; } +bool recomp::mods::ZipModHandle::file_exists(const std::string& filepath) const { + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(archive.get(), filepath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE, &file_index)) { + return false; + } + + return true; +} + recomp::mods::LooseModHandle::~LooseModHandle() { // Nothing to do here, members will be destroyed automatically. } @@ -82,7 +91,7 @@ recomp::mods::LooseModHandle::LooseModHandle(const std::filesystem::path& mod_pa error = ModLoadError::Good; } -std::vector recomp::mods::LooseModHandle::read_file(const std::string& filepath, bool& exists) { +std::vector recomp::mods::LooseModHandle::read_file(const std::string& filepath, bool& exists) const { std::vector ret{}; std::filesystem::path full_path = root_path / filepath; @@ -110,6 +119,17 @@ std::vector recomp::mods::LooseModHandle::read_file(const std::string& fil return ret; } +bool recomp::mods::LooseModHandle::file_exists(const std::string& filepath) const { + std::filesystem::path full_path = root_path / filepath; + + std::error_code ec; + if (!std::filesystem::is_regular_file(full_path, ec) || ec) { + return false; + } + + return true; +} + enum class ManifestField { Id, MajorVersion, @@ -122,15 +142,20 @@ enum class ManifestField { Invalid, }; +const std::string binary_path_key = "binary"; +const std::string binary_syms_path_key = "binary_syms"; +const std::string rom_patch_path_key = "rom_patch"; +const std::string rom_patch_syms_path_key = "rom_patch_syms"; + std::unordered_map 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 }, + { "id", ManifestField::Id }, + { "major_version", ManifestField::MajorVersion }, + { "minor_version", ManifestField::MinorVersion }, + { "patch_version", ManifestField::PatchVersion }, + { binary_path_key, ManifestField::BinaryPath }, + { binary_syms_path_key, ManifestField::BinarySymsPath }, + { rom_patch_path_key, ManifestField::RomPatchPath }, + { rom_patch_syms_path_key, ManifestField::RomPatchSymsPath }, }; template @@ -231,6 +256,57 @@ bool parse_manifest(recomp::mods::ModManifest& ret, const std::vector& man return true; } +bool validate_file_exists(const recomp::mods::ModManifest& manifest, const std::string& filepath, recomp::mods::ModLoadError& error, std::string& error_param) { + // No file provided, so nothing to check for. + if (filepath.empty()) { + return true; + } + if (!manifest.mod_handle->file_exists(filepath)) { + error = recomp::mods::ModLoadError::InnerFileDoesNotExist; + error_param = filepath; + return false; + } + return true; +} + +bool validate_manifest(const recomp::mods::ModManifest& manifest, recomp::mods::ModLoadError& error, std::string& error_param) { + using namespace recomp::mods; + // If either a binary file or binary symbol file is provided, the other must be as well. + if (manifest.binary_path.empty() != manifest.binary_syms_path.empty()) { + error = ModLoadError::MissingManifestField; + if (manifest.binary_path.empty()) { + error_param = binary_path_key; + } + else { + error_param = binary_syms_path_key; + } + return false; + } + + // If a ROM patch symbol file is provided, a ROM patch file must be as well. + if (!manifest.rom_patch_syms_path.empty() && manifest.rom_patch_path.empty()) { + error = ModLoadError::MissingManifestField; + error_param = rom_patch_path_key; + return false; + } + + // Validate that provided files exist. + if (!validate_file_exists(manifest, manifest.binary_path, error, error_param)) { + return false; + } + if (!validate_file_exists(manifest, manifest.binary_syms_path, error, error_param)) { + return false; + } + if (!validate_file_exists(manifest, manifest.rom_patch_path, error, error_param)) { + return false; + } + if (!validate_file_exists(manifest, manifest.rom_patch_syms_path, error, error_param)) { + return false; + } + + return true; +} + recomp::mods::ModManifest recomp::mods::load_mod(const std::filesystem::path& mod_path, ModLoadError& error, std::string& error_param) { ModManifest ret{}; std::error_code ec; @@ -285,7 +361,41 @@ recomp::mods::ModManifest recomp::mods::load_mod(const std::filesystem::path& mo } } + if (!validate_manifest(ret, error, error_param)) { + return {}; + } + // Return the loaded mod manifest error = ModLoadError::Good; return ret; } + +std::string recomp::mods::error_to_string(ModLoadError error) { + switch (error) { + case ModLoadError::Good: + return "Good"; + case ModLoadError::DoesNotExist: + return "Mod does not exist"; + case ModLoadError::NotAFileOrFolder: + return "Mod is not a file or folder"; + case ModLoadError::FileError: + return "Error reading mod file(s)"; + case ModLoadError::InvalidZip: + return "Mod is an invalid zip file"; + case ModLoadError::NoManifest: + return "Mod is missing a manifest.json"; + case ModLoadError::FailedToParseManifest: + return "Failed to parse mod's manifest.json"; + case ModLoadError::InvalidManifestSchema: + return "Mod's manifest.json has an invalid schema"; + case ModLoadError::UnrecognizedManifestField: + return "Unrecognized field in manifest.json"; + case ModLoadError::IncorrectManifestFieldType: + return "Incorrect type for field in manifest.json"; + case ModLoadError::MissingManifestField: + return "Missing required field in manifest"; + case ModLoadError::InnerFileDoesNotExist: + return "File inside mod does not exist"; + } + return "Unknown error " + std::to_string((int)error); +}