Update codebase with changes for Zelda 64: Recompiled 1.2

This commit is contained in:
Mr-Wiseguy
2025-06-03 01:14:09 -04:00
parent 6a10095c88
commit 1e19dad587
71 changed files with 4913 additions and 674 deletions
+30 -8
View File
@@ -2,6 +2,7 @@
#include "recomp_input.h"
#include "banjo_sound.h"
#include "banjo_render.h"
#include "banjo_support.h"
#include "ultramodern/config.hpp"
#include "librecomp/files.hpp"
#include <filesystem>
@@ -13,6 +14,8 @@
#elif defined(__linux__)
#include <unistd.h>
#include <pwd.h>
#elif defined(__APPLE__)
#include "apple/rt64_apple.h"
#endif
constexpr std::u8string_view general_filename = u8"general.json";
@@ -71,7 +74,7 @@ T from_or_default(const json& j, const std::string& key, T default_value) {
else {
ret = default_value;
}
return ret;
}
@@ -129,11 +132,19 @@ namespace recomp {
}
std::filesystem::path banjo::get_app_folder_path() {
// directly check for portable.txt (windows and native linux binary)
// directly check for portable.txt (windows and native linux binary)
if (std::filesystem::exists("portable.txt")) {
return std::filesystem::current_path();
}
#if defined(__APPLE__)
// Check for portable file in the directory containing the app bundle.
const auto app_bundle_path = banjo::get_bundle_directory().parent_path();
if (std::filesystem::exists(app_bundle_path / "portable.txt")) {
return app_bundle_path;
}
#endif
std::filesystem::path recomp_dir{};
#if defined(_WIN32)
@@ -145,16 +156,27 @@ std::filesystem::path banjo::get_app_folder_path() {
}
CoTaskMemFree(known_path);
#elif defined(__linux__)
// check for APP_FOLDER_PATH env var used by AppImage
#elif defined(__linux__) || defined(__APPLE__)
// check for APP_FOLDER_PATH env var
if (getenv("APP_FOLDER_PATH") != nullptr) {
return std::filesystem::path{getenv("APP_FOLDER_PATH")};
}
#if defined(__APPLE__)
const auto supportdir = banjo::get_application_support_directory();
if (supportdir) {
return *supportdir / banjo::program_id;
}
#endif
const char *homedir;
if ((homedir = getenv("HOME")) == nullptr) {
#if defined(__linux__)
homedir = getpwuid(getuid())->pw_dir;
#elif defined(__APPLE__)
homedir = GetHomeDirectory();
#endif
}
if (homedir != nullptr) {
@@ -206,7 +228,7 @@ bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::j
return recomp::finalize_output_file_with_backup(path);
}
bool save_general_config(const std::filesystem::path& path) {
bool save_general_config(const std::filesystem::path& path) {
nlohmann::json config_json{};
recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode());
@@ -218,7 +240,7 @@ bool save_general_config(const std::filesystem::path& path) {
config_json["analog_cam_mode"] = banjo::get_analog_cam_mode();
config_json["analog_camera_invert_mode"] = banjo::get_analog_camera_invert_mode();
config_json["debug_mode"] = banjo::get_debug_mode_enabled();
return save_json_with_backups(path, config_json);
}
@@ -433,7 +455,7 @@ bool save_sound_config(const std::filesystem::path& path) {
config_json["main_volume"] = banjo::get_main_volume();
config_json["bgm_volume"] = banjo::get_bgm_volume();
return save_json_with_backups(path, config_json);
}
@@ -494,7 +516,7 @@ void banjo::save_config() {
}
std::filesystem::create_directories(recomp_dir);
// TODO error handling for failing to save config files.
save_general_config(recomp_dir / general_filename);
+129 -102
View File
@@ -31,7 +31,7 @@ static struct {
std::mutex cur_controllers_mutex;
std::vector<SDL_GameController*> cur_controllers{};
std::unordered_map<SDL_JoystickID, ControllerState> controller_states;
std::array<float, 2> rotation_delta{};
std::array<float, 2> mouse_delta{};
std::mutex pending_input_mutex;
@@ -42,6 +42,10 @@ static struct {
bool rumble_active;
} InputState;
static struct {
std::list<std::filesystem::path> files_dropped;
} DropState;
std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
std::atomic<recomp::InputField> scanned_input;
@@ -93,85 +97,82 @@ bool should_override_keystate(SDL_Scancode key, SDL_Keymod mod) {
}
}
return false;
return false;
}
bool sdl_event_filter(void* userdata, SDL_Event* event) {
switch (event->type) {
case SDL_EventType::SDL_KEYDOWN:
{
SDL_KeyboardEvent* keyevent = &event->key;
{
SDL_KeyboardEvent* keyevent = &event->key;
// Skip repeated events when not in the menu
if (!recompui::is_context_taking_input() &&
event->key.repeat) {
break;
}
// Skip repeated events when not in the menu
if (!recompui::is_context_capturing_input() &&
event->key.repeat) {
break;
}
if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) ||
keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11
if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) ||
keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11
) {
recompui::toggle_fullscreen();
recompui::toggle_fullscreen();
}
if (scanning_device != recomp::InputDevice::COUNT) {
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
recomp::cancel_scanning_input();
}
if (scanning_device != recomp::InputDevice::COUNT) {
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
recomp::cancel_scanning_input();
} else if (scanning_device == recomp::InputDevice::Keyboard) {
set_scanned_input({(uint32_t)InputType::Keyboard, keyevent->keysym.scancode});
}
} else {
if (!should_override_keystate(keyevent->keysym.scancode, static_cast<SDL_Keymod>(keyevent->keysym.mod))) {
queue_if_enabled(event);
}
else if (scanning_device == recomp::InputDevice::Keyboard) {
set_scanned_input({ (uint32_t)InputType::Keyboard, keyevent->keysym.scancode });
}
}
break;
else {
if (!should_override_keystate(keyevent->keysym.scancode, static_cast<SDL_Keymod>(keyevent->keysym.mod))) {
queue_if_enabled(event);
}
}
}
break;
case SDL_EventType::SDL_CONTROLLERDEVICEADDED:
{
SDL_ControllerDeviceEvent* controller_event = &event->cdevice;
SDL_GameController* controller = SDL_GameControllerOpen(controller_event->which);
printf("Controller added: %d\n", controller_event->which);
if (controller != nullptr) {
printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)));
ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))];
state.controller = controller;
{
SDL_ControllerDeviceEvent* controller_event = &event->cdevice;
SDL_GameController* controller = SDL_GameControllerOpen(controller_event->which);
printf("Controller added: %d\n", controller_event->which);
if (controller != nullptr) {
printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)));
ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))];
state.controller = controller;
if (SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_GYRO) && SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_ACCEL)) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_GYRO, SDL_TRUE);
SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE);
}
if (SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_GYRO) && SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_ACCEL)) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_GYRO, SDL_TRUE);
SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE);
}
}
break;
}
break;
case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED:
{
SDL_ControllerDeviceEvent* controller_event = &event->cdevice;
printf("Controller removed: %d\n", controller_event->which);
InputState.controller_states.erase(controller_event->which);
}
break;
{
SDL_ControllerDeviceEvent* controller_event = &event->cdevice;
printf("Controller removed: %d\n", controller_event->which);
InputState.controller_states.erase(controller_event->which);
}
break;
case SDL_EventType::SDL_QUIT: {
if (!ultramodern::is_game_started()) {
ultramodern::quit();
return true;
}
recompui::ContextId config_context_id = recompui::get_config_context_id();
if (!recompui::is_context_shown(config_context_id)) {
recompui::show_context(config_context_id, "");
}
banjo::open_quit_game_prompt();
recompui::activate_mouse();
break;
}
case SDL_EventType::SDL_MOUSEWHEEL:
{
SDL_MouseWheelEvent* wheel_event = &event->wheel;
InputState.mouse_wheel_pos.fetch_add(wheel_event->y * (wheel_event->direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1));
}
queue_if_enabled(event);
break;
{
SDL_MouseWheelEvent* wheel_event = &event->wheel;
InputState.mouse_wheel_pos.fetch_add(wheel_event->y * (wheel_event->direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1));
}
queue_if_enabled(event);
break;
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN:
if (scanning_device != recomp::InputDevice::COUNT) {
auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
@@ -180,22 +181,24 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
if ((menuToggleBinding0.input_type != 0 && event->cbutton.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != 0 && event->cbutton.button == menuToggleBinding1.input_id)) {
recomp::cancel_scanning_input();
} else if (scanning_device == recomp::InputDevice::Controller) {
}
else if (scanning_device == recomp::InputDevice::Controller) {
SDL_ControllerButtonEvent* button_event = &event->cbutton;
auto scanned_input_index = recomp::get_scanned_input_index();
if ((scanned_input_index == static_cast<int>(recomp::GameInput::TOGGLE_MENU) ||
scanned_input_index == static_cast<int>(recomp::GameInput::ACCEPT_MENU) ||
scanned_input_index == static_cast<int>(recomp::GameInput::APPLY_MENU)) && (
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP ||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN ||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) {
scanned_input_index == static_cast<int>(recomp::GameInput::ACCEPT_MENU) ||
scanned_input_index == static_cast<int>(recomp::GameInput::APPLY_MENU)) && (
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP ||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN ||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) {
break;
}
set_scanned_input({(uint32_t)InputType::ControllerDigital, button_event->button});
set_scanned_input({ (uint32_t)InputType::ControllerDigital, button_event->button });
}
} else {
}
else {
queue_if_enabled(event);
}
break;
@@ -217,8 +220,8 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
set_stick_return_event.user.data1 = nullptr;
set_stick_return_event.user.data2 = nullptr;
recompui::queue_event(set_stick_return_event);
set_scanned_input({(uint32_t)InputType::ControllerAnalog, axis_event->axis + 1});
set_scanned_input({ (uint32_t)InputType::ControllerAnalog, axis_event->axis + 1 });
}
else if (axis_value < -axis_threshold) {
SDL_Event set_stick_return_event;
@@ -228,9 +231,10 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
set_stick_return_event.user.data2 = nullptr;
recompui::queue_event(set_stick_return_event);
set_scanned_input({(uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1});
set_scanned_input({ (uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1 });
}
} else {
}
else {
queue_if_enabled(event);
}
break;
@@ -276,6 +280,22 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
InputState.pending_mouse_delta[0] += motion_event->xrel;
InputState.pending_mouse_delta[1] += motion_event->yrel;
}
queue_if_enabled(event);
break;
case SDL_EventType::SDL_DROPBEGIN:
DropState.files_dropped.clear();
break;
case SDL_EventType::SDL_DROPFILE:
DropState.files_dropped.emplace_back(std::filesystem::path(std::u8string_view((const char8_t*)(event->drop.file))));
SDL_free(event->drop.file);
break;
case SDL_EventType::SDL_DROPCOMPLETE:
recompui::drop_files(DropState.files_dropped);
break;
case SDL_EventType::SDL_CONTROLLERBUTTONUP:
// Always queue button up events to avoid missing them during binding.
recompui::queue_event(*event);
break;
default:
queue_if_enabled(event);
break;
@@ -285,6 +305,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
void recomp::handle_events() {
SDL_Event cur_event;
static bool started = false;
static bool exited = false;
while (SDL_PollEvent(&cur_event) && !exited) {
exited = sdl_event_filter(nullptr, &cur_event);
@@ -301,6 +322,11 @@ void recomp::handle_events() {
SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE);
SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE);
}
if (!started && ultramodern::is_game_started()) {
started = true;
recompui::process_game_started();
}
}
constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A;
@@ -465,7 +491,7 @@ void recomp::poll_inputs() {
// Read the deltas while resetting them to zero.
{
std::lock_guard lock{ InputState.pending_input_mutex };
InputState.rotation_delta = InputState.pending_rotation_delta;
InputState.pending_rotation_delta = { 0.0f, 0.0f };
@@ -482,14 +508,14 @@ void recomp::set_rumble(int controller_num, bool on) {
ultramodern::input::connected_device_info_t recomp::get_connected_device_info(int controller_num) {
switch (controller_num) {
case 0:
return ultramodern::input::connected_device_info_t {
.connected_device = ultramodern::input::Device::Controller,
.connected_pak = ultramodern::input::Pak::RumblePak,
};
case 0:
return ultramodern::input::connected_device_info_t{
.connected_device = ultramodern::input::Device::Controller,
.connected_pak = ultramodern::input::Pak::RumblePak,
};
}
return ultramodern::input::connected_device_info_t {
return ultramodern::input::connected_device_info_t{
.connected_device = ultramodern::input::Device::None,
.connected_pak = ultramodern::input::Pak::None,
};
@@ -506,7 +532,8 @@ void recomp::update_rumble() {
if (InputState.rumble_active) {
InputState.cur_rumble += 0.17f;
if (InputState.cur_rumble > 1) InputState.cur_rumble = 1;
} else {
}
else {
InputState.cur_rumble *= 0.92f;
InputState.cur_rumble -= 0.01f;
if (InputState.cur_rumble < 0) InputState.cur_rumble = 0;
@@ -647,13 +674,13 @@ void recomp::get_mouse_deltas(float* x, float* y) {
void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out) {
float joystick_deadzone = (float)recomp::get_joystick_deadzone() / 100.0f;
if(fabsf(x_in) < joystick_deadzone) {
if (fabsf(x_in) < joystick_deadzone) {
x_in = 0.0f;
}
else {
if(x_in > 0.0f) {
if (x_in > 0.0f) {
x_in -= joystick_deadzone;
}
}
else {
x_in += joystick_deadzone;
}
@@ -661,13 +688,13 @@ void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float
x_in /= (1.0f - joystick_deadzone);
}
if(fabsf(y_in) < joystick_deadzone) {
if (fabsf(y_in) < joystick_deadzone) {
y_in = 0.0f;
}
else {
if(y_in > 0.0f) {
if (y_in > 0.0f) {
y_in -= joystick_deadzone;
}
}
else {
y_in += joystick_deadzone;
}
@@ -695,7 +722,7 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
bool recomp::game_input_disabled() {
// Disable input if any menu that blocks input is open.
return recompui::is_context_taking_input();
return recompui::is_context_capturing_input();
}
bool recomp::all_input_disabled() {
@@ -735,16 +762,16 @@ std::string controller_button_to_string(SDL_GameControllerButton button) {
return PF_DPAD_LEFT;
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
return PF_DPAD_RIGHT;
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3:
// return "";
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4:
// return "";
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_TOUCHPAD:
return PF_SONY_TOUCHPAD;
default:
@@ -752,7 +779,7 @@ std::string controller_button_to_string(SDL_GameControllerButton button) {
}
}
std::unordered_map<SDL_Scancode, std::string> scancode_codepoints {
std::unordered_map<SDL_Scancode, std::string> scancode_codepoints{
{SDL_SCANCODE_LEFT, PF_KEYBOARD_LEFT},
// NOTE: UP and RIGHT are swapped with promptfont.
{SDL_SCANCODE_UP, PF_KEYBOARD_RIGHT},
@@ -856,15 +883,15 @@ std::string controller_axis_to_string(int axis) {
std::string recomp::InputField::to_string() const {
switch ((InputType)input_type) {
case InputType::None:
return "";
case InputType::ControllerDigital:
return controller_button_to_string((SDL_GameControllerButton)input_id);
case InputType::ControllerAnalog:
return controller_axis_to_string(input_id);
case InputType::Keyboard:
return keyboard_input_to_string((SDL_Scancode)input_id);
default:
return std::to_string(input_type) + "," + std::to_string(input_id);
case InputType::None:
return "";
case InputType::ControllerDigital:
return controller_button_to_string((SDL_GameControllerButton)input_id);
case InputType::ControllerAnalog:
return controller_axis_to_string(input_id);
case InputType::Keyboard:
return keyboard_input_to_string((SDL_Scancode)input_id);
default:
return std::to_string(input_type) + "," + std::to_string(input_id);
}
}
@@ -8,7 +8,7 @@
#include "ultramodern/error_handling.hpp"
#include "recomp_ui.h"
#include "recomp_data.h"
#include "../patches/mem_funcs.h"
#include "../patches/actor_funcs.h"
struct ExtensionInfo {
// Either the actor's type ID, or 0xFFFFFFFF if this is for generic data.
@@ -41,7 +41,7 @@ bool can_register = false;
size_t alloc_count = 0;
size_t free_count = 0;
void recomp::init_extended_actor_data() {
void recomputil::init_extended_actor_data() {
std::lock_guard lock{ actor_data_mutex };
actor_data_sizes.clear();
@@ -54,7 +54,7 @@ void recomp::init_extended_actor_data() {
actor_extensions.push_back({});
}
void recomp::reset_actor_data() {
void recomputil::reset_actor_data() {
std::lock_guard lock{ actor_data_mutex };
actor_data.reset();
actor_spawn_count = 0;
@@ -113,7 +113,7 @@ extern "C" void recomp_register_actor_extension_generic(uint8_t* rdram, recomp_c
extern "C" void recomp_clear_all_actor_data(uint8_t* rdram, recomp_context* ctx) {
(void)rdram;
(void)ctx;
recomp::reset_actor_data();
recomputil::reset_actor_data();
}
extern "C" void recomp_create_actor_data(uint8_t* rdram, recomp_context* ctx) {
+727
View File
@@ -0,0 +1,727 @@
#include <vector>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include "slot_map.h"
#include "recomp_data.h"
#include "recomp_ui.h"
#include "librecomp/helpers.hpp"
#include "librecomp/overlays.hpp"
#include "librecomp/addresses.hpp"
#include "ultramodern/error_handling.hpp"
template <typename KeyType, typename ValueType>
class LockedMap {
private:
std::mutex mutex{};
std::unordered_map<KeyType, ValueType> map{};
public:
bool get(const KeyType& key, ValueType& out) {
std::lock_guard lock{mutex};
auto find_it = map.find(key);
if (find_it == map.end()) {
return false;
}
out = find_it->second;
return true;
}
bool insert(const KeyType& key, ValueType val) {
std::lock_guard lock{mutex};
auto ret = map.insert_or_assign(key, val);
return ret.second;
}
bool erase(const KeyType& key) {
std::lock_guard lock{mutex};
size_t num_erased = map.erase(key);
return num_erased != 0;
}
void clear() {
std::lock_guard lock{mutex};
map.clear();
}
bool erase_first(ValueType& out) {
std::lock_guard lock{mutex};
auto it = map.begin();
if (it == map.end()) {
return false;
}
out = it->second;
map.erase(it);
return true;
}
bool contains(const KeyType& key) {
std::lock_guard lock{mutex};
return map.contains(key);
}
size_t size() {
std::lock_guard lock{mutex};
return map.size();
}
};
template <typename KeyType>
class LockedSet {
private:
std::mutex mutex{};
std::unordered_set<KeyType> set{};
public:
bool contains(const KeyType& key) {
std::lock_guard lock{mutex};
return set.contains(key);
}
bool insert(const KeyType& key) {
std::lock_guard lock{mutex};
auto it = set.insert(key);
return it.second;
}
bool erase(const KeyType& key) {
std::lock_guard lock{mutex};
size_t num_erased = set.erase(key);
return num_erased != 0;
}
void clear() {
std::lock_guard lock{mutex};
set.clear();
}
size_t size() {
std::lock_guard lock{mutex};
return set.size();
}
};
template <typename ValueType>
class LockedSlotmap {
private:
std::mutex mutex{};
dod::slot_map32<ValueType> map{};
using key_t = typename dod::slot_map32<ValueType>::key;
public:
bool get(uint32_t key, ValueType** out) {
std::lock_guard lock{mutex};
ValueType* ret = map.get(key_t{key});
*out = ret;
return ret != nullptr;
}
uint32_t create() {
std::lock_guard lock{mutex};
return map.emplace().raw;
}
bool erase(uint32_t key) {
std::lock_guard lock{mutex};
if (!map.has_key(key_t{ key })) {
return false;
}
map.erase(key_t{ key });
return true;
}
void clear() {
std::lock_guard lock{mutex};
map.clear();
}
bool erase_first(ValueType& out) {
std::lock_guard lock{mutex};
auto it = map.items().begin();
if (it == map.items().end()) {
return false;
}
out = it->second;
map.erase(it->first);
return true;
}
size_t size() {
std::lock_guard lock{mutex};
return map.size();
}
};
using U32ValueMap = LockedMap<uint32_t, uint32_t>;
using U32MemoryMap = std::pair<LockedMap<uint32_t, PTR(void)>, u32>;
using U32HashSet = LockedSet<uint32_t>;
using U32Slotmap = LockedSlotmap<uint32_t>;
using MemorySlotmap = std::pair<LockedSlotmap<PTR(void)>, u32>;
LockedSlotmap<U32ValueMap> u32_value_hashmaps{};
LockedSlotmap<U32MemoryMap> u32_memory_hashmaps{};
LockedSlotmap<U32HashSet> u32_hashsets{};
LockedSlotmap<U32Slotmap> u32_slotmaps{};
LockedSlotmap<MemorySlotmap> memory_slotmaps{};
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
static void show_fatal_error_message_box(const char* funcname, const char* errstr) {
std::string message = std::string{"Fatal error in mod - "} + funcname + " : " + errstr;
recompui::message_box(message.c_str());
}
#define HANDLE_INVALID_ERROR() \
show_fatal_error_message_box(__FUNCTION__, "handle is invalid"); \
assert(false); \
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
#define SLOTMAP_KEY_INVALID_ERROR() \
show_fatal_error_message_box(__FUNCTION__, "slotmap key is invalid"); \
assert(false); \
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
// u32 -> 32-bit value hashmap.
void recomputil_create_u32_value_hashmap(uint8_t* rdram, recomp_context* ctx) {
(void)rdram;
_return(ctx, u32_value_hashmaps.create());
}
void recomputil_destroy_u32_value_hashmap(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
if (!u32_value_hashmaps.erase(mapkey)) {
HANDLE_INVALID_ERROR();
}
}
void recomputil_u32_value_hashmap_contains(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32ValueMap* map;
if (!u32_value_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, map->contains(key));
}
void recomputil_u32_value_hashmap_insert(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
uint32_t value = _arg<2, uint32_t>(rdram, ctx);
U32ValueMap* map;
if (!u32_value_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, map->insert(key, value));
}
void recomputil_u32_value_hashmap_get(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx);
U32ValueMap* map;
if (!u32_value_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
uint32_t ret;
if (map->get(key, ret)) {
MEM_W(0, val_out) = ret;
_return(ctx, 1);
return;
}
else {
_return(ctx, 0);
return;
}
}
void recomputil_u32_value_hashmap_erase(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32ValueMap* map;
if (!u32_value_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, map->erase(key));
}
void recomputil_u32_value_hashmap_size(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
U32ValueMap* map;
if (!u32_value_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, static_cast<uint32_t>(map->size()));
}
// u32 -> memory hashmap.
void recomputil_create_u32_memory_hashmap(uint8_t* rdram, recomp_context* ctx) {
uint32_t element_size = _arg<0, uint32_t>(rdram, ctx);
// Create the map.
uint32_t map_key = u32_memory_hashmaps.create();
// Retrieve the map and set its element size to the provided value.
U32MemoryMap* map;
u32_memory_hashmaps.get(map_key, &map);
map->second = element_size;
// Return the created map's key.
_return(ctx, map_key);
}
void recomputil_destroy_u32_memory_hashmap(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
// Retrieve the map.
U32MemoryMap* map;
if (!u32_memory_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
// Free all of the entries in the map.
PTR(void) cur_mem;
while (map->first.erase_first(cur_mem)) {
recomp::free(rdram, TO_PTR(void, cur_mem));
}
// Destroy the map itself.
u32_memory_hashmaps.erase(mapkey);
}
void recomputil_u32_memory_hashmap_contains(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32MemoryMap* map;
if (!u32_memory_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, map->first.contains(key));
}
void recomputil_u32_memory_hashmap_create(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32MemoryMap* map;
if (!u32_memory_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
// Check if the map contains the key already to prevent inserting it twice.
PTR(void) dummy;
if (map->first.get(key, dummy)) {
_return(ctx, 0);
return;
}
// Allocate the map's size and return the pointer.
void* mem = recomp::alloc(rdram, map->second);
gpr addr = reinterpret_cast<uint8_t*>(mem) - rdram + 0xFFFFFFFF80000000ULL;
// Zero the memory.
for (size_t i = 0; i < map->second; i++) {
MEM_B(i, addr) = 0;
}
PTR(void) ret = static_cast<PTR(void)>(addr);
map->first.insert(key, ret);
_return(ctx, 1);
}
void recomputil_u32_memory_hashmap_get(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32MemoryMap* map;
if (!u32_memory_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
PTR(void) ret;
if (map->first.get(key, ret)) {
_return(ctx, ret);
return;
}
else {
_return(ctx, NULLPTR);
return;
}
}
void recomputil_u32_memory_hashmap_erase(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32MemoryMap* map;
if (!u32_memory_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
// Free the memory for this key if the key exists.
PTR(void) addr;
bool has_value = map->first.get(key, addr);
if (has_value) {
void* mem = TO_PTR(void, addr);
recomp::free(rdram, mem);
}
_return(ctx, map->first.erase(key));
}
void recomputil_u32_memory_hashmap_size(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
U32MemoryMap* map;
if (!u32_memory_hashmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, static_cast<uint32_t>(map->first.size()));
}
// u32 hashset.
void recomputil_create_u32_hashset(uint8_t* rdram, recomp_context* ctx) {
(void)rdram;
_return(ctx, u32_hashsets.create());
}
void recomputil_destroy_u32_hashset(uint8_t* rdram, recomp_context* ctx) {
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
if (!u32_hashsets.erase(setkey)) {
HANDLE_INVALID_ERROR();
}
}
void recomputil_u32_hashset_contains(uint8_t* rdram, recomp_context* ctx) {
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32HashSet* set;
if (!u32_hashsets.get(setkey, &set)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, set->contains(key));
}
void recomputil_u32_hashset_insert(uint8_t* rdram, recomp_context* ctx) {
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32HashSet* set;
if (!u32_hashsets.get(setkey, &set)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, set->insert(key));
}
void recomputil_u32_hashset_erase(uint8_t* rdram, recomp_context* ctx) {
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32HashSet* set;
if (!u32_hashsets.get(setkey, &set)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, set->erase(key));
}
void recomputil_u32_hashset_size(uint8_t* rdram, recomp_context* ctx) {
uint32_t setkey = _arg<0, uint32_t>(rdram, ctx);
U32HashSet* set;
if (!u32_hashsets.get(setkey, &set)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, static_cast<uint32_t>(set->size()));
}
// u32 value slotmap.
void recomputil_create_u32_slotmap(uint8_t* rdram, recomp_context* ctx) {
(void)rdram;
_return(ctx, u32_slotmaps.create());
}
void recomputil_destroy_u32_slotmap(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
if (!u32_slotmaps.erase(mapkey)) {
HANDLE_INVALID_ERROR();
}
}
void recomputil_u32_slotmap_contains(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32Slotmap* map;
if (!u32_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
uint32_t* dummy_ptr;
_return(ctx, map->get(key, &dummy_ptr));
}
void recomputil_u32_slotmap_create(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
U32Slotmap* map;
if (!u32_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, map->create());
}
void recomputil_u32_slotmap_get(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx);
U32Slotmap* map;
if (!u32_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
uint32_t* ret;
if (!map->get(key, &ret)) {
_return(ctx, 0);
}
MEM_W(0, val_out) = *ret;
_return(ctx, 1);
}
void recomputil_u32_slotmap_set(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
uint32_t value = _arg<2, uint32_t>(rdram, ctx);
U32Slotmap* map;
if (!u32_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
uint32_t* value_ptr;
if (!map->get(key, &value_ptr)) {
_return(ctx, 0);
}
*value_ptr = value;
_return(ctx, 1);
}
void recomputil_u32_slotmap_erase(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
U32Slotmap* map;
if (!u32_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
if (!map->erase(key)) {
_return(ctx, 0);
}
_return(ctx, 1);
}
void recomputil_u32_slotmap_size(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
U32Slotmap* map;
if (!u32_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, static_cast<uint32_t>(map->size()));
}
// memory slotmap.
void recomputil_create_memory_slotmap(uint8_t* rdram, recomp_context* ctx) {
(void)rdram;
_return(ctx, memory_slotmaps.create());
}
void recomputil_destroy_memory_slotmap(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
// Retrieve the map.
MemorySlotmap* map;
if (!memory_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
// Free all of the entries in the map.
PTR(void) cur_mem;
while (map->first.erase_first(cur_mem)) {
recomp::free(rdram, TO_PTR(void, cur_mem));
}
// Destroy the map itself.
memory_slotmaps.erase(mapkey);
}
void recomputil_memory_slotmap_contains(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
MemorySlotmap* map;
if (!memory_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
PTR(void)* dummy_ptr;
_return(ctx, map->first.get(key, &dummy_ptr));
}
void recomputil_memory_slotmap_create(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
MemorySlotmap* map;
if (!memory_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
// Create the slotmap element.
u32 key = map->first.create();
// Allocate the map's element size.
void* mem = recomp::alloc(rdram, map->second);
gpr addr = reinterpret_cast<uint8_t*>(mem) - rdram + 0xFFFFFFFF80000000ULL;
// Zero the memory.
for (size_t i = 0; i < map->second; i++) {
MEM_B(i, addr) = 0;
}
// Store the allocated pointer.
PTR(void)* value_ptr;
map->first.get(key, &value_ptr);
MEM_W(0, *value_ptr) = addr;
// Return the key.
_return(ctx, key);
}
void recomputil_memory_slotmap_get(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
PTR(uint32_t) val_out = _arg<2, PTR(uint32_t)>(rdram, ctx);
MemorySlotmap* map;
if (!memory_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
PTR(void)* ret;
if (!map->first.get(key, &ret)) {
SLOTMAP_KEY_INVALID_ERROR();
}
MEM_W(0, val_out) = *ret;
}
void recomputil_memory_slotmap_erase(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
uint32_t key = _arg<1, uint32_t>(rdram, ctx);
MemorySlotmap* map;
if (!memory_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
// Free the memory for this key if the key exists.
PTR(void)* addr;
bool has_value = map->first.get(key, &addr);
if (has_value) {
void* mem = TO_PTR(void, addr);
recomp::free(rdram, mem);
}
_return(ctx, map->first.erase(key));
}
void recomputil_memory_slotmap_size(uint8_t* rdram, recomp_context* ctx) {
uint32_t mapkey = _arg<0, uint32_t>(rdram, ctx);
MemorySlotmap* map;
if (!memory_slotmaps.get(mapkey, &map)) {
HANDLE_INVALID_ERROR();
}
_return(ctx, static_cast<uint32_t>(map->first.size()));
}
// Exports.
void recomputil::register_data_api_exports() {
REGISTER_FUNC(recomputil_create_u32_value_hashmap);
REGISTER_FUNC(recomputil_destroy_u32_value_hashmap);
REGISTER_FUNC(recomputil_u32_value_hashmap_contains);
REGISTER_FUNC(recomputil_u32_value_hashmap_insert);
REGISTER_FUNC(recomputil_u32_value_hashmap_get);
REGISTER_FUNC(recomputil_u32_value_hashmap_erase);
REGISTER_FUNC(recomputil_u32_value_hashmap_size);
REGISTER_FUNC(recomputil_create_u32_memory_hashmap);
REGISTER_FUNC(recomputil_destroy_u32_memory_hashmap);
REGISTER_FUNC(recomputil_u32_memory_hashmap_contains);
REGISTER_FUNC(recomputil_u32_memory_hashmap_create);
REGISTER_FUNC(recomputil_u32_memory_hashmap_get);
REGISTER_FUNC(recomputil_u32_memory_hashmap_erase);
REGISTER_FUNC(recomputil_u32_memory_hashmap_size);
REGISTER_FUNC(recomputil_create_u32_hashset);
REGISTER_FUNC(recomputil_destroy_u32_hashset);
REGISTER_FUNC(recomputil_u32_hashset_contains);
REGISTER_FUNC(recomputil_u32_hashset_insert);
REGISTER_FUNC(recomputil_u32_hashset_erase);
REGISTER_FUNC(recomputil_u32_hashset_size);
REGISTER_FUNC(recomputil_create_u32_slotmap);
REGISTER_FUNC(recomputil_destroy_u32_slotmap);
REGISTER_FUNC(recomputil_u32_slotmap_contains);
REGISTER_FUNC(recomputil_u32_slotmap_create);
REGISTER_FUNC(recomputil_u32_slotmap_get);
REGISTER_FUNC(recomputil_u32_slotmap_set);
REGISTER_FUNC(recomputil_u32_slotmap_erase);
REGISTER_FUNC(recomputil_u32_slotmap_size);
REGISTER_FUNC(recomputil_create_memory_slotmap);
REGISTER_FUNC(recomputil_destroy_memory_slotmap);
REGISTER_FUNC(recomputil_memory_slotmap_contains);
REGISTER_FUNC(recomputil_memory_slotmap_create);
REGISTER_FUNC(recomputil_memory_slotmap_get);
REGISTER_FUNC(recomputil_memory_slotmap_erase);
REGISTER_FUNC(recomputil_memory_slotmap_size);
}