mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-11 03:12:15 +00:00
Implement function hooking for functions replaced by mods
This commit is contained in:
parent
c01e1108b2
commit
b69905525e
7 changed files with 237 additions and 20 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit fc696046da3e703450559154d9370ca74c197f8b
|
||||
Subproject commit 58b2f8698fb7c9adef4b8cd18ef50154c3ac416b
|
||||
|
|
@ -18,6 +18,7 @@ add_library(librecomp STATIC
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/src/math_routines.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mods.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_events.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_hooks.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
|
||||
|
|
|
|||
|
|
@ -28,6 +28,30 @@ namespace N64Recomp {
|
|||
struct LiveGeneratorOutput;
|
||||
};
|
||||
|
||||
namespace recomp {
|
||||
namespace mods {
|
||||
struct HookDefinition {
|
||||
uint32_t section_rom;
|
||||
uint32_t function_vram;
|
||||
bool at_return;
|
||||
bool operator==(const HookDefinition& rhs) const = default;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::hash<recomp::mods::HookDefinition>
|
||||
{
|
||||
std::size_t operator()(const recomp::mods::HookDefinition& def) const {
|
||||
// This hash packing only works if the resulting value is 64 bits.
|
||||
static_assert(sizeof(std::size_t) == 8);
|
||||
// Combine the three values into a single 64-bit value.
|
||||
// The lower 2 bits of a function address will always be zero, so pack
|
||||
// the value of at_return into the lowest bit.
|
||||
return (size_t(def.section_rom) << 32) | size_t(def.function_vram) | size_t(def.at_return ? 1 : 0);
|
||||
}
|
||||
};
|
||||
|
||||
namespace recomp {
|
||||
namespace mods {
|
||||
enum class ModOpenError {
|
||||
|
|
@ -81,6 +105,7 @@ namespace recomp {
|
|||
BaseRecompConflict,
|
||||
ModConflict,
|
||||
DuplicateExport,
|
||||
OfflineModHooked,
|
||||
NoSpecifiedApiVersion,
|
||||
UnsupportedApiVersion,
|
||||
};
|
||||
|
|
@ -231,7 +256,8 @@ namespace recomp {
|
|||
ModOpenError open_mod(const std::filesystem::path& mod_path, std::string& error_param, const std::vector<ModContentTypeId>& supported_content_types, bool requires_manifest);
|
||||
ModLoadError load_mod(recomp::mods::ModHandle& mod, std::string& error_param);
|
||||
void check_dependencies(recomp::mods::ModHandle& mod, std::vector<std::pair<recomp::mods::ModLoadError, std::string>>& errors);
|
||||
CodeModLoadError load_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param);
|
||||
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param);
|
||||
CodeModLoadError load_mod_code(uint8_t* rdram, recomp::mods::ModHandle& mod, uint32_t base_event_index, std::string& error_param);
|
||||
CodeModLoadError resolve_code_dependencies(recomp::mods::ModHandle& mod, const std::unordered_set<recomp_func_t*> base_patched_funcs, std::string& error_param);
|
||||
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types);
|
||||
void close_mods();
|
||||
|
|
@ -249,6 +275,11 @@ namespace recomp {
|
|||
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
|
||||
std::unordered_map<std::string, size_t> loaded_mods_by_id;
|
||||
std::vector<size_t> loaded_code_mods;
|
||||
// Map of hook definition to the entry hook slot's index.
|
||||
std::unordered_map<HookDefinition, size_t> hook_slots;
|
||||
// Tracks which hook slots have already been processed. Used to regenerate vanilla functions as needed
|
||||
// to add hooks to any functions that weren't already replaced by a mod.
|
||||
std::vector<bool> processed_hook_slots;
|
||||
size_t num_events = 0;
|
||||
ModContentTypeId code_content_type_id;
|
||||
size_t active_game = (size_t)-1;
|
||||
|
|
@ -368,7 +399,8 @@ namespace recomp {
|
|||
|
||||
class LiveRecompilerCodeHandle : public ModCodeHandle {
|
||||
public:
|
||||
LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs);
|
||||
LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs,
|
||||
std::unordered_map<size_t, size_t>&& entry_func_hooks, std::unordered_map<size_t, size_t>&& return_func_hooks);
|
||||
|
||||
~LiveRecompilerCodeHandle() = default;
|
||||
|
||||
|
|
@ -398,6 +430,12 @@ namespace recomp {
|
|||
void setup_events(size_t num_events);
|
||||
void register_event_callback(size_t event_index, GenericFunction callback);
|
||||
void reset_events();
|
||||
|
||||
void setup_hooks(size_t num_hook_slots);
|
||||
void register_hook(size_t hook_slot_index, GenericFunction callback);
|
||||
void reset_hooks();
|
||||
void run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index);
|
||||
|
||||
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
|
||||
|
||||
void initialize_mod_recompiler();
|
||||
|
|
|
|||
51
librecomp/src/mod_hooks.cpp
Normal file
51
librecomp/src/mod_hooks.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include <vector>
|
||||
#include "librecomp/mods.hpp"
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
// Vector of individual hooks for each hook slot.
|
||||
std::vector<std::vector<recomp::mods::GenericFunction>> hook_table{};
|
||||
|
||||
void recomp::mods::run_hook(uint8_t* rdram, recomp_context* ctx, size_t hook_slot_index) {
|
||||
// Sanity check the hook slot index.
|
||||
if (hook_slot_index >= hook_table.size()) {
|
||||
printf("Hook slot %zu triggered, but only %zu hook slots have been registered!\n", hook_slot_index, hook_table.size());
|
||||
assert(false);
|
||||
ultramodern::error_handling::message_box("Encountered an error with loaded mods: hook slot out of bounds");
|
||||
ULTRAMODERN_QUICK_EXIT();
|
||||
}
|
||||
|
||||
// Copy the initial context state to restore it after running each callback.
|
||||
recomp_context initial_context = *ctx;
|
||||
|
||||
// Call every hook attached to the hook slot.
|
||||
const std::vector<recomp::mods::GenericFunction>& hooks = hook_table[hook_slot_index];
|
||||
for (recomp::mods::GenericFunction func : hooks) {
|
||||
// Run the hook.
|
||||
std::visit(overloaded {
|
||||
[rdram, ctx](recomp_func_t* native_func) {
|
||||
native_func(rdram, ctx);
|
||||
},
|
||||
}, func);
|
||||
|
||||
// Restore the original context.
|
||||
*ctx = initial_context;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::mods::setup_hooks(size_t num_hook_slots) {
|
||||
hook_table.resize(num_hook_slots);
|
||||
}
|
||||
|
||||
void recomp::mods::register_hook(size_t hook_slot_index, GenericFunction callback) {
|
||||
hook_table[hook_slot_index].emplace_back(callback);
|
||||
}
|
||||
|
||||
void recomp::mods::reset_hooks() {
|
||||
hook_table.clear();
|
||||
}
|
||||
|
|
@ -589,6 +589,8 @@ std::string recomp::mods::error_to_string(CodeModLoadError error) {
|
|||
return "Conflicts with other mod";
|
||||
case CodeModLoadError::DuplicateExport:
|
||||
return "Duplicate exports in mod";
|
||||
case CodeModLoadError::OfflineModHooked:
|
||||
return "Offline recompiled mod has a function hooked by another mod";
|
||||
case CodeModLoadError::NoSpecifiedApiVersion:
|
||||
return "Mod DLL does not specify an API version";
|
||||
case CodeModLoadError::UnsupportedApiVersion:
|
||||
|
|
|
|||
|
|
@ -418,7 +418,10 @@ recomp::mods::CodeModLoadError recomp::mods::DynamicLibraryCodeHandle::populate_
|
|||
return CodeModLoadError::Good;
|
||||
}
|
||||
|
||||
recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(const N64Recomp::Context& context, const ModCodeHandleInputs& inputs) {
|
||||
recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(
|
||||
const N64Recomp::Context& context, const ModCodeHandleInputs& inputs,
|
||||
std::unordered_map<size_t, size_t>&& entry_func_hooks, std::unordered_map<size_t, size_t>&& return_func_hooks)
|
||||
{
|
||||
section_addresses = std::make_unique<int32_t[]>(context.sections.size());
|
||||
base_event_index = inputs.base_event_index;
|
||||
|
||||
|
|
@ -434,6 +437,9 @@ recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle(const N64Recomp
|
|||
.trigger_event = inputs.recomp_trigger_event,
|
||||
.reference_section_addresses = inputs.reference_section_addresses,
|
||||
.local_section_addresses = section_addresses.get(),
|
||||
.run_hook = run_hook,
|
||||
.entry_func_hooks = std::move(entry_func_hooks),
|
||||
.return_func_hooks = std::move(return_func_hooks)
|
||||
};
|
||||
|
||||
N64Recomp::LiveGenerator generator{ context.functions.size(), recompiler_inputs };
|
||||
|
|
@ -815,12 +821,15 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
return ret;
|
||||
}
|
||||
|
||||
// Load the code and exports from all mods.
|
||||
std::vector<uint32_t> base_event_indices;
|
||||
base_event_indices.resize(opened_mods.size());
|
||||
|
||||
// Parse the code mods and load their binary data.
|
||||
for (size_t mod_index : loaded_code_mods) {
|
||||
uint32_t cur_ram_used = 0;
|
||||
auto& mod = opened_mods[mod_index];
|
||||
std::string cur_error_param;
|
||||
CodeModLoadError cur_error = load_mod_code(rdram, section_vrom_map, mod, load_address, cur_ram_used, cur_error_param);
|
||||
CodeModLoadError cur_error = init_mod_code(rdram, section_vrom_map, mod, load_address, cur_ram_used, cur_error_param);
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
if (cur_error_param.empty()) {
|
||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
||||
|
|
@ -832,6 +841,7 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
else {
|
||||
load_address += cur_ram_used;
|
||||
ram_used += cur_ram_used;
|
||||
base_event_indices[mod_index] = static_cast<uint32_t>(num_events);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -843,6 +853,34 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
|
||||
// Set up the event callbacks based on the number of events allocated.
|
||||
recomp::mods::setup_events(num_events);
|
||||
|
||||
// Set up the hook slots based on the number of unique hooks.
|
||||
recomp::mods::setup_hooks(hook_slots.size());
|
||||
|
||||
// Allocate room for tracking the processed hook slots.
|
||||
processed_hook_slots.clear();
|
||||
processed_hook_slots.resize(hook_slots.size());
|
||||
|
||||
// Load the code and exports from all mods.
|
||||
for (size_t mod_index : loaded_code_mods) {
|
||||
auto& mod = opened_mods[mod_index];
|
||||
std::string cur_error_param;
|
||||
CodeModLoadError cur_error = load_mod_code(rdram, mod, base_event_indices[mod_index], cur_error_param);
|
||||
if (cur_error != CodeModLoadError::Good) {
|
||||
if (cur_error_param.empty()) {
|
||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error));
|
||||
}
|
||||
else {
|
||||
ret.emplace_back(mod.manifest.mod_id, ModLoadError::FailedToLoadCode, error_to_string(cur_error) + ":" + cur_error_param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit early if errors were found.
|
||||
if (!ret.empty()) {
|
||||
unload_mods();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Resolve code dependencies for all mods.
|
||||
for (size_t mod_index : loaded_code_mods) {
|
||||
|
|
@ -865,6 +903,9 @@ std::vector<recomp::mods::ModLoadErrorDetails> recomp::mods::ModContext::load_mo
|
|||
return ret;
|
||||
}
|
||||
|
||||
// Regenerate any remaining hook slots that weren't handled during mod recompilation.
|
||||
// TODO
|
||||
|
||||
active_game = mod_game_index;
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -899,7 +940,7 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod,
|
|||
}
|
||||
}
|
||||
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, recomp::mods::ModHandle& mod, int32_t load_address, uint32_t& ram_used, std::string& error_param) {
|
||||
// Load the mod symbol data from the file provided in the manifest.
|
||||
bool binary_syms_exists = false;
|
||||
std::vector<char> syms_data = mod.manifest.file_handle->read_file(std::string{ modpaths::binary_syms_path }, binary_syms_exists);
|
||||
|
|
@ -998,10 +1039,69 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
|||
|
||||
ram_used = cur_section_addr - load_address;
|
||||
|
||||
// Allocate the event indices used by the mod.
|
||||
num_events += mod.num_events();
|
||||
|
||||
// Read the mod's hooks and allocate hook slots as needed.
|
||||
for (const N64Recomp::FunctionHook& hook : mod.recompiler_context->hooks) {
|
||||
// Get the definition of this hook.
|
||||
HookDefinition def {
|
||||
.section_rom = hook.original_section_vrom,
|
||||
.function_vram = hook.original_vram,
|
||||
.at_return = (hook.flags & N64Recomp::HookFlags::AtReturn) == N64Recomp::HookFlags::AtReturn
|
||||
};
|
||||
// Check if the hook definition already exists in the hook slots.
|
||||
auto find_it = hook_slots.find(def);
|
||||
if (find_it == hook_slots.end()) {
|
||||
// The hook definition is new, so assign a hook slot index and add it to the slots.
|
||||
hook_slots.emplace(def, hook_slots.size());
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the mod's binary into the recompiler context so it can be analyzed during code loading.
|
||||
// TODO move it instead, right now the move can't be done because of a signedness difference in the types.
|
||||
mod.recompiler_context->rom.assign(binary_span.begin(), binary_span.end());
|
||||
|
||||
return CodeModLoadError::Good;
|
||||
}
|
||||
|
||||
recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* rdram, recomp::mods::ModHandle& mod, uint32_t base_event_index, std::string& error_param) {
|
||||
// Build the hook list for this mod. Maps function index within mod to hook slot index.
|
||||
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
|
||||
for (const auto& replacement : mod.recompiler_context->replacements) {
|
||||
// Check if there's a hook slot for the entry of this function.
|
||||
HookDefinition entry_def {
|
||||
.section_rom = replacement.original_section_vrom,
|
||||
.function_vram = replacement.original_vram,
|
||||
.at_return = false
|
||||
};
|
||||
auto find_entry_it = hook_slots.find(entry_def);
|
||||
if (find_entry_it != hook_slots.end()) {
|
||||
entry_func_hooks.emplace(replacement.func_index, find_entry_it->second);
|
||||
processed_hook_slots[find_entry_it->second] = true;
|
||||
}
|
||||
|
||||
// Check if there's a hook slot for the return of this function.
|
||||
HookDefinition return_def {
|
||||
.section_rom = replacement.original_section_vrom,
|
||||
.function_vram = replacement.original_vram,
|
||||
.at_return = true
|
||||
};
|
||||
auto find_return_it = hook_slots.find(return_def);
|
||||
if (find_return_it != hook_slots.end()) {
|
||||
return_func_hooks.emplace(replacement.func_index, find_return_it->second);
|
||||
processed_hook_slots[find_return_it->second] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the inputs for the mod code handle.
|
||||
std::string cur_error_param;
|
||||
CodeModLoadError cur_error;
|
||||
ModCodeHandleInputs handle_inputs{
|
||||
.base_event_index = static_cast<uint32_t>(num_events),
|
||||
.base_event_index = base_event_index,
|
||||
.recomp_trigger_event = recomp_trigger_event,
|
||||
.get_function = get_function,
|
||||
.cop0_status_write = cop0_status_write,
|
||||
|
|
@ -1011,17 +1111,15 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
|||
.reference_section_addresses = section_addresses,
|
||||
};
|
||||
|
||||
// Allocate the event indices used by the mod.
|
||||
num_events += mod.num_events();
|
||||
|
||||
// Copy the mod's binary into the recompiler context so it can be analyzed during code loading.
|
||||
// TODO move it instead, right now the move can't be done because of a signedness difference in the types.
|
||||
mod.recompiler_context->rom.assign(binary_span.begin(), binary_span.end());
|
||||
|
||||
// Use a dynamic library code handle. This feature isn't meant to be used by end users, but provides a more debuggable
|
||||
// experience than the live recompiler for mod developers.
|
||||
// Enabled if the mod's filename ends with ".offline.dll".
|
||||
if (mod.manifest.mod_root_path.filename().string().ends_with(".offline.nrm")) {
|
||||
// Hooks can't be generated for native mods, so return an error if any of the functions this mod replaces are also hooked by another mod.
|
||||
if (!entry_func_hooks.empty() || !return_func_hooks.empty()) {
|
||||
return CodeModLoadError::OfflineModHooked;
|
||||
}
|
||||
|
||||
std::filesystem::path dll_path = mod.manifest.mod_root_path;
|
||||
dll_path.replace_extension(DynamicLibrary::PlatformExtension);
|
||||
mod.code_handle = std::make_unique<DynamicLibraryCodeHandle>(dll_path, *mod.recompiler_context, handle_inputs);
|
||||
|
|
@ -1045,7 +1143,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
|||
}
|
||||
// Live recompiler code handle.
|
||||
else {
|
||||
mod.code_handle = std::make_unique<LiveRecompilerCodeHandle>(*mod.recompiler_context, handle_inputs);
|
||||
mod.code_handle = std::make_unique<LiveRecompilerCodeHandle>(*mod.recompiler_context, handle_inputs, std::move(entry_func_hooks), std::move(return_func_hooks));
|
||||
|
||||
if (!mod.code_handle->good()) {
|
||||
mod.code_handle.reset();
|
||||
|
|
@ -1064,6 +1162,8 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t*
|
|||
}
|
||||
}
|
||||
|
||||
const std::vector<N64Recomp::Section>& mod_sections = mod.recompiler_context->sections;
|
||||
|
||||
// Add each function from the mod into the function lookup table.
|
||||
for (size_t func_index = 0; func_index < mod.recompiler_context->functions.size(); func_index++) {
|
||||
const auto& func = mod.recompiler_context->functions[func_index];
|
||||
|
|
@ -1176,6 +1276,28 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci
|
|||
recomp::mods::register_event_callback(event_index, func);
|
||||
}
|
||||
|
||||
// Register hooks.
|
||||
for (const auto& cur_hook : mod.recompiler_context->hooks) {
|
||||
// Get the definition of this hook.
|
||||
HookDefinition def {
|
||||
.section_rom = cur_hook.original_section_vrom,
|
||||
.function_vram = cur_hook.original_vram,
|
||||
.at_return = (cur_hook.flags & N64Recomp::HookFlags::AtReturn) == N64Recomp::HookFlags::AtReturn
|
||||
};
|
||||
|
||||
// Find the hook's slot from the definition.
|
||||
auto find_it = hook_slots.find(def);
|
||||
if (find_it == hook_slots.end()) {
|
||||
error_param = "Failed to register hook";
|
||||
// This should never happen, as hooks are scanned earlier to generate hook_slots.
|
||||
return CodeModLoadError::InternalError;
|
||||
}
|
||||
|
||||
// Register the function handle for this hook slot.
|
||||
GenericFunction func = mod.code_handle->get_function_handle(cur_hook.func_index);
|
||||
recomp::mods::register_hook(find_it->second, func);
|
||||
}
|
||||
|
||||
// Populate the relocated section addresses for the mod.
|
||||
for (size_t section_index = 0; section_index < mod.section_load_addresses.size(); section_index++) {
|
||||
mod.code_handle->set_local_section_address(section_index, mod.section_load_addresses[section_index]);
|
||||
|
|
@ -1232,7 +1354,10 @@ void recomp::mods::ModContext::unload_mods() {
|
|||
}
|
||||
patched_funcs.clear();
|
||||
loaded_mods_by_id.clear();
|
||||
hook_slots.clear();
|
||||
processed_hook_slots.clear();
|
||||
recomp::mods::reset_events();
|
||||
recomp::mods::reset_hooks();
|
||||
num_events = recomp::overlays::num_base_events();
|
||||
active_game = (size_t)-1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -460,13 +460,13 @@ void init(uint8_t* rdram, recomp_context* ctx, gpr entrypoint) {
|
|||
|
||||
// Initialize variables normally set by IPL3
|
||||
constexpr int32_t osTvType = 0x80000300;
|
||||
constexpr int32_t osRomType = 0x80000304;
|
||||
//constexpr int32_t osRomType = 0x80000304;
|
||||
constexpr int32_t osRomBase = 0x80000308;
|
||||
constexpr int32_t osResetType = 0x8000030c;
|
||||
constexpr int32_t osCicId = 0x80000310;
|
||||
constexpr int32_t osVersion = 0x80000314;
|
||||
//constexpr int32_t osCicId = 0x80000310;
|
||||
//constexpr int32_t osVersion = 0x80000314;
|
||||
constexpr int32_t osMemSize = 0x80000318;
|
||||
constexpr int32_t osAppNMIBuffer = 0x8000031c;
|
||||
//constexpr int32_t osAppNMIBuffer = 0x8000031c;
|
||||
MEM_W(osTvType, 0) = 1; // NTSC
|
||||
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
|
||||
MEM_W(osResetType, 0) = 0; // cold reset
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue