Implemented proper ROM selection menu

This commit is contained in:
Mr-Wiseguy 2024-03-03 21:00:49 -05:00
parent 302d88fd84
commit ca66fdddbb
16 changed files with 387 additions and 97 deletions

View File

@ -166,6 +166,7 @@ target_include_directories(MMRecomp PRIVATE
${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/rhi ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/rhi
${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/render ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/render
${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include
${CMAKE_SOURCE_DIR}/lib/lib/nativefiledialog-extended/src/include
${CMAKE_BINARY_DIR}/shaders ${CMAKE_BINARY_DIR}/shaders
) )

View File

@ -12,7 +12,7 @@
</style> </style>
</head> </head>
<body> <body>
<div id="window"> <div id="window" data-model="launcher_model">
<div class="launcher"> <div class="launcher">
<div class="launcher__vertical-split"> <div class="launcher__vertical-split">
<div class="launcher__title-quadrant"> <div class="launcher__title-quadrant">
@ -35,7 +35,11 @@
</button> </button>
</div> </div>
<div class="launcher__content-quadrant"> <div class="launcher__content-quadrant">
<button onclick="start_game" class="menu-list-item menu-list-item--right" autofocus> <button data-if="!mm_rom_valid" onclick="select_rom" class="menu-list-item menu-list-item--right" autofocus>
<div class="menu-list-item__bullet">•</div>
<div class="menu-list-item__label">Select ROM</div>
</button>
<button data-if="mm_rom_valid" onclick="start_game" class="menu-list-item menu-list-item--right" autofocus>
<div class="menu-list-item__bullet">•</div> <div class="menu-list-item__bullet">•</div>
<div class="menu-list-item__label">Start game</div> <div class="menu-list-item__label">Start game</div>
</button> </button>

View File

@ -1,6 +1,7 @@
#ifndef __RECOMP_CONFIG_H__ #ifndef __RECOMP_CONFIG_H__
#define __RECOMP_CONFIG_H__ #define __RECOMP_CONFIG_H__
#include <filesystem>
#include <string_view> #include <string_view>
#include "../ultramodern/config.hpp" #include "../ultramodern/config.hpp"
@ -12,6 +13,8 @@ namespace recomp {
void reset_input_bindings(); void reset_input_bindings();
void reset_graphics_options(); void reset_graphics_options();
std::filesystem::path get_app_folder_path();
}; };
#endif #endif

39
include/recomp_game.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef __RECOMP_GAME__
#define __RECOMP_GAME__
#include <vector>
#include <filesystem>
#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<uint8_t>&& 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

View File

@ -12,7 +12,7 @@ namespace ultramodern {
struct WindowHandle; 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 RT64UpdateConfig(RT64::Application* application, const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config);
void RT64EnableInstantPresent(RT64::Application* application); void RT64EnableInstantPresent(RT64::Application* application);
void RT64SendDL(uint8_t* rdram, const OSTask* task); void RT64SendDL(uint8_t* rdram, const OSTask* task);

View File

@ -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. // Force skipping interpolation if the previous view was kaleido and this one isn't.
if (prev_in_kaleido && !in_kaleido) { if (prev_in_kaleido && !in_kaleido) {
camera_skip_interpolation_forced = true; camera_skip_interpolation_forced = true;
recomp_printf("exited kaleido\n");
} }
// @recomp Apply camera interpolation overrides. // @recomp Apply camera interpolation overrides.

View File

@ -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{}; std::filesystem::path recomp_dir{};
#if defined(_WIN32) #if defined(_WIN32)
@ -232,7 +232,7 @@ void load_controls_config(const std::filesystem::path& path) {
} }
void recomp::load_config() { 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 graphics_path = recomp_dir / graphics_filename;
std::filesystem::path controls_path = recomp_dir / controls_filename; std::filesystem::path controls_path = recomp_dir / controls_filename;
@ -254,7 +254,7 @@ void recomp::load_config() {
} }
void recomp::save_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()) { if (recomp_dir.empty()) {
return; return;

View File

@ -20,6 +20,7 @@
#include "recomp_ui.h" #include "recomp_ui.h"
#include "recomp_input.h" #include "recomp_input.h"
#include "recomp_config.h" #include "recomp_config.h"
#include "recomp_game.h"
#ifdef _WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
@ -229,13 +230,12 @@ int main(int argc, char** argv) {
std::setlocale(LC_ALL, "en_US.UTF-8"); std::setlocale(LC_ALL, "en_US.UTF-8");
#endif #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. // Initialize SDL audio and set the output frequency.
SDL_InitSubSystem(SDL_INIT_AUDIO); SDL_InitSubSystem(SDL_INIT_AUDIO);
reset_audio(48000); reset_audio(48000);
init();
recomp::load_config(); recomp::load_config();
ultramodern::gfx_callbacks_t gfx_callbacks{ ultramodern::gfx_callbacks_t gfx_callbacks{
@ -255,7 +255,7 @@ int main(int argc, char** argv) {
.get_input = recomp::get_n64_input, .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; return EXIT_SUCCESS;
} }

View File

@ -3,9 +3,20 @@
#include <array> #include <array>
#include <cstring> #include <cstring>
#include "recomp.h" #include "recomp.h"
#include "recomp_game.h"
#include "../ultramodern/ultra64.h" #include "../ultramodern/ultra64.h"
#include "../ultramodern/ultramodern.hpp" #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 // 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 // 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. // 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; return addr | 0xA0000000;
} }
extern std::unique_ptr<uint8_t[]> rom;
extern size_t rom_size;
extern "C" void osCartRomInit_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); OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::cart_handle);
handle->type = 0; // cart 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 use word copies when possible
// TODO handle misaligned DMA // TODO handle misaligned DMA
assert((physical_addr & 0x1) == 0 && "Only PI DMA from aligned ROM addresses is currently supported"); 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((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"); 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++) { for (size_t i = 0; i < num_bytes; i++) {
MEM_B(i, ram_address) = *rom_addr; MEM_B(i, ram_address) = *rom_addr;
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 (direction == 0) {
if (physical_addr >= rom_base) { if (physical_addr >= rom_base) {
// read cart rom // 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 // Send a message to the mq to indicate that the transfer completed
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); 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) { if (physical_addr > rom_base) {
// cart rom // cart rom
do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t)); recomp::do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t));
} else { } else {
// sram // sram
assert(false && "SRAM ReadIo unimplemented"); assert(false && "SRAM ReadIo unimplemented");

View File

@ -7,9 +7,13 @@
#include <memory> #include <memory>
#include <cmath> #include <cmath>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include "recomp.h" #include "recomp.h"
#include "recomp_game.h"
#include "recomp_config.h"
#include "xxHash/xxh3.h"
#include "../ultramodern/ultramodern.hpp" #include "../ultramodern/ultramodern.hpp"
#ifdef _WIN32 #ifdef _WIN32
@ -28,13 +32,204 @@ constexpr uint32_t byteswap(uint32_t val) {
} }
#endif #endif
extern "C" void _bzero(uint8_t* rdram, recomp_context* ctx) { struct RomEntry {
gpr start_addr = ctx->r4; uint64_t xxhash3_value;
gpr size = ctx->r5; std::u8string stored_filename;
std::string internal_rom_name;
};
for (uint32_t i = 0; i < size; i++) { const std::unordered_map<recomp::Game, RomEntry> game_roms {
MEM_B(start_addr, i) = 0; { recomp::Game::MM, { 0xEF18B4A9E2386169ULL, u8"mm.n64.us.1.0.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;
}
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) { 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); func(rdram, &ctx);
} }
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t dev_address, size_t num_bytes);
std::unique_ptr<uint8_t[]> rom;
size_t rom_size;
// Recomp generation functions // Recomp generation functions
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx); extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
gpr get_entrypoint_address(); 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 load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size);
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
std::unique_ptr<uint8_t[]> rdram_buffer;
recomp_context context{};
void read_patch_data(uint8_t* rdram, gpr patch_data_address) { void read_patch_data(uint8_t* rdram, gpr patch_data_address) {
const char patches_data_file_path[] = "patches/patches.bin"; const char patches_data_file_path[] = "patches/patches.bin";
std::ifstream patches_data_file{ patches_data_file_path, std::ios::binary }; 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() { void init(uint8_t* rdram, recomp_context* ctx) {
{
std::ifstream rom_file{ get_rom_name(), std::ios::binary };
size_t iobuf_size = 0x100000;
std::unique_ptr<char[]> iobuf = std::make_unique<char[]>(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<uint8_t[]>(rom_size);
rom_file.read(reinterpret_cast<char*>(rom.get()), rom_size);
}
// Initialize the overlays // Initialize the overlays
init_overlays(); init_overlays();
@ -175,22 +341,18 @@ EXPORT extern "C" void init() {
// Load overlays in the first 1MB // Load overlays in the first 1MB
load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024);
// Allocate rdram_buffer
rdram_buffer = std::make_unique<uint8_t[]>(ultramodern::rdram_size);
std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size);
// Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) // 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 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 // Set up stack pointer
context.r29 = 0xFFFFFFFF803FFFF0u; ctx->r29 = 0xFFFFFFFF803FFFF0u;
// Set up context floats // Set up context floats
context.f_odd = &context.f0.u32h; ctx->f_odd = &ctx->f0.u32h;
context.mips3_float_mode = false; ctx->mips3_float_mode = false;
// Initialize variables normally set by IPL3 // Initialize variables normally set by IPL3
constexpr int32_t osTvType = 0x80000300; constexpr int32_t osTvType = 0x80000300;
@ -201,22 +363,21 @@ EXPORT extern "C" void init() {
constexpr int32_t osVersion = 0x80000314; constexpr int32_t osVersion = 0x80000314;
constexpr int32_t osMemSize = 0x80000318; constexpr int32_t osMemSize = 0x80000318;
constexpr int32_t osAppNMIBuffer = 0x8000031c; constexpr int32_t osAppNMIBuffer = 0x8000031c;
uint8_t *rdram = rdram_buffer.get();
MEM_W(osTvType, 0) = 1; // NTSC MEM_W(osTvType, 0) = 1; // NTSC
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
MEM_W(osResetType, 0) = 0; // cold reset MEM_W(osResetType, 0) = 0; // cold reset
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
} }
std::atomic_int game_started = -1; std::atomic<recomp::Game> game_started = recomp::Game::None;
void ultramodern::start_game(int game) { void recomp::start_game(recomp::Game game) {
game_started.store(game); game_started.store(game);
game_started.notify_all(); game_started.notify_all();
} }
bool ultramodern::is_game_started() { 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); void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks);
@ -226,24 +387,25 @@ std::atomic_bool exited = false;
void ultramodern::quit() { void ultramodern::quit() {
exited.store(true); exited.store(true);
int desired = -1; recomp::Game desired = recomp::Game::None;
game_started.compare_exchange_strong(desired, -2); game_started.compare_exchange_strong(desired, recomp::Game::Quit);
game_started.notify_all(); 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_audio_callbacks(audio_callbacks);
set_input_callbacks(input_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) { if (gfx_callbacks.create_gfx) {
gfx_data = gfx_callbacks.create_gfx(); gfx_data = gfx_callbacks.create_gfx();
} }
if (window_handle == WindowHandle{}) { if (window_handle == ultramodern::WindowHandle{}) {
if (gfx_callbacks.create_window) { if (gfx_callbacks.create_window) {
window_handle = gfx_callbacks.create_window(gfx_data); 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<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"); debug_printf("[Recomp] Starting\n");
ultramodern::set_native_thread_name("Game Start Thread"); ultramodern::set_native_thread_name("Game Start Thread");
ultramodern::preinit(rdram_buffer.get(), rom.get(), window_handle); ultramodern::preinit(rdram, window_handle);
game_started.wait(-1); game_started.wait(recomp::Game::None);
recomp_context context{};
switch (game_started.load()) { switch (game_started.load()) {
case 0: case recomp::Game::MM:
recomp_entrypoint(rdram_buffer.get(), &context); 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; break;
case -2: case recomp::Game::Quit:
break; break;
} }
debug_printf("[Recomp] Quitting\n"); debug_printf("[Recomp] Quitting\n");
}, window_handle}; }, window_handle, rdram_buffer.get()};
while (!exited) { while (!exited) {
using namespace std::chrono_literals; using namespace std::chrono_literals;

View File

@ -130,12 +130,13 @@ RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampl
return RT64::UserConfiguration::Antialiasing::None; 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(); set_rt64_hooks();
GFX_INFO gfx_info{}; GFX_INFO gfx_info{};
gfx_info.HEADER = rom; gfx_info.HEADER = dummy_rom_header;
gfx_info.RDRAM = rdram; gfx_info.RDRAM = rdram;
gfx_info.DMEM = DMEM; gfx_info.DMEM = DMEM;
gfx_info.IMEM = IMEM; gfx_info.IMEM = IMEM;

View File

@ -1,11 +1,59 @@
#include "recomp_ui.h" #include "recomp_ui.h"
#include "recomp_config.h"
#include "recomp_game.h"
#include "../../ultramodern/ultramodern.hpp" #include "../../ultramodern/ultramodern.hpp"
#include "RmlUi/Core.h" #include "RmlUi/Core.h"
#include "nfd.h"
#include <filesystem>
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 { class LauncherMenu : public recomp::MenuController {
public: public:
LauncherMenu() { LauncherMenu() {
mm_rom_valid = recomp::is_rom_valid(recomp::Game::MM);
} }
~LauncherMenu() override { ~LauncherMenu() override {
@ -14,9 +62,20 @@ public:
return context->LoadDocument("assets/launcher.rml"); return context->LoadDocument("assets/launcher.rml");
} }
void register_events(recomp::UiEventListenerInstancer& listener) override { 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", recomp::register_event(listener, "start_game",
[](const std::string& param, Rml::Event& event) { [](const std::string& param, Rml::Event& event) {
ultramodern::start_game(0); recomp::start_game(recomp::Game::MM);
recomp::set_current_menu(recomp::Menu::None); recomp::set_current_menu(recomp::Menu::None);
} }
); );
@ -38,7 +97,11 @@ public:
); );
} }
void make_bindings(Rml::Context* context) override { 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();
} }
}; };

