/* ** core.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2013 - 2021 Amaryllis Kulla ** ** mkxp is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) any later version. ** ** mkxp is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with mkxp. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "binding-util.h" #include "mkxp-polyfill.h" // std::mutex, std::strtoul #include "git-hash.h" #include "core.h" #include "binding-sandbox.h" #include "sandbox-serial-util.h" #include "al-util.h" #include "audio.h" #include "eventthread.h" #include "filesystem.h" #include "gl-fun.h" #include "glstate.h" #include "graphics.h" #include "sharedmidistate.h" #include "sharedstate.h" static const struct retro_core_option_v2_category core_option_categories[] = { { "runtime", "Runtime", nullptr, }, { "video", "Video", nullptr, }, { "audio", "Audio", nullptr, }, { nullptr, nullptr, nullptr, }, }; static const struct retro_core_option_v2_definition core_option_definitions[] = { { "mkxp-z_rgssVersion", "RGSS Version", nullptr, ( "Specify the RGSS version to run under." " By default, mkxp will try to guess the required version" " based on the game files." " If this fails, the version defaults to 1." " Changes will take effect after the core is reset." ), nullptr, "runtime", { {"inherit", "Inherit from mkxp.json"}, {"default", "Default"}, {"1", "1 (RPG Maker XP)"}, {"2", "2 (RPG Maker VX)"}, {"3", "3 (RPG Maker VX Ace)"}, {nullptr, nullptr}, }, "inherit", }, { "mkxp-z_saveStateSize", "Save State Size", nullptr, ( "Maximum size of each save state, in mebibytes." " If the game uses more than this much memory, save state creation will fail." " Changes to this setting will not take effect until the core is unloaded." ), nullptr, "runtime", { {"64", "64"}, {"66", "66"}, {"68", "68"}, {"70", "70"}, {"72", "72"}, {"74", "74"}, {"76", "76"}, {"78", "78"}, {"80", "80"}, {"82", "82"}, {"84", "84"}, {"86", "86"}, {"88", "88"}, {"90", "90"}, {"92", "92"}, {"94", "94"}, {"96", "96"}, {"98", "98"}, {"100", "100"}, {"102", "102"}, {"104", "104"}, {"106", "106"}, {"108", "108"}, {"110", "110"}, {"112", "112"}, {"114", "114"}, {"116", "116"}, {"118", "118"}, {"120", "120"}, {"122", "122"}, {"124", "124"}, {"126", "126"}, {"128", "128"}, {"132", "132"}, {"136", "136"}, {"140", "140"}, {"144", "144"}, {"148", "148"}, {"152", "152"}, {"156", "156"}, {"160", "160"}, {"164", "164"}, {"168", "168"}, {"172", "172"}, {"176", "176"}, {"180", "180"}, {"184", "184"}, {"188", "188"}, {"192", "192"}, {"196", "196"}, {"200", "200"}, {"204", "204"}, {"208", "208"}, {"212", "212"}, {"216", "216"}, {"220", "220"}, {"224", "224"}, {"228", "228"}, {"232", "232"}, {"236", "236"}, {"240", "240"}, {"244", "244"}, {"248", "248"}, {"252", "252"}, {"256", "256"}, {"264", "264"}, {"272", "272"}, {"280", "280"}, {"288", "288"}, {"296", "296"}, {"304", "304"}, {"312", "312"}, {"320", "320"}, {"328", "328"}, {"336", "336"}, {"344", "344"}, {"352", "352"}, {"360", "360"}, {"368", "368"}, {"376", "376"}, {"384", "384"}, {"392", "392"}, {"400", "400"}, {"408", "408"}, {"416", "416"}, {"424", "424"}, {"432", "432"}, {"440", "440"}, {"448", "448"}, {"456", "456"}, {"464", "464"}, {"472", "472"}, {"480", "480"}, {"488", "488"}, {"496", "496"}, {"504", "504"}, {"512", "512"}, {"528", "528"}, {"544", "544"}, {"560", "560"}, {"576", "576"}, {"592", "592"}, {"608", "608"}, {"624", "624"}, {"640", "640"}, {"656", "656"}, {"672", "672"}, {"688", "688"}, {"704", "704"}, {"720", "720"}, {"736", "736"}, {"752", "752"}, {"768", "768"}, {"784", "784"}, {"800", "800"}, {"816", "816"}, {"832", "832"}, {"848", "848"}, {"864", "864"}, {"880", "880"}, {"896", "896"}, {"912", "912"}, {"928", "928"}, {"944", "944"}, {"960", "960"}, {"976", "976"}, {"992", "992"}, {nullptr, nullptr}, }, "100", }, { "mkxp-z_frameSkip", "Frame Skip", nullptr, ( "Skip (don't draw) frames when behind." ), nullptr, "video", { {"inherit", "Inherit from mkxp.json"}, {"enabled", "Enabled"}, {"disabled", "Disabled"}, {nullptr, nullptr}, }, "disabled", }, { "mkxp-z_subImageFix", "Subimage Fix", nullptr, ( "Work around buggy graphics drivers which don't" " properly synchronize texture access, most" " apparent when text doesn't show up or the map" " tileset doesn't render at all." " (default: enabled for systems using OpenGL ES, disabled on other systems)" ), nullptr, "video", { {"inherit", "Inherit from mkxp.json"}, {"default", "Default"}, {"enabled", "Enabled"}, {"disabled", "Disabled"}, {nullptr, nullptr}, }, "default", }, { "mkxp-z_enableBlitting", "Framebuffer Blitting", nullptr, ( "Enable framebuffer blitting if the driver is" " capable of it. Some drivers carry buggy" " implementations of this functionality, so" " disabling it can be used as a workaround." " (default: disabled on Windows, enabled on other systems)" ), nullptr, "video", { {"inherit", "Inherit from mkxp.json"}, {"default", "Default"}, {"enabled", "Enabled"}, {"disabled", "Disabled"}, {nullptr, nullptr}, }, "default", }, { "mkxp-z_threadedAudio", "Threaded Audio", nullptr, ( "Use a worker thread for rendering the audio instead of" " rendering in the main thread, if possible. Reduces audio" " crackling, especially on systems with slow file system" " access speed. Changes to this setting will not take effect" " until the game is closed." ), nullptr, "audio", { {"enabled", "Enabled"}, {"disabled", "Disabled"}, {nullptr, nullptr}, }, "enabled", }, { "mkxp-z_midiChorus", "MIDI Chorus", nullptr, ( "Activate \"chorus\" effect for midi playback." ), nullptr, "audio", { {"inherit", "Inherit from mkxp.json"}, {"enabled", "Enabled"}, {"disabled", "Disabled"}, {nullptr, nullptr}, }, "inherit", }, { "mkxp-z_midiReverb", "MIDI Reverb", nullptr, ( "Activate \"reverb\" effect for midi playback." ), nullptr, "audio", { {"inherit", "Inherit from mkxp.json"}, {"enabled", "Enabled"}, {"disabled", "Disabled"}, {nullptr, nullptr}, }, "inherit", }, { "mkxp-z_SESourceCount", "SE Source Count", nullptr, ( "Number of OpenAL sources to allocate for SE playback." " If there are a lot of sounds playing at the same time" " and audibly cutting each other off, try increasing" " this number." " Changes will take effect after the core is reset." " (if this value is also set in the game's mkxp.json," " the maximum of the value set here and the value in" " mkxp.json will be used)" ), nullptr, "audio", { {"6", "6"}, {"7", "7"}, {"8", "8"}, {"9", "9"}, {"10", "10"}, {"11", "11"}, {"12", "12"}, {"13", "13"}, {"14", "14"}, {"15", "15"}, {"16", "16"}, {"17", "17"}, {"18", "18"}, {"19", "19"}, {"20", "20"}, {"21", "21"}, {"22", "22"}, {"23", "23"}, {"24", "24"}, {"25", "25"}, {"26", "26"}, {"27", "27"}, {"28", "28"}, {"29", "29"}, {"30", "30"}, {"31", "31"}, {"32", "32"}, {"33", "33"}, {"34", "34"}, {"35", "35"}, {"36", "36"}, {"37", "37"}, {"38", "38"}, {"39", "39"}, {"40", "40"}, {"41", "41"}, {"42", "42"}, {"43", "43"}, {"44", "44"}, {"45", "45"}, {"46", "46"}, {"47", "47"}, {"48", "48"}, {"49", "49"}, {"50", "50"}, {"51", "51"}, {"52", "52"}, {"53", "53"}, {"54", "54"}, {"55", "55"}, {"56", "56"}, {"57", "57"}, {"58", "58"}, {"59", "59"}, {"60", "60"}, {"61", "61"}, {"62", "62"}, {"63", "63"}, {"64", "64"}, {nullptr, nullptr}, }, "6", }, { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, {{nullptr, nullptr}}, nullptr, }, }; #define THREADED_AUDIO_SAMPLES (((size_t)SYNTH_SAMPLERATE * (size_t)AUDIO_SLEEP) / (size_t)1000) using namespace mkxp_retro; using namespace mkxp_sandbox; struct lock_guard { std::mutex &mutex; lock_guard(std::mutex &mutex) : mutex(mutex) { mutex.lock(); } lock_guard(const struct lock_guard &guard) = delete; lock_guard(struct lock_guard &&guard) noexcept = delete; struct lock_guard &operator=(const struct lock_guard &guard) = delete; struct lock_guard &operator=(struct lock_guard &&guard) noexcept = delete; ~lock_guard() { mutex.unlock(); } }; template struct atomic { #ifndef MKXPZ_NO_THREADED_AUDIO std::atomic atom; #else T atom; #endif // MKXPZ_NO_THREADED_AUDIO atomic() {} atomic(T value) : atom(value) {} atomic(const struct atomic &guard) = delete; atomic(struct atomic &&guard) noexcept = delete; struct atomic &operator=(const struct atomic &guard) = delete; struct atomic &operator=(struct atomic &&guard) noexcept = delete; T load_relaxed() const noexcept { #ifndef MKXPZ_NO_THREADED_AUDIO return atom.load(std::memory_order_relaxed); #else return atom; #endif // MKXPZ_NO_THREADED_AUDIO } operator T() const noexcept { #ifndef MKXPZ_NO_THREADED_AUDIO return atom.load(std::memory_order_seq_cst); #else return atom; #endif // MKXPZ_NO_THREADED_AUDIO } void operator=(T value) noexcept { #ifndef MKXPZ_NO_THREADED_AUDIO atom.store(value, std::memory_order_seq_cst); #else atom = value; #endif // MKXPZ_NO_THREADED_AUDIO } void operator+=(T value) noexcept { #ifndef MKXPZ_NO_THREADED_AUDIO atom.fetch_add(value, std::memory_order_seq_cst); #else atom += value; #endif // MKXPZ_NO_THREADED_AUDIO } }; #if !defined(MKXPZ_NO_THREADED_AUDIO) && defined(MKXPZ_NO_STD_ATOMIC_UINT64_T) template <> struct atomic { mutable std::mutex mutex; uint64_t atom; atomic() {} atomic(uint64_t value) : atom(value) {} atomic(const struct atomic &guard) = delete; atomic(struct atomic &&guard) noexcept = delete; struct atomic &operator=(const struct atomic &guard) = delete; struct atomic &operator=(struct atomic &&guard) noexcept = delete; uint64_t load_relaxed() const noexcept { return atom; } operator uint64_t() const noexcept { struct lock_guard guard(mutex); return atom; } void operator=(uint64_t value) noexcept { struct lock_guard guard(mutex); atom = value; } void operator+=(uint64_t value) noexcept { struct lock_guard guard(mutex); atom += value; } }; #endif // !defined(MKXPZ_NO_THREADED_AUDIO) && defined(MKXPZ_NO_STD_ATOMIC_UINT64_T) int mkxp_physfs_allow_duplicates = false; struct physfs_allow_duplicates_guard { bool old_value; physfs_allow_duplicates_guard() : old_value(mkxp_physfs_allow_duplicates) { mkxp_physfs_allow_duplicates = true; } physfs_allow_duplicates_guard(const struct physfs_allow_duplicates_guard &guard) = delete; physfs_allow_duplicates_guard(struct physfs_allow_duplicates_guard &&guard) noexcept = delete; struct physfs_allow_duplicates_guard &operator=(const struct physfs_allow_duplicates_guard &guard) = delete; struct physfs_allow_duplicates_guard &operator=(struct physfs_allow_duplicates_guard &&guard) noexcept = delete; ~physfs_allow_duplicates_guard() { mkxp_physfs_allow_duplicates = old_value; } }; static uint64_t frame_count; static struct atomic frame_time; static uint64_t frame_time_remainder; static uint64_t retro_run_count; extern const uint8_t dist_zip[]; extern const size_t dist_zip_len; static bool initialized = false; static ALCdevice *al_device = nullptr; static ALCcontext *al_context = nullptr; static LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT = nullptr; static LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT = nullptr; static int16_t *sound_buf = nullptr; static bool retro_framebuffer_supported; static bool dupe_supported; static size_t save_state_size = 0; static retro_system_av_info av_info; static struct retro_audio_callback audio_callback; static struct retro_frame_time_callback frame_time_callback = { [](retro_usec_t delta) { frame_time += delta; frame_time_remainder += delta; }, }; static std::mutex threaded_audio_mutex; static bool threaded_audio_enabled = false; static bool frame_time_callback_enabled = false; static struct atomic shared_state_initialized(false); static std::string previous_frame_skip_value; namespace mkxp_retro { retro_log_printf_t log_printf; retro_video_refresh_t video_refresh; retro_audio_sample_batch_t audio_sample_batch; retro_environment_t environment; retro_input_poll_t input_poll; retro_input_state_t input_state; struct retro_perf_callback perf; retro_hw_render_callback hw_render; bool keyboard_state[RETROK_LAST]; bool input_polled; uint8_t sub_image_fix_override; uint8_t enable_blitting_override; uint8_t midi_chorus_override; uint8_t midi_reverb_override; uint64_t get_ticks_ms() noexcept { return frame_time / 1000; } uint64_t get_ticks_us() noexcept { return frame_time; } double get_refresh_rate() noexcept { return av_info.timing.fps; } bool using_threaded_audio() noexcept { return threaded_audio_enabled; } } static void fallback_log(enum retro_log_level level, const char *fmt, ...) { std::va_list va; va_start(va, fmt); std::vfprintf(stderr, fmt, va); va_end(va); } static void fluid_log(int level, const char *message, void *data) { switch (level) { case FLUID_PANIC: log_printf(RETRO_LOG_ERROR, "fluidsynth: panic: %s\n", message); break; case FLUID_ERR: log_printf(RETRO_LOG_ERROR, "fluidsynth: error: %s\n", message); break; case FLUID_WARN: log_printf(RETRO_LOG_WARN, "fluidsynth: warning: %s\n", message); break; case FLUID_INFO: log_printf(RETRO_LOG_INFO, "fluidsynth: %s\n", message); break; case FLUID_DBG: log_printf(RETRO_LOG_DEBUG, "fluidsynth: debug: %s\n", message); break; } } static uint32_t *frame_buf; boost::optional mkxp_retro::sandbox; boost::optional