mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp
synced 2026-08-06 18:03:38 -04:00
Mod Support (#499)
Integrates the modding functionality in N64ModernRuntime and adds several exported functions for mods to use. Also adds a ROM decompressor so that the runtime has access to the uncompressed code in the ROM for hooking purposes.
This commit is contained in:
@@ -19,7 +19,6 @@ 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::renderer::Resolution::Auto;
|
||||
constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "librecomp/recomp.h"
|
||||
#include "recomp.h"
|
||||
#include "recomp_input.h"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_ui.h"
|
||||
|
||||
+15
-4
@@ -1,6 +1,6 @@
|
||||
#include <cmath>
|
||||
|
||||
#include "librecomp/recomp.h"
|
||||
#include "recomp.h"
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_input.h"
|
||||
@@ -58,7 +58,18 @@ extern "C" void recomp_get_target_framerate(uint8_t* rdram, recomp_context* ctx)
|
||||
_return(ctx, ultramodern::get_target_framerate(60 / frame_divisor));
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
extern "C" void recomp_get_window_resolution(uint8_t* rdram, recomp_context* ctx) {
|
||||
int width, height;
|
||||
recompui::get_window_size(width, height);
|
||||
|
||||
gpr width_out = _arg<0, PTR(u32)>(rdram, ctx);
|
||||
gpr height_out = _arg<1, PTR(u32)>(rdram, ctx);
|
||||
|
||||
MEM_W(0, width_out) = (u32)width;
|
||||
MEM_W(0, height_out) = (u32)height;
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_target_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::renderer::GraphicsConfig graphics_config = ultramodern::renderer::get_graphics_config();
|
||||
float original = _arg<0, float>(rdram, ctx);
|
||||
int width, height;
|
||||
@@ -91,7 +102,7 @@ extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(ultramodern::time_since_start()).count()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_autosave_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
extern "C" void recomp_get_autosave_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<s32>(zelda64::get_autosave_mode() == zelda64::AutosaveMode::On));
|
||||
}
|
||||
|
||||
@@ -131,7 +142,7 @@ extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context*
|
||||
*y_out = (mode == zelda64::CameraInvertMode::InvertY || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
}
|
||||
|
||||
extern "C" void recomp_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
extern "C" void recomp_get_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return<s32>(ctx, zelda64::get_analog_cam_mode() == zelda64::AnalogCamMode::On);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
#include "zelda_game.h"
|
||||
|
||||
void naive_copy(std::span<uint8_t> dst, std::span<const uint8_t> src) {
|
||||
for (size_t i = 0; i < src.size(); i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
void yaz0_decompress(std::span<const uint8_t> input, std::span<uint8_t> output) {
|
||||
int32_t layoutBitIndex;
|
||||
uint8_t layoutBits;
|
||||
|
||||
size_t input_pos = 0;
|
||||
size_t output_pos = 0;
|
||||
|
||||
size_t input_size = input.size();
|
||||
size_t output_size = output.size();
|
||||
|
||||
while (input_pos < input_size) {
|
||||
int32_t layoutBitIndex = 0;
|
||||
uint8_t layoutBits = input[input_pos++];
|
||||
|
||||
while (layoutBitIndex < 8 && input_pos < input_size && output_pos < output_size) {
|
||||
if (layoutBits & 0x80) {
|
||||
output[output_pos++] = input[input_pos++];
|
||||
} else {
|
||||
int32_t firstByte = input[input_pos++];
|
||||
int32_t secondByte = input[input_pos++];
|
||||
uint32_t bytes = firstByte << 8 | secondByte;
|
||||
uint32_t offset = (bytes & 0x0FFF) + 1;
|
||||
uint32_t length;
|
||||
|
||||
// Check how the group length is encoded
|
||||
if ((firstByte & 0xF0) == 0) {
|
||||
// 3 byte encoding, 0RRRNN
|
||||
int32_t thirdByte = input[input_pos++];
|
||||
length = thirdByte + 0x12;
|
||||
} else {
|
||||
// 2 byte encoding, NRRR
|
||||
length = ((bytes & 0xF000) >> 12) + 2;
|
||||
}
|
||||
|
||||
naive_copy(output.subspan(output_pos, length), output.subspan(output_pos - offset, length));
|
||||
output_pos += length;
|
||||
}
|
||||
|
||||
layoutBitIndex++;
|
||||
layoutBits <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
// Produces a decompressed MM rom. This is only needed because the game has compressed code.
|
||||
// For other recomps using this repo as an example, you can omit the decompression routine and
|
||||
// set the corresponding fields in the GameEntry if the game doesn't have compressed code,
|
||||
// even if it does have compressed data.
|
||||
std::vector<uint8_t> zelda64::decompress_mm(std::span<const uint8_t> compressed_rom) {
|
||||
// Sanity check the rom size and header. These should already be correct from the runtime's check,
|
||||
// but it should prevent this file from accidentally being copied to another recomp.
|
||||
if (compressed_rom.size() != 0x2000000) {
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (compressed_rom[0x3B] != 'N' || compressed_rom[0x3C] != 'Z' || compressed_rom[0x3D] != 'S' || compressed_rom[0x3E] != 'E') {
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
struct DmaDataEntry {
|
||||
uint32_t vrom_start;
|
||||
uint32_t vrom_end;
|
||||
uint32_t rom_start;
|
||||
uint32_t rom_end;
|
||||
|
||||
void bswap() {
|
||||
vrom_start = byteswap(vrom_start);
|
||||
vrom_end = byteswap(vrom_end);
|
||||
rom_start = byteswap(rom_start);
|
||||
rom_end = byteswap(rom_end);
|
||||
}
|
||||
};
|
||||
|
||||
DmaDataEntry cur_entry{};
|
||||
size_t cur_entry_index = 0;
|
||||
|
||||
constexpr size_t dma_data_rom_addr = 0x1A500;
|
||||
|
||||
std::vector<uint8_t> ret{};
|
||||
ret.resize(0x2F00000);
|
||||
|
||||
size_t content_end = 0;
|
||||
|
||||
do {
|
||||
// Read the entry from the compressed rom.
|
||||
size_t cur_entry_rom_address = dma_data_rom_addr + (cur_entry_index++) * sizeof(DmaDataEntry);
|
||||
memcpy(&cur_entry, compressed_rom.data() + cur_entry_rom_address, sizeof(DmaDataEntry));
|
||||
// Swap the entry to native endianness after reading from the big endian data.
|
||||
cur_entry.bswap();
|
||||
|
||||
// Rom end being 0 means the data is already uncompressed, so copy it as-is to vrom start.
|
||||
size_t entry_decompressed_size = cur_entry.vrom_end - cur_entry.vrom_start;
|
||||
if (cur_entry.rom_end == 0) {
|
||||
memcpy(ret.data() + cur_entry.vrom_start, compressed_rom.data() + cur_entry.rom_start, entry_decompressed_size);
|
||||
|
||||
// Edit the entry to account for it being in a new location now.
|
||||
cur_entry.rom_start = cur_entry.vrom_start;
|
||||
}
|
||||
// Otherwise, decompress the input data into the output data.
|
||||
else {
|
||||
if (cur_entry.rom_end != cur_entry.rom_start) {
|
||||
// Validate the presence of the yaz0 header.
|
||||
if (compressed_rom[cur_entry.rom_start + 0] != 'Y' ||
|
||||
compressed_rom[cur_entry.rom_start + 1] != 'a' ||
|
||||
compressed_rom[cur_entry.rom_start + 2] != 'z' ||
|
||||
compressed_rom[cur_entry.rom_start + 3] != '0')
|
||||
{
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
// Skip the yaz0 header.
|
||||
size_t compressed_data_rom_start = cur_entry.rom_start + 0x10;
|
||||
size_t entry_compressed_size = cur_entry.rom_end - compressed_data_rom_start;
|
||||
|
||||
std::span input_span = std::span{ compressed_rom }.subspan(compressed_data_rom_start, entry_compressed_size);
|
||||
std::span output_span = std::span{ ret }.subspan(cur_entry.vrom_start, entry_decompressed_size);
|
||||
yaz0_decompress(input_span, output_span);
|
||||
|
||||
// Edit the entry to account for it being decompressed now.
|
||||
cur_entry.rom_start = cur_entry.vrom_start;
|
||||
cur_entry.rom_end = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry_decompressed_size != 0) {
|
||||
if (cur_entry.vrom_end > content_end) {
|
||||
content_end = cur_entry.vrom_end;
|
||||
}
|
||||
}
|
||||
|
||||
// Swap the entry back to big endian for writing.
|
||||
cur_entry.bswap();
|
||||
// Write the modified entry to the decompressed rom.
|
||||
memcpy(ret.data() + cur_entry_rom_address, &cur_entry, sizeof(DmaDataEntry));
|
||||
} while (cur_entry.vrom_end != 0);
|
||||
|
||||
// Align the start of padding to the closest 0x1000 (matches decomp rom decompression behavior).
|
||||
content_end = (content_end + 0x1000 - 1) & -0x1000;
|
||||
|
||||
// Write 0xFF as the padding.
|
||||
std::fill(ret.begin() + content_end, ret.end(), 0xFF);
|
||||
|
||||
return ret;
|
||||
}
|
||||
+102
-16
@@ -25,8 +25,16 @@
|
||||
#include "zelda_config.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "zelda_render.h"
|
||||
#include "zelda_game.h"
|
||||
#include "ovl_patches.hpp"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "librecomp/mods.hpp"
|
||||
#include "librecomp/helpers.hpp"
|
||||
|
||||
#include "../../patches/graphics.h"
|
||||
#include "../../patches/input.h"
|
||||
#include "../../patches/sound.h"
|
||||
#include "../../patches/misc_funcs.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -36,6 +44,8 @@
|
||||
|
||||
#include "../../lib/rt64/src/contrib/stb/stb_image.h"
|
||||
|
||||
const std::string version_string = "1.2.0-dev";
|
||||
|
||||
template<typename... Ts>
|
||||
void exit_error(const char* str, Ts ...args) {
|
||||
// TODO pop up an error
|
||||
@@ -52,14 +62,12 @@ ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() {
|
||||
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
#if defined(__linux__)
|
||||
SDL_SetHint(SDL_HINT_VIDEODRIVER, "x11");
|
||||
#endif
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) > 0) {
|
||||
exit_error("Failed to initialize SDL2: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
fprintf(stdout, "SDL Video Driver: %s\n", SDL_GetCurrentVideoDriver());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -115,7 +123,13 @@ bool SetImageAsIcon(const char* filename, SDL_Window* window)
|
||||
SDL_Window* window;
|
||||
|
||||
ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
|
||||
window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, SDL_WINDOW_RESIZABLE );
|
||||
uint32_t flags = SDL_WINDOW_RESIZABLE;
|
||||
|
||||
#if defined(RT64_SDL_WINDOW_VULKAN)
|
||||
flags |= SDL_WINDOW_VULKAN;
|
||||
#endif
|
||||
|
||||
window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags);
|
||||
#if defined(__linux__)
|
||||
SetImageAsIcon("icons/512.png",window);
|
||||
if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux
|
||||
@@ -135,14 +149,8 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::
|
||||
|
||||
#if defined(_WIN32)
|
||||
return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
|
||||
#elif defined(__ANDROID__)
|
||||
static_assert(false && "Unimplemented");
|
||||
#elif defined(__linux__)
|
||||
if (wmInfo.subsystem != SDL_SYSWM_X11) {
|
||||
exit_error("Unsupported SDL2 video driver \"%s\". Only X11 is supported on Linux.\n", SDL_GetCurrentVideoDriver());
|
||||
}
|
||||
|
||||
return ultramodern::renderer::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window };
|
||||
#elif defined(__linux__) || defined(__ANDROID__)
|
||||
return ultramodern::renderer::WindowHandle{ window };
|
||||
#else
|
||||
static_assert(false && "Unimplemented");
|
||||
#endif
|
||||
@@ -327,7 +335,11 @@ std::vector<recomp::GameEntry> supported_games = {
|
||||
.rom_hash = 0xEF18B4A9E2386169ULL,
|
||||
.internal_name = "ZELDA MAJORA'S MASK",
|
||||
.game_id = u8"mm.n64.us.1.0",
|
||||
.is_enabled = true,
|
||||
.mod_game_id = "mm",
|
||||
.save_type = recomp::SaveType::Flashram,
|
||||
.is_enabled = false,
|
||||
.decompression_routine = zelda64::decompress_mm,
|
||||
.has_compressed_code = true,
|
||||
.entrypoint_address = get_entrypoint_address(),
|
||||
.entrypoint = recomp_entrypoint,
|
||||
},
|
||||
@@ -524,7 +536,27 @@ void release_preload(PreloadContext& context) {
|
||||
|
||||
#endif
|
||||
|
||||
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||
(void)context;
|
||||
zelda64::renderer::enable_texture_pack(mod);
|
||||
}
|
||||
|
||||
void disable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||
(void)context;
|
||||
zelda64::renderer::disable_texture_pack(mod);
|
||||
}
|
||||
|
||||
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
recomp::Version project_version{};
|
||||
if (!recomp::Version::from_string(version_string, project_version)) {
|
||||
ultramodern::error_handling::message_box(("Invalid version string: " + version_string).c_str());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Map this executable into memory and lock it, which should keep it in physical memory. This ensures
|
||||
// that there are no stutters from the OS having to load new pages of the executable whenever a new code page is run.
|
||||
PreloadContext preload_context;
|
||||
@@ -568,15 +600,29 @@ int main(int argc, char** argv) {
|
||||
fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
recomp::register_config_path(zelda64::get_app_folder_path());
|
||||
|
||||
// Register supported games and patches
|
||||
for (const auto& game : supported_games) {
|
||||
recomp::register_game(game);
|
||||
}
|
||||
|
||||
REGISTER_FUNC(recomp_get_window_resolution);
|
||||
REGISTER_FUNC(recomp_get_target_aspect_ratio);
|
||||
REGISTER_FUNC(recomp_get_target_framerate);
|
||||
REGISTER_FUNC(recomp_get_autosave_enabled);
|
||||
REGISTER_FUNC(recomp_get_analog_cam_enabled);
|
||||
REGISTER_FUNC(recomp_get_camera_inputs);
|
||||
REGISTER_FUNC(recomp_get_targeting_mode);
|
||||
REGISTER_FUNC(recomp_get_bgm_volume);
|
||||
REGISTER_FUNC(recomp_get_low_health_beeps_enabled);
|
||||
REGISTER_FUNC(recomp_get_gyro_deltas);
|
||||
REGISTER_FUNC(recomp_get_mouse_deltas);
|
||||
REGISTER_FUNC(recomp_get_inverted_axes);
|
||||
REGISTER_FUNC(recomp_get_analog_inverted_axes);
|
||||
|
||||
zelda64::register_overlays();
|
||||
zelda64::register_patches();
|
||||
|
||||
recomp::register_config_path(zelda64::get_app_folder_path());
|
||||
zelda64::load_config();
|
||||
|
||||
recomp::rsp::callbacks_t rsp_callbacks{
|
||||
@@ -619,7 +665,47 @@ int main(int argc, char** argv) {
|
||||
.get_game_thread_name = zelda64::get_game_thread_name,
|
||||
};
|
||||
|
||||
// Register the texture pack content type with rt64.json as its content file.
|
||||
recomp::mods::ModContentType texture_pack_content_type{
|
||||
.content_filename = "rt64.json",
|
||||
.allow_runtime_toggle = true,
|
||||
.on_enabled = enable_texture_pack,
|
||||
.on_disabled = disable_texture_pack,
|
||||
};
|
||||
auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type);
|
||||
|
||||
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
|
||||
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
|
||||
|
||||
recomp::mods::scan_mods();
|
||||
|
||||
printf("Found mods:\n");
|
||||
for (const auto& mod : recomp::mods::get_mod_details("mm")) {
|
||||
printf(" %s(%s)\n", mod.mod_id.c_str(), mod.version.to_string().c_str());
|
||||
if (!mod.authors.empty()) {
|
||||
printf(" Authors: %s", mod.authors[0].c_str());
|
||||
for (size_t author_index = 1; author_index < mod.authors.size(); author_index++) {
|
||||
const std::string& author = mod.authors[author_index];
|
||||
printf(", %s", author.c_str());
|
||||
}
|
||||
printf("\n");
|
||||
printf(" Runtime toggleable: %d\n", mod.runtime_toggleable);
|
||||
}
|
||||
if (!mod.dependencies.empty()) {
|
||||
printf(" Dependencies: %s:%s", mod.dependencies[0].mod_id.c_str(), mod.dependencies[0].version.to_string().c_str());
|
||||
for (size_t dep_index = 1; dep_index < mod.dependencies.size(); dep_index++) {
|
||||
const recomp::mods::Dependency& dep = mod.dependencies[dep_index];
|
||||
printf(", %s:%s", dep.mod_id.c_str(), dep.version.to_string().c_str());
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
// TODO load all mods as a temporary solution to not having a UI yet.
|
||||
recomp::mods::enable_mod(mod.mod_id, true);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
recomp::start(
|
||||
project_version,
|
||||
{},
|
||||
rsp_callbacks,
|
||||
renderer_callbacks,
|
||||
|
||||
@@ -7,4 +7,7 @@
|
||||
|
||||
void zelda64::register_patches() {
|
||||
recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table, ARRLEN(section_table));
|
||||
recomp::overlays::register_base_exports(export_table);
|
||||
recomp::overlays::register_base_events(event_names);
|
||||
recomp::overlays::register_manual_patch_symbols(manual_patch_symbols);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <variant>
|
||||
|
||||
#define HLSL_CPU
|
||||
#include "hle/rt64_application.h"
|
||||
@@ -10,6 +11,13 @@
|
||||
|
||||
#include "zelda_render.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
// Helper class for variant visiting.
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||
static bool sample_positions_supported = false;
|
||||
@@ -18,6 +26,18 @@ static bool high_precision_fb_enabled = false;
|
||||
static uint8_t DMEM[0x1000];
|
||||
static uint8_t IMEM[0x1000];
|
||||
|
||||
struct TexturePackEnableAction {
|
||||
std::filesystem::path path;
|
||||
};
|
||||
|
||||
struct TexturePackDisableAction {
|
||||
std::filesystem::path path;
|
||||
};
|
||||
|
||||
using TexturePackAction = std::variant<TexturePackEnableAction, TexturePackDisableAction>;
|
||||
|
||||
static moodycamel::ConcurrentQueue<TexturePackAction> texture_pack_action_queue;
|
||||
|
||||
unsigned int MI_INTR_REG = 0;
|
||||
|
||||
unsigned int DPC_START_REG = 0;
|
||||
@@ -180,11 +200,8 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere
|
||||
RT64::Application::Core appCore{};
|
||||
#if defined(_WIN32)
|
||||
appCore.window = window_handle.window;
|
||||
#elif defined(__ANDROID__)
|
||||
assert(false && "Unimplemented");
|
||||
#elif defined(__linux__)
|
||||
appCore.window.display = window_handle.display;
|
||||
appCore.window.window = window_handle.window;
|
||||
#elif defined(__linux__) || defined(__ANDROID__)
|
||||
appCore.window = window_handle;
|
||||
#elif defined(__APPLE__)
|
||||
appCore.window.window = window_handle.window;
|
||||
appCore.window.view = window_handle.view;
|
||||
@@ -286,6 +303,32 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere
|
||||
zelda64::renderer::RT64Context::~RT64Context() = default;
|
||||
|
||||
void zelda64::renderer::RT64Context::send_dl(const OSTask* task) {
|
||||
bool packs_disabled = false;
|
||||
TexturePackAction cur_action;
|
||||
while (texture_pack_action_queue.try_dequeue(cur_action)) {
|
||||
std::visit(overloaded{
|
||||
[&](TexturePackDisableAction& to_disable) {
|
||||
enabled_texture_packs.erase(to_disable.path);
|
||||
packs_disabled = true;
|
||||
},
|
||||
[&](TexturePackEnableAction& to_enable) {
|
||||
enabled_texture_packs.insert(to_enable.path);
|
||||
// Load the pack now if no packs have been disabled.
|
||||
if (!packs_disabled) {
|
||||
app->textureCache->loadReplacementDirectory(to_enable.path);
|
||||
}
|
||||
}
|
||||
}, cur_action);
|
||||
}
|
||||
|
||||
// If any packs were disabled, unload all packs and load all the active ones.
|
||||
if (packs_disabled) {
|
||||
app->textureCache->clearReplacementDirectories();
|
||||
for (const std::filesystem::path& cur_pack_path : enabled_texture_packs) {
|
||||
app->textureCache->loadReplacementDirectory(cur_pack_path);
|
||||
}
|
||||
}
|
||||
|
||||
app->state->rsp->reset();
|
||||
app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true);
|
||||
app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true);
|
||||
@@ -351,16 +394,6 @@ float zelda64::renderer::RT64Context::get_resolution_scale() const {
|
||||
}
|
||||
}
|
||||
|
||||
void zelda64::renderer::RT64Context::load_shader_cache(std::span<const char> cache_binary) {
|
||||
// TODO figure out how to avoid a copy here.
|
||||
std::istringstream cache_stream{std::string{cache_binary.data(), cache_binary.size()}};
|
||||
|
||||
if (!app->rasterShaderCache->loadOfflineList(cache_stream)) {
|
||||
printf("Failed to preload shader cache!\n");
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing zelda64::renderer::RT64MaxMSAA() {
|
||||
return device_max_msaa;
|
||||
}
|
||||
@@ -376,3 +409,11 @@ bool zelda64::renderer::RT64SamplePositionsSupported() {
|
||||
bool zelda64::renderer::RT64HighPrecisionFBEnabled() {
|
||||
return high_precision_fb_enabled;
|
||||
}
|
||||
|
||||
void zelda64::renderer::enable_texture_pack(const recomp::mods::ModHandle& mod) {
|
||||
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_root_path});
|
||||
}
|
||||
|
||||
void zelda64::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) {
|
||||
texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_root_path});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "nfd.h"
|
||||
#include <filesystem>
|
||||
|
||||
std::string version_number = "v1.1.1";
|
||||
static std::string version_string;
|
||||
|
||||
Rml::DataModelHandle model_handle;
|
||||
bool mm_rom_valid = false;
|
||||
@@ -103,7 +103,9 @@ public:
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("launcher_model");
|
||||
|
||||
constructor.Bind("mm_rom_valid", &mm_rom_valid);
|
||||
constructor.Bind("version_number", &version_number);
|
||||
|
||||
version_string = recomp::get_project_version().to_string();
|
||||
constructor.Bind("version_number", &version_string);
|
||||
|
||||
model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
@@ -1287,6 +1287,12 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
static recompui::Menu prev_menu = recompui::Menu::None;
|
||||
recompui::Menu cur_menu = open_menu.load();
|
||||
|
||||
// Return to the launcher if no menu is open and the game isn't started.
|
||||
if (cur_menu == recompui::Menu::None && !ultramodern::is_game_started()) {
|
||||
cur_menu = recompui::Menu::Launcher;
|
||||
recompui::set_current_menu(cur_menu);
|
||||
}
|
||||
|
||||
if (reload_sheets) {
|
||||
ui_context->rml.load_documents();
|
||||
prev_menu = recompui::Menu::None;
|
||||
|
||||
Reference in New Issue
Block a user