diff --git a/include/recomp_config.h b/include/recomp_config.h index fa5e42d..e616a80 100644 --- a/include/recomp_config.h +++ b/include/recomp_config.h @@ -8,6 +8,7 @@ namespace recomp { constexpr std::u8string_view program_id = u8"Zelda64Recompiled"; constexpr std::u8string_view mm_game_id = u8"mm.n64.us.1.0"; + constexpr std::string_view program_name = "Zelda 64: Recompiled"; void load_config(); void save_config(); diff --git a/include/rt64_layer.h b/include/rt64_layer.h index 1b38ed6..a138785 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -9,12 +9,21 @@ namespace RT64 { } namespace ultramodern { + enum class RT64SetupResult { + Success, + DynamicLibrariesNotFound, + InvalidGraphicsAPI, + GraphicsAPINotFound, + GraphicsDeviceNotFound + }; + struct WindowHandle; struct RT64Context { public: ~RT64Context(); RT64Context(uint8_t* rdram, WindowHandle window_handle, bool developer_mode); bool valid() { return static_cast(app); } + RT64SetupResult get_setup_result() { return setup_result; } void update_config(const GraphicsConfig& old_config, const GraphicsConfig& new_config); void enable_instant_present(); @@ -25,6 +34,7 @@ namespace ultramodern { uint32_t get_display_framerate(); void load_shader_cache(std::span cache_binary); private: + RT64SetupResult setup_result; std::unique_ptr app; }; diff --git a/patches/autosaving.c b/patches/autosaving.c index 2b056eb..56da1a0 100644 --- a/patches/autosaving.c +++ b/patches/autosaving.c @@ -70,7 +70,18 @@ void Sram_SyncWriteToFlash(SramContext* sramCtx, s32 curPage, s32 numPages); void autosave_reset_timer(); void autosave_reset_timer_slow(); -void do_autosave(SramContext* sramCtx) { +void do_autosave(PlayState* play) { + // Transfer the scene flags into the cycle flags. + Play_SaveCycleSceneFlags(&play->state); + // Transfer the cycle flags into the save buffer. Logic copied from func_8014546C. + for (s32 i = 0; i < ARRAY_COUNT(gSaveContext.cycleSceneFlags); i++) { + gSaveContext.save.saveInfo.permanentSceneFlags[i].chest = gSaveContext.cycleSceneFlags[i].chest; + gSaveContext.save.saveInfo.permanentSceneFlags[i].switch0 = gSaveContext.cycleSceneFlags[i].switch0; + gSaveContext.save.saveInfo.permanentSceneFlags[i].switch1 = gSaveContext.cycleSceneFlags[i].switch1; + gSaveContext.save.saveInfo.permanentSceneFlags[i].clearedRoom = gSaveContext.cycleSceneFlags[i].clearedRoom; + gSaveContext.save.saveInfo.permanentSceneFlags[i].collectible = gSaveContext.cycleSceneFlags[i].collectible; + } + s32 fileNum = gSaveContext.fileNum; gSaveContext.save.isOwlSave = SAVE_TYPE_AUTOSAVE; @@ -78,6 +89,7 @@ void do_autosave(SramContext* sramCtx) { gSaveContext.save.saveInfo.checksum = 0; gSaveContext.save.saveInfo.checksum = Sram_CalcChecksum(&gSaveContext, offsetof(SaveContext, fileNum)); + SramContext* sramCtx = &play->sramCtx; // Copy the saved parts of the global save context into the sram saving buffer. Lib_MemCpy(sramCtx->saveBuf, &gSaveContext, offsetof(SaveContext, fileNum)); // Synchronously save into the owl save slot and the backup owl save slot. @@ -413,7 +425,7 @@ void autosave_post_play_update(PlayState* play) { frames_since_autosave_ready >= MIN_FRAMES_SINCE_READY && time_now - last_autosave_time > (OS_USEC_TO_CYCLES(1000 * (recomp_autosave_interval() + extra_autosave_delay_milliseconds))) ) { - do_autosave(&play->sramCtx); + do_autosave(play); show_autosave_icon(); autosave_reset_timer(); } diff --git a/src/ui/ui_launcher.cpp b/src/ui/ui_launcher.cpp index 13cbc47..5c25218 100644 --- a/src/ui/ui_launcher.cpp +++ b/src/ui/ui_launcher.cpp @@ -6,7 +6,7 @@ #include "nfd.h" #include -std::string version_number = "v1.0.0"; +std::string version_number = "v1.0.1"; Rml::DataModelHandle model_handle; bool mm_rom_valid = false; diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index f62c992..c885184 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -8,6 +8,7 @@ #include "recomp_ui.h" #include "recomp_input.h" #include "recomp_game.h" +#include "recomp_config.h" #include "ui_rml_hacks.hpp" #include "concurrentqueue.h" @@ -1464,5 +1465,5 @@ recomp::Menu recomp::get_current_menu() { } void recomp::message_box(const char* msg) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", msg, nullptr); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, recomp::program_name.data(), msg, nullptr); } diff --git a/ultramodern/events.cpp b/ultramodern/events.cpp index 1597f76..957d704 100644 --- a/ultramodern/events.cpp +++ b/ultramodern/events.cpp @@ -16,6 +16,7 @@ #include "config.hpp" #include "rt64_layer.h" #include "recomp.h" +#include "recomp_game.h" #include "recomp_ui.h" #include "recomp_input.h" #include "rsp.h" @@ -301,6 +302,8 @@ void ultramodern::load_shader_cache(std::span cache_data) { events_context.action_queue.enqueue(LoadShaderCacheAction{cache_data}); } +std::atomic rt64_setup_result = ultramodern::RT64SetupResult::Success; + void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready, ultramodern::WindowHandle window_handle) { bool enabled_instant_present = false; using namespace std::chrono_literals; @@ -313,7 +316,11 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re ultramodern::RT64Context rt64{rdram, window_handle, cur_config.load().developer_mode}; if (!rt64.valid()) { - throw std::runtime_error("Failed to initialize RT64!"); + // TODO move recomp code out of ultramodern. + rt64_setup_result.store(rt64.get_setup_result()); + // Notify the caller thread that this thread is ready. + thread_ready->signal(); + return; } // TODO move recomp code out of ultramodern. @@ -537,6 +544,27 @@ void ultramodern::send_si_message(RDRAM_ARG1) { osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); } +std::string get_graphics_api_name(ultramodern::GraphicsApi api) { + if (api == ultramodern::GraphicsApi::Auto) { +#if defined(_WIN32) + api = ultramodern::GraphicsApi::D3D12; +#elif defined(__gnu_linux__) + api = ultramodern::GraphicsApi::Vulkan; +#else + static_assert(false && "Unimplemented") +#endif + } + + switch (api) { + case ultramodern::GraphicsApi::D3D12: + return "D3D12"; + case ultramodern::GraphicsApi::Vulkan: + return "Vulkan"; + default: + return "[Unknown graphics API]"; + } +} + void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) { moodycamel::LightweightSemaphore gfx_thread_ready; moodycamel::LightweightSemaphore task_thread_ready; @@ -549,6 +577,30 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) gfx_thread_ready.wait(); task_thread_ready.wait(); + ultramodern::RT64SetupResult setup_result = rt64_setup_result.load(); + if (rt64_setup_result != ultramodern::RT64SetupResult::Success) { + auto show_rt64_error = [](const std::string& msg) { + // TODO move recomp code out of ultramodern (message boxes). + recomp::message_box(("An error has been encountered on startup: " + msg).c_str()); + }; + const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date."; + switch (rt64_setup_result) { + case ultramodern::RT64SetupResult::DynamicLibrariesNotFound: + show_rt64_error("Failed to load dynamic libraries. Make sure the DLLs are next to the recomp executable."); + break; + case ultramodern::RT64SetupResult::InvalidGraphicsAPI: + show_rt64_error(get_graphics_api_name(cur_config.load().api_option) + " is not supported on this platform. Please select a different graphics API."); + break; + case ultramodern::RT64SetupResult::GraphicsAPINotFound: + show_rt64_error("Unable to initialize " + get_graphics_api_name(cur_config.load().api_option) + "." + driver_os_suffix); + break; + case ultramodern::RT64SetupResult::GraphicsDeviceNotFound: + show_rt64_error("Unable to find compatible graphics device." + driver_os_suffix); + break; + } + throw std::runtime_error("Failed to initialize RT64"); + } + events_context.vi.thread = std::thread{ vi_thread_func }; } diff --git a/ultramodern/rt64_layer.cpp b/ultramodern/rt64_layer.cpp index db7a1bd..611e1eb 100644 --- a/ultramodern/rt64_layer.cpp +++ b/ultramodern/rt64_layer.cpp @@ -97,6 +97,21 @@ void set_application_user_config(RT64::Application* application, const ultramode application->userConfig.refreshRateTarget = config.rr_manual_value; } +ultramodern::RT64SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) { + switch (rt64_result) { + case RT64::Application::SetupResult::Success: + return ultramodern::RT64SetupResult::Success; + case RT64::Application::SetupResult::DynamicLibrariesNotFound: + return ultramodern::RT64SetupResult::DynamicLibrariesNotFound; + case RT64::Application::SetupResult::InvalidGraphicsAPI: + return ultramodern::RT64SetupResult::InvalidGraphicsAPI; + case RT64::Application::SetupResult::GraphicsAPINotFound: + return ultramodern::RT64SetupResult::GraphicsAPINotFound; + case RT64::Application::SetupResult::GraphicsDeviceNotFound: + return ultramodern::RT64SetupResult::GraphicsDeviceNotFound; + } +} + ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) { static unsigned char dummy_rom_header[0x40]; set_rt64_hooks(); @@ -179,7 +194,8 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle #ifdef _WIN32 thread_id = window_handle.thread_id; #endif - if (app->setup(thread_id) != RT64::Application::SetupResult::Success) { + setup_result = map_setup_result(app->setup(thread_id)); + if (setup_result != ultramodern::RT64SetupResult::Success) { app = nullptr; return; }