View File

@ -7,6 +7,7 @@
#include "recomp_ui.h" #include "recomp_ui.h"
#include "recomp_input.h" #include "recomp_input.h"
#include "recomp_game.h"
#include "concurrentqueue.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) { void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
printf("RT64 hook init\n");
ui_context = std::make_unique<UIContext>(); 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::Config, recomp::create_config_menu());
@ -1207,3 +1206,7 @@ void recomp::destroy_ui() {
recomp::Menu recomp::get_current_menu() { recomp::Menu recomp::get_current_menu() {
return open_menu.load(); return open_menu.load();
} }
void recomp::message_box(const char* msg) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", msg, nullptr);
}

View File

@ -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_name("SP Task Thread");
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal); 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; bool enabled_instant_present = false;
using namespace std::chrono_literals; 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; 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) { if (application == nullptr) {
throw std::runtime_error("Failed to initialize RT64!"); 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); 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 gfx_thread_ready;
std::atomic_flag task_thread_ready; std::atomic_flag task_thread_ready;
events_context.rdram = rdram; events_context.rdram = rdram;
events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, rom, &gfx_thread_ready, window_handle }; 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, rom, &task_thread_ready }; 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 // 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. // running before we're able to handle RSP tasks.

View File

@ -1,9 +1,9 @@
#include "ultra64.h" #include "ultra64.h"
#include "ultramodern.hpp" #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::set_main_thread();
ultramodern::init_events(rdram, rom, window_handle); ultramodern::init_events(rdram, window_handle);
ultramodern::init_timers(rdram); ultramodern::init_timers(rdram);
ultramodern::init_audio(); ultramodern::init_audio();
ultramodern::save_init(); ultramodern::save_init();

View File

@ -51,10 +51,10 @@ constexpr int32_t cart_handle = 0x80800000;
constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); 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 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 save_init();
void init_scheduler(); 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 init_timers(RDRAM_ARG1);
void set_self_paused(RDRAM_ARG1); void set_self_paused(RDRAM_ARG1);
void yield_self(RDRAM_ARG1); void yield_self(RDRAM_ARG1);
@ -129,8 +129,6 @@ struct gfx_callbacks_t {
create_window_t* create_window; create_window_t* create_window;
update_gfx_t* update_gfx; 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(); bool is_game_started();
void quit(); void quit();
void join_event_threads(); void join_event_threads();