mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp
synced 2026-06-29 03:40:44 -04:00
Migrate to N64ModernRuntime (#354)
This commit is contained in:
+39
-38
@@ -1,8 +1,8 @@
|
||||
#include "recomp_config.h"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_files.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include "zelda_sound.h"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "librecomp/files.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
@@ -18,6 +18,7 @@ constexpr std::u8string_view general_filename = u8"general.json";
|
||||
constexpr std::u8string_view graphics_filename = u8"graphics.json";
|
||||
constexpr std::u8string_view controls_filename = u8"controls.json";
|
||||
constexpr std::u8string_view sound_filename = u8"sound.json";
|
||||
constexpr std::u8string_view program_id = u8"Zelda64Recompiled";
|
||||
|
||||
constexpr auto res_default = ultramodern::Resolution::Auto;
|
||||
constexpr auto hr_default = ultramodern::HUDRatioMode::Clamp16x9;
|
||||
@@ -127,7 +128,7 @@ namespace recomp {
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path recomp::get_app_folder_path() {
|
||||
std::filesystem::path zelda64::get_app_folder_path() {
|
||||
std::filesystem::path recomp_dir{};
|
||||
|
||||
#if defined(_WIN32)
|
||||
@@ -135,7 +136,7 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||
PWSTR known_path = NULL;
|
||||
HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path);
|
||||
if (result == S_OK) {
|
||||
recomp_dir = std::filesystem::path{known_path} / recomp::program_id;
|
||||
recomp_dir = std::filesystem::path{known_path} / zelda64::program_id;
|
||||
}
|
||||
|
||||
CoTaskMemFree(known_path);
|
||||
@@ -147,7 +148,7 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||
}
|
||||
|
||||
if (homedir != nullptr) {
|
||||
recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{recomp::program_id});
|
||||
recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{zelda64::program_id});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -198,33 +199,33 @@ bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::j
|
||||
bool save_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
recomp::to_json(config_json["targeting_mode"], recomp::get_targeting_mode());
|
||||
zelda64::to_json(config_json["targeting_mode"], zelda64::get_targeting_mode());
|
||||
recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode());
|
||||
config_json["rumble_strength"] = recomp::get_rumble_strength();
|
||||
config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity();
|
||||
config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity();
|
||||
config_json["joystick_deadzone"] = recomp::get_joystick_deadzone();
|
||||
config_json["autosave_mode"] = recomp::get_autosave_mode();
|
||||
config_json["camera_invert_mode"] = recomp::get_camera_invert_mode();
|
||||
config_json["analog_cam_mode"] = recomp::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = recomp::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = recomp::get_debug_mode_enabled();
|
||||
config_json["autosave_mode"] = zelda64::get_autosave_mode();
|
||||
config_json["camera_invert_mode"] = zelda64::get_camera_invert_mode();
|
||||
config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = zelda64::get_debug_mode_enabled();
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
recomp::set_targeting_mode(from_or_default(config_json, "targeting_mode", recomp::TargetingMode::Switch));
|
||||
zelda64::set_targeting_mode(from_or_default(config_json, "targeting_mode", zelda64::TargetingMode::Switch));
|
||||
recomp::set_background_input_mode(from_or_default(config_json, "background_input_mode", recomp::BackgroundInputMode::On));
|
||||
recomp::set_rumble_strength(from_or_default(config_json, "rumble_strength", 25));
|
||||
recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50));
|
||||
recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", is_steam_deck ? 50 : 0));
|
||||
recomp::set_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5));
|
||||
recomp::set_autosave_mode(from_or_default(config_json, "autosave_mode", recomp::AutosaveMode::On));
|
||||
recomp::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", recomp::CameraInvertMode::InvertY));
|
||||
recomp::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", recomp::AnalogCamMode::Off));
|
||||
recomp::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", recomp::CameraInvertMode::InvertNone));
|
||||
recomp::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
zelda64::set_autosave_mode(from_or_default(config_json, "autosave_mode", zelda64::AutosaveMode::On));
|
||||
zelda64::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", zelda64::CameraInvertMode::InvertY));
|
||||
zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off));
|
||||
zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone));
|
||||
zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
}
|
||||
|
||||
bool load_general_config(const std::filesystem::path& path) {
|
||||
@@ -277,16 +278,16 @@ void assign_all_mappings(recomp::InputDevice device, const recomp::DefaultN64Map
|
||||
assign_mapping_complete(device, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
|
||||
};
|
||||
|
||||
void recomp::reset_input_bindings() {
|
||||
void zelda64::reset_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void recomp::reset_cont_input_bindings() {
|
||||
void zelda64::reset_cont_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void recomp::reset_kb_input_bindings() {
|
||||
void zelda64::reset_kb_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
}
|
||||
|
||||
@@ -369,8 +370,8 @@ bool load_input_device_from_json(const nlohmann::json& config_json, recomp::Inpu
|
||||
cur_input,
|
||||
recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
cur_input
|
||||
)
|
||||
);
|
||||
@@ -408,10 +409,10 @@ bool load_controls_config(const std::filesystem::path& path) {
|
||||
bool save_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["main_volume"] = recomp::get_main_volume();
|
||||
config_json["bgm_volume"] = recomp::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = recomp::get_low_health_beeps_enabled();
|
||||
|
||||
config_json["main_volume"] = zelda64::get_main_volume();
|
||||
config_json["bgm_volume"] = zelda64::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = zelda64::get_low_health_beeps_enabled();
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
@@ -421,17 +422,17 @@ bool load_sound_config(const std::filesystem::path& path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recomp::reset_sound_settings();
|
||||
call_if_key_exists(recomp::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(recomp::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(recomp::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||
zelda64::reset_sound_settings();
|
||||
call_if_key_exists(zelda64::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(zelda64::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(zelda64::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||
return true;
|
||||
}
|
||||
|
||||
void recomp::load_config() {
|
||||
void zelda64::load_config() {
|
||||
detect_steam_deck();
|
||||
|
||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
||||
std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||
std::filesystem::path general_path = recomp_dir / general_filename;
|
||||
std::filesystem::path graphics_path = recomp_dir / graphics_filename;
|
||||
std::filesystem::path controls_path = recomp_dir / controls_filename;
|
||||
@@ -455,18 +456,18 @@ void recomp::load_config() {
|
||||
}
|
||||
|
||||
if (!load_controls_config(controls_path)) {
|
||||
recomp::reset_input_bindings();
|
||||
zelda64::reset_input_bindings();
|
||||
save_controls_config(controls_path);
|
||||
}
|
||||
|
||||
if (!load_sound_config(sound_path)) {
|
||||
recomp::reset_sound_settings();
|
||||
zelda64::reset_sound_settings();
|
||||
save_sound_config(sound_path);
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::save_config() {
|
||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
||||
void zelda64::save_config() {
|
||||
std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||
|
||||
if (recomp_dir.empty()) {
|
||||
return;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <array>
|
||||
|
||||
#include "recomp_helpers.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "recomp_input.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
// Arrays that hold the mappings for every input for keyboard and controller respectively.
|
||||
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
|
||||
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
#include <atomic>
|
||||
#include "recomp_debug.h"
|
||||
#include "recomp_helpers.h"
|
||||
#include "zelda_debug.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "../patches/input.h"
|
||||
|
||||
std::atomic<uint16_t> pending_warp = 0xFFFF;
|
||||
std::atomic<uint32_t> pending_set_time = 0xFFFF;
|
||||
|
||||
void recomp::do_warp(int area, int scene, int entrance) {
|
||||
const recomp::SceneWarps game_scene = recomp::game_warps[area].scenes[scene];
|
||||
void zelda64::do_warp(int area, int scene, int entrance) {
|
||||
const zelda64::SceneWarps game_scene = zelda64::game_warps[area].scenes[scene];
|
||||
int game_scene_index = game_scene.index;
|
||||
pending_warp.store(((game_scene_index & 0xFF) << 8) | ((entrance & 0x0F) << 4));
|
||||
}
|
||||
@@ -17,7 +17,7 @@ extern "C" void recomp_get_pending_warp(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, pending_warp.exchange(0xFFFF));
|
||||
}
|
||||
|
||||
void recomp::set_time(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||
void zelda64::set_time(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||
pending_set_time.store((day << 16) | (uint16_t(hour) << 8) | minute);
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -1,12 +1,12 @@
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "librecomp/recomp.h"
|
||||
#include "recomp_input.h"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "SDL.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "promptfont.h"
|
||||
#include "GamepadMotion.hpp"
|
||||
|
||||
@@ -75,13 +75,13 @@ void recomp::stop_scanning_input() {
|
||||
|
||||
void queue_if_enabled(SDL_Event* event) {
|
||||
if (!recomp::all_input_disabled()) {
|
||||
recomp::queue_event(*event);
|
||||
recompui::queue_event(*event);
|
||||
}
|
||||
}
|
||||
|
||||
static std::atomic_bool cursor_enabled = true;
|
||||
|
||||
void recomp::set_cursor_visible(bool visible) {
|
||||
void recompui::set_cursor_visible(bool visible) {
|
||||
cursor_enabled.store(visible);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) ||
|
||||
keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11
|
||||
) {
|
||||
recomp::toggle_fullscreen();
|
||||
recompui::toggle_fullscreen();
|
||||
}
|
||||
if (scanning_device != recomp::InputDevice::COUNT) {
|
||||
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
|
||||
@@ -155,12 +155,12 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recomp::get_current_menu() != recomp::Menu::Config) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
if (recompui::get_current_menu() != recompui::Menu::Config) {
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
}
|
||||
|
||||
recomp::open_quit_game_prompt();
|
||||
recomp::activate_mouse();
|
||||
zelda64::open_quit_game_prompt();
|
||||
recompui::activate_mouse();
|
||||
break;
|
||||
}
|
||||
case SDL_EventType::SDL_MOUSEWHEEL:
|
||||
@@ -435,10 +435,10 @@ void recomp::poll_inputs() {
|
||||
bool save_is_held = InputState.keys[SDL_SCANCODE_F5] != 0;
|
||||
bool load_is_held = InputState.keys[SDL_SCANCODE_F7] != 0;
|
||||
if (save_is_held && !save_was_held) {
|
||||
recomp::quicksave_save();
|
||||
zelda64::quicksave_save();
|
||||
}
|
||||
else if (load_is_held && !load_was_held) {
|
||||
recomp::quicksave_load();
|
||||
zelda64::quicksave_load();
|
||||
}
|
||||
save_was_held = save_is_held;
|
||||
}
|
||||
@@ -649,7 +649,7 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
|
||||
|
||||
bool recomp::game_input_disabled() {
|
||||
// Disable input if any menu is open.
|
||||
return recomp::get_current_menu() != recomp::Menu::None;
|
||||
return recompui::get_current_menu() != recompui::Menu::None;
|
||||
}
|
||||
|
||||
bool recomp::all_input_disabled() {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
#if 0
|
||||
|
||||
#include "recomp_helpers.h"
|
||||
#include "recomp_input.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "librecomp/input.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
enum class QuicksaveAction {
|
||||
None,
|
||||
@@ -14,11 +14,11 @@ enum class QuicksaveAction {
|
||||
|
||||
std::atomic<QuicksaveAction> cur_quicksave_action = QuicksaveAction::None;
|
||||
|
||||
void recomp::quicksave_save() {
|
||||
void zelda64::quicksave_save() {
|
||||
cur_quicksave_action.store(QuicksaveAction::Save);
|
||||
}
|
||||
|
||||
void recomp::quicksave_load() {
|
||||
void zelda64::quicksave_load() {
|
||||
cur_quicksave_action.store(QuicksaveAction::Load);
|
||||
}
|
||||
|
||||
|
||||
+20
-20
@@ -1,18 +1,18 @@
|
||||
#include <cmath>
|
||||
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "recomp_config.h"
|
||||
#include "librecomp/recomp.h"
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_helpers.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "../patches/input.h"
|
||||
#include "../patches/graphics.h"
|
||||
#include "../patches/sound.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../ultramodern/config.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "ultramodern/rt64_layer.hpp"
|
||||
|
||||
extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
recomp::poll_inputs();
|
||||
@@ -62,7 +62,7 @@ extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config();
|
||||
float original = _arg<0, float>(rdram, ctx);
|
||||
int width, height;
|
||||
recomp::get_window_size(width, height);
|
||||
recompui::get_window_size(width, height);
|
||||
|
||||
switch (graphics_config.ar_option) {
|
||||
case RT64::UserConfiguration::AspectRatio::Original:
|
||||
@@ -76,15 +76,15 @@ extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_targeting_mode(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<int>(recomp::get_targeting_mode()));
|
||||
_return(ctx, static_cast<int>(zelda64::get_targeting_mode()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_bgm_volume(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, recomp::get_bgm_volume() / 100.0f);
|
||||
_return(ctx, zelda64::get_bgm_volume() / 100.0f);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_low_health_beeps_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<u32>(recomp::get_low_health_beeps_enabled()));
|
||||
_return(ctx, static_cast<u32>(zelda64::get_low_health_beeps_enabled()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
|
||||
@@ -92,7 +92,7 @@ extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void recomp_autosave_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<s32>(recomp::get_autosave_mode() == recomp::AutosaveMode::On));
|
||||
_return(ctx, static_cast<s32>(zelda64::get_autosave_mode() == zelda64::AutosaveMode::On));
|
||||
}
|
||||
|
||||
extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) {
|
||||
@@ -115,24 +115,24 @@ extern "C" void recomp_get_inverted_axes(uint8_t* rdram, recomp_context* ctx) {
|
||||
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
||||
s32* y_out = _arg<1, s32*>(rdram, ctx);
|
||||
|
||||
recomp::CameraInvertMode mode = recomp::get_camera_invert_mode();
|
||||
zelda64::CameraInvertMode mode = zelda64::get_camera_invert_mode();
|
||||
|
||||
*x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == zelda64::CameraInvertMode::InvertY || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context* ctx) {
|
||||
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
||||
s32* y_out = _arg<1, s32*>(rdram, ctx);
|
||||
|
||||
recomp::CameraInvertMode mode = recomp::get_analog_camera_invert_mode();
|
||||
zelda64::CameraInvertMode mode = zelda64::get_analog_camera_invert_mode();
|
||||
|
||||
*x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == zelda64::CameraInvertMode::InvertY || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
}
|
||||
|
||||
extern "C" void recomp_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return<s32>(ctx, recomp::get_analog_cam_mode() == recomp::AnalogCamMode::On);
|
||||
_return<s32>(ctx, zelda64::get_analog_cam_mode() == zelda64::AnalogCamMode::On);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "recomp_debug.h"
|
||||
#include "zelda_debug.h"
|
||||
|
||||
std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
std::vector<zelda64::AreaWarps> zelda64::game_warps {
|
||||
{ "Clock Town", {
|
||||
{
|
||||
0, "Mayor's Residence", {
|
||||
|
||||
+73
-9
@@ -6,11 +6,12 @@
|
||||
#include <filesystem>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "nfd.h"
|
||||
|
||||
#include "../../ultramodern/ultra64.h"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/ultra64.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#define SDL_MAIN_HANDLED
|
||||
#ifdef _WIN32
|
||||
#include "SDL.h"
|
||||
@@ -21,9 +22,14 @@
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "zelda_config.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "ovl_patches.hpp"
|
||||
#include "librecomp/game.hpp"
|
||||
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
#include "mm_shader_cache.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -192,7 +198,7 @@ void queue_samples(int16_t* audio_data, size_t sample_count) {
|
||||
|
||||
// Convert the audio from 16-bit values to floats and swap the audio channels into the
|
||||
// swap buffer to correct for the address xor caused by endianness handling.
|
||||
float cur_main_volume = recomp::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
float cur_main_volume = zelda64::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
for (size_t i = 0; i < sample_count; i += input_channels) {
|
||||
swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
@@ -302,6 +308,42 @@ void reset_audio(uint32_t output_freq) {
|
||||
update_audio_converter();
|
||||
}
|
||||
|
||||
extern RspUcodeFunc njpgdspMain;
|
||||
extern RspUcodeFunc aspMain;
|
||||
|
||||
RspUcodeFunc* get_rsp_microcode(const OSTask* task) {
|
||||
switch (task->t.type) {
|
||||
case M_AUDTASK:
|
||||
return aspMain;
|
||||
|
||||
case M_NJPEGTASK:
|
||||
return njpgdspMain;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown task: %" PRIu32 "\n", task->t.type);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
|
||||
gpr get_entrypoint_address();
|
||||
|
||||
// array of supported GameEntry objects
|
||||
std::vector<recomp::GameEntry> supported_games = {
|
||||
{
|
||||
.rom_hash = 0xEF18B4A9E2386169ULL,
|
||||
.internal_name = "ZELDA MAJORA'S MASK",
|
||||
.game_id = u8"mm.n64.us.1.0",
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
.cache_data = {mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)},
|
||||
#endif
|
||||
.is_enabled = true,
|
||||
.entrypoint_address = get_entrypoint_address(),
|
||||
.entrypoint = recomp_entrypoint,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -333,7 +375,20 @@ int main(int argc, char** argv) {
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
reset_audio(48000);
|
||||
|
||||
recomp::load_config();
|
||||
// Register supported games and patches
|
||||
for (const auto& game : supported_games) {
|
||||
recomp::register_game(game);
|
||||
}
|
||||
|
||||
zelda64::register_overlays();
|
||||
zelda64::register_patches();
|
||||
|
||||
recomp::register_config_path(zelda64::get_app_folder_path());
|
||||
zelda64::load_config();
|
||||
|
||||
recomp::rsp::callbacks_t rsp_callbacks{
|
||||
.get_rsp_microcode = get_rsp_microcode,
|
||||
};
|
||||
|
||||
ultramodern::gfx_callbacks_t gfx_callbacks{
|
||||
.create_gfx = create_gfx,
|
||||
@@ -353,8 +408,17 @@ int main(int argc, char** argv) {
|
||||
.set_rumble = recomp::set_rumble,
|
||||
};
|
||||
|
||||
recomp::start({}, audio_callbacks, input_callbacks, gfx_callbacks);
|
||||
|
||||
ultramodern::events::callbacks_t thread_callbacks{
|
||||
.vi_callback = recomp::update_rumble,
|
||||
.gfx_init_callback = recompui::update_supported_options,
|
||||
};
|
||||
|
||||
ultramodern::error_handling::callbacks_t error_handling_callbacks{
|
||||
.message_box = recompui::message_box,
|
||||
};
|
||||
|
||||
recomp::start({}, rsp_callbacks, audio_callbacks, input_callbacks, gfx_callbacks, thread_callbacks, error_handling_callbacks);
|
||||
|
||||
NFD_Quit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "ovl_patches.hpp"
|
||||
#include "../../RecompiledFuncs/recomp_overlays.inl"
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
|
||||
void zelda64::register_overlays() {
|
||||
recomp::overlay_section_table_data_t sections {
|
||||
.code_sections = section_table,
|
||||
.num_code_sections = ARRLEN(section_table),
|
||||
.total_num_sections = num_sections,
|
||||
};
|
||||
|
||||
recomp::overlays_by_index_t overlays {
|
||||
.table = overlay_sections_by_index,
|
||||
.len = ARRLEN(overlay_sections_by_index),
|
||||
};
|
||||
|
||||
recomp::register_overlays(sections, overlays);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "ovl_patches.hpp"
|
||||
#include "../../RecompiledPatches/patches_bin.h"
|
||||
#include "../../RecompiledPatches/recomp_overlays.inl"
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/game.hpp"
|
||||
|
||||
void zelda64::register_patches() {
|
||||
// TODO: This is a bit awful, maybe provide only one functions that does both in librecomp?
|
||||
recomp::register_patch(mm_patches_bin, sizeof(mm_patches_bin));
|
||||
recomp::register_patch_section(section_table);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
#define VI_NTSC_CLOCK 48681812
|
||||
|
||||
extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint32_t freq = ctx->r4;
|
||||
// This makes actual audio frequency more accurate to console, but may not be desirable
|
||||
//uint32_t dacRate = (uint32_t)(((float)VI_NTSC_CLOCK / freq) + 0.5f);
|
||||
//freq = VI_NTSC_CLOCK / dacRate;
|
||||
ctx->r2 = freq;
|
||||
ultramodern::set_audio_frequency(freq);
|
||||
}
|
||||
|
||||
extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::queue_audio_buffer(rdram, ctx->r4, ctx->r5);
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osAiGetLength_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = ultramodern::get_remaining_audio_bytes();
|
||||
}
|
||||
|
||||
extern "C" void osAiGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 0x00000000; // Pretend the audio DMAs finish instantly
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp_helpers.h"
|
||||
|
||||
static ultramodern::input_callbacks_t input_callbacks;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point input_poll_time;
|
||||
|
||||
void update_poll_time() {
|
||||
input_poll_time = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
extern "C" void recomp_set_current_frame_poll_id(uint8_t* rdram, recomp_context* ctx) {
|
||||
// TODO reimplement the system for tagging polls with IDs to handle games with multithreaded input polling.
|
||||
}
|
||||
|
||||
extern "C" void recomp_measure_latency(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::measure_input_latency();
|
||||
}
|
||||
|
||||
void ultramodern::measure_input_latency() {
|
||||
// printf("Delta: %ld micros\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - input_poll_time));
|
||||
}
|
||||
|
||||
void set_input_callbacks(const ultramodern::input_callbacks_t& callbacks) {
|
||||
input_callbacks = callbacks;
|
||||
}
|
||||
|
||||
static int max_controllers = 0;
|
||||
|
||||
extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) bitpattern = _arg<1, PTR(void)>(rdram, ctx);
|
||||
PTR(void) status = _arg<2, PTR(void)>(rdram, ctx);
|
||||
|
||||
// Set bit 0 to indicate that controller 0 is present
|
||||
MEM_B(0, bitpattern) = 0x01;
|
||||
|
||||
// Mark controller 0 as present
|
||||
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
|
||||
MEM_B(2, status) = 0x00; // status: 0 (from joybus)
|
||||
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
||||
|
||||
max_controllers = 4;
|
||||
|
||||
// Mark controllers 1-3 as not connected
|
||||
for (size_t controller = 1; controller < max_controllers; controller++) {
|
||||
// Libultra doesn't write status or type for absent controllers
|
||||
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
if (input_callbacks.poll_input) {
|
||||
input_callbacks.poll_input();
|
||||
}
|
||||
update_poll_time();
|
||||
|
||||
ultramodern::send_si_message(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pad = _arg<0, PTR(void)>(rdram, ctx);
|
||||
|
||||
uint16_t buttons = 0;
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
|
||||
if (input_callbacks.get_input) {
|
||||
input_callbacks.get_input(&buttons, &x, &y);
|
||||
}
|
||||
|
||||
if (max_controllers > 0) {
|
||||
// button
|
||||
MEM_H(0, pad) = buttons;
|
||||
// stick_x
|
||||
MEM_B(2, pad) = (int8_t)(127 * x);
|
||||
// stick_y
|
||||
MEM_B(3, pad) = (int8_t)(127 * y);
|
||||
// errno
|
||||
MEM_B(4, pad) = 0;
|
||||
}
|
||||
for (int controller = 1; controller < max_controllers; controller++) {
|
||||
MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ultramodern::send_si_message(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(void) status = _arg<0, PTR(void)>(rdram, ctx);
|
||||
|
||||
// Mark controller 0 as present
|
||||
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
|
||||
MEM_B(2, status) = 0x01; // status: 0x01 (from joybus, indicates that a pak is plugged into the controller)
|
||||
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
||||
|
||||
// Mark controllers 1-3 as not connected
|
||||
for (size_t controller = 1; controller < max_controllers; controller++) {
|
||||
// Libultra doesn't write status or type for absent controllers
|
||||
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
max_controllers = std::min(_arg<0, u8>(rdram, ctx), u8(4));
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 flag = _arg<1, s32>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(flag);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<1, PTR(void)>(rdram, ctx);
|
||||
s32 channel = _arg<2, s32>(rdram, ctx);
|
||||
MEM_W(8, pfs) = channel;
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(true);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(false);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "recomp.h"
|
||||
|
||||
enum class RDPStatusBit {
|
||||
XbusDmem = 0,
|
||||
Freeze = 1,
|
||||
Flush = 2,
|
||||
CommandBusy = 6,
|
||||
BufferReady = 7,
|
||||
DmaBusy = 8,
|
||||
EndValid = 9,
|
||||
StartValid = 10,
|
||||
};
|
||||
|
||||
constexpr void update_bit(uint32_t& state, uint32_t flags, RDPStatusBit bit) {
|
||||
int set_bit_pos = (int)bit * 2 + 0;
|
||||
int reset_bit_pos = (int)bit * 2 + 1;
|
||||
bool set = (flags & (1U << set_bit_pos)) != 0;
|
||||
bool reset = (flags & (1U << reset_bit_pos)) != 0;
|
||||
|
||||
if (set ^ reset) {
|
||||
if (set) {
|
||||
state |= (1U << (int)bit);
|
||||
}
|
||||
else {
|
||||
state &= ~(1U << (int)bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rdp_state = 1 << (int)RDPStatusBit::BufferReady;
|
||||
|
||||
extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osDpGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = rdp_state;
|
||||
}
|
||||
|
||||
extern "C" void osDpSetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::XbusDmem);
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::Freeze);
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::Flush);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
|
||||
constexpr int eeprom_block_size = 8;
|
||||
constexpr int eep4_size = 4096;
|
||||
constexpr int eep4_block_count = eep4_size / eeprom_block_size;
|
||||
constexpr int eep16_size = 16384;
|
||||
constexpr int eep16_block_count = eep16_size / eeprom_block_size;
|
||||
|
||||
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 0x02; // EEP16K
|
||||
}
|
||||
|
||||
extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = ctx->r7;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = ctx->r7;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
#ifndef __EUC_JP_H__
|
||||
#define __EUC_JP_H__
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Encoding {
|
||||
std::string decode_eucjp(std::string_view src);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "recomp_files.h"
|
||||
|
||||
constexpr std::u8string_view backup_suffix = u8".bak";
|
||||
constexpr std::u8string_view temp_suffix = u8".temp";
|
||||
|
||||
std::ifstream recomp::open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::filesystem::path backup_path{filepath};
|
||||
backup_path += backup_suffix;
|
||||
return std::ifstream{backup_path, mode};
|
||||
}
|
||||
|
||||
std::ifstream recomp::open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::ifstream ret{filepath, mode};
|
||||
|
||||
// Check if the file failed to open and open the corresponding backup file instead if so.
|
||||
if (!ret.good()) {
|
||||
return open_input_backup_file(filepath, mode);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::ofstream recomp::open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::filesystem::path temp_path{filepath};
|
||||
temp_path += temp_suffix;
|
||||
std::ofstream temp_file_out{ temp_path, mode };
|
||||
|
||||
return temp_file_out;
|
||||
}
|
||||
|
||||
bool recomp::finalize_output_file_with_backup(const std::filesystem::path& filepath) {
|
||||
std::filesystem::path backup_path{filepath};
|
||||
backup_path += backup_suffix;
|
||||
|
||||
std::filesystem::path temp_path{filepath};
|
||||
temp_path += temp_suffix;
|
||||
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(filepath, ec)) {
|
||||
std::filesystem::copy_file(filepath, backup_path, std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::filesystem::copy_file(temp_path, filepath, std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
std::filesystem::remove(temp_path, ec);
|
||||
return true;
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
// TODO move this out into ultramodern code
|
||||
|
||||
constexpr uint32_t flash_size = 1024 * 1024 / 8; // 1Mbit
|
||||
constexpr uint32_t page_size = 128;
|
||||
constexpr uint32_t pages_per_sector = 128;
|
||||
constexpr uint32_t page_count = flash_size / page_size;
|
||||
constexpr uint32_t sector_size = page_size * pages_per_sector;
|
||||
constexpr uint32_t sector_count = flash_size / sector_size;
|
||||
|
||||
void save_write_ptr(const void* in, uint32_t offset, uint32_t count);
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_clear(uint32_t start, uint32_t size, char value);
|
||||
|
||||
std::array<char, page_size> write_buffer;
|
||||
|
||||
extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = ultramodern::flash_handle;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(u8) flash_status = ctx->r4;
|
||||
|
||||
MEM_B(0, flash_status) = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(u32) flash_type = ctx->r4;
|
||||
PTR(u32) flash_maker = ctx->r5;
|
||||
|
||||
// Mimic a real flash chip's type and maker, as some games actually check if one is present.
|
||||
MEM_W(0, flash_type) = 0x11118001;
|
||||
MEM_W(0, flash_maker) = 0x00C2001E;
|
||||
}
|
||||
|
||||
extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
|
||||
}
|
||||
|
||||
extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
save_clear(0, ultramodern::save_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
save_clear(0, ultramodern::save_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// This function is named sector but really means page.
|
||||
extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = (uint32_t)ctx->r4;
|
||||
|
||||
// Prevent out of bounds erase
|
||||
if (page_num >= page_count) {
|
||||
ctx->r2 = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
save_clear(page_num * page_size, page_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// Same naming issue as above.
|
||||
extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = (uint32_t)ctx->r4;
|
||||
|
||||
// Prevent out of bounds erase
|
||||
if (page_num >= page_count) {
|
||||
ctx->r2 = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
save_clear(page_num * page_size, page_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// All erases are blocking in this implementation, so this should always return OK.
|
||||
ctx->r2 = 0; // FLASH_STATUS_ERASE_OK
|
||||
}
|
||||
|
||||
extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
|
||||
int32_t pri = ctx->r5;
|
||||
PTR(void) dramAddr = ctx->r6;
|
||||
PTR(OSMesgQueue) mq = ctx->r7;
|
||||
|
||||
// Copy the input data into the write buffer
|
||||
for (size_t i = 0; i < page_size; i++) {
|
||||
write_buffer[i] = MEM_B(i, dramAddr);
|
||||
}
|
||||
|
||||
// Send the message indicating write completion
|
||||
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = ctx->r4;
|
||||
|
||||
// Copy the write buffer into the save file
|
||||
save_write_ptr(write_buffer.data(), page_num * page_size, page_size);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
|
||||
int32_t pri = ctx->r5;
|
||||
uint32_t page_num = ctx->r6;
|
||||
PTR(void) dramAddr = ctx->r7;
|
||||
uint32_t n_pages = MEM_W(0x10, ctx->r29);
|
||||
PTR(OSMesgQueue) mq = MEM_W(0x14, ctx->r29);
|
||||
|
||||
uint32_t offset = page_num * page_size;
|
||||
uint32_t count = n_pages * page_size;
|
||||
|
||||
// Read from the save file into the provided buffer
|
||||
save_read(PASS_RDRAM dramAddr, offset, count);
|
||||
|
||||
// Send the message indicating read completion
|
||||
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
|
||||
extern "C" void __udivdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __divdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
int64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __umoddi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a % b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_div_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ll_div_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
int64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ll_mul_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a * b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_rem_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a % b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_to_d_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
double ret = (double)a;
|
||||
|
||||
ctx->f0.d = ret;
|
||||
}
|
||||
|
||||
extern "C" void __ull_to_f_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
float ret = (float)a;
|
||||
|
||||
ctx->f0.fl = ret;
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "../RecompiledFuncs/recomp_overlays.inl"
|
||||
|
||||
constexpr size_t num_code_sections = ARRLEN(section_table);
|
||||
|
||||
// SectionTableEntry sections[] defined in recomp_overlays.inl
|
||||
|
||||
struct LoadedSection {
|
||||
int32_t loaded_ram_addr;
|
||||
size_t section_table_index;
|
||||
|
||||
LoadedSection(int32_t loaded_ram_addr_, size_t section_table_index_) {
|
||||
loaded_ram_addr = loaded_ram_addr_;
|
||||
section_table_index = section_table_index_;
|
||||
}
|
||||
|
||||
bool operator<(const LoadedSection& rhs) {
|
||||
return loaded_ram_addr < rhs.loaded_ram_addr;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<LoadedSection> loaded_sections{};
|
||||
std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
||||
|
||||
void load_overlay(size_t section_table_index, int32_t ram) {
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
|
||||
const FuncEntry& func = section.funcs[function_index];
|
||||
func_map[ram + func.offset] = func.func;
|
||||
}
|
||||
loaded_sections.emplace_back(ram, section_table_index);
|
||||
section_addresses[section.index] = ram;
|
||||
}
|
||||
|
||||
void load_special_overlay(const SectionTableEntry& section, int32_t ram) {
|
||||
for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
|
||||
const FuncEntry& func = section.funcs[function_index];
|
||||
func_map[ram + func.offset] = func.func;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" {
|
||||
int32_t section_addresses[num_sections];
|
||||
}
|
||||
|
||||
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) {
|
||||
// Search for the first section that's included in the loaded rom range
|
||||
// Sections were sorted by `init_overlays` so we can use the bounds functions
|
||||
auto lower = std::lower_bound(§ion_table[0], §ion_table[num_code_sections], rom,
|
||||
[](const SectionTableEntry& entry, uint32_t addr) {
|
||||
return entry.rom_addr < addr;
|
||||
}
|
||||
);
|
||||
auto upper = std::upper_bound(§ion_table[0], §ion_table[num_code_sections], (uint32_t)(rom + size),
|
||||
[](uint32_t addr, const SectionTableEntry& entry) {
|
||||
return addr < entry.size + entry.rom_addr;
|
||||
}
|
||||
);
|
||||
// Load the overlays that were found
|
||||
for (auto it = lower; it != upper; ++it) {
|
||||
load_overlay(std::distance(§ion_table[0], it), it->rom_addr - rom + ram_addr);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
||||
|
||||
extern "C" void unload_overlay_by_id(uint32_t id) {
|
||||
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
|
||||
auto find_it = std::find_if(loaded_sections.begin(), loaded_sections.end(), [section_table_index](const LoadedSection& s) { return s.section_table_index == section_table_index; });
|
||||
|
||||
if (find_it != loaded_sections.end()) {
|
||||
// Determine where each function was loaded to and remove that entry from the function map
|
||||
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
|
||||
const auto& func = section.funcs[func_index];
|
||||
uint32_t func_address = func.offset + find_it->loaded_ram_addr;
|
||||
func_map.erase(func_address);
|
||||
}
|
||||
// Reset the section's address in the address table
|
||||
section_addresses[section.index] = section.ram_addr;
|
||||
// Remove the section from the loaded section map
|
||||
loaded_sections.erase(find_it);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void load_overlay_by_id(uint32_t id, uint32_t ram_addr) {
|
||||
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
int32_t prev_address = section_addresses[section.index];
|
||||
if (/*ram_addr >= 0x80000000 && ram_addr < 0x81000000) {*/ prev_address == section.ram_addr) {
|
||||
load_overlay(section_table_index, ram_addr);
|
||||
}
|
||||
else {
|
||||
int32_t new_address = prev_address + ram_addr;
|
||||
unload_overlay_by_id(id);
|
||||
load_overlay(section_table_index, new_address);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
|
||||
for (auto it = loaded_sections.begin(); it != loaded_sections.end();) {
|
||||
const auto& section = section_table[it->section_table_index];
|
||||
|
||||
// Check if the unloaded region overlaps with the loaded section
|
||||
if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) {
|
||||
// Check if the section isn't entirely in the loaded region
|
||||
if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) {
|
||||
fprintf(stderr,
|
||||
"Cannot partially unload section\n"
|
||||
" rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n"
|
||||
" unloaded_ram: 0x%08X unloaded_size : 0x%08X\n",
|
||||
section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size);
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
// Determine where each function was loaded to and remove that entry from the function map
|
||||
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
|
||||
const auto& func = section.funcs[func_index];
|
||||
uint32_t func_address = func.offset + it->loaded_ram_addr;
|
||||
func_map.erase(func_address);
|
||||
}
|
||||
// Reset the section's address in the address table
|
||||
section_addresses[section.index] = section.ram_addr;
|
||||
// Remove the section from the loaded section map
|
||||
it = loaded_sections.erase(it);
|
||||
// Skip incrementing the iterator
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void load_patch_functions();
|
||||
|
||||
void init_overlays() {
|
||||
for (size_t section_index = 0; section_index < num_code_sections; section_index++) {
|
||||
section_addresses[section_table[section_index].index] = section_table[section_index].ram_addr;
|
||||
}
|
||||
|
||||
// Sort the executable sections by rom address
|
||||
std::sort(§ion_table[0], §ion_table[num_code_sections],
|
||||
[](const SectionTableEntry& a, const SectionTableEntry& b) {
|
||||
return a.rom_addr < b.rom_addr;
|
||||
}
|
||||
);
|
||||
|
||||
load_patch_functions();
|
||||
}
|
||||
|
||||
extern "C" recomp_func_t * get_function(int32_t addr) {
|
||||
auto func_find = func_map.find(addr);
|
||||
if (func_find == func_map.end()) {
|
||||
fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
return func_find->second;
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsChecker_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "recomp.h"
|
||||
#include "../../RecompiledPatches/recomp_overlays.inl"
|
||||
|
||||
void load_special_overlay(const SectionTableEntry& section, int32_t ram);
|
||||
|
||||
void load_patch_functions() {
|
||||
load_special_overlay(section_table[0], section_table[0].ram_addr);
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include "recomp.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_files.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
static std::vector<uint8_t> rom;
|
||||
|
||||
bool recomp::is_rom_loaded() {
|
||||
return !rom.empty();
|
||||
}
|
||||
|
||||
void recomp::set_rom_contents(std::vector<uint8_t>&& new_rom) {
|
||||
rom = std::move(new_rom);
|
||||
}
|
||||
|
||||
// Flashram occupies the same physical address as sram, but that issue is avoided because libultra exposes
|
||||
// a high-level interface for flashram. Because that high-level interface is reimplemented, low level accesses
|
||||
// that involve physical addresses don't need to be handled for flashram.
|
||||
constexpr uint32_t sram_base = 0x08000000;
|
||||
constexpr uint32_t rom_base = 0x10000000;
|
||||
constexpr uint32_t drive_base = 0x06000000;
|
||||
|
||||
constexpr uint32_t k1_to_phys(uint32_t addr) {
|
||||
return addr & 0x1FFFFFFF;
|
||||
}
|
||||
|
||||
constexpr uint32_t phys_to_k1(uint32_t addr) {
|
||||
return addr | 0xA0000000;
|
||||
}
|
||||
|
||||
extern "C" void __osPiGetAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osPiRelAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::cart_handle);
|
||||
handle->type = 0; // cart
|
||||
handle->baseAddress = phys_to_k1(rom_base);
|
||||
handle->domain = 0;
|
||||
|
||||
ctx->r2 = (gpr)ultramodern::cart_handle;
|
||||
}
|
||||
|
||||
extern "C" void osDriveRomInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::drive_handle);
|
||||
handle->type = 1; // bulk
|
||||
handle->baseAddress = phys_to_k1(drive_base);
|
||||
handle->domain = 0;
|
||||
|
||||
ctx->r2 = (gpr)ultramodern::drive_handle;
|
||||
}
|
||||
|
||||
extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
void recomp::do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) {
|
||||
// TODO use word copies when possible
|
||||
|
||||
// TODO handle misaligned DMA
|
||||
assert((physical_addr & 0x1) == 0 && "Only PI DMA from aligned ROM addresses is currently supported");
|
||||
assert((ram_address & 0x7) == 0 && "Only PI DMA to aligned RDRAM addresses is currently supported");
|
||||
assert((num_bytes & 0x1) == 0 && "Only PI DMA with aligned sizes is currently supported");
|
||||
uint8_t* rom_addr = rom.data() + physical_addr - rom_base;
|
||||
for (size_t i = 0; i < num_bytes; i++) {
|
||||
MEM_B(i, ram_address) = *rom_addr;
|
||||
rom_addr++;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr) {
|
||||
assert((physical_addr & 0x3) == 0 && "PIO not 4-byte aligned in device, currently unsupported");
|
||||
assert((ram_address & 0x3) == 0 && "PIO not 4-byte aligned in RDRAM, currently unsupported");
|
||||
uint8_t* rom_addr = rom.data() + physical_addr - rom_base;
|
||||
MEM_B(0, ram_address) = *rom_addr++;
|
||||
MEM_B(1, ram_address) = *rom_addr++;
|
||||
MEM_B(2, ram_address) = *rom_addr++;
|
||||
MEM_B(3, ram_address) = *rom_addr++;
|
||||
}
|
||||
|
||||
struct {
|
||||
std::array<char, 0x20000> save_buffer;
|
||||
std::thread saving_thread;
|
||||
moodycamel::LightweightSemaphore write_sempahore;
|
||||
std::mutex save_buffer_mutex;
|
||||
} save_context;
|
||||
|
||||
const std::u8string save_folder = u8"saves";
|
||||
const std::u8string save_filename = std::u8string{recomp::mm_game_id} + u8".bin";
|
||||
|
||||
std::filesystem::path get_save_file_path() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename;
|
||||
}
|
||||
|
||||
void update_save_file() {
|
||||
bool saving_failed = false;
|
||||
{
|
||||
std::ofstream save_file = recomp::open_output_file_with_backup(get_save_file_path(), std::ios_base::binary);
|
||||
|
||||
if (save_file.good()) {
|
||||
std::lock_guard lock{ save_context.save_buffer_mutex };
|
||||
save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
}
|
||||
else {
|
||||
saving_failed = false;
|
||||
}
|
||||
}
|
||||
if (!saving_failed) {
|
||||
saving_failed = !recomp::finalize_output_file_with_backup(get_save_file_path());
|
||||
}
|
||||
if (saving_failed) {
|
||||
recomp::message_box("Failed to write to the save file. Check your file permissions and whether the save folder has been moved to Dropbox or similar, as this can cause issues.");
|
||||
}
|
||||
}
|
||||
|
||||
extern std::atomic_bool exited;
|
||||
|
||||
void saving_thread_func(RDRAM_ARG1) {
|
||||
while (!exited) {
|
||||
bool save_buffer_updated = false;
|
||||
// Repeatedly wait for a new action to be sent.
|
||||
constexpr int64_t wait_time_microseconds = 10000;
|
||||
constexpr int max_actions = 128;
|
||||
int num_actions = 0;
|
||||
|
||||
// Wait up to the given timeout for a write to come in. Allow multiple writes to coalesce together into a single save.
|
||||
// Cap the number of coalesced writes to guarantee that the save buffer eventually gets written out to the file even if the game
|
||||
// is constantly sending writes.
|
||||
while (save_context.write_sempahore.wait(wait_time_microseconds) && num_actions < max_actions) {
|
||||
save_buffer_updated = true;
|
||||
num_actions++;
|
||||
}
|
||||
|
||||
// If an action came through that affected the save file, save the updated contents.
|
||||
if (save_buffer_updated) {
|
||||
update_save_file();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void save_write_ptr(const void* in, uint32_t offset, uint32_t count) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
memcpy(&save_context.save_buffer[offset], in, count);
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
save_context.save_buffer[offset + i] = MEM_B(i, rdram_address);
|
||||
}
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
MEM_B(i, rdram_address) = save_context.save_buffer[offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
void save_clear(uint32_t start, uint32_t size, char value) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
std::fill_n(save_context.save_buffer.begin() + start, size, value);
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void ultramodern::init_saving(RDRAM_ARG1) {
|
||||
std::filesystem::path save_file_path = get_save_file_path();
|
||||
|
||||
// Ensure the save file directory exists.
|
||||
std::filesystem::create_directories(save_file_path.parent_path());
|
||||
|
||||
// Read the save file if it exists.
|
||||
std::ifstream save_file = recomp::open_input_file_with_backup(save_file_path, std::ios_base::binary);
|
||||
if (save_file.good()) {
|
||||
save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
}
|
||||
else {
|
||||
// Otherwise clear the save file to all zeroes.
|
||||
save_context.save_buffer.fill(0);
|
||||
}
|
||||
|
||||
save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM};
|
||||
}
|
||||
|
||||
void ultramodern::join_saving_thread() {
|
||||
save_context.saving_thread.join();
|
||||
}
|
||||
|
||||
void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) {
|
||||
// TODO asynchronous transfer
|
||||
// TODO implement unaligned DMA correctly
|
||||
if (direction == 0) {
|
||||
if (physical_addr >= rom_base) {
|
||||
// read cart rom
|
||||
recomp::do_rom_read(rdram, rdram_address, physical_addr, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else if (physical_addr >= sram_base) {
|
||||
// read sram
|
||||
save_read(rdram, rdram_address, physical_addr - sram_base, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else {
|
||||
fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr);
|
||||
}
|
||||
} else {
|
||||
if (physical_addr >= rom_base) {
|
||||
// write cart rom
|
||||
throw std::runtime_error("ROM DMA write unimplemented");
|
||||
} else if (physical_addr >= sram_base) {
|
||||
// write sram
|
||||
save_write(rdram, rdram_address, physical_addr - sram_base, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else {
|
||||
fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
|
||||
uint32_t mb = ctx->r4;
|
||||
uint32_t pri = ctx->r5;
|
||||
uint32_t direction = ctx->r6;
|
||||
uint32_t devAddr = ctx->r7 | rom_base;
|
||||
gpr dramAddr = MEM_W(0x10, ctx->r29);
|
||||
uint32_t size = MEM_W(0x14, ctx->r29);
|
||||
PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29);
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
|
||||
|
||||
do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5);
|
||||
uint32_t direction = ctx->r6;
|
||||
uint32_t devAddr = handle->baseAddress | mb->devAddr;
|
||||
gpr dramAddr = mb->dramAddr;
|
||||
uint32_t size = mb->size;
|
||||
PTR(OSMesgQueue) mq = mb->hdr.retQueue;
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
|
||||
|
||||
do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEPiReadIo_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||
uint32_t devAddr = handle->baseAddress | ctx->r5;
|
||||
gpr dramAddr = ctx->r6;
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
if (physical_addr > rom_base) {
|
||||
// cart rom
|
||||
recomp::do_rom_pio(PASS_RDRAM dramAddr, physical_addr);
|
||||
} else {
|
||||
// sram
|
||||
assert(false && "SRAM ReadIo unimplemented");
|
||||
}
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osPiGetStatus_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osPiRawStartDma_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
#include "euc-jp.h"
|
||||
|
||||
extern "C" void __checkHardware_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __checkHardware_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __checkHardware_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void isPrintfInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osRdbSend_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
gpr buf = ctx->r4;
|
||||
size_t size = ctx->r5;
|
||||
u32 type = (u32)ctx->r6;
|
||||
std::unique_ptr<char[]> to_print = std::make_unique<char[]>(size + 1);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
to_print[i] = MEM_B(i, buf);
|
||||
}
|
||||
to_print[size] = '\x00';
|
||||
|
||||
fwrite(to_print.get(), 1, size, stdout);
|
||||
|
||||
ctx->r2 = size;
|
||||
}
|
||||
|
||||
extern "C" void is_proutSyncPrintf_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// Buffering to speed up print performance
|
||||
static std::vector<char> print_buffer;
|
||||
|
||||
gpr buf = ctx->r5;
|
||||
size_t size = ctx->r6;
|
||||
|
||||
//for (size_t i = 0; i < size; i++) {
|
||||
// // Add the new character to the buffer
|
||||
// char cur_char = MEM_B(i, buf);
|
||||
|
||||
// // If the new character is a newline, flush the buffer
|
||||
// if (cur_char == '\n') {
|
||||
// std::string utf8_str = Encoding::decode_eucjp(std::string_view{ print_buffer.data(), print_buffer.size() });
|
||||
// puts(utf8_str.c_str());
|
||||
// print_buffer.clear();
|
||||
// } else {
|
||||
// print_buffer.push_back(cur_char);
|
||||
// }
|
||||
//}
|
||||
|
||||
//fwrite(to_print.get(), size, 1, stdout);
|
||||
|
||||
ctx->r2 = 1;
|
||||
}
|
||||
@@ -1,445 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "xxHash/xxh3.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../../RecompiledPatches/patches_bin.h"
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
#include "mm_shader_cache.h"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
inline uint32_t byteswap(uint32_t val) {
|
||||
return _byteswap_ulong(val);
|
||||
}
|
||||
#else
|
||||
constexpr uint32_t byteswap(uint32_t val) {
|
||||
return __builtin_bswap32(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct RomEntry {
|
||||
uint64_t xxhash3_value;
|
||||
std::u8string stored_filename;
|
||||
std::string internal_rom_name;
|
||||
};
|
||||
|
||||
const std::unordered_map<recomp::Game, RomEntry> game_roms {
|
||||
{ recomp::Game::MM, { 0xEF18B4A9E2386169ULL, std::u8string{recomp::mm_game_id} + u8".z64", "ZELDA MAJORA'S MASK" }},
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> read_file(const std::filesystem::path& path) {
|
||||
std::vector<uint8_t> ret;
|
||||
|
||||
std::ifstream file{ path, std::ios::binary};
|
||||
|
||||
if (file.good()) {
|
||||
file.seekg(0, std::ios::end);
|
||||
ret.resize(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
file.read(reinterpret_cast<char*>(ret.data()), ret.size());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool write_file(const std::filesystem::path& path, const std::vector<uint8_t>& data) {
|
||||
std::ofstream out_file{ path, std::ios::binary };
|
||||
|
||||
if (!out_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out_file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_stored_rom(const RomEntry& game_entry) {
|
||||
|
||||
std::vector stored_rom_data = read_file(recomp::get_app_folder_path() / game_entry.stored_filename);
|
||||
|
||||
if (!check_hash(stored_rom_data, game_entry.xxhash3_value)) {
|
||||
// Incorrect hash, remove the stored ROM file if it exists.
|
||||
std::filesystem::remove(recomp::get_app_folder_path() / game_entry.stored_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::unordered_set<recomp::Game> valid_game_roms;
|
||||
|
||||
bool recomp::is_rom_valid(recomp::Game game) {
|
||||
return valid_game_roms.contains(game);
|
||||
}
|
||||
|
||||
void recomp::check_all_stored_roms() {
|
||||
for (const auto& cur_rom_entry: game_roms) {
|
||||
if (check_stored_rom(cur_rom_entry.second)) {
|
||||
valid_game_roms.insert(cur_rom_entry.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::load_stored_rom(recomp::Game game) {
|
||||
auto find_it = game_roms.find(game);
|
||||
|
||||
if (find_it == game_roms.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> stored_rom_data = read_file(recomp::get_app_folder_path() / find_it->second.stored_filename);
|
||||
|
||||
if (!check_hash(stored_rom_data, find_it->second.xxhash3_value)) {
|
||||
// The ROM no longer has the right hash, delete it.
|
||||
std::filesystem::remove(recomp::get_app_folder_path() / find_it->second.stored_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
recomp::set_rom_contents(std::move(stored_rom_data));
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::array<uint8_t, 4> first_rom_bytes { 0x80, 0x37, 0x12, 0x40 };
|
||||
|
||||
enum class ByteswapType {
|
||||
NotByteswapped,
|
||||
Byteswapped4,
|
||||
Byteswapped2,
|
||||
Invalid
|
||||
};
|
||||
|
||||
ByteswapType check_rom_start(const std::vector<uint8_t>& rom_data) {
|
||||
if (rom_data.size() < 4) {
|
||||
return ByteswapType::Invalid;
|
||||
}
|
||||
|
||||
bool matched_all = true;
|
||||
|
||||
auto check_match = [&](uint8_t index0, uint8_t index1, uint8_t index2, uint8_t index3) {
|
||||
return
|
||||
rom_data[0] == first_rom_bytes[index0] &&
|
||||
rom_data[1] == first_rom_bytes[index1] &&
|
||||
rom_data[2] == first_rom_bytes[index2] &&
|
||||
rom_data[3] == first_rom_bytes[index3];
|
||||
};
|
||||
|
||||
// Check if the ROM is already in the correct byte order.
|
||||
if (check_match(0,1,2,3)) {
|
||||
return ByteswapType::NotByteswapped;
|
||||
}
|
||||
|
||||
// Check if the ROM has been byteswapped in groups of 4 bytes.
|
||||
if (check_match(3,2,1,0)) {
|
||||
return ByteswapType::Byteswapped4;
|
||||
}
|
||||
|
||||
// Check if the ROM has been byteswapped in groups of 2 bytes.
|
||||
if (check_match(1,0,3,2)) {
|
||||
return ByteswapType::Byteswapped2;
|
||||
}
|
||||
|
||||
// No match found.
|
||||
return ByteswapType::Invalid;
|
||||
}
|
||||
|
||||
void byteswap_data(std::vector<uint8_t>& rom_data, size_t index_xor) {
|
||||
for (size_t rom_pos = 0; rom_pos < rom_data.size(); rom_pos += 4) {
|
||||
uint8_t temp0 = rom_data[rom_pos + 0];
|
||||
uint8_t temp1 = rom_data[rom_pos + 1];
|
||||
uint8_t temp2 = rom_data[rom_pos + 2];
|
||||
uint8_t temp3 = rom_data[rom_pos + 3];
|
||||
|
||||
rom_data[rom_pos + (0 ^ index_xor)] = temp0;
|
||||
rom_data[rom_pos + (1 ^ index_xor)] = temp1;
|
||||
rom_data[rom_pos + (2 ^ index_xor)] = temp2;
|
||||
rom_data[rom_pos + (3 ^ index_xor)] = temp3;
|
||||
}
|
||||
}
|
||||
|
||||
recomp::RomValidationError recomp::select_rom(const std::filesystem::path& rom_path, Game game) {
|
||||
auto find_it = game_roms.find(game);
|
||||
|
||||
if (find_it == game_roms.end()) {
|
||||
return recomp::RomValidationError::OtherError;
|
||||
}
|
||||
|
||||
const RomEntry& game_entry = find_it->second;
|
||||
|
||||
std::vector<uint8_t> rom_data = read_file(rom_path);
|
||||
|
||||
if (rom_data.empty()) {
|
||||
return recomp::RomValidationError::FailedToOpen;
|
||||
}
|
||||
|
||||
// Pad the rom to the nearest multiple of 4 bytes.
|
||||
rom_data.resize((rom_data.size() + 3) & ~3);
|
||||
|
||||
ByteswapType byteswap_type = check_rom_start(rom_data);
|
||||
|
||||
switch (byteswap_type) {
|
||||
case ByteswapType::Invalid:
|
||||
return recomp::RomValidationError::NotARom;
|
||||
case ByteswapType::Byteswapped2:
|
||||
byteswap_data(rom_data, 1);
|
||||
break;
|
||||
case ByteswapType::Byteswapped4:
|
||||
byteswap_data(rom_data, 3);
|
||||
break;
|
||||
case ByteswapType::NotByteswapped:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!check_hash(rom_data, game_entry.xxhash3_value)) {
|
||||
const std::string_view name{ reinterpret_cast<const char*>(rom_data.data()) + 0x20, game_entry.internal_rom_name.size()};
|
||||
if (name == game_entry.internal_rom_name) {
|
||||
return recomp::RomValidationError::IncorrectVersion;
|
||||
}
|
||||
else {
|
||||
if (game == recomp::Game::MM && std::string_view{ reinterpret_cast<const char*>(rom_data.data()) + 0x20, 19 } == "THE LEGEND OF ZELDA") {
|
||||
return recomp::RomValidationError::NotYet;
|
||||
}
|
||||
else {
|
||||
return recomp::RomValidationError::IncorrectRom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_file(recomp::get_app_folder_path() / game_entry.stored_filename, rom_data);
|
||||
|
||||
return recomp::RomValidationError::Good;
|
||||
}
|
||||
|
||||
extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 8 * 1024 * 1024;
|
||||
}
|
||||
|
||||
enum class StatusReg {
|
||||
FR = 0x04000000,
|
||||
};
|
||||
|
||||
extern "C" void cop0_status_write(recomp_context* ctx, gpr value) {
|
||||
uint32_t old_sr = ctx->status_reg;
|
||||
uint32_t new_sr = (uint32_t)value;
|
||||
uint32_t changed = old_sr ^ new_sr;
|
||||
|
||||
// Check if the FR bit changed
|
||||
if (changed & (uint32_t)StatusReg::FR) {
|
||||
// Check if the FR bit was set
|
||||
if (new_sr & (uint32_t)StatusReg::FR) {
|
||||
// FR = 1, odd single floats point to their own registers
|
||||
ctx->f_odd = &ctx->f1.u32l;
|
||||
ctx->mips3_float_mode = true;
|
||||
}
|
||||
// Otherwise, it was cleared
|
||||
else {
|
||||
// FR = 0, odd single floats point to the upper half of the previous register
|
||||
ctx->f_odd = &ctx->f0.u32h;
|
||||
ctx->mips3_float_mode = false;
|
||||
}
|
||||
|
||||
// Remove the FR bit from the changed bits as it's been handled
|
||||
changed &= ~(uint32_t)StatusReg::FR;
|
||||
}
|
||||
|
||||
// If any other bits were changed, assert false as they're not handled currently
|
||||
if (changed) {
|
||||
printf("Unhandled status register bits changed: 0x%08X\n", changed);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Update the status register in the context
|
||||
ctx->status_reg = new_sr;
|
||||
}
|
||||
|
||||
extern "C" gpr cop0_status_read(recomp_context* ctx) {
|
||||
return (gpr)(int32_t)ctx->status_reg;
|
||||
}
|
||||
|
||||
extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
|
||||
printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
extern "C" void do_break(uint32_t vram) {
|
||||
printf("Encountered break at original vram 0x%08X\n", vram);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg) {
|
||||
recomp_context ctx{};
|
||||
ctx.r29 = sp;
|
||||
ctx.r4 = arg;
|
||||
ctx.mips3_float_mode = 0;
|
||||
ctx.f_odd = &ctx.f0.u32h;
|
||||
recomp_func_t* func = get_function(addr);
|
||||
func(rdram, &ctx);
|
||||
}
|
||||
|
||||
// Recomp generation functions
|
||||
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
|
||||
gpr get_entrypoint_address();
|
||||
const char* get_rom_name();
|
||||
|
||||
void read_patch_data(uint8_t* rdram, gpr patch_data_address) {
|
||||
for (size_t i = 0; i < sizeof(mm_patches_bin); i++) {
|
||||
MEM_B(i, patch_data_address) = mm_patches_bin[i];
|
||||
}
|
||||
}
|
||||
|
||||
void init(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Initialize the overlays
|
||||
init_overlays();
|
||||
|
||||
// Get entrypoint from recomp function
|
||||
gpr entrypoint = get_entrypoint_address();
|
||||
|
||||
// Load overlays in the first 1MB
|
||||
load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024);
|
||||
|
||||
// Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000)
|
||||
recomp::do_rom_read(rdram, entrypoint, 0x10001000, 0x100000);
|
||||
|
||||
// Read in any extra data from patches
|
||||
read_patch_data(rdram, (gpr)(s32)0x80801000);
|
||||
|
||||
// Set up stack pointer
|
||||
ctx->r29 = 0xFFFFFFFF803FFFF0u;
|
||||
|
||||
// Set up context floats
|
||||
ctx->f_odd = &ctx->f0.u32h;
|
||||
ctx->mips3_float_mode = false;
|
||||
|
||||
// Initialize variables normally set by IPL3
|
||||
constexpr int32_t osTvType = 0x80000300;
|
||||
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 osMemSize = 0x80000318;
|
||||
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
|
||||
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
|
||||
}
|
||||
|
||||
std::atomic<recomp::Game> game_started = recomp::Game::None;
|
||||
|
||||
void recomp::start_game(recomp::Game game) {
|
||||
game_started.store(game);
|
||||
game_started.notify_all();
|
||||
}
|
||||
|
||||
bool ultramodern::is_game_started() {
|
||||
return game_started.load() != recomp::Game::None;
|
||||
}
|
||||
|
||||
void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks);
|
||||
void set_input_callbacks(const ultramodern::input_callbacks_t& callback);
|
||||
|
||||
std::atomic_bool exited = false;
|
||||
|
||||
void ultramodern::quit() {
|
||||
exited.store(true);
|
||||
recomp::Game desired = recomp::Game::None;
|
||||
game_started.compare_exchange_strong(desired, recomp::Game::Quit);
|
||||
game_started.notify_all();
|
||||
}
|
||||
|
||||
void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_) {
|
||||
recomp::check_all_stored_roms();
|
||||
set_audio_callbacks(audio_callbacks);
|
||||
set_input_callbacks(input_callbacks);
|
||||
|
||||
ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_;
|
||||
|
||||
ultramodern::gfx_callbacks_t::gfx_data_t gfx_data{};
|
||||
|
||||
if (gfx_callbacks.create_gfx) {
|
||||
gfx_data = gfx_callbacks.create_gfx();
|
||||
}
|
||||
|
||||
if (window_handle == ultramodern::WindowHandle{}) {
|
||||
if (gfx_callbacks.create_window) {
|
||||
window_handle = gfx_callbacks.create_window(gfx_data);
|
||||
}
|
||||
else {
|
||||
assert(false && "No create_window callback provided");
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate rdram_buffer
|
||||
std::unique_ptr<uint8_t[]> rdram_buffer = std::make_unique<uint8_t[]>(ultramodern::rdram_size);
|
||||
std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size);
|
||||
|
||||
std::thread game_thread{[](ultramodern::WindowHandle window_handle, uint8_t* rdram) {
|
||||
debug_printf("[Recomp] Starting\n");
|
||||
|
||||
ultramodern::set_native_thread_name("Game Start Thread");
|
||||
|
||||
ultramodern::preinit(rdram, window_handle);
|
||||
|
||||
game_started.wait(recomp::Game::None);
|
||||
recomp_context context{};
|
||||
|
||||
switch (game_started.load()) {
|
||||
case recomp::Game::MM:
|
||||
if (!recomp::load_stored_rom(recomp::Game::MM)) {
|
||||
recomp::message_box("Error opening stored ROM! Please restart this program.");
|
||||
}
|
||||
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
ultramodern::load_shader_cache({mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)});
|
||||
#endif
|
||||
|
||||
init(rdram, &context);
|
||||
try {
|
||||
recomp_entrypoint(rdram, &context);
|
||||
} catch (ultramodern::thread_terminated& terminated) {
|
||||
|
||||
}
|
||||
break;
|
||||
case recomp::Game::Quit:
|
||||
break;
|
||||
}
|
||||
|
||||
debug_printf("[Recomp] Quitting\n");
|
||||
}, window_handle, rdram_buffer.get()};
|
||||
|
||||
while (!exited) {
|
||||
ultramodern::sleep_milliseconds(1);
|
||||
if (gfx_callbacks.update_gfx != nullptr) {
|
||||
gfx_callbacks.update_gfx(gfx_data);
|
||||
}
|
||||
}
|
||||
game_thread.join();
|
||||
ultramodern::join_event_threads();
|
||||
ultramodern::join_thread_cleaner_thread();
|
||||
ultramodern::join_saving_thread();
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
bool dump_frame = false;
|
||||
|
||||
extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
//printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
|
||||
OSTask* task = TO_PTR(OSTask, ctx->r4);
|
||||
if (task->t.type == M_GFXTASK) {
|
||||
//printf("[sp] Gfx task: %08X\n", (uint32_t)ctx->r4);
|
||||
} else if (task->t.type == M_AUDTASK) {
|
||||
//printf("[sp] Audio task: %08X\n", (uint32_t)ctx->r4);
|
||||
}
|
||||
// For debugging
|
||||
if (dump_frame) {
|
||||
char addr_str[32];
|
||||
constexpr size_t ram_size = 0x800000;
|
||||
std::unique_ptr<char[]> ram_unswapped = std::make_unique<char[]>(ram_size);
|
||||
snprintf(addr_str, sizeof(addr_str) - 1, "%08X", task->t.data_ptr);
|
||||
addr_str[sizeof(addr_str) - 1] = '\0';
|
||||
std::ofstream dump_file{ "ramdump" + std::string{ addr_str } + ".bin", std::ios::binary};
|
||||
|
||||
for (size_t i = 0; i < ram_size; i++) {
|
||||
ram_unswapped[i] = rdram[i ^ 3];
|
||||
}
|
||||
|
||||
dump_file.write(ram_unswapped.get(), ram_size);
|
||||
dump_frame = false;
|
||||
}
|
||||
ultramodern::submit_rsp_task(rdram, ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYield_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Ignore yield requests (acts as if the task completed before it received the yield request)
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYielded_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Task yield requests are ignored, so always return 0 as tasks will never be yielded
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __osSpSetPc_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
// None of these functions need to be reimplemented, so stub them out
|
||||
extern "C" void osUnmapTLBAll_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// TODO this will need to be implemented in the future for any games that actually use the TLB
|
||||
}
|
||||
|
||||
extern "C" void osVoiceInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 11; // CONT_ERR_DEVICE
|
||||
}
|
||||
|
||||
extern "C" void osVoiceSetWord_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceCheckWord_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceStopReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceMaskDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceStartReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceControlGain_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceGetReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceClearDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#include <memory>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osInitialize_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osInitialize();
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_common_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osInitialize();
|
||||
}
|
||||
|
||||
extern "C" void osCreateThread_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osCreateThread(rdram, (int32_t)ctx->r4, (OSId)ctx->r5, (int32_t)ctx->r6, (int32_t)ctx->r7,
|
||||
(int32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29));
|
||||
}
|
||||
|
||||
extern "C" void osStartThread_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osStartThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osStopThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osStopThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osDestroyThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osYieldThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
// osYieldThread(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5);
|
||||
}
|
||||
|
||||
extern "C" void osGetThreadPri_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetThreadPri(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osGetThreadId_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetThreadId(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osCreateMesgQueue_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osCreateMesgQueue(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osRecvMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osRecvMesg(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSendMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osSendMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osJamMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osJamMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSetEventMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osSetEventMesg(rdram, (OSEvent)ctx->r4, (int32_t)ctx->r5, (OSMesg)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osViSetEvent_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osViSetEvent(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (u32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetCount();
|
||||
}
|
||||
|
||||
extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t total_count = osGetTime();
|
||||
ctx->r2 = (int32_t)(total_count >> 32);
|
||||
ctx->r3 = (int32_t)(total_count >> 0);
|
||||
}
|
||||
|
||||
extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu);
|
||||
uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10);
|
||||
ctx->r2 = osSetTimer(rdram, (int32_t)ctx->r4, countdown, interval, (int32_t)MEM_W(0x18, ctx->r29), (OSMesg)MEM_W(0x1C, ctx->r29));
|
||||
}
|
||||
|
||||
extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osStopTimer(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osVirtualToPhysical((int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osInvalICache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCacheAll_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osSetIntMask_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osDisableInt_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osRestoreInt_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osSetFpcCsr_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// For the Mario Party games (not working)
|
||||
//extern "C" void longjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// RecompJmpBuf* buf = TO_PTR(RecompJmpBuf, ctx->r4);
|
||||
//
|
||||
// // Check if this is a buffer that was set up with setjmp
|
||||
// if (buf->magic == SETJMP_MAGIC) {
|
||||
// // If so, longjmp to it
|
||||
// // Setjmp/longjmp does not work across threads, so verify that this buffer was made by this thread
|
||||
// assert(buf->owner == ultramodern::this_thread());
|
||||
// longjmp(buf->storage->buffer, ctx->r5);
|
||||
// } else {
|
||||
// // Otherwise, check if it was one built manually by the game with $ra pointing to a function
|
||||
// gpr sp = MEM_W(0, ctx->r4);
|
||||
// gpr ra = MEM_W(4, ctx->r4);
|
||||
// ctx->r29 = sp;
|
||||
// recomp_func_t* target = LOOKUP_FUNC(ra);
|
||||
// if (target == nullptr) {
|
||||
// fprintf(stderr, "Failed to find function for manual longjmp\n");
|
||||
// std::quick_exit(EXIT_FAILURE);
|
||||
// }
|
||||
// target(rdram, ctx);
|
||||
//
|
||||
// // TODO kill this thread if the target function returns
|
||||
// assert(false);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//#undef setjmp_recomp
|
||||
//extern "C" void setjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// fprintf(stderr, "Program called setjmp_recomp\n");
|
||||
// std::quick_exit(EXIT_FAILURE);
|
||||
//}
|
||||
//
|
||||
//extern "C" int32_t osGetThreadEx(void) {
|
||||
// return ultramodern::this_thread();
|
||||
//}
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osViSetYScale_recomp(uint8_t* rdram, recomp_context * ctx) {
|
||||
osViSetYScale(ctx->f12.fl);
|
||||
}
|
||||
|
||||
extern "C" void osViSetXScale_recomp(uint8_t* rdram, recomp_context * ctx) {
|
||||
osViSetXScale(ctx->f12.fl);
|
||||
}
|
||||
|
||||
extern "C" void osCreateViManager_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViBlack_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViBlack((uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViSetSpecialFeatures_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSetSpecialFeatures((uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = (gpr)(int32_t)osViGetCurrentFramebuffer();
|
||||
}
|
||||
|
||||
extern "C" void osViGetNextFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = (gpr)(int32_t)osViGetNextFramebuffer();
|
||||
}
|
||||
|
||||
extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSwapBuffer(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSetMode(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern uint64_t total_vis;
|
||||
|
||||
extern "C" void wait_one_frame(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint64_t cur_vis = total_vis;
|
||||
while (cur_vis == total_vis) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
using ColourMap = Rml::UnorderedMap<Rml::String, Rml::Colourb>;
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class PropertyParserColorHack : public Rml::PropertyParser {
|
||||
public:
|
||||
PropertyParserColorHack();
|
||||
|
||||
+73
-73
@@ -1,13 +1,13 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_debug.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "zelda_config.h"
|
||||
#include "zelda_debug.h"
|
||||
#include "promptfont.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "RmlUi/Core.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "ultramodern/rt64_layer.hpp"
|
||||
|
||||
ultramodern::GraphicsConfig new_options;
|
||||
Rml::DataModelHandle nav_help_model_handle;
|
||||
@@ -16,9 +16,9 @@ Rml::DataModelHandle controls_model_handle;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
|
||||
recomp::PromptContext prompt_context;
|
||||
recompui::PromptContext prompt_context;
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
const std::unordered_map<ButtonVariant, std::string> button_variants {
|
||||
{ButtonVariant::Primary, "primary"},
|
||||
{ButtonVariant::Secondary, "secondary"},
|
||||
@@ -164,7 +164,7 @@ static bool cont_active = true;
|
||||
static recomp::InputDevice cur_device = recomp::InputDevice::Controller;
|
||||
|
||||
void recomp::finish_scanning_input(recomp::InputField scanned_field) {
|
||||
recomp::set_input_binding(static_cast<recomp::GameInput>(scanned_input_index), scanned_binding_index, cur_device, scanned_field);
|
||||
recomp::set_input_binding(static_cast<recomp::GameInput>(scanned_input_index), scanned_binding_index, cur_device, scanned_field);
|
||||
scanned_input_index = -1;
|
||||
scanned_binding_index = -1;
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
@@ -173,7 +173,7 @@ void recomp::finish_scanning_input(recomp::InputField scanned_field) {
|
||||
}
|
||||
|
||||
void recomp::cancel_scanning_input() {
|
||||
recomp::stop_scanning_input();
|
||||
recomp::stop_scanning_input();
|
||||
scanned_input_index = -1;
|
||||
scanned_binding_index = -1;
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
@@ -198,13 +198,13 @@ void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
|
||||
}
|
||||
|
||||
void close_config_menu_impl() {
|
||||
recomp::save_config();
|
||||
zelda64::save_config();
|
||||
|
||||
if (ultramodern::is_game_started()) {
|
||||
recomp::set_current_menu(recomp::Menu::None);
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
}
|
||||
else {
|
||||
recomp::set_current_menu(recomp::Menu::Launcher);
|
||||
recompui::set_current_menu(recompui::Menu::Launcher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,8 +241,8 @@ void close_config_menu() {
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
close_config_menu_impl();
|
||||
},
|
||||
recomp::ButtonVariant::Success,
|
||||
recomp::ButtonVariant::Error,
|
||||
recompui::ButtonVariant::Success,
|
||||
recompui::ButtonVariant::Error,
|
||||
true,
|
||||
"config__close-menu-button"
|
||||
);
|
||||
@@ -252,7 +252,7 @@ void close_config_menu() {
|
||||
close_config_menu_impl();
|
||||
}
|
||||
|
||||
void recomp::open_quit_game_prompt() {
|
||||
void zelda64::open_quit_game_prompt() {
|
||||
prompt_context.open_prompt(
|
||||
"Are you sure you want to quit?",
|
||||
"Any progress since your last save will be lost.",
|
||||
@@ -262,8 +262,8 @@ void recomp::open_quit_game_prompt() {
|
||||
ultramodern::quit();
|
||||
},
|
||||
[]() {},
|
||||
recomp::ButtonVariant::Error,
|
||||
recomp::ButtonVariant::Tertiary,
|
||||
recompui::ButtonVariant::Error,
|
||||
recompui::ButtonVariant::Tertiary,
|
||||
true,
|
||||
"config__quit-game-button"
|
||||
);
|
||||
@@ -275,12 +275,12 @@ struct ControlOptionsContext {
|
||||
int gyro_sensitivity; // 0 to 100
|
||||
int mouse_sensitivity; // 0 to 100
|
||||
int joystick_deadzone; // 0 to 100
|
||||
recomp::TargetingMode targeting_mode;
|
||||
zelda64::TargetingMode targeting_mode;
|
||||
recomp::BackgroundInputMode background_input_mode;
|
||||
recomp::AutosaveMode autosave_mode;
|
||||
recomp::CameraInvertMode camera_invert_mode;
|
||||
recomp::AnalogCamMode analog_cam_mode;
|
||||
recomp::CameraInvertMode analog_camera_invert_mode;
|
||||
zelda64::AutosaveMode autosave_mode;
|
||||
zelda64::CameraInvertMode camera_invert_mode;
|
||||
zelda64::AnalogCamMode analog_cam_mode;
|
||||
zelda64::CameraInvertMode analog_camera_invert_mode;
|
||||
};
|
||||
|
||||
ControlOptionsContext control_options_context;
|
||||
@@ -329,11 +329,11 @@ void recomp::set_joystick_deadzone(int deadzone) {
|
||||
}
|
||||
}
|
||||
|
||||
recomp::TargetingMode recomp::get_targeting_mode() {
|
||||
zelda64::TargetingMode zelda64::get_targeting_mode() {
|
||||
return control_options_context.targeting_mode;
|
||||
}
|
||||
|
||||
void recomp::set_targeting_mode(recomp::TargetingMode mode) {
|
||||
void zelda64::set_targeting_mode(zelda64::TargetingMode mode) {
|
||||
control_options_context.targeting_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("targeting_mode");
|
||||
@@ -357,44 +357,44 @@ void recomp::set_background_input_mode(recomp::BackgroundInputMode mode) {
|
||||
);
|
||||
}
|
||||
|
||||
recomp::AutosaveMode recomp::get_autosave_mode() {
|
||||
zelda64::AutosaveMode zelda64::get_autosave_mode() {
|
||||
return control_options_context.autosave_mode;
|
||||
}
|
||||
|
||||
void recomp::set_autosave_mode(recomp::AutosaveMode mode) {
|
||||
void zelda64::set_autosave_mode(zelda64::AutosaveMode mode) {
|
||||
control_options_context.autosave_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("autosave_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::CameraInvertMode recomp::get_camera_invert_mode() {
|
||||
zelda64::CameraInvertMode zelda64::get_camera_invert_mode() {
|
||||
return control_options_context.camera_invert_mode;
|
||||
}
|
||||
|
||||
void recomp::set_camera_invert_mode(recomp::CameraInvertMode mode) {
|
||||
void zelda64::set_camera_invert_mode(zelda64::CameraInvertMode mode) {
|
||||
control_options_context.camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("camera_invert_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::AnalogCamMode recomp::get_analog_cam_mode() {
|
||||
zelda64::AnalogCamMode zelda64::get_analog_cam_mode() {
|
||||
return control_options_context.analog_cam_mode;
|
||||
}
|
||||
|
||||
void recomp::set_analog_cam_mode(recomp::AnalogCamMode mode) {
|
||||
void zelda64::set_analog_cam_mode(zelda64::AnalogCamMode mode) {
|
||||
control_options_context.analog_cam_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_cam_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::CameraInvertMode recomp::get_analog_camera_invert_mode() {
|
||||
zelda64::CameraInvertMode zelda64::get_analog_camera_invert_mode() {
|
||||
return control_options_context.analog_camera_invert_mode;
|
||||
}
|
||||
|
||||
void recomp::set_analog_camera_invert_mode(recomp::CameraInvertMode mode) {
|
||||
void zelda64::set_analog_camera_invert_mode(zelda64::CameraInvertMode mode) {
|
||||
control_options_context.analog_camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_camera_invert_mode");
|
||||
@@ -417,43 +417,43 @@ struct SoundOptionsContext {
|
||||
|
||||
SoundOptionsContext sound_options_context;
|
||||
|
||||
void recomp::reset_sound_settings() {
|
||||
void zelda64::reset_sound_settings() {
|
||||
sound_options_context.reset();
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyAllVariables();
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_main_volume(int volume) {
|
||||
void zelda64::set_main_volume(int volume) {
|
||||
sound_options_context.main_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("main_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_main_volume() {
|
||||
int zelda64::get_main_volume() {
|
||||
return sound_options_context.main_volume.load();
|
||||
}
|
||||
|
||||
void recomp::set_bgm_volume(int volume) {
|
||||
void zelda64::set_bgm_volume(int volume) {
|
||||
sound_options_context.bgm_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("bgm_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_bgm_volume() {
|
||||
int zelda64::get_bgm_volume() {
|
||||
return sound_options_context.bgm_volume.load();
|
||||
}
|
||||
|
||||
void recomp::set_low_health_beeps_enabled(bool enabled) {
|
||||
void zelda64::set_low_health_beeps_enabled(bool enabled) {
|
||||
sound_options_context.low_health_beeps_enabled.store((int)enabled);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("low_health_beeps_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::get_low_health_beeps_enabled() {
|
||||
bool zelda64::get_low_health_beeps_enabled() {
|
||||
return (bool)sound_options_context.low_health_beeps_enabled.load();
|
||||
}
|
||||
|
||||
@@ -471,7 +471,7 @@ struct DebugContext {
|
||||
bool debug_enabled = false;
|
||||
|
||||
DebugContext() {
|
||||
for (const auto& area : recomp::game_warps) {
|
||||
for (const auto& area : zelda64::game_warps) {
|
||||
area_names.emplace_back(area.name);
|
||||
}
|
||||
update_warp_names();
|
||||
@@ -479,15 +479,15 @@ struct DebugContext {
|
||||
|
||||
void update_warp_names() {
|
||||
scene_names.clear();
|
||||
for (const auto& scene : recomp::game_warps[area_index].scenes) {
|
||||
for (const auto& scene : zelda64::game_warps[area_index].scenes) {
|
||||
scene_names.emplace_back(scene.name);
|
||||
}
|
||||
|
||||
entrance_names = recomp::game_warps[area_index].scenes[scene_index].entrances;
|
||||
entrance_names = zelda64::game_warps[area_index].scenes[scene_index].entrances;
|
||||
}
|
||||
};
|
||||
|
||||
void recomp::update_rml_display_refresh_rate() {
|
||||
void recompui::update_rml_display_refresh_rate() {
|
||||
static uint32_t lastRate = 0;
|
||||
if (!graphics_model_handle) return;
|
||||
|
||||
@@ -500,7 +500,7 @@ void recomp::update_rml_display_refresh_rate() {
|
||||
|
||||
DebugContext debug_context;
|
||||
|
||||
class ConfigMenu : public recomp::MenuController {
|
||||
class ConfigMenu : public recompui::MenuController {
|
||||
public:
|
||||
ConfigMenu() {
|
||||
|
||||
@@ -511,13 +511,13 @@ public:
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/config_menu.rml");
|
||||
}
|
||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
||||
recomp::register_event(listener, "apply_options",
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "apply_options",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
apply_graphics_config();
|
||||
});
|
||||
recomp::register_event(listener, "config_keydown",
|
||||
recompui::register_event(listener, "config_keydown",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (!prompt_context.open && event.GetId() == Rml::EventId::Keydown) {
|
||||
auto key = event.GetParameter<Rml::Input::KeyIdentifier>("key_identifier", Rml::Input::KeyIdentifier::KI_UNKNOWN);
|
||||
@@ -533,23 +533,23 @@ public:
|
||||
}
|
||||
});
|
||||
// This needs to be separate from `close_config_menu` so it ensures that the event is only on the target
|
||||
recomp::register_event(listener, "close_config_menu_backdrop",
|
||||
recompui::register_event(listener, "close_config_menu_backdrop",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||
close_config_menu();
|
||||
}
|
||||
});
|
||||
recomp::register_event(listener, "close_config_menu",
|
||||
recompui::register_event(listener, "close_config_menu",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
close_config_menu();
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "open_quit_game_prompt",
|
||||
recompui::register_event(listener, "open_quit_game_prompt",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::open_quit_game_prompt();
|
||||
zelda64::open_quit_game_prompt();
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "toggle_input_device",
|
||||
recompui::register_event(listener, "toggle_input_device",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
cur_device = cur_device == recomp::InputDevice::Controller
|
||||
? recomp::InputDevice::Keyboard
|
||||
@@ -558,7 +558,7 @@ public:
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "area_index_changed",
|
||||
recompui::register_event(listener, "area_index_changed",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
debug_context.area_index = event.GetParameter<int>("value", 0);
|
||||
debug_context.scene_index = 0;
|
||||
@@ -570,7 +570,7 @@ public:
|
||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "scene_index_changed",
|
||||
recompui::register_event(listener, "scene_index_changed",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
debug_context.scene_index = event.GetParameter<int>("value", 0);
|
||||
debug_context.entrance_index = 0;
|
||||
@@ -579,14 +579,14 @@ public:
|
||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "do_warp",
|
||||
recompui::register_event(listener, "do_warp",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::do_warp(debug_context.area_index, debug_context.scene_index, debug_context.entrance_index);
|
||||
zelda64::do_warp(debug_context.area_index, debug_context.scene_index, debug_context.entrance_index);
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "set_time",
|
||||
recompui::register_event(listener, "set_time",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_time(debug_context.set_time_day, debug_context.set_time_hour, debug_context.set_time_minute);
|
||||
zelda64::set_time(debug_context.set_time_day, debug_context.set_time_hour, debug_context.set_time_minute);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -722,7 +722,7 @@ public:
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
scanned_input_index = inputs.at(0).Get<size_t>();
|
||||
scanned_binding_index = inputs.at(1).Get<size_t>();
|
||||
recomp::start_scanning_input(cur_device);
|
||||
recomp::start_scanning_input(cur_device);
|
||||
model_handle.DirtyVariable("active_binding_input");
|
||||
model_handle.DirtyVariable("active_binding_slot");
|
||||
});
|
||||
@@ -730,18 +730,18 @@ public:
|
||||
constructor.BindEventCallback("reset_input_bindings_to_defaults",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
if (cur_device == recomp::InputDevice::Controller) {
|
||||
recomp::reset_cont_input_bindings();
|
||||
zelda64::reset_cont_input_bindings();
|
||||
} else {
|
||||
recomp::reset_kb_input_bindings();
|
||||
zelda64::reset_kb_input_bindings();
|
||||
}
|
||||
model_handle.DirtyAllVariables();
|
||||
});
|
||||
|
||||
constructor.BindEventCallback("clear_input_bindings",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
recomp::set_input_binding(input, binding_index, cur_device, recomp::InputField{});
|
||||
recomp::set_input_binding(input, binding_index, cur_device, recomp::InputField{});
|
||||
}
|
||||
model_handle.DirtyVariable("inputs");
|
||||
});
|
||||
@@ -776,7 +776,7 @@ public:
|
||||
|
||||
virtual int Size(void* ptr) override { return recomp::bindings_per_input; }
|
||||
virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>((uintptr_t)ptr);
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>((uintptr_t)ptr);
|
||||
return Rml::DataVariable{&input_field_definition_instance, &recomp::get_input_binding(input, address.index, cur_device)};
|
||||
}
|
||||
};
|
||||
@@ -812,7 +812,7 @@ public:
|
||||
return Rml::DataVariable(&binding_array_var_instance, nullptr);
|
||||
}
|
||||
else {
|
||||
recomp::GameInput input = recomp::get_input_from_enum_name(address.name);
|
||||
recomp::GameInput input = recomp::get_input_from_enum_name(address.name);
|
||||
if (input != recomp::GameInput::COUNT) {
|
||||
return Rml::DataVariable(&binding_container_var_instance, (void*)(uintptr_t)input);
|
||||
}
|
||||
@@ -966,14 +966,14 @@ public:
|
||||
constructor.Bind("prompt__confirmLabel", &prompt_context.confirmLabel);
|
||||
constructor.Bind("prompt__cancelLabel", &prompt_context.cancelLabel);
|
||||
|
||||
constructor.BindEventCallback("prompt__on_click", &recomp::PromptContext::on_click, &prompt_context);
|
||||
constructor.BindEventCallback("prompt__on_click", &recompui::PromptContext::on_click, &prompt_context);
|
||||
|
||||
prompt_context.model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_bindings(Rml::Context* context) override {
|
||||
// initially set cont state for ui help
|
||||
recomp::config_menu_set_cont_or_kb(recomp::get_cont_active());
|
||||
recomp::config_menu_set_cont_or_kb(recompui::get_cont_active());
|
||||
make_nav_help_bindings(context);
|
||||
make_general_bindings(context);
|
||||
make_controls_bindings(context);
|
||||
@@ -984,22 +984,22 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<recomp::MenuController> recomp::create_config_menu() {
|
||||
std::unique_ptr<recompui::MenuController> recompui::create_config_menu() {
|
||||
return std::make_unique<ConfigMenu>();
|
||||
}
|
||||
|
||||
bool recomp::get_debug_mode_enabled() {
|
||||
bool zelda64::get_debug_mode_enabled() {
|
||||
return debug_context.debug_enabled;
|
||||
}
|
||||
|
||||
void recomp::set_debug_mode_enabled(bool enabled) {
|
||||
void zelda64::set_debug_mode_enabled(bool enabled) {
|
||||
debug_context.debug_enabled = enabled;
|
||||
if (debug_context.model_handle) {
|
||||
debug_context.model_handle.DirtyVariable("debug_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::update_supported_options() {
|
||||
void recompui::update_supported_options() {
|
||||
msaa2x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
msaa4x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
msaa8x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
@@ -1010,7 +1010,7 @@ void recomp::update_supported_options() {
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
}
|
||||
|
||||
void recomp::toggle_fullscreen() {
|
||||
void recompui::toggle_fullscreen() {
|
||||
new_options.wm_option = (new_options.wm_option == ultramodern::WindowMode::Windowed) ? ultramodern::WindowMode::Fullscreen : ultramodern::WindowMode::Windowed;
|
||||
apply_graphics_config();
|
||||
graphics_model_handle.DirtyVariable("wm_option");
|
||||
|
||||
+48
-46
@@ -1,7 +1,7 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_game.h"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "RmlUi/Core.h"
|
||||
#include "nfd.h"
|
||||
#include <filesystem>
|
||||
@@ -11,6 +11,8 @@ std::string version_number = "v1.1.1";
|
||||
Rml::DataModelHandle model_handle;
|
||||
bool mm_rom_valid = false;
|
||||
|
||||
extern std::vector<recomp::GameEntry> supported_games;
|
||||
|
||||
void select_rom() {
|
||||
nfdnchar_t* native_path = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
|
||||
@@ -21,39 +23,39 @@ void select_rom() {
|
||||
NFD_FreePathN(native_path);
|
||||
native_path = nullptr;
|
||||
|
||||
recomp::RomValidationError rom_error = recomp::select_rom(path, recomp::Game::MM);
|
||||
|
||||
switch (rom_error) {
|
||||
case recomp::RomValidationError::Good:
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
break;
|
||||
case recomp::RomValidationError::FailedToOpen:
|
||||
recomp::message_box("Failed to open ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotARom:
|
||||
recomp::message_box("This is not a valid ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectRom:
|
||||
recomp::message_box("This ROM is not the correct game.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotYet:
|
||||
recomp::message_box("This game isn't supported yet.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectVersion:
|
||||
recomp::message_box("This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||
break;
|
||||
case recomp::RomValidationError::OtherError:
|
||||
recomp::message_box("An unknown error has occurred.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
|
||||
switch (rom_error) {
|
||||
case recomp::RomValidationError::Good:
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
break;
|
||||
case recomp::RomValidationError::FailedToOpen:
|
||||
recompui::message_box("Failed to open ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotARom:
|
||||
recompui::message_box("This is not a valid ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectRom:
|
||||
recompui::message_box("This ROM is not the correct game.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotYet:
|
||||
recompui::message_box("This game isn't supported yet.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectVersion:
|
||||
recompui::message_box(
|
||||
"This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||
break;
|
||||
case recomp::RomValidationError::OtherError:
|
||||
recompui::message_box("An unknown error has occurred.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LauncherMenu : public recomp::MenuController {
|
||||
class LauncherMenu : public recompui::MenuController {
|
||||
public:
|
||||
LauncherMenu() {
|
||||
mm_rom_valid = recomp::is_rom_valid(recomp::Game::MM);
|
||||
mm_rom_valid = recomp::is_rom_valid(supported_games[0].game_id);
|
||||
}
|
||||
~LauncherMenu() override {
|
||||
|
||||
@@ -61,37 +63,37 @@ public:
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/launcher.rml");
|
||||
}
|
||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
||||
recomp::register_event(listener, "select_rom",
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "select_rom",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
select_rom();
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "rom_selected",
|
||||
recompui::register_event(listener, "rom_selected",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "start_game",
|
||||
recompui::register_event(listener, "start_game",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::start_game(recomp::Game::MM);
|
||||
recomp::set_current_menu(recomp::Menu::None);
|
||||
recomp::start_game(supported_games[0].game_id);
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "open_controls",
|
||||
recompui::register_event(listener, "open_controls",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
recomp::set_config_submenu(recomp::ConfigSubmenu::Controls);
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::Controls);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "open_settings",
|
||||
recompui::register_event(listener, "open_settings",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
recomp::set_config_submenu(recomp::ConfigSubmenu::General);
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::General);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "exit_game",
|
||||
recompui::register_event(listener, "exit_game",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
ultramodern::quit();
|
||||
}
|
||||
@@ -107,6 +109,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<recomp::MenuController> recomp::create_launcher_menu() {
|
||||
std::unique_ptr<recompui::MenuController> recompui::create_launcher_menu() {
|
||||
return std::make_unique<LauncherMenu>();
|
||||
}
|
||||
|
||||
+55
-50
@@ -4,16 +4,22 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#ifdef _WIN32
|
||||
#include <SDL_video.h>
|
||||
#else
|
||||
#include <SDL2/SDL_video.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "ui_rml_hacks.hpp"
|
||||
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
#include "rt64_layer.h"
|
||||
#include "ultramodern/rt64_layer.hpp"
|
||||
#include "rt64_render_hooks.h"
|
||||
#include "rt64_render_interface_builders.h"
|
||||
|
||||
@@ -718,7 +724,7 @@ Rml::Element* get_target(Rml::ElementDocument* document, Rml::Element* element)
|
||||
return element;
|
||||
}
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class UiEventListener : public Rml::EventListener {
|
||||
event_handler_t* handler_;
|
||||
Rml::String param_;
|
||||
@@ -759,7 +765,7 @@ namespace recomp {
|
||||
};
|
||||
}
|
||||
|
||||
void recomp::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) {
|
||||
void recompui::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) {
|
||||
listener.register_event(name, handler);
|
||||
}
|
||||
|
||||
@@ -779,8 +785,8 @@ Rml::Element* find_autofocus_element(Rml::Element* start) {
|
||||
struct UIContext {
|
||||
struct UIRenderContext render;
|
||||
class {
|
||||
std::unordered_map<recomp::Menu, std::unique_ptr<recomp::MenuController>> menus;
|
||||
std::unordered_map<recomp::Menu, Rml::ElementDocument*> documents;
|
||||
std::unordered_map<recompui::Menu, std::unique_ptr<recompui::MenuController>> menus;
|
||||
std::unordered_map<recompui::Menu, Rml::ElementDocument*> documents;
|
||||
Rml::ElementDocument* current_document;
|
||||
Rml::Element* prev_focused;
|
||||
bool mouse_is_active_changed = false;
|
||||
@@ -794,13 +800,13 @@ struct UIContext {
|
||||
std::unique_ptr<SystemInterface_SDL> system_interface;
|
||||
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
|
||||
Rml::Context* context;
|
||||
recomp::UiEventListenerInstancer event_listener_instancer;
|
||||
recompui::UiEventListenerInstancer event_listener_instancer;
|
||||
|
||||
void unload() {
|
||||
render_interface.reset();
|
||||
}
|
||||
|
||||
void swap_document(recomp::Menu menu) {
|
||||
void swap_document(recompui::Menu menu) {
|
||||
if (current_document != nullptr) {
|
||||
Rml::Element* window_el = current_document->GetElementById("window");
|
||||
if (window_el != nullptr) {
|
||||
@@ -831,7 +837,7 @@ struct UIContext {
|
||||
mouse_is_active_initialized = false;
|
||||
}
|
||||
|
||||
void swap_config_menu(recomp::ConfigSubmenu submenu) {
|
||||
void swap_config_menu(recompui::ConfigSubmenu submenu) {
|
||||
if (current_document != nullptr) {
|
||||
Rml::Element* config_tabset_base = current_document->GetElementById("config_tabset");
|
||||
if (config_tabset_base != nullptr) {
|
||||
@@ -911,7 +917,7 @@ struct UIContext {
|
||||
}
|
||||
|
||||
if (mouse_is_active_initialized) {
|
||||
recomp::set_cursor_visible(mouse_is_active);
|
||||
recompui::set_cursor_visible(mouse_is_active);
|
||||
}
|
||||
|
||||
if (current_document == nullptr) {
|
||||
@@ -939,7 +945,6 @@ struct UIContext {
|
||||
if (cont_is_active || non_mouse_interacted) {
|
||||
if (non_mouse_interacted) {
|
||||
auto focusedEl = current_document->GetFocusLeafNode();
|
||||
Rml::Variant* ti = focusedEl == nullptr ? nullptr : focusedEl->GetAttribute("tab-index");
|
||||
if (focusedEl == nullptr || RecompRml::CanFocusElement(focusedEl) != RecompRml::CanFocus::Yes) {
|
||||
Rml::Element* element = find_autofocus_element(current_document);
|
||||
if (element != nullptr) {
|
||||
@@ -981,14 +986,14 @@ struct UIContext {
|
||||
}
|
||||
}
|
||||
|
||||
void add_menu(recomp::Menu menu, std::unique_ptr<recomp::MenuController>&& controller) {
|
||||
void add_menu(recompui::Menu menu, std::unique_ptr<recompui::MenuController>&& controller) {
|
||||
menus.emplace(menu, std::move(controller));
|
||||
}
|
||||
|
||||
void update_config_menu_loop(bool menu_changed) {
|
||||
static int prevTab = -1;
|
||||
if (menu_changed) prevTab = -1;
|
||||
recomp::update_rml_display_refresh_rate();
|
||||
recompui::update_rml_display_refresh_rate();
|
||||
|
||||
Rml::ElementTabSet *tabset = (Rml::ElementTabSet *)current_document->GetElementById("config_tabset");
|
||||
if (tabset == nullptr) return;
|
||||
@@ -1022,7 +1027,7 @@ struct UIContext {
|
||||
void update_prompt_loop(void) {
|
||||
static bool wasShowingPrompt = false;
|
||||
|
||||
recomp::PromptContext *ctx = recomp::get_prompt_context();
|
||||
recompui::PromptContext *ctx = recompui::get_prompt_context();
|
||||
if (!ctx->open && wasShowingPrompt) {
|
||||
Rml::Element* focused = current_document->GetFocusLeafNode();
|
||||
if (focused) focused->Blur();
|
||||
@@ -1088,8 +1093,8 @@ struct UIContext {
|
||||
|
||||
Rml::Element *confirmButton = current_document->GetElementById("prompt__confirm-button");
|
||||
Rml::Element *cancelButton = current_document->GetElementById("prompt__cancel-button");
|
||||
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recomp::button_variants.at(ctx->confirmVariant));
|
||||
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recomp::button_variants.at(ctx->cancelVariant));
|
||||
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recompui::button_variants.at(ctx->confirmVariant));
|
||||
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recompui::button_variants.at(ctx->cancelVariant));
|
||||
}
|
||||
} rml;
|
||||
};
|
||||
@@ -1100,7 +1105,7 @@ std::mutex ui_context_mutex{};
|
||||
// TODO make this not be global
|
||||
extern SDL_Window* window;
|
||||
|
||||
void recomp::get_window_size(int& width, int& height) {
|
||||
void recompui::get_window_size(int& width, int& height) {
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
}
|
||||
|
||||
@@ -1110,8 +1115,8 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
#endif
|
||||
ui_context = std::make_unique<UIContext>();
|
||||
|
||||
ui_context->rml.add_menu(recomp::Menu::Config, recomp::create_config_menu());
|
||||
ui_context->rml.add_menu(recomp::Menu::Launcher, recomp::create_launcher_menu());
|
||||
ui_context->rml.add_menu(recompui::Menu::Config, recompui::create_config_menu());
|
||||
ui_context->rml.add_menu(recompui::Menu::Launcher, recompui::create_launcher_menu());
|
||||
|
||||
ui_context->render.interface = interface;
|
||||
ui_context->render.device = device;
|
||||
@@ -1129,7 +1134,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
Rml::Initialise();
|
||||
|
||||
// Apply the hack to replace RmlUi's default color parser with one that conforms to HTML5 alpha parsing for SASS compatibility
|
||||
recomp::apply_color_hack();
|
||||
recompui::apply_color_hack();
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
@@ -1167,16 +1172,16 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
|
||||
moodycamel::ConcurrentQueue<SDL_Event> ui_event_queue{};
|
||||
|
||||
void recomp::queue_event(const SDL_Event& event) {
|
||||
void recompui::queue_event(const SDL_Event& event) {
|
||||
ui_event_queue.enqueue(event);
|
||||
}
|
||||
|
||||
bool recomp::try_deque_event(SDL_Event& out) {
|
||||
bool recompui::try_deque_event(SDL_Event& out) {
|
||||
return ui_event_queue.try_dequeue(out);
|
||||
}
|
||||
|
||||
std::atomic<recomp::Menu> open_menu = recomp::Menu::Launcher;
|
||||
std::atomic<recomp::ConfigSubmenu> open_config_submenu = recomp::ConfigSubmenu::Count;
|
||||
std::atomic<recompui::Menu> open_menu = recompui::Menu::Launcher;
|
||||
std::atomic<recompui::ConfigSubmenu> open_config_submenu = recompui::ConfigSubmenu::Count;
|
||||
|
||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||
switch (button.button) {
|
||||
@@ -1236,15 +1241,15 @@ void apply_background_input_mode() {
|
||||
last_input_mode = cur_input_mode;
|
||||
}
|
||||
|
||||
bool recomp::get_cont_active() {
|
||||
bool recompui::get_cont_active() {
|
||||
return ui_context->rml.cont_is_active;
|
||||
}
|
||||
|
||||
void recomp::set_cont_active(bool active) {
|
||||
void recompui::set_cont_active(bool active) {
|
||||
ui_context->rml.cont_is_active = active;
|
||||
}
|
||||
|
||||
void recomp::activate_mouse() {
|
||||
void recompui::activate_mouse() {
|
||||
ui_context->rml.update_primary_input(true, false);
|
||||
ui_context->rml.update_focus(true, false);
|
||||
}
|
||||
@@ -1267,12 +1272,12 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
bool reload_sheets = is_reload_held && !was_reload_held;
|
||||
was_reload_held = is_reload_held;
|
||||
|
||||
static recomp::Menu prev_menu = recomp::Menu::None;
|
||||
recomp::Menu cur_menu = open_menu.load();
|
||||
static recompui::Menu prev_menu = recompui::Menu::None;
|
||||
recompui::Menu cur_menu = open_menu.load();
|
||||
|
||||
if (reload_sheets) {
|
||||
ui_context->rml.load_documents();
|
||||
prev_menu = recomp::Menu::None;
|
||||
prev_menu = recompui::Menu::None;
|
||||
}
|
||||
|
||||
bool menu_changed = cur_menu != prev_menu;
|
||||
@@ -1280,10 +1285,10 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
ui_context->rml.swap_document(cur_menu);
|
||||
}
|
||||
|
||||
recomp::ConfigSubmenu config_submenu = open_config_submenu.load();
|
||||
if (config_submenu != recomp::ConfigSubmenu::Count) {
|
||||
recompui::ConfigSubmenu config_submenu = open_config_submenu.load();
|
||||
if (config_submenu != recompui::ConfigSubmenu::Count) {
|
||||
ui_context->rml.swap_config_menu(config_submenu);
|
||||
open_config_submenu.store(recomp::ConfigSubmenu::Count);
|
||||
open_config_submenu.store(recompui::ConfigSubmenu::Count);
|
||||
}
|
||||
|
||||
prev_menu = cur_menu;
|
||||
@@ -1296,15 +1301,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
bool cont_interacted = false;
|
||||
bool kb_interacted = false;
|
||||
|
||||
if (cur_menu == recomp::Menu::Config) {
|
||||
if (cur_menu == recompui::Menu::Config) {
|
||||
ui_context->rml.update_config_menu_loop(menu_changed);
|
||||
}
|
||||
if (cur_menu != recomp::Menu::None) {
|
||||
if (cur_menu != recompui::Menu::None) {
|
||||
ui_context->rml.update_prompt_loop();
|
||||
}
|
||||
|
||||
while (recomp::try_deque_event(cur_event)) {
|
||||
bool menu_is_open = cur_menu != recomp::Menu::None;
|
||||
while (recompui::try_deque_event(cur_event)) {
|
||||
bool menu_is_open = cur_menu != recompui::Menu::None;
|
||||
|
||||
if (!recomp::all_input_disabled()) {
|
||||
// Implement some additional behavior for specific events on top of what RmlUi normally does with them.
|
||||
@@ -1323,7 +1328,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
last_mouse_pos[1] = cur_event.motion.y;
|
||||
|
||||
// if controller is the primary input, don't use mouse movement to allow cursor to reactivate
|
||||
if (recomp::get_cont_active()) {
|
||||
if (recompui::get_cont_active()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1401,15 +1406,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
|
||||
if (open_config) {
|
||||
cur_menu = recomp::Menu::Config;
|
||||
open_menu.store(recomp::Menu::Config);
|
||||
cur_menu = recompui::Menu::Config;
|
||||
open_menu.store(recompui::Menu::Config);
|
||||
ui_context->rml.swap_document(cur_menu);
|
||||
}
|
||||
}
|
||||
} // end dequeue event loop
|
||||
|
||||
if (cont_interacted || kb_interacted || mouse_clicked) {
|
||||
recomp::set_cont_active(cont_interacted);
|
||||
recompui::set_cont_active(cont_interacted);
|
||||
}
|
||||
recomp::config_menu_set_cont_or_kb(ui_context->rml.cont_is_active);
|
||||
|
||||
@@ -1421,7 +1426,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
ui_context->rml.update_primary_input(mouse_moved, non_mouse_interacted);
|
||||
ui_context->rml.update_focus(mouse_moved, non_mouse_interacted);
|
||||
|
||||
if (cur_menu != recomp::Menu::None) {
|
||||
if (cur_menu != recompui::Menu::None) {
|
||||
int width = swap_chain_framebuffer->getWidth();
|
||||
int height = swap_chain_framebuffer->getHeight();
|
||||
|
||||
@@ -1457,25 +1462,25 @@ void set_rt64_hooks() {
|
||||
RT64::SetRenderHooks(init_hook, draw_hook, deinit_hook);
|
||||
}
|
||||
|
||||
void recomp::set_current_menu(Menu menu) {
|
||||
void recompui::set_current_menu(Menu menu) {
|
||||
open_menu.store(menu);
|
||||
if (menu == recomp::Menu::None) {
|
||||
if (menu == recompui::Menu::None) {
|
||||
ui_context->rml.system_interface->SetMouseCursor("arrow");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_config_submenu(recomp::ConfigSubmenu submenu) {
|
||||
void recompui::set_config_submenu(recompui::ConfigSubmenu submenu) {
|
||||
open_config_submenu.store(submenu);
|
||||
}
|
||||
|
||||
void recomp::destroy_ui() {
|
||||
void recompui::destroy_ui() {
|
||||
}
|
||||
|
||||
recomp::Menu recomp::get_current_menu() {
|
||||
recompui::Menu recompui::get_current_menu() {
|
||||
return open_menu.load();
|
||||
}
|
||||
|
||||
void recomp::message_box(const char* msg) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, recomp::program_name.data(), msg, nullptr);
|
||||
void recompui::message_box(const char* msg) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, zelda64::program_name.data(), msg, nullptr);
|
||||
printf("[ERROR] %s\n", msg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user