From ca66fdddbbc2da772fe016fc09ebf0f870d3d661 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 3 Mar 2024 21:00:49 -0500 Subject: [PATCH] Implemented proper ROM selection menu --- CMakeLists.txt | 1 + assets/launcher.rml | 8 +- include/recomp_config.h | 3 + include/recomp_game.h | 39 ++++ include/rt64_layer.h | 2 +- patches/camera_transform_tagging.c | 1 - src/game/config.cpp | 6 +- src/main/main.cpp | 6 +- src/recomp/pi.cpp | 22 ++- src/recomp/recomp.cpp | 295 +++++++++++++++++++++++------ src/recomp/rt64_layer.cpp | 5 +- src/ui/ui_launcher.cpp | 67 ++++++- src/ui/ui_renderer.cpp | 7 +- ultramodern/events.cpp | 12 +- ultramodern/ultrainit.cpp | 4 +- ultramodern/ultramodern.hpp | 6 +- 16 files changed, 387 insertions(+), 97 deletions(-) create mode 100644 include/recomp_game.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c366c76..a09ca6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,7 @@ target_include_directories(MMRecomp PRIVATE ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/rhi ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/render ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include + ${CMAKE_SOURCE_DIR}/lib/lib/nativefiledialog-extended/src/include ${CMAKE_BINARY_DIR}/shaders ) diff --git a/assets/launcher.rml b/assets/launcher.rml index 12f1864..1748505 100644 --- a/assets/launcher.rml +++ b/assets/launcher.rml @@ -12,7 +12,7 @@ -
+
@@ -35,7 +35,11 @@
- + diff --git a/include/recomp_config.h b/include/recomp_config.h index c15604b..9310d53 100644 --- a/include/recomp_config.h +++ b/include/recomp_config.h @@ -1,6 +1,7 @@ #ifndef __RECOMP_CONFIG_H__ #define __RECOMP_CONFIG_H__ +#include #include #include "../ultramodern/config.hpp" @@ -12,6 +13,8 @@ namespace recomp { void reset_input_bindings(); void reset_graphics_options(); + + std::filesystem::path get_app_folder_path(); }; #endif \ No newline at end of file diff --git a/include/recomp_game.h b/include/recomp_game.h new file mode 100644 index 0000000..13f0061 --- /dev/null +++ b/include/recomp_game.h @@ -0,0 +1,39 @@ +#ifndef __RECOMP_GAME__ +#define __RECOMP_GAME__ + +#include +#include + +#include "recomp.h" +#include "../ultramodern/ultramodern.hpp" +#include "rt64_layer.h" + +namespace recomp { + enum class Game { + OoT, + MM, + None, + Quit + }; + enum class RomValidationError { + Good, + FailedToOpen, + NotARom, + IncorrectRom, + NotYet, + IncorrectVersion, + OtherError + }; + void check_all_stored_roms(); + bool load_stored_rom(Game game); + RomValidationError select_rom(const std::filesystem::path& rom_path, Game game); + bool is_rom_valid(Game game); + bool is_rom_loaded(); + void set_rom_contents(std::vector&& new_rom); + void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes); + void 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); + void start_game(Game game); + void message_box(const char* message); +} + +#endif diff --git a/include/rt64_layer.h b/include/rt64_layer.h index fe2550a..b5df376 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -12,7 +12,7 @@ namespace ultramodern { struct WindowHandle; } -RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle, bool developer_mode); +RT64::Application* RT64Init(uint8_t* rdram, ultramodern::WindowHandle window_handle, bool developer_mode); void RT64UpdateConfig(RT64::Application* application, const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config); void RT64EnableInstantPresent(RT64::Application* application); void RT64SendDL(uint8_t* rdram, const OSTask* task); diff --git a/patches/camera_transform_tagging.c b/patches/camera_transform_tagging.c index 0a6e533..8ba4aff 100644 --- a/patches/camera_transform_tagging.c +++ b/patches/camera_transform_tagging.c @@ -165,7 +165,6 @@ void View_Apply(View* view, s32 mask) { // Force skipping interpolation if the previous view was kaleido and this one isn't. if (prev_in_kaleido && !in_kaleido) { camera_skip_interpolation_forced = true; - recomp_printf("exited kaleido\n"); } // @recomp Apply camera interpolation overrides. diff --git a/src/game/config.cpp b/src/game/config.cpp index 6de4d16..8b227e1 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -69,7 +69,7 @@ namespace recomp { } } -std::filesystem::path get_config_folder_path() { +std::filesystem::path recomp::get_app_folder_path() { std::filesystem::path recomp_dir{}; #if defined(_WIN32) @@ -232,7 +232,7 @@ void load_controls_config(const std::filesystem::path& path) { } void recomp::load_config() { - std::filesystem::path recomp_dir = get_config_folder_path(); + std::filesystem::path recomp_dir = recomp::get_app_folder_path(); std::filesystem::path graphics_path = recomp_dir / graphics_filename; std::filesystem::path controls_path = recomp_dir / controls_filename; @@ -254,7 +254,7 @@ void recomp::load_config() { } void recomp::save_config() { - std::filesystem::path recomp_dir = get_config_folder_path(); + std::filesystem::path recomp_dir = recomp::get_app_folder_path(); if (recomp_dir.empty()) { return; diff --git a/src/main/main.cpp b/src/main/main.cpp index 73bf06c..f73bf78 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -20,6 +20,7 @@ #include "recomp_ui.h" #include "recomp_input.h" #include "recomp_config.h" +#include "recomp_game.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -229,13 +230,12 @@ int main(int argc, char** argv) { std::setlocale(LC_ALL, "en_US.UTF-8"); #endif - printf("Current dir: %ls\n", std::filesystem::current_path().c_str()); + //printf("Current dir: %ls\n", std::filesystem::current_path().c_str()); // Initialize SDL audio and set the output frequency. SDL_InitSubSystem(SDL_INIT_AUDIO); reset_audio(48000); - init(); recomp::load_config(); ultramodern::gfx_callbacks_t gfx_callbacks{ @@ -255,7 +255,7 @@ int main(int argc, char** argv) { .get_input = recomp::get_n64_input, }; - ultramodern::start({}, audio_callbacks, input_callbacks, gfx_callbacks); + recomp::start({}, audio_callbacks, input_callbacks, gfx_callbacks); return EXIT_SUCCESS; } diff --git a/src/recomp/pi.cpp b/src/recomp/pi.cpp index 0db725f..5ee5796 100644 --- a/src/recomp/pi.cpp +++ b/src/recomp/pi.cpp @@ -3,9 +3,20 @@ #include #include #include "recomp.h" +#include "recomp_game.h" #include "../ultramodern/ultra64.h" #include "../ultramodern/ultramodern.hpp" +static std::vector rom; + +bool recomp::is_rom_loaded() { + return !rom.empty(); +} + +void recomp::set_rom_contents(std::vector&& 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. @@ -20,9 +31,6 @@ constexpr uint32_t phys_to_k1(uint32_t addr) { return addr | 0xA0000000; } -extern std::unique_ptr rom; -extern size_t rom_size; - extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) { OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::cart_handle); handle->type = 0; // cart @@ -36,14 +44,14 @@ extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) { ; } -void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) { +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.get() + physical_addr - rom_base; + 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++; @@ -110,7 +118,7 @@ void do_dma(uint8_t* rdram, PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t phy if (direction == 0) { if (physical_addr >= rom_base) { // read cart rom - do_rom_read(rdram, rdram_address, physical_addr, size); + 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); @@ -181,7 +189,7 @@ extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) { if (physical_addr > rom_base) { // cart rom - do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t)); + recomp::do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t)); } else { // sram assert(false && "SRAM ReadIo unimplemented"); diff --git a/src/recomp/recomp.cpp b/src/recomp/recomp.cpp index d7271f1..00435f8 100644 --- a/src/recomp/recomp.cpp +++ b/src/recomp/recomp.cpp @@ -7,9 +7,13 @@ #include #include #include +#include #include #include #include "recomp.h" +#include "recomp_game.h" +#include "recomp_config.h" +#include "xxHash/xxh3.h" #include "../ultramodern/ultramodern.hpp" #ifdef _WIN32 @@ -28,13 +32,204 @@ constexpr uint32_t byteswap(uint32_t val) { } #endif -extern "C" void _bzero(uint8_t* rdram, recomp_context* ctx) { - gpr start_addr = ctx->r4; - gpr size = ctx->r5; +struct RomEntry { + uint64_t xxhash3_value; + std::u8string stored_filename; + std::string internal_rom_name; +}; - for (uint32_t i = 0; i < size; i++) { - MEM_B(start_addr, i) = 0; +const std::unordered_map game_roms { + { recomp::Game::MM, { 0xEF18B4A9E2386169ULL, u8"mm.n64.us.1.0.z64", "ZELDA MAJORA'S MASK" }}, +}; + +bool check_hash(const std::vector& rom_data, uint64_t expected_hash) { + uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size()); + + return calculated_hash == expected_hash; +} + +std::vector read_file(const std::filesystem::path& path) { + std::vector 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(ret.data()), ret.size()); } + + return ret; +} + +bool write_file(const std::filesystem::path& path, const std::vector& data) { + std::ofstream out_file{ path, std::ios::binary }; + + if (!out_file.good()) { + return false; + } + + out_file.write(reinterpret_cast(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 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 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 first_rom_bytes { 0x80, 0x37, 0x12, 0x40 }; + +enum class ByteswapType { + NotByteswapped, + Byteswapped4, + Byteswapped2, + Invalid +}; + +ByteswapType check_rom_start(const std::vector& 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& 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 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(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(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) { @@ -106,11 +301,6 @@ void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t ar func(rdram, &ctx); } -void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t dev_address, size_t num_bytes); - -std::unique_ptr rom; -size_t rom_size; - // Recomp generation functions extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx); gpr get_entrypoint_address(); @@ -119,9 +309,6 @@ void init_overlays(); extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size); extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); -std::unique_ptr rdram_buffer; -recomp_context context{}; - void read_patch_data(uint8_t* rdram, gpr patch_data_address) { const char patches_data_file_path[] = "patches/patches.bin"; std::ifstream patches_data_file{ patches_data_file_path, std::ios::binary }; @@ -144,28 +331,7 @@ void read_patch_data(uint8_t* rdram, gpr patch_data_address) { } } -EXPORT extern "C" void init() { - { - std::ifstream rom_file{ get_rom_name(), std::ios::binary }; - - size_t iobuf_size = 0x100000; - std::unique_ptr iobuf = std::make_unique(iobuf_size); - rom_file.rdbuf()->pubsetbuf(iobuf.get(), iobuf_size); - - if (!rom_file) { - fprintf(stderr, "Failed to open rom: %s\n", get_rom_name()); - exit(EXIT_FAILURE); - } - - rom_file.seekg(0, std::ios::end); - rom_size = rom_file.tellg(); - rom_file.seekg(0, std::ios::beg); - - rom = std::make_unique(rom_size); - - rom_file.read(reinterpret_cast(rom.get()), rom_size); - } - +void init(uint8_t* rdram, recomp_context* ctx) { // Initialize the overlays init_overlays(); @@ -175,22 +341,18 @@ EXPORT extern "C" void init() { // Load overlays in the first 1MB load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); - // Allocate rdram_buffer - rdram_buffer = std::make_unique(ultramodern::rdram_size); - std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size); - // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) - do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); + recomp::do_rom_read(rdram, entrypoint, 0x10001000, 0x100000); // Read in any extra data from patches - read_patch_data(rdram_buffer.get(), (gpr)(s32)0x80800100); + read_patch_data(rdram, (gpr)(s32)0x80800100); // Set up stack pointer - context.r29 = 0xFFFFFFFF803FFFF0u; + ctx->r29 = 0xFFFFFFFF803FFFF0u; // Set up context floats - context.f_odd = &context.f0.u32h; - context.mips3_float_mode = false; + ctx->f_odd = &ctx->f0.u32h; + ctx->mips3_float_mode = false; // Initialize variables normally set by IPL3 constexpr int32_t osTvType = 0x80000300; @@ -201,22 +363,21 @@ EXPORT extern "C" void init() { constexpr int32_t osVersion = 0x80000314; constexpr int32_t osMemSize = 0x80000318; constexpr int32_t osAppNMIBuffer = 0x8000031c; - uint8_t *rdram = rdram_buffer.get(); 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_int game_started = -1; +std::atomic game_started = recomp::Game::None; -void ultramodern::start_game(int game) { +void recomp::start_game(recomp::Game game) { game_started.store(game); game_started.notify_all(); } bool ultramodern::is_game_started() { - return game_started.load() != -1; + return game_started.load() != recomp::Game::None; } void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks); @@ -226,24 +387,25 @@ std::atomic_bool exited = false; void ultramodern::quit() { exited.store(true); - int desired = -1; - game_started.compare_exchange_strong(desired, -2); + recomp::Game desired = recomp::Game::None; + game_started.compare_exchange_strong(desired, recomp::Game::Quit); game_started.notify_all(); } -void ultramodern::start(WindowHandle window_handle, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks_) { +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); - gfx_callbacks_t gfx_callbacks = gfx_callbacks_; + ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_; - gfx_callbacks_t::gfx_data_t gfx_data{}; + ultramodern::gfx_callbacks_t::gfx_data_t gfx_data{}; if (gfx_callbacks.create_gfx) { gfx_data = gfx_callbacks.create_gfx(); } - if (window_handle == WindowHandle{}) { + if (window_handle == ultramodern::WindowHandle{}) { if (gfx_callbacks.create_window) { window_handle = gfx_callbacks.create_window(gfx_data); } @@ -252,25 +414,34 @@ void ultramodern::start(WindowHandle window_handle, const audio_callbacks_t& aud } } - std::thread game_thread{[](ultramodern::WindowHandle window_handle) { + // Allocate rdram_buffer + std::unique_ptr rdram_buffer = std::make_unique(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); - ultramodern::preinit(rdram_buffer.get(), rom.get(), window_handle); - - game_started.wait(-1); + game_started.wait(recomp::Game::None); + recomp_context context{}; switch (game_started.load()) { - case 0: - recomp_entrypoint(rdram_buffer.get(), &context); + case recomp::Game::MM: + if (!recomp::load_stored_rom(recomp::Game::MM)) { + recomp::message_box("Error opening stored ROM! Please restart this program."); + } + init(rdram, &context); + recomp_entrypoint(rdram, &context); break; - case -2: + case recomp::Game::Quit: break; } debug_printf("[Recomp] Quitting\n"); - }, window_handle}; + }, window_handle, rdram_buffer.get()}; while (!exited) { using namespace std::chrono_literals; diff --git a/src/recomp/rt64_layer.cpp b/src/recomp/rt64_layer.cpp index 047d6a9..f6286ac 100644 --- a/src/recomp/rt64_layer.cpp +++ b/src/recomp/rt64_layer.cpp @@ -130,12 +130,13 @@ RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampl return RT64::UserConfiguration::Antialiasing::None; } -RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) { +RT64::Application* RT64Init(uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) { + static unsigned char dummy_rom_header[0x40]; set_rt64_hooks(); GFX_INFO gfx_info{}; - gfx_info.HEADER = rom; + gfx_info.HEADER = dummy_rom_header; gfx_info.RDRAM = rdram; gfx_info.DMEM = DMEM; gfx_info.IMEM = IMEM; diff --git a/src/ui/ui_launcher.cpp b/src/ui/ui_launcher.cpp index 8787353..ca176ca 100644 --- a/src/ui/ui_launcher.cpp +++ b/src/ui/ui_launcher.cpp @@ -1,11 +1,59 @@ #include "recomp_ui.h" +#include "recomp_config.h" +#include "recomp_game.h" #include "../../ultramodern/ultramodern.hpp" #include "RmlUi/Core.h" +#include "nfd.h" +#include + +Rml::DataModelHandle model_handle; +bool mm_rom_valid = false; + +void select_rom() { + nfdnchar_t* native_path = nullptr; + nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr); + + if (result == NFD_OKAY) { + printf("Path: %ls\n", native_path); + + std::filesystem::path path{native_path}; + + 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; + } + } +} class LauncherMenu : public recomp::MenuController { public: LauncherMenu() { - + mm_rom_valid = recomp::is_rom_valid(recomp::Game::MM); } ~LauncherMenu() override { @@ -14,9 +62,20 @@ public: return context->LoadDocument("assets/launcher.rml"); } void register_events(recomp::UiEventListenerInstancer& listener) override { + recomp::register_event(listener, "select_rom", + [](const std::string& param, Rml::Event& event) { + select_rom(); + } + ); + recomp::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", [](const std::string& param, Rml::Event& event) { - ultramodern::start_game(0); + recomp::start_game(recomp::Game::MM); recomp::set_current_menu(recomp::Menu::None); } ); @@ -38,7 +97,11 @@ public: ); } void make_bindings(Rml::Context* context) override { + Rml::DataModelConstructor constructor = context->CreateDataModel("launcher_model"); + constructor.Bind("mm_rom_valid", &mm_rom_valid); + + model_handle = constructor.GetModelHandle(); } }; diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index 0303054..4e6708b 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -7,6 +7,7 @@ #include "recomp_ui.h" #include "recomp_input.h" +#include "recomp_game.h" #include "concurrentqueue.h" @@ -950,8 +951,6 @@ void recomp::get_window_size(int& width, int& height) { } void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) { - printf("RT64 hook init\n"); - ui_context = std::make_unique(); ui_context->rml.add_menu(recomp::Menu::Config, recomp::create_config_menu()); @@ -1207,3 +1206,7 @@ void recomp::destroy_ui() { recomp::Menu recomp::get_current_menu() { return open_menu.load(); } + +void recomp::message_box(const char* msg) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", msg, nullptr); +} diff --git a/ultramodern/events.cpp b/ultramodern/events.cpp index e6eeadd..3cad0b0 100644 --- a/ultramodern/events.cpp +++ b/ultramodern/events.cpp @@ -218,7 +218,7 @@ void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_f } -void task_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready) { +void task_thread_func(uint8_t* rdram, std::atomic_flag* thread_ready) { ultramodern::set_native_thread_name("SP Task Thread"); ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal); @@ -284,7 +284,7 @@ uint32_t ultramodern::get_target_framerate(uint32_t original) { } } -void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready, ultramodern::WindowHandle window_handle) { +void gfx_thread_func(uint8_t* rdram, std::atomic_flag* thread_ready, ultramodern::WindowHandle window_handle) { bool enabled_instant_present = false; using namespace std::chrono_literals; @@ -293,7 +293,7 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read ultramodern::GraphicsConfig old_config; - RT64::Application* application = RT64Init(rom, rdram, window_handle, cur_config.load().developer_mode); + RT64::Application* application = RT64Init(rdram, window_handle, cur_config.load().developer_mode); if (application == nullptr) { throw std::runtime_error("Failed to initialize RT64!"); @@ -511,12 +511,12 @@ void ultramodern::send_si_message() { osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); } -void ultramodern::init_events(uint8_t* rdram, uint8_t* rom, ultramodern::WindowHandle window_handle) { +void ultramodern::init_events(uint8_t* rdram, ultramodern::WindowHandle window_handle) { std::atomic_flag gfx_thread_ready; std::atomic_flag task_thread_ready; events_context.rdram = rdram; - events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, rom, &gfx_thread_ready, window_handle }; - events_context.sp.task_thread = std::thread{ task_thread_func, rdram, rom, &task_thread_ready }; + events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, &gfx_thread_ready, window_handle }; + events_context.sp.task_thread = std::thread{ task_thread_func, rdram, &task_thread_ready }; // Wait for the two sp threads to be ready before continuing to prevent the game from // running before we're able to handle RSP tasks. diff --git a/ultramodern/ultrainit.cpp b/ultramodern/ultrainit.cpp index 4d51781..21bbedd 100644 --- a/ultramodern/ultrainit.cpp +++ b/ultramodern/ultrainit.cpp @@ -1,9 +1,9 @@ #include "ultra64.h" #include "ultramodern.hpp" -void ultramodern::preinit(uint8_t* rdram, uint8_t* rom, ultramodern::WindowHandle window_handle) { +void ultramodern::preinit(uint8_t* rdram, ultramodern::WindowHandle window_handle) { ultramodern::set_main_thread(); - ultramodern::init_events(rdram, rom, window_handle); + ultramodern::init_events(rdram, window_handle); ultramodern::init_timers(rdram); ultramodern::init_audio(); ultramodern::save_init(); diff --git a/ultramodern/ultramodern.hpp b/ultramodern/ultramodern.hpp index 41b3432..ba04079 100644 --- a/ultramodern/ultramodern.hpp +++ b/ultramodern/ultramodern.hpp @@ -51,10 +51,10 @@ constexpr int32_t cart_handle = 0x80800000; constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash -void preinit(uint8_t* rdram, uint8_t* rom, WindowHandle window_handle); +void preinit(uint8_t* rdram, WindowHandle window_handle); void save_init(); void init_scheduler(); -void init_events(uint8_t* rdram, uint8_t* rom, WindowHandle window_handle); +void init_events(uint8_t* rdram, WindowHandle window_handle); void init_timers(RDRAM_ARG1); void set_self_paused(RDRAM_ARG1); void yield_self(RDRAM_ARG1); @@ -129,8 +129,6 @@ struct gfx_callbacks_t { create_window_t* create_window; update_gfx_t* update_gfx; }; -void start(WindowHandle window_handle, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks); -void start_game(int game); bool is_game_started(); void quit(); void join_event_threads();