mirror of
				https://github.com/Zelda64Recomp/Zelda64Recomp.git
				synced 2025-10-30 08:03:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			726 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			726 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <cstdio>
 | 
						|
#include <cassert>
 | 
						|
#include <unordered_map>
 | 
						|
#include <vector>
 | 
						|
#include <array>
 | 
						|
#include <filesystem>
 | 
						|
#include <numeric>
 | 
						|
#include <stdexcept>
 | 
						|
#include <cinttypes>
 | 
						|
 | 
						|
#include "nfd.h"
 | 
						|
 | 
						|
#include "ultramodern/ultra64.h"
 | 
						|
#include "ultramodern/ultramodern.hpp"
 | 
						|
#define SDL_MAIN_HANDLED
 | 
						|
#ifdef _WIN32
 | 
						|
#include "SDL.h"
 | 
						|
#else
 | 
						|
#include "SDL2/SDL.h"
 | 
						|
#include "SDL2/SDL_syswm.h"
 | 
						|
#endif
 | 
						|
 | 
						|
#include "recomp_ui.h"
 | 
						|
#include "recomp_input.h"
 | 
						|
#include "zelda_config.h"
 | 
						|
#include "zelda_sound.h"
 | 
						|
#include "zelda_render.h"
 | 
						|
#include "zelda_support.h"
 | 
						|
#include "zelda_game.h"
 | 
						|
#include "recomp_data.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"
 | 
						|
 | 
						|
#include "mods/mm_recomp_dpad_builtin.h"
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
#define WIN32_LEAN_AND_MEAN
 | 
						|
#include <Windows.h>
 | 
						|
#include "SDL_syswm.h"
 | 
						|
#endif
 | 
						|
 | 
						|
#include "../../lib/rt64/src/contrib/stb/stb_image.h"
 | 
						|
 | 
						|
const std::string version_string = "1.2.0";
 | 
						|
 | 
						|
template<typename... Ts>
 | 
						|
void exit_error(const char* str, Ts ...args) {
 | 
						|
    // TODO pop up an error
 | 
						|
    ((void)fprintf(stderr, str, args), ...);
 | 
						|
    assert(false);
 | 
						|
        
 | 
						|
    ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
 | 
						|
}
 | 
						|
 | 
						|
ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() {
 | 
						|
    SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
 | 
						|
    SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
 | 
						|
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
 | 
						|
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
 | 
						|
    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
 | 
						|
    SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
 | 
						|
 | 
						|
    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 {};
 | 
						|
}
 | 
						|
 | 
						|
#if defined(__gnu_linux__)
 | 
						|
#include "icon_bytes.h"
 | 
						|
 | 
						|
