mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-10 19:01:53 +00:00
Added version parsing with version number as argument to recomp initialization and minimum recomp versions for mods
This commit is contained in:
parent
5699906f34
commit
09f5759d3a
5 changed files with 171 additions and 69 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue