Compare commits

...

6 commits

Author SHA1 Message Date
Ethan Lafrenais
626cb4682d
Merge 4b32a19531 into cea072b59b 2025-08-09 23:33:30 -04:00
Manuel Alfayate Corchete
cea072b59b
Remove unnecessary x11 header include. (#122)
Some checks failed
validate / ubuntu (arm64, Debug) (push) Has been cancelled
validate / ubuntu (arm64, Release) (push) Has been cancelled
validate / ubuntu (x64, Debug) (push) Has been cancelled
validate / ubuntu (x64, Release) (push) Has been cancelled
validate / windows (x64, Debug) (push) Has been cancelled
validate / windows (x64, Release) (push) Has been cancelled
validate / macos (arm64, Debug) (push) Has been cancelled
validate / macos (arm64, Release) (push) Has been cancelled
validate / macos (x64, Debug) (push) Has been cancelled
validate / macos (x64, Release) (push) Has been cancelled
2025-07-24 15:00:45 -04:00
Wiseguy
83891b4231
Fix handling of mod callbacks for events in optional dependencies (#121)
Some checks failed
validate / windows (x64, Debug) (push) Has been cancelled
validate / ubuntu (arm64, Debug) (push) Has been cancelled
validate / ubuntu (arm64, Release) (push) Has been cancelled
validate / ubuntu (x64, Debug) (push) Has been cancelled
validate / ubuntu (x64, Release) (push) Has been cancelled
validate / windows (x64, Release) (push) Has been cancelled
validate / macos (arm64, Debug) (push) Has been cancelled
validate / macos (arm64, Release) (push) Has been cancelled
validate / macos (x64, Debug) (push) Has been cancelled
validate / macos (x64, Release) (push) Has been cancelled
2025-07-23 00:08:44 -04:00
Wiseguy
df547d2c06
Update runtime for fixed address mod sections, fix some live recompiler errors not triggering mod loading errors (#120)
Some checks failed
validate / ubuntu (arm64, Debug) (push) Has been cancelled
validate / ubuntu (arm64, Release) (push) Has been cancelled
validate / ubuntu (x64, Debug) (push) Has been cancelled
validate / ubuntu (x64, Release) (push) Has been cancelled
validate / windows (x64, Debug) (push) Has been cancelled
validate / windows (x64, Release) (push) Has been cancelled
validate / macos (arm64, Debug) (push) Has been cancelled
validate / macos (arm64, Release) (push) Has been cancelled
validate / macos (x64, Debug) (push) Has been cancelled
validate / macos (x64, Release) (push) Has been cancelled
2025-07-19 04:09:14 -04:00
Wiseguy
bd1dde8774
Implement optional dependencies for mods and add recomp_get_mod_file_path export (#118) 2025-07-19 03:44:52 -04:00
Ethan Lafrenais
4b32a19531
Move dummy VI origin beyond any known game's load address 2025-05-07 01:07:27 -04:00
8 changed files with 225 additions and 55 deletions

@ -1 +1 @@
Subproject commit 8781eb44acbf55cb6a109d2aa5529aadb95a419d
Subproject commit c1a6dc93bfa5977de0ea256562058be4f1b73353

View file

@ -96,6 +96,7 @@ namespace recomp {
};
std::string error_to_string(ModLoadError);
void unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg);
enum class CodeModLoadError {
Good,
@ -133,6 +134,19 @@ namespace recomp {
String
};
enum class DependencyStatus {
// Do not change these values as they're exposed in the mod API!
// The dependency was found and the version requirement was met.
Found = 0,
// The ID given is not a dependency of the mod in question.
InvalidDependency = 1,
// The dependency was not found.
NotFound = 2,
// The dependency was found, but the version requirement was not met.
WrongVersion = 3
};
struct ModFileHandle {
virtual ~ModFileHandle() = default;
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0;
@ -171,6 +185,7 @@ namespace recomp {
struct Dependency {
std::string mod_id;
Version version;
bool optional;
};
struct ConfigOptionEnum {
@ -363,6 +378,10 @@ namespace recomp {
bool register_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
ModContentTypeId get_code_content_type() const { return code_content_type_id; }
bool is_content_runtime_toggleable(ModContentTypeId content_type) const;
std::string get_mod_display_name(size_t mod_index) const;
std::filesystem::path get_mod_path(size_t mod_index) const;
std::pair<std::string, std::string> get_mod_import_info(size_t mod_index, size_t import_index) const;
DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id) const;
private:
ModOpenError open_mod_from_manifest(ModManifest &manifest, std::string &error_param, const std::vector<ModContentTypeId> &supported_content_types, bool requires_manifest);
ModOpenError open_mod_from_path(const std::filesystem::path& mod_path, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest);
@ -621,6 +640,10 @@ namespace recomp {
size_t get_mod_order_index(size_t mod_index);
ModContentTypeId register_mod_content_type(const ModContentType& type);
bool register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
std::string get_mod_display_name(size_t mod_index);
std::filesystem::path get_mod_path(size_t mod_index);
std::pair<std::string, std::string> get_mod_import_info(size_t mod_index, size_t import_index);
DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id);
void register_config_exports();
}

View file

@ -96,6 +96,18 @@ void recomp_get_mod_folder_path(uint8_t* rdram, recomp_context* ctx) {
return_string(rdram, ctx, std::filesystem::absolute(mod_folder_path).u8string());
}
void recomp_get_mod_file_path(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
std::filesystem::path mod_file_path = recomp::mods::get_mod_path(mod_index);
return_string(rdram, ctx, std::filesystem::absolute(mod_file_path).u8string());
}
void recomp_is_dependency_met(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
std::string dependency_id = _arg_string<0>(rdram, ctx);
recomp::mods::DependencyStatus status = recomp::mods::is_dependency_met(mod_index, dependency_id);
_return(ctx, static_cast<uint32_t>(status));
}
void recomp::mods::register_config_exports() {
recomp::overlays::register_ext_base_export("recomp_get_config_u32", recomp_get_config_u32);
recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double);
@ -105,4 +117,6 @@ void recomp::mods::register_config_exports() {
recomp::overlays::register_ext_base_export("recomp_change_save_file", recomp_change_save_file);
recomp::overlays::register_base_export("recomp_get_save_file_path", recomp_get_save_file_path);
recomp::overlays::register_base_export("recomp_get_mod_folder_path", recomp_get_mod_folder_path);
recomp::overlays::register_ext_base_export("recomp_get_mod_file_path", recomp_get_mod_file_path);
recomp::overlays::register_ext_base_export("recomp_is_dependency_met", recomp_is_dependency_met);
}

View file

@ -181,6 +181,7 @@ const std::string authors_key = "authors";
const std::string minimum_recomp_version_key = "minimum_recomp_version";
const std::string enabled_by_default_key = "enabled_by_default";
const std::string dependencies_key = "dependencies";
const std::string optional_dependencies_key = "optional_dependencies";
const std::string native_libraries_key = "native_libraries";
const std::string config_schema_key = "config_schema";
@ -602,6 +603,26 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const
error_param = dep_string;
return ModOpenError::InvalidDependencyString;
}
cur_dep.optional = false;
size_t dependency_index = ret.dependencies.size();
ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index);
ret.dependencies.emplace_back(std::move(cur_dep));
}
// Optional dependencies (optional)
std::vector<std::string> optional_dep_strings{};
current_error = try_get_vec<json::string_t>(optional_dep_strings, manifest_json, optional_dependencies_key, false, error_param);
if (current_error != ModOpenError::Good) {
return current_error;
}
for (const std::string& dep_string : optional_dep_strings) {
Dependency cur_dep;
if (!parse_dependency(dep_string, cur_dep)) {
error_param = dep_string;
return ModOpenError::InvalidDependencyString;
}
cur_dep.optional = true;
size_t dependency_index = ret.dependencies.size();
ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index);

View file

@ -505,18 +505,20 @@ recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(
N64Recomp::LiveGenerator generator{ context.functions.size(), recompiler_inputs };
std::vector<std::vector<uint32_t>> dummy_static_funcs{};
bool errored = false;
for (size_t func_index = 0; func_index < context.functions.size(); func_index++) {
std::ostringstream dummy_ostream{};
if (!N64Recomp::recompile_function_live(generator, context, func_index, dummy_ostream, dummy_static_funcs, true)) {
is_good = false;
errored = true;
break;
}
}
// Generate the code.
recompiler_output = std::make_unique<N64Recomp::LiveGeneratorOutput>(generator.finish());
is_good = recompiler_output->good;
is_good = !errored && recompiler_output->good;
}
void recomp::mods::LiveRecompilerCodeHandle::set_imported_function(size_t import_index, GenericFunction func) {
@ -975,6 +977,45 @@ bool recomp::mods::ModContext::register_container_type(const std::string& extens
return true;
}
std::string recomp::mods::ModContext::get_mod_display_name(size_t mod_index) const {
return opened_mods[mod_index].manifest.display_name;
}
std::filesystem::path recomp::mods::ModContext::get_mod_path(size_t mod_index) const {
return opened_mods[mod_index].manifest.mod_root_path;
}
std::pair<std::string, std::string> recomp::mods::ModContext::get_mod_import_info(size_t mod_index, size_t import_index) const {
const ModHandle& mod = opened_mods[mod_index];
const N64Recomp::ImportSymbol& imported_func = mod.recompiler_context->import_symbols[import_index];
const std::string& dependency_id = mod.recompiler_context->dependencies[imported_func.dependency_index];
return std::make_pair<std::string, std::string>(std::string{ dependency_id }, std::string{ imported_func.base.name });
}
recomp::mods::DependencyStatus recomp::mods::ModContext::is_dependency_met(size_t mod_index, const std::string& dependency_id) const {
const ModHandle& mod = opened_mods[mod_index];
auto find_dep = mod.manifest.dependencies_by_id.find(dependency_id);
if (find_dep == mod.manifest.dependencies_by_id.end()) {
return DependencyStatus::InvalidDependency;
}
auto find_dep_mod = loaded_mods_by_id.find(dependency_id);
if (find_dep_mod == loaded_mods_by_id.end()) {
return DependencyStatus::NotFound;
}
const Dependency& dep = mod.manifest.dependencies[find_dep->second];
const ModHandle& dep_mod = opened_mods[find_dep_mod->second];
if (dep_mod.manifest.version < dep.version) {
return DependencyStatus::WrongVersion;
}
return DependencyStatus::Found;
}
bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId content_type) const {
assert(content_type.value < content_types.size());
@ -1026,7 +1067,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable
if (mod_from_stack_it != opened_mods_by_id.end()) {
const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second];
for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) {
if (!auto_enabled_mods.contains(dependency.mod_id)) {
if (!dependency.optional && !auto_enabled_mods.contains(dependency.mod_id)) {
auto_enabled_mods.emplace(dependency.mod_id);
mod_stack.emplace_back(dependency.mod_id);
@ -1071,7 +1112,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable
if (mod_from_stack_it != opened_mods_by_id.end()) {
const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second];
for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) {
if (!new_auto_enabled_mods.contains(dependency.mod_id)) {
if (!dependency.optional && !new_auto_enabled_mods.contains(dependency.mod_id)) {
new_auto_enabled_mods.emplace(dependency.mod_id);
mod_stack.emplace_back(dependency.mod_id);
}
@ -2038,25 +2079,28 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod,
mod.disable_runtime_toggle();
}
for (const recomp::mods::Dependency& cur_dep : mod.manifest.dependencies) {
// Look for the dependency in the loaded mod mapping.
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id);
if (find_loaded_dep_it == loaded_mods_by_id.end()) {
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
continue;
}
if (!cur_dep.optional) {
// Look for the dependency in the loaded mod mapping.
auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id);
if (find_loaded_dep_it != loaded_mods_by_id.end()) {
ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second];
if (cur_dep.version > dep_mod.manifest.version)
{
std::stringstream error_param_stream{};
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
(int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " <<
(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());
}
ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second];
if (cur_dep.version > dep_mod.manifest.version)
{
std::stringstream error_param_stream{};
error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " <<
(int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " <<
(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());
// Prevent the dependency from being toggled at runtime, as it's required for this mod.
dep_mod.disable_runtime_toggle();
}
// Add an error for this mod if the dependency isn't optional.
else {
errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id);
}
}
// Prevent the dependency from being toggled at runtime, as it's required for this mod.
dep_mod.disable_runtime_toggle();
}
}
@ -2125,22 +2169,28 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::init_mod_code(uint8_t*
int32_t cur_section_addr = load_address;
for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) {
const auto& section = mod_sections[section_index];
for (size_t i = 0; i < section.size; i++) {
MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i];
// Do not load fixed address sections into mod memory. Use their address as-is.
if (section.fixed_address) {
mod.section_load_addresses[section_index] = section.ram_addr;
}
mod.section_load_addresses[section_index] = cur_section_addr;
// Calculate the bss section's address based on the size of this section.
cur_section_addr += section.size;
// Zero the bss section.
for (size_t i = 0; i < section.bss_size; i++) {
MEM_B(i, (gpr)cur_section_addr) = 0;
else {
for (size_t i = 0; i < section.size; i++) {
MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i];
}
mod.section_load_addresses[section_index] = cur_section_addr;
// Calculate the bss section's address based on the size of this section.
cur_section_addr += section.size;
// Zero the bss section.
for (size_t i = 0; i < section.bss_size; i++) {
MEM_B(i, (gpr)cur_section_addr) = 0;
}
// Calculate the next section's address based on the size of the bss section.
cur_section_addr += section.bss_size;
// Align the next section's address to 16 bytes.
cur_section_addr = (cur_section_addr + 15) & ~15;
// Add some empty space between mods to act as a buffer for misbehaving mods that have out of bounds accesses.
cur_section_addr += 0x400;
}
// Calculate the next section's address based on the size of the bss section.
cur_section_addr += section.bss_size;
// Align the next section's address to 16 bytes.
cur_section_addr = (cur_section_addr + 15) & ~15;
// Add some empty space between mods to act as a buffer for misbehaving mods that have out of bounds accesses.
cur_section_addr += 0x400;
}
// Iterate over each section again after loading them to perform R_MIPS_32 relocations.
@ -2203,7 +2253,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
std::unordered_map<size_t, size_t> entry_func_hooks{};
std::unordered_map<size_t, size_t> return_func_hooks{};
// Scan the replacements and check for any
// Scan the replacements to handle hooks on the replaced functions.
for (const auto& replacement : mod.recompiler_context->replacements) {
// Check if there's a hook slot for the entry of this function.
HookDefinition entry_def {
@ -2362,14 +2412,24 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
}
else {
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
if (find_mod_it == loaded_mods_by_id.end()) {
error_param = "Failed to find import dependency while loading code: " + dependency_id;
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
// is validated against the manifest's.
return CodeModLoadError::InternalError;
if (find_mod_it != loaded_mods_by_id.end()) {
const auto& dependency = opened_mods[find_mod_it->second];
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
}
else {
auto find_optional_it = mod.manifest.dependencies_by_id.find(dependency_id);
if (find_optional_it != mod.manifest.dependencies_by_id.end()) {
uintptr_t shim_argument = ((import_index & 0xFFFFFFFFu) << 32) | (mod_index & 0xFFFFFFFFu);
func_handle = shim_functions.emplace_back(std::make_unique<N64Recomp::ShimFunction>(unmet_dependency_handler, shim_argument)).get()->get_func();
did_find_func = true;
}
else {
error_param = "Failed to find import dependency while loading code: " + dependency_id;
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
// is validated against the manifest's.
return CodeModLoadError::InternalError;
}
}
const auto& dependency = opened_mods[find_mod_it->second];
did_find_func = dependency.get_export_function(imported_func.base.name, func_handle);
}
if (!did_find_func) {
@ -2387,6 +2447,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
GenericFunction func = mod.code_handle->get_function_handle(callback.function_index);
size_t event_index = 0;
bool did_find_event = false;
bool optional = false;
if (dependency_id == N64Recomp::DependencyBaseRecomp) {
event_index = recomp::overlays::get_base_event_index(dependency_event.event_name);
@ -2398,23 +2459,40 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
did_find_event = mod.get_global_event_index(dependency_event.event_name, event_index);
}
else {
// Check if the dependency is optional.
auto find_mod_it = loaded_mods_by_id.find(dependency_id);
if (find_mod_it == loaded_mods_by_id.end()) {
error_param = "Failed to find callback dependency while loading code: " + dependency_id;
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
// is validated against the manifest's.
return CodeModLoadError::InternalError;
// Get the manifest's version of the dependency.
auto find_manifest_dep = mod.manifest.dependencies_by_id.find(dependency_id);
// This should always be found, but just in case validate that the find was successful.
// This will get treated as an error if it wasn't found in the manifest.
if (find_manifest_dep != mod.manifest.dependencies_by_id.end()) {
const auto& manifest_dep = mod.manifest.dependencies[find_manifest_dep->second];
if (manifest_dep.optional) {
optional = true;
}
}
if (!optional) {
error_param = "Failed to find callback dependency while loading code: " + dependency_id;
// This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list
// is validated against the manifest's.
return CodeModLoadError::InternalError;
}
}
else {
const auto& dependency_mod = opened_mods[find_mod_it->second];
did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index);
}
const auto& dependency_mod = opened_mods[find_mod_it->second];
did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index);
}
if (!did_find_event) {
if (did_find_event) {
recomp::mods::register_event_callback(event_index, mod_index, func);
}
else if (!optional) {
error_param = dependency_id + ":" + dependency_event.event_name;
return CodeModLoadError::InvalidCallbackEvent;
}
recomp::mods::register_event_callback(event_index, mod_index, func);
}
// Register hooks.
@ -2503,3 +2581,18 @@ void recomp::mods::ModContext::unload_mods() {
num_events = recomp::overlays::num_base_events();
active_game = (size_t)-1;
}
void recomp::mods::unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg) {
size_t caller_mod_index = (arg >> 0) & uint64_t(0xFFFFFFFF);
size_t import_index = (arg >> 32) & uint64_t(0xFFFFFFFF);
std::string mod_name = recomp::mods::get_mod_display_name(caller_mod_index);
std::pair<std::string, std::string> import_info = recomp::mods::get_mod_import_info(caller_mod_index, import_index);
ultramodern::error_handling::message_box(
(
"Fatal error in mod \"" + mod_name + "\": Called function \"" + import_info.second + "\" in unmet optional dependency \"" + import_info.first + "\".\n"
).c_str()
);
ULTRAMODERN_QUICK_EXIT();
}

View file

@ -129,6 +129,26 @@ bool recomp::mods::register_mod_container_type(const std::string& extension, con
return mod_context->register_container_type(extension, content_types, requires_manifest);
}
std::string recomp::mods::get_mod_display_name(size_t mod_index) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->get_mod_display_name(mod_index);
}
std::filesystem::path recomp::mods::get_mod_path(size_t mod_index) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->get_mod_path(mod_index);
}
std::pair<std::string, std::string> recomp::mods::get_mod_import_info(size_t mod_index, size_t import_index) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->get_mod_import_info(mod_index, import_index);
}
recomp::mods::DependencyStatus recomp::mods::is_dependency_met(size_t mod_index, const std::string& dependency_id) {
std::lock_guard mod_lock{ mod_context_mutex };
return mod_context->is_dependency_met(mod_index, dependency_id);
}
bool check_hash(const std::vector<uint8_t>& rom_data, uint64_t expected_hash) {
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
return calculated_hash == expected_hash;

View file

@ -12,7 +12,6 @@
#elif defined(__ANDROID__)
# include "android/native_window.h"
#elif defined(__linux__)
# include "X11/Xlib.h"
# undef None
# undef Status
# undef LockMask

View file

@ -160,7 +160,7 @@ void vi_thread_func() {
else {
set_dummy_vi();
static bool swap = false;
uint32_t vi_origin = 0x400 + 0x280; // Skip initial RDRAM contents and add the usual origin offset
uint32_t vi_origin = 0x80700000; // Skip initial RDRAM contents
// Offset by one FB every other frame so RT64 continues drawing
if (swap) {
vi_origin += 0x25800;