Added version parsing with version number as argument to recomp initialization and minimum recomp versions for mods

This commit is contained in:
Mr-Wiseguy 2024-09-02 20:01:59 -04:00
parent 5699906f34
commit 09f5759d3a
5 changed files with 171 additions and 69 deletions

View file

@ -22,6 +22,28 @@ namespace recomp {
std::u8string stored_filename() const;
};
struct Version {
int major = -1;
int minor = -1;
int patch = -1;
std::string suffix;
std::string to_string() const {
return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch) + suffix;
}
static bool from_string(const std::string& str, Version& out);
auto operator<=>(const Version& rhs) {
if (major != rhs.major) {
return major <=> rhs.major;
}
if (minor != rhs.minor) {
return minor <=> rhs.minor;
}
return patch <=> rhs.patch;
}
};
enum class RomValidationError {
Good,
FailedToOpen,
@ -41,6 +63,7 @@ namespace recomp {
void set_rom_contents(std::vector<uint8_t>&& new_rom);
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
const Version& get_project_version();
/**
* The following arguments contain mandatory callbacks that need to be registered (i.e., can't be `nullptr`):
@ -51,6 +74,7 @@ namespace recomp {
*/
void start(
uint32_t rdram_size,
const Version& project_version,
ultramodern::renderer::WindowHandle window_handle,
const recomp::rsp::callbacks_t& rsp_callbacks,
const ultramodern::renderer::callbacks_t& renderer_callbacks,

View file

@ -19,6 +19,7 @@
#include "miniz.h"
#include "miniz_zip.h"
#include "librecomp/game.hpp"
#include "librecomp/recomp.h"
#include "librecomp/sections.h"
@ -39,6 +40,8 @@ namespace recomp {
InvalidManifestSchema,
UnrecognizedManifestField,
IncorrectManifestFieldType,
InvalidVersionString,
InvalidMinimumRecompVersionString,
MissingManifestField,
InnerFileDoesNotExist,
DuplicateMod,
@ -50,6 +53,7 @@ namespace recomp {
enum class ModLoadError {
Good,
InvalidGame,
MinimumRecompVersionNotMet,
FailedToLoadSyms,
FailedToLoadBinary,
FailedToLoadNativeCode,
@ -105,15 +109,25 @@ namespace recomp {
std::vector<std::string> exports;
};
struct DependencyDetails {
std::string mod_id;
Version version;
};
struct ModDetails {
std::string mod_id;
Version version;
std::vector<std::string> authors;
std::vector<DependencyDetails> dependencies;
};
struct ModManifest {
std::filesystem::path mod_root_path;
std::vector<std::string> mod_game_ids;
std::string mod_id;
int major_version = -1;
int minor_version = -1;
int patch_version = -1;
Version minimum_recomp_version;
Version version;
// These are all relative to the base path for loose mods or inside the zip for zipped mods.
std::string binary_path;

View file

@ -133,9 +133,8 @@ bool recomp::mods::LooseModFileHandle::file_exists(const std::string& filepath)
enum class ManifestField {
GameModId,
Id,
MajorVersion,
MinorVersion,
PatchVersion,
Version,
MinimumRecompVersion,
BinaryPath,
BinarySymsPath,
RomPatchPath,
@ -145,9 +144,8 @@ enum class ManifestField {
const std::string game_mod_id_key = "game_id";
const std::string mod_id_key = "id";
const std::string major_version_key = "major_version";
const std::string minor_version_key = "minor_version";
const std::string patch_version_key = "patch_version";
const std::string version_key = "version";
const std::string minimum_recomp_version_key = "minimum_recomp_version";
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";
@ -155,16 +153,15 @@ const std::string rom_patch_syms_path_key = "rom_patch_syms";
const std::string native_library_paths_key = "native_libraries";
std::unordered_map<std::string, ManifestField> field_map {
{ game_mod_id_key, ManifestField::GameModId },
{ mod_id_key, ManifestField::Id },
{ major_version_key, ManifestField::MajorVersion },
{ minor_version_key, ManifestField::MinorVersion },
{ patch_version_key, 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 },
{ native_library_paths_key, ManifestField::NativeLibraryPaths },
{ game_mod_id_key, ManifestField::GameModId },
{ mod_id_key, ManifestField::Id },
{ version_key, ManifestField::Version },
{ minimum_recomp_version_key, ManifestField::MinimumRecompVersion },
{ binary_path_key, ManifestField::BinaryPath },
{ binary_syms_path_key, ManifestField::BinarySymsPath },
{ rom_patch_path_key, ManifestField::RomPatchPath },
{ rom_patch_syms_path_key, ManifestField::RomPatchSymsPath },
{ native_library_paths_key, ManifestField::NativeLibraryPaths },
};
template <typename T1, typename T2>
@ -239,22 +236,31 @@ recomp::mods::ModOpenError parse_manifest(recomp::mods::ModManifest& ret, const
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
}
break;
case ManifestField::MajorVersion:
if (!get_to<json::number_unsigned_t>(val, ret.major_version)) {
error_param = key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
case ManifestField::Version:
{
const std::string* version_str = val.get_ptr<const std::string*>();
if (version_str == nullptr) {
error_param = key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
}
if (!recomp::Version::from_string(*version_str, ret.version)) {
error_param = *version_str;
return recomp::mods::ModOpenError::InvalidVersionString;
}
}
break;
case ManifestField::MinorVersion:
if (!get_to<json::number_unsigned_t>(val, ret.minor_version)) {
error_param = key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
}
break;
case ManifestField::PatchVersion:
if (!get_to<json::number_unsigned_t>(val, ret.patch_version)) {
error_param = key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
case ManifestField::MinimumRecompVersion:
{
const std::string* version_str = val.get_ptr<const std::string*>();
if (version_str == nullptr) {
error_param = key;
return recomp::mods::ModOpenError::IncorrectManifestFieldType;
}
if (!recomp::Version::from_string(*version_str, ret.minimum_recomp_version)) {
error_param = *version_str;
return recomp::mods::ModOpenError::InvalidMinimumRecompVersionString;
}
ret.minimum_recomp_version.suffix.clear();
}
break;
case ManifestField::BinaryPath:
@ -328,16 +334,12 @@ recomp::mods::ModOpenError validate_manifest(const recomp::mods::ModManifest& ma
error_param = mod_id_key;
return ModOpenError::MissingManifestField;
}
if (manifest.major_version == -1) {
error_param = major_version_key;
if (manifest.version.major == -1 || manifest.version.major == -1 || manifest.version.major == -1) {
error_param = version_key;
return ModOpenError::MissingManifestField;
}
if (manifest.minor_version == -1) {
error_param = minor_version_key;
return ModOpenError::MissingManifestField;
}
if (manifest.patch_version == -1) {
error_param = patch_version_key;
if (manifest.minimum_recomp_version.major == -1 || manifest.minimum_recomp_version.major == -1 || manifest.minimum_recomp_version.major == -1) {
error_param = minimum_recomp_version_key;
return ModOpenError::MissingManifestField;
}
@ -477,6 +479,10 @@ std::string recomp::mods::error_to_string(ModOpenError error) {
return "Unrecognized field in manifest.json";
case ModOpenError::IncorrectManifestFieldType:
return "Incorrect type for field in manifest.json";
case ModOpenError::InvalidVersionString:
return "Invalid version string in manifest.json";
case ModOpenError::InvalidMinimumRecompVersionString:
return "Invalid minimum recomp version string in manifest.json";
case ModOpenError::MissingManifestField:
return "Missing required field in manifest";
case ModOpenError::InnerFileDoesNotExist:
@ -495,6 +501,8 @@ std::string recomp::mods::error_to_string(ModLoadError error) {
return "Good";
case ModLoadError::InvalidGame:
return "Invalid game";
case ModLoadError::MinimumRecompVersionNotMet:
return "Mod requires a newer version of this project";
case ModLoadError::FailedToLoadSyms:
return "Failed to load mod symbol file";
case ModLoadError::FailedToLoadBinary:

View file

@ -314,6 +314,12 @@ void recomp::mods::ModContext::add_opened_mod(ModManifest&& manifest, std::vecto
recomp::mods::ModLoadError recomp::mods::ModContext::load_mod(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& handle, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
using namespace recomp::mods;
handle.section_load_addresses.clear();
// Check that the mod's minimum recomp version is met.
if (get_project_version() < handle.manifest.minimum_recomp_version) {
error_param = handle.manifest.minimum_recomp_version.to_string();
return recomp::mods::ModLoadError::MinimumRecompVersionNotMet;
}
// Load the mod symbol data from the file provided in the manifest.
bool binary_syms_exists = false;
@ -365,7 +371,7 @@ std::vector<recomp::mods::ModOpenErrorDetails> recomp::mods::ModContext::scan_mo
std::vector<recomp::mods::ModOpenErrorDetails> ret{};
std::error_code ec;
for (const auto& mod_path : std::filesystem::directory_iterator{mod_folder, std::filesystem::directory_options::skip_permission_denied, ec}) {
if ((mod_path.is_regular_file() && mod_path.path().extension() == ".zip") || mod_path.is_directory()) {
if ((mod_path.is_regular_file() && mod_path.path().extension() == ".nrm") || mod_path.is_directory()) {
printf("Opening mod " PATHFMT "\n", mod_path.path().stem().c_str());
std::string open_error_param;
ModOpenError open_error = open_mod(mod_path, open_error_param);
@ -510,27 +516,6 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
return ret;
}
bool dependency_version_met(uint8_t major, uint8_t minor, uint8_t patch, uint8_t major_target, uint8_t minor_target, uint8_t patch_target) {
if (major > major_target) {
return true;
}
else if (major < major_target) {
return false;
}
if (minor > minor_target) {
return true;
}
else if (minor < minor_target) {
return false;
}
if (patch >= patch_target) {
return true;
}
return false;
}
void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors) {
errors.clear();
for (N64Recomp::Dependency& cur_dep : mod.recompiler_context->dependencies) {
@ -546,15 +531,18 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod,
continue;
}
const auto& mod = opened_mods[find_it->second];
if (!dependency_version_met(
mod.manifest.major_version, mod.manifest.minor_version, mod.manifest.patch_version,
cur_dep.major_version, cur_dep.minor_version, cur_dep.patch_version))
const ModHandle& dep_mod = opened_mods[find_it->second];
Version dep_version {
.major = cur_dep.major_version,
.minor = cur_dep.minor_version,
.patch = cur_dep.patch_version
};
if (dep_version > dep_mod.manifest.version)
{
std::stringstream error_param_stream{};
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
(int)cur_dep.major_version << "." << (int)cur_dep.minor_version << "." << (int)cur_dep.patch_version << ", got " <<
(int)mod.manifest.major_version << "." << (int)mod.manifest.minor_version << "." << (int)mod.manifest.patch_version << "";
(int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << "";
errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str());
}
}

View file

@ -55,6 +55,8 @@ std::filesystem::path config_path;
std::unordered_map<std::u8string, recomp::GameEntry> game_roms {};
// The global mod context.
std::unique_ptr<recomp::mods::ModContext> mod_context = std::make_unique<recomp::mods::ModContext>();
// The project's version.
recomp::Version project_version;
std::u8string recomp::GameEntry::stored_filename() const {
return game_id + u8".z64";
@ -167,6 +169,70 @@ bool recomp::load_stored_rom(std::u8string& game_id) {
return true;
}
const recomp::Version& recomp::get_project_version() {
return project_version;
}
bool recomp::Version::from_string(const std::string& str, Version& out) {
std::array<size_t, 2> period_indices;
size_t num_periods = 0;
size_t cur_pos = 0;
uint16_t major;
uint16_t minor;
uint16_t patch;
std::string suffix;
// Find the 2 required periods.
cur_pos = str.find('.', cur_pos);
period_indices[0] = cur_pos;
cur_pos = str.find('.', cur_pos + 1);
period_indices[1] = cur_pos;
// Check that both were found.
if (period_indices[0] == std::string::npos || period_indices[1] == std::string::npos) {
return false;
}
// Parse the 3 numbers formed by splitting the string via the periods.
std::array<std::from_chars_result, 3> parse_results;
std::array<size_t, 3> parse_starts { 0, period_indices[0] + 1, period_indices[1] + 1 };
std::array<size_t, 3> parse_ends { period_indices[0], period_indices[1], str.size() };
parse_results[0] = std::from_chars(str.data() + parse_starts[0], str.data() + parse_ends[0], major);
parse_results[1] = std::from_chars(str.data() + parse_starts[1], str.data() + parse_ends[1], minor);
parse_results[2] = std::from_chars(str.data() + parse_starts[2], str.data() + parse_ends[2], patch);
// Check that the first two parsed correctly.
auto did_parse = [&](size_t i) {
return parse_results[i].ec == std::errc{} && parse_results[i].ptr == str.data() + parse_ends[i];
};
if (!did_parse(0) || !did_parse(1)) {
return false;
}
// Check that the third had a successful parse, but not necessarily read all the characters.
if (parse_results[2].ec != std::errc{}) {
return false;
}
// Allow a plus or minus directly after the third number.
if (parse_results[2].ptr != str.data() + parse_ends[2]) {
if (*parse_results[2].ptr == '+' || *parse_results[2].ptr == '-') {
suffix = str.substr(std::distance(str.data(), parse_results[2].ptr));
}
// Failed to parse, as nothing is allowed directly after the last number besides a plus or minus.
else {
return false;
}
}
out.major = major;
out.minor = minor;
out.patch = patch;
out.suffix = std::move(suffix);
return true;
}
const std::array<uint8_t, 4> first_rom_bytes { 0x80, 0x37, 0x12, 0x40 };
enum class ByteswapType {
@ -477,6 +543,7 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
void recomp::start(
uint32_t rdram_size,
const recomp::Version& version,
ultramodern::renderer::WindowHandle window_handle,
const recomp::rsp::callbacks_t& rsp_callbacks,
const ultramodern::renderer::callbacks_t& renderer_callbacks,
@ -487,6 +554,7 @@ void recomp::start(
const ultramodern::error_handling::callbacks_t& error_handling_callbacks,
const ultramodern::threads::callbacks_t& threads_callbacks
) {
project_version = version;
recomp::check_all_stored_roms();
recomp::rsp::set_callbacks(rsp_callbacks);