bool SetImageAsIcon(const char* filename, SDL_Window* window)
 | 
						|
{
 | 
						|
    // Read data
 | 
						|
    int width, height, bytesPerPixel;
 | 
						|
    void* data = stbi_load_from_memory(reinterpret_cast<const uint8_t*>(icon_bytes), sizeof(icon_bytes), &width, &height, &bytesPerPixel, 4);
 | 
						|
 | 
						|
    // Calculate pitch
 | 
						|
    int pitch;
 | 
						|
    pitch = width * 4;
 | 
						|
    pitch = (pitch + 3) & ~3;
 | 
						|
 | 
						|
    // Setup relevance bitmask
 | 
						|
    int Rmask, Gmask, Bmask, Amask;
 | 
						|
 | 
						|
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
 | 
						|
    Rmask = 0x000000FF;
 | 
						|
    Gmask = 0x0000FF00;
 | 
						|
    Bmask = 0x00FF0000;
 | 
						|
    Amask = 0xFF000000;
 | 
						|
#else
 | 
						|
    Rmask = 0xFF000000;
 | 
						|
    Gmask = 0x00FF0000;
 | 
						|
    Bmask = 0x0000FF00;
 | 
						|
    Amask = 0x000000FF;
 | 
						|
#endif
 | 
						|
 | 
						|
    SDL_Surface* surface = nullptr;
 | 
						|
    if (data != nullptr) {
 | 
						|
        surface = SDL_CreateRGBSurfaceFrom(data, width, height, 32, pitch, Rmask, Gmask,
 | 
						|
                            Bmask, Amask);
 | 
						|
    }
 | 
						|
 | 
						|
    if (surface == nullptr) {   
 | 
						|
        if (data != nullptr) {
 | 
						|
            stbi_image_free(data);
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
	} else {
 | 
						|
        SDL_SetWindowIcon(window,surface);
 | 
						|
        SDL_FreeSurface(surface);
 | 
						|
        stbi_image_free(data);
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
SDL_Window* window;
 | 
						|
 | 
						|
ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
 | 
						|
    uint32_t flags = SDL_WINDOW_RESIZABLE;
 | 
						|
 | 
						|
#if defined(__APPLE__)
 | 
						|
    flags |= SDL_WINDOW_METAL;
 | 
						|
#elif 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
 | 
						|
        SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP);
 | 
						|
    } else {
 | 
						|
        SDL_SetWindowFullscreen(window,0);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    if (window == nullptr) {
 | 
						|
        exit_error("Failed to create window: %s\n", SDL_GetError());
 | 
						|
    }
 | 
						|
 | 
						|
    SDL_SysWMinfo wmInfo;
 | 
						|
    SDL_VERSION(&wmInfo.version);
 | 
						|
    SDL_GetWindowWMInfo(window, &wmInfo);
 | 
						|
 | 
						|
#if defined(_WIN32)
 | 
						|
    return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
 | 
						|
#elif defined(__linux__) || defined(__ANDROID__)
 | 
						|
    return ultramodern::renderer::WindowHandle{ window };
 | 
						|
#elif defined(__APPLE__)
 | 
						|
    SDL_MetalView view = SDL_Metal_CreateView(window);
 | 
						|
    return ultramodern::renderer::WindowHandle{ wmInfo.info.cocoa.window,  SDL_Metal_GetLayer(view) };
 | 
						|
#else
 | 
						|
    static_assert(false && "Unimplemented");
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
void update_gfx(void*) {
 | 
						|
    recomp::handle_events();
 | 
						|
}
 | 
						|
 | 
						|
static SDL_AudioCVT audio_convert;
 | 
						|
static SDL_AudioDeviceID audio_device = 0;
 | 
						|
 | 
						|
// Samples per channel per second.
 | 
						|
static uint32_t sample_rate = 48000;
 | 
						|
static uint32_t output_sample_rate = 48000;
 | 
						|
// Channel count.
 | 
						|
constexpr uint32_t input_channels = 2;
 | 
						|
static uint32_t output_channels = 2;
 | 
						|
 | 
						|
// Terminology: a frame is a collection of samples for each channel. e.g. 2 input samples is one input frame. This is unrelated to graphical frames.
 | 
						|
 | 
						|
// Number of frames to duplicate for fixing interpolation at the start and end of a chunk.
 | 
						|
constexpr uint32_t duplicated_input_frames = 4;
 | 
						|
// The number of output frames to skip for playback (to avoid playing duplicate inputs twice).
 | 
						|
static uint32_t discarded_output_frames;
 | 
						|
 | 
						|
constexpr uint32_t bytes_per_frame = input_channels * sizeof(float);
 | 
						|
 | 
						|
void queue_samples(int16_t* audio_data, size_t sample_count) {
 | 
						|
    // Buffer for holding the output of swapping the audio channels. This is reused across
 | 
						|
    // calls to reduce runtime allocations.
 | 
						|
    static std::vector<float> swap_buffer;
 | 
						|
    static std::array<float, duplicated_input_frames * input_channels> duplicated_sample_buffer;
 | 
						|
 | 
						|
    // Make sure the swap buffer is large enough to hold the audio data, including any extra space needed for resampling.
 | 
						|
    size_t resampled_sample_count = sample_count + duplicated_input_frames * input_channels;
 | 
						|
    size_t max_sample_count = std::max(resampled_sample_count, resampled_sample_count * audio_convert.len_mult);
 | 
						|
    if (max_sample_count > swap_buffer.size()) {
 | 
						|
        swap_buffer.resize(max_sample_count);
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Copy the duplicated frames from last chunk into this chunk
 | 
						|
    for (size_t i = 0; i < duplicated_input_frames * input_channels; i++) {
 | 
						|
        swap_buffer[i] = duplicated_sample_buffer[i];
 | 
						|
    }
 | 
						|
 | 
						|
    // Convert the audio from 16-bit values to floats and swap the audio channels into the
 | 
						|
    // swap buffer to correct for the address xor caused by endianness handling.
 | 
						|
    float cur_main_volume = zelda64::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
 | 
						|
    for (size_t i = 0; i < sample_count; i += input_channels) {
 | 
						|
        swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume;
 | 
						|
        swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume;
 | 
						|
    }
 | 
						|
    
 | 
						|
    // TODO handle cases where a chunk is smaller than the duplicated frame count.
 | 
						|
    assert(sample_count > duplicated_input_frames * input_channels);
 | 
						|
 | 
						|
    // Copy the last converted samples into the duplicated sample buffer to reuse in resampling the next queued chunk.
 | 
						|
    for (size_t i = 0; i < duplicated_input_frames * input_channels; i++) {
 | 
						|
        duplicated_sample_buffer[i] = swap_buffer[i + sample_count];
 | 
						|
    }
 | 
						|
    
 | 
						|
    audio_convert.buf = reinterpret_cast<Uint8*>(swap_buffer.data());
 | 
						|
    audio_convert.len = (sample_count + duplicated_input_frames * input_channels) * sizeof(swap_buffer[0]);
 | 
						|
 | 
						|
    int ret = SDL_ConvertAudio(&audio_convert);
 | 
						|
 | 
						|
    if (ret < 0) {
 | 
						|
        printf("Error using SDL audio converter: %s\n", SDL_GetError());
 | 
						|
        throw std::runtime_error("Error using SDL audio converter");
 | 
						|
    }
 | 
						|
 | 
						|
    uint64_t cur_queued_microseconds = uint64_t(SDL_GetQueuedAudioSize(audio_device)) / bytes_per_frame * 1000000 / sample_rate;
 | 
						|
    uint32_t num_bytes_to_queue = audio_convert.len_cvt - output_channels * discarded_output_frames * sizeof(swap_buffer[0]);
 | 
						|
    float* samples_to_queue = swap_buffer.data() + output_channels * discarded_output_frames / 2;
 | 
						|
 | 
						|
    // Prevent audio latency from building up by skipping samples in incoming audio when too many samples are already queued.
 | 
						|
    // Skip samples based on how many microseconds of samples are queued already.
 | 
						|
    uint32_t skip_factor = cur_queued_microseconds / 100000;
 | 
						|
    if (skip_factor != 0) {
 | 
						|
        uint32_t skip_ratio = 1 << skip_factor;
 | 
						|
        num_bytes_to_queue /= skip_ratio;
 | 
						|
        for (size_t i = 0; i < num_bytes_to_queue / (output_channels * sizeof(swap_buffer[0])); i++) {
 | 
						|
            samples_to_queue[2 * i + 0] = samples_to_queue[2 * skip_ratio * i + 0];
 | 
						|
            samples_to_queue[2 * i + 1] = samples_to_queue[2 * skip_ratio * i + 1];
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Queue the swapped audio data.
 | 
						|
    // Offset the data start by only half the discarded frame count as the other half of the discarded frames are at the end of the buffer.
 | 
						|
    SDL_QueueAudio(audio_device, samples_to_queue, num_bytes_to_queue);
 | 
						|
}
 | 
						|
 | 
						|
size_t get_frames_remaining() {
 | 
						|
    constexpr float buffer_offset_frames = 1.0f;
 | 
						|
    // Get the number of remaining buffered audio bytes.
 | 
						|
    uint64_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device);
 | 
						|
 | 
						|
    // Scale the byte count based on the ratio of sample rates and channel counts.
 | 
						|
    buffered_byte_count = buffered_byte_count * 2 * sample_rate / output_sample_rate / output_channels;
 | 
						|
 | 
						|
    // Adjust the reported count to be some number of refreshes in the future, which helps ensure that
 | 
						|
    // there are enough samples even if the audio thread experiences a small amount of lag. This prevents
 | 
						|
    // audio popping on games that use the buffered audio byte count to determine how many samples
 | 
						|
    // to generate.
 | 
						|
    uint32_t frames_per_vi = (sample_rate / 60);
 | 
						|
    if (buffered_byte_count > (buffer_offset_frames * bytes_per_frame * frames_per_vi)) {
 | 
						|
        buffered_byte_count -= (buffer_offset_frames * bytes_per_frame * frames_per_vi);
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        buffered_byte_count = 0;
 | 
						|
    }
 | 
						|
    // Convert from byte count to sample count.
 | 
						|
    return static_cast<uint32_t>(buffered_byte_count / bytes_per_frame);
 | 
						|
}
 | 
						|
 | 
						|
void update_audio_converter() {
 | 
						|
    int ret = SDL_BuildAudioCVT(&audio_convert, AUDIO_F32, input_channels, sample_rate, AUDIO_F32, output_channels, output_sample_rate);
 | 
						|
 | 
						|
    if (ret < 0) {
 | 
						|
        printf("Error creating SDL audio converter: %s\n", SDL_GetError());
 | 
						|
        throw std::runtime_error("Error creating SDL audio converter");
 | 
						|
    }
 | 
						|
 | 
						|
    // Calculate the number of samples to discard based on the sample rate ratio and the duplicate frame count.
 | 
						|
    discarded_output_frames = duplicated_input_frames * output_sample_rate / sample_rate;
 | 
						|
}
 | 
						|
 | 
						|
void set_frequency(uint32_t freq) {
 | 
						|
    sample_rate = freq;
 | 
						|
    
 | 
						|
    update_audio_converter();
 | 
						|
}
 | 
						|
 | 
						|
void reset_audio(uint32_t output_freq) {
 | 
						|
    SDL_AudioSpec spec_desired{
 | 
						|
        .freq = (int)output_freq,
 | 
						|
        .format = AUDIO_F32,
 | 
						|
        .channels = (Uint8)output_channels,
 | 
						|
        .silence = 0, // calculated
 | 
						|
        .samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering
 | 
						|
        .padding = 0, // unused
 | 
						|
        .size = 0, // calculated
 | 
						|
        .callback = nullptr,
 | 
						|
        .userdata = nullptr
 | 
						|
    };
 | 
						|
 | 
						|
 | 
						|
    audio_device = SDL_OpenAudioDevice(nullptr, false, &spec_desired, nullptr, 0);
 | 
						|
    if (audio_device == 0) {
 | 
						|
        exit_error("SDL error opening audio device: %s\n", SDL_GetError());
 | 
						|
    }
 | 
						|
    SDL_PauseAudioDevice(audio_device, 0);
 | 
						|
 | 
						|
    output_sample_rate = output_freq;
 | 
						|
    update_audio_converter();
 | 
						|
}
 | 
						|
 | 
						|
extern RspUcodeFunc njpgdspMain;
 | 
						|
extern RspUcodeFunc aspMain;
 | 
						|
 | 
						|
RspUcodeFunc* get_rsp_microcode(const OSTask* task) {
 | 
						|
    switch (task->t.type) {
 | 
						|
    case M_AUDTASK:
 | 
						|
        return aspMain;
 | 
						|
 | 
						|
    case M_NJPEGTASK:
 | 
						|
        return njpgdspMain;
 | 
						|
 | 
						|
    default:
 | 
						|
        fprintf(stderr, "Unknown task: %" PRIu32 "\n", task->t.type);
 | 
						|
        return nullptr;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
 | 
						|
gpr get_entrypoint_address();
 | 
						|
 | 
						|
// array of supported GameEntry objects
 | 
						|
std::vector<recomp::GameEntry> supported_games = {
 | 
						|
    {
 | 
						|
        .rom_hash = 0xEF18B4A9E2386169ULL,
 | 
						|
        .internal_name = "ZELDA MAJORA'S MASK",
 | 
						|
        .game_id = u8"mm.n64.us.1.0",
 | 
						|
        .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,
 | 
						|
    },
 | 
						|
};
 | 
						|
 | 
						|
// TODO: move somewhere else
 | 
						|
namespace zelda64 {
 | 
						|
    std::string get_game_thread_name(const OSThread* t) {
 | 
						|
        std::string name = "[Game] ";
 | 
						|
 | 
						|
        switch (t->id) {
 | 
						|
            case 0:
 | 
						|
                switch (t->priority) {
 | 
						|
                    case 150:
 | 
						|
                        name += "PIMGR";
 | 
						|
                        break;
 | 
						|
 | 
						|
                    case 254:
 | 
						|
                        name += "VIMGR";
 | 
						|
                        break;
 | 
						|
 | 
						|
                    default:
 | 
						|
                        name += std::to_string(t->id);
 | 
						|
                        break;
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
            case 1:
 | 
						|
                name += "IDLE";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 2:
 | 
						|
                switch (t->priority) {
 | 
						|
                    case 5:
 | 
						|
                        name += "SLOWLY";
 | 
						|
                        break;
 | 
						|
 | 
						|
                    case 127:
 | 
						|
                        name += "FAULT";
 | 
						|
                        break;
 | 
						|
 | 
						|
                    default:
 | 
						|
                        name += std::to_string(t->id);
 | 
						|
                        break;
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
            case 3:
 | 
						|
                name += "MAIN";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 4:
 | 
						|
                name += "GRAPH";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 5:
 | 
						|
                name += "SCHED";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 7:
 | 
						|
                name += "PADMGR";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 10:
 | 
						|
                name += "AUDIOMGR";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 13:
 | 
						|
                name += "FLASHROM";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 18:
 | 
						|
                name += "DMAMGR";
 | 
						|
                break;
 | 
						|
 | 
						|
            case 19:
 | 
						|
                name += "IRQMGR";
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                name += std::to_string(t->id);
 | 
						|
                break;
 | 
						|
        }
 | 
						|
 | 
						|
        return name;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
 | 
						|
struct PreloadContext {
 | 
						|
    HANDLE handle;
 | 
						|
    HANDLE mapping_handle;
 | 
						|
    SIZE_T size;
 | 
						|
    PVOID view;
 | 
						|
};
 | 
						|
 | 
						|
bool preload_executable(PreloadContext& context) {
 | 
						|
    wchar_t module_name[MAX_PATH];
 | 
						|
    GetModuleFileNameW(NULL, module_name, MAX_PATH);
 | 
						|
 | 
						|
    context.handle = CreateFileW(module_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
 | 
						|
    if (context.handle == INVALID_HANDLE_VALUE) {
 | 
						|
        fprintf(stderr, "Failed to load executable into memory!");
 | 
						|
        context = {};
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    LARGE_INTEGER module_size;
 | 
						|
    if (!GetFileSizeEx(context.handle, &module_size)) {
 | 
						|
        fprintf(stderr, "Failed to get size of executable!");
 | 
						|
        CloseHandle(context.handle);
 | 
						|
        context = {};
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    context.size = module_size.QuadPart;
 | 
						|
 | 
						|
    context.mapping_handle = CreateFileMappingW(context.handle, nullptr, PAGE_READONLY, 0, 0, nullptr);
 | 
						|
    if (context.mapping_handle == nullptr) {
 | 
						|
        fprintf(stderr, "Failed to create file mapping of executable!");
 | 
						|
        CloseHandle(context.handle);
 | 
						|
        context = {};
 | 
						|
        return EXIT_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    context.view = MapViewOfFile(context.mapping_handle, FILE_MAP_READ, 0, 0, 0);
 | 
						|
    if (context.view == nullptr) {
 | 
						|
        fprintf(stderr, "Failed to map view of of executable!");
 | 
						|
        CloseHandle(context.mapping_handle);
 | 
						|
        CloseHandle(context.handle);
 | 
						|
        context = {};
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    DWORD pid = GetCurrentProcessId();
 | 
						|
    HANDLE process_handle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_QUERY_INFORMATION, FALSE, pid);
 | 
						|
    if (process_handle == nullptr) {
 | 
						|
        fprintf(stderr, "Failed to open own process!");
 | 
						|
        CloseHandle(context.mapping_handle);
 | 
						|
        CloseHandle(context.handle);
 | 
						|
        context = {};
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    SIZE_T minimum_set_size, maximum_set_size;
 | 
						|
    if (!GetProcessWorkingSetSize(process_handle, &minimum_set_size, &maximum_set_size)) {
 | 
						|
        fprintf(stderr, "Failed to get working set size!");
 | 
						|
        CloseHandle(context.mapping_handle);
 | 
						|
        CloseHandle(context.handle);
 | 
						|
        context = {};
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!SetProcessWorkingSetSize(process_handle, minimum_set_size + context.size, maximum_set_size + context.size)) {
 | 
						|
        fprintf(stderr, "Failed to set working set size!");
 | 
						|
        CloseHandle(context.mapping_handle);
 | 
						|
        CloseHandle(context.handle);
 | 
						|
        context = {};
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (VirtualLock(context.view, context.size) == 0) {
 | 
						|
        fprintf(stderr, "Failed to lock view of executable! (Error: %08lx)\n", GetLastError());
 | 
						|
        CloseHandle(context.mapping_handle);
 | 
						|
        CloseHandle(context.handle);
 | 
						|
        context = {};
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
void release_preload(PreloadContext& context) {
 | 
						|
    VirtualUnlock(context.view, context.size);
 | 
						|
    CloseHandle(context.mapping_handle);
 | 
						|
    CloseHandle(context.handle);
 | 
						|
    context = {};
 | 
						|
}
 | 
						|
 | 
						|
#else
 | 
						|
 | 
						|
struct PreloadContext {
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
// TODO implement on other platforms
 | 
						|
bool preload_executable(PreloadContext& context) {
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
void release_preload(PreloadContext& context) {
 | 
						|
}
 | 
						|
 | 
						|
#endif
 | 
						|
 | 
						|
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
 | 
						|
    zelda64::renderer::enable_texture_pack(context, mod);
 | 
						|
}
 | 
						|
 | 
						|
void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) {
 | 
						|
    zelda64::renderer::disable_texture_pack(mod);
 | 
						|
}
 | 
						|
 | 
						|
void reorder_texture_pack(recomp::mods::ModContext&) {
 | 
						|
    zelda64::renderer::trigger_texture_pack_update();
 | 
						|
}
 | 
						|
 | 
						|
#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;
 | 
						|
    bool preloaded = preload_executable(preload_context);
 | 
						|
 | 
						|
    if (!preloaded) {
 | 
						|
        fprintf(stderr, "Failed to preload executable!\n");
 | 
						|
    }
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
    // Set up console output to accept UTF-8 on windows
 | 
						|
    SetConsoleOutputCP(CP_UTF8);
 | 
						|
 | 
						|
    // Initialize native file dialogs.
 | 
						|
    NFD_Init();
 | 
						|
 | 
						|
    // Change to a font that supports Japanese characters
 | 
						|
    CONSOLE_FONT_INFOEX cfi;
 | 
						|
    cfi.cbSize = sizeof cfi;
 | 
						|
    cfi.nFont = 0;
 | 
						|
    cfi.dwFontSize.X = 0;
 | 
						|
    cfi.dwFontSize.Y = 16;
 | 
						|
    cfi.FontFamily = FF_DONTCARE;
 | 
						|
    cfi.FontWeight = FW_NORMAL;
 | 
						|
    wcscpy_s(cfi.FaceName, L"NSimSun");
 | 
						|
    SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
    // Force wasapi on Windows, as there seems to be some issue with sample queueing with directsound currently.
 | 
						|
    SDL_setenv("SDL_AUDIODRIVER", "wasapi", true);
 | 
						|
#endif
 | 
						|
 | 
						|
#if defined(__linux__) && defined(RECOMP_FLATPAK)
 | 
						|
    // When using Flatpak, applications tend to launch from the home directory by default.
 | 
						|
    // Mods might use the current working directory to store the data, so we switch it to a directory
 | 
						|
    // with persistent data storage and write permissions under Flatpak to ensure it works.
 | 
						|
    std::error_code ec;
 | 
						|
    std::filesystem::current_path("/var/data", ec);
 | 
						|
#endif
 | 
						|
 | 
						|
    // Initialize SDL audio and set the output frequency.
 | 
						|
    SDL_InitSubSystem(SDL_INIT_AUDIO);
 | 
						|
    reset_audio(48000);
 | 
						|
 | 
						|
    // Source controller mappings file
 | 
						|
    std::u8string controller_db_path = (zelda64::get_program_path() / "recompcontrollerdb.txt").u8string();
 | 
						|
    if (SDL_GameControllerAddMappingsFromFile(reinterpret_cast<const char *>(controller_db_path.c_str())) < 0) {
 | 
						|
        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);
 | 
						|
    }
 | 
						|
 | 
						|
    recomp::mods::register_embedded_mod("mm_recomp_dpad_builtin", { (const uint8_t*)(mm_recomp_dpad_builtin), std::size(mm_recomp_dpad_builtin)});
 | 
						|
 | 
						|
    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);
 | 
						|
    recompui::register_ui_exports();
 | 
						|
    recomputil::register_data_api_exports();
 | 
						|
 | 
						|
    zelda64::register_overlays();
 | 
						|
    zelda64::register_patches();
 | 
						|
    recomputil::init_extended_actor_data();
 | 
						|
    zelda64::load_config();
 | 
						|
 | 
						|
    recomp::rsp::callbacks_t rsp_callbacks{
 | 
						|
        .get_rsp_microcode = get_rsp_microcode,
 | 
						|
    };
 | 
						|
 | 
						|
    ultramodern::renderer::callbacks_t renderer_callbacks{
 | 
						|
        .create_render_context = zelda64::renderer::create_render_context,
 | 
						|
    };
 | 
						|
 | 
						|
    ultramodern::gfx_callbacks_t gfx_callbacks{
 | 
						|
        .create_gfx = create_gfx,
 | 
						|
        .create_window = create_window,
 | 
						|
        .update_gfx = update_gfx,
 | 
						|
    };
 | 
						|
 | 
						|
    ultramodern::audio_callbacks_t audio_callbacks{
 | 
						|
        .queue_samples = queue_samples,
 | 
						|
        .get_frames_remaining = get_frames_remaining,
 | 
						|
        .set_frequency = set_frequency,
 | 
						|
    };
 | 
						|
 | 
						|
    ultramodern::input::callbacks_t input_callbacks{
 | 
						|
        .poll_input = recomp::poll_inputs,
 | 
						|
        .get_input = recomp::get_n64_input,
 | 
						|
        .set_rumble = recomp::set_rumble,
 | 
						|
        .get_connected_device_info = recomp::get_connected_device_info,
 | 
						|
    };
 | 
						|
 | 
						|
    ultramodern::events::callbacks_t thread_callbacks{
 | 
						|
        .vi_callback = recomp::update_rumble,
 | 
						|
        .gfx_init_callback = recompui::update_supported_options,
 | 
						|
    };
 | 
						|
 | 
						|
    ultramodern::error_handling::callbacks_t error_handling_callbacks{
 | 
						|
        .message_box = recompui::message_box,
 | 
						|
    };
 | 
						|
 | 
						|
    ultramodern::threads::callbacks_t threads_callbacks{
 | 
						|
        .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,
 | 
						|
        .on_reordered = reorder_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::start(
 | 
						|
        project_version,
 | 
						|
        {},
 | 
						|
        rsp_callbacks,
 | 
						|
        renderer_callbacks,
 | 
						|
        audio_callbacks,
 | 
						|
        input_callbacks,
 | 
						|
        gfx_callbacks,
 | 
						|
        thread_callbacks,
 | 
						|
        error_handling_callbacks,
 | 
						|
        threads_callbacks
 | 
						|
    );
 | 
						|
 | 
						|
    NFD_Quit();
 | 
						|
 | 
						|
    if (preloaded) {
 | 
						|
        release_preload(preload_context);
 | 
						|
    }
 | 
						|
 | 
						|
    return EXIT_SUCCESS;
 | 
						|
}
 |