mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
Shader compilation defaults to Async (UberShaders): a draw whose own pipeline is not built yet is drawn with an ubershader one, exact rendering, while its own builds on the compiler threads, which now number as many as the machine has to spare (cores - 3, 1 to 4) instead of one. What is left to stutter is an ubershader pipeline itself, built on the spot the first time a draw needs one: one per vertex layout and fixed function state. Every ubershader pipeline a draw uses is now recorded, in <game id>.uberuidcache next to the specialized UID cache, and those are built before the game starts the next time it boots; every other ubershader pipeline there can be, and the specialized pipelines known, build in the background while it runs instead of before it starts, which took 21 s on lavapipe. The record is written when the cache closes, into room reserved when it opens, so recording allocates nothing in the middle of a game. Without compiler threads (OpenGL here, with no shared context for them), asking for a pipeline in the background builds it on the spot and an ubershader one besides: the asynchronous modes draw as Synchronous does then, and nothing extra is built before the game starts. Booting a FIFO log no longer hangs: the CPU thread clears the EFB, which waits on the GPU, before it reaches its run loop, and the frontend's thread waited for that run loop without running the GPU. It runs the GPU until then.
1020 lines
36 KiB
C++
1020 lines
36 KiB
C++
#include <cstdio>
|
|
#include <libretro.h>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
|
|
#include "Common/CommonPaths.h"
|
|
#include "Common/Thread.h"
|
|
#include "Core/CommonTitles.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/StringUtil.h"
|
|
#include "Common/Version.h"
|
|
#include "Core/Boot/Boot.h"
|
|
#include "Core/BootManager.h"
|
|
#include "Core/Config/GraphicsSettings.h"
|
|
#include "Core/Config/MainSettings.h"
|
|
#include "Core/Config/SYSCONFSettings.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/GeckoCodeConfig.h"
|
|
#include "Core/HW/DVD/DVDInterface.h"
|
|
#include "Core/HW/EXI/EXI_Device.h"
|
|
#include "Core/HW/HSP/HSP_Device.h"
|
|
#include "Core/HW/VideoInterface.h"
|
|
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
|
#include "Core/PowerPC/PowerPC.h"
|
|
#include "Core/System.h"
|
|
#include "DolphinLibretro/Audio.h"
|
|
#include "DolphinLibretro/Input.h"
|
|
#include "DolphinLibretro/Log.h"
|
|
#include "DolphinLibretro/Common/VFile.h"
|
|
#include "DolphinLibretro/Common/Globals.h"
|
|
#include "DolphinLibretro/Common/Options.h"
|
|
#include "DolphinLibretro/Video.h"
|
|
#include "DolphinLibretro/VR.h"
|
|
#include "DolphinLibretro/VideoContexts/ContextStatus.h"
|
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
|
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h"
|
|
#include "UICommon/UICommon.h"
|
|
#include "VideoCommon/AsyncRequests.h"
|
|
#include "VideoCommon/VideoBackendBase.h"
|
|
#include "VideoCommon/VideoConfig.h"
|
|
#include "VideoCommon/Resources/CustomResourceManager.h"
|
|
#include "VideoCommon/Fifo.h"
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace Libretro
|
|
{
|
|
extern retro_environment_t environ_cb;
|
|
|
|
// Disk swapping
|
|
static void InitDiskControlInterface();
|
|
static unsigned disk_index = 0;
|
|
static bool eject_state;
|
|
static std::vector<std::string> disk_paths;
|
|
|
|
static void ResetDiskControlState()
|
|
{
|
|
disk_index = 0;
|
|
eject_state = false;
|
|
disk_paths.clear();
|
|
}
|
|
|
|
// GBPlayer
|
|
static std::string GBPlayer_rom_path;
|
|
static bool GBPlayer_active;
|
|
|
|
// silence forward declaration warnings
|
|
void reload_cheats_from_ini();
|
|
void generate_cht_from_ini(std::string fileName);
|
|
|
|
void reload_cheats_from_ini()
|
|
{
|
|
// Load Dolphin INI files
|
|
const auto& sconfig = SConfig::GetInstance();
|
|
Common::IniFile globalIni = sconfig.LoadDefaultGameIni();
|
|
Common::IniFile localIni = sconfig.LoadLocalGameIni();
|
|
|
|
// Load ActionReplay codes
|
|
Libretro::g_ar_codes = ActionReplay::LoadCodes(globalIni, localIni);
|
|
|
|
// Load Gecko codes
|
|
Libretro::g_gecko_codes = Gecko::LoadCodes(globalIni, localIni);
|
|
}
|
|
|
|
void generate_cht_from_ini(std::string fileName)
|
|
{
|
|
const char* sysdir = nullptr;
|
|
|
|
if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysdir) || !sysdir)
|
|
return;
|
|
|
|
std::string base = sysdir;
|
|
size_t pos = base.find_last_of("/\\");
|
|
if (pos != std::string::npos)
|
|
base = base.substr(0, pos);
|
|
|
|
// Write RetroArch .cht
|
|
std::string cheatDir = base + "/cheats/dolphin-emu/";
|
|
std::string cheatFile = cheatDir + fileName + ".cht";
|
|
|
|
if (File::Exists(cheatFile))
|
|
{
|
|
INFO_LOG_FMT(BOOT, "Cheats: pre-existing cht file found: {}", cheatFile);
|
|
return;
|
|
}
|
|
|
|
size_t total = Libretro::g_ar_codes.size() + Libretro::g_gecko_codes.size();
|
|
if (total == 0)
|
|
{
|
|
INFO_LOG_FMT(BOOT, "Cheats: not generating cht file as no ActionReply or Gecko codes found");
|
|
return;
|
|
}
|
|
|
|
if (!File::CreateFullPath(cheatFile))
|
|
return;
|
|
|
|
std::ofstream out(cheatFile);
|
|
if (!out.is_open())
|
|
{
|
|
ERROR_LOG_FMT(BOOT, "Cheats: unable to create a new cht file at: {}", cheatFile);
|
|
return;
|
|
}
|
|
|
|
out << "cheats = " << total << "\n\n";
|
|
|
|
size_t cheat_index = 0;
|
|
|
|
// -------------------------
|
|
// Action Replay
|
|
// -------------------------
|
|
for (const auto& c : Libretro::g_ar_codes)
|
|
{
|
|
std::string code;
|
|
|
|
for (const auto& op : c.ops)
|
|
{
|
|
if (!code.empty())
|
|
code += "+";
|
|
|
|
code += fmt::format("{:08X} {:08X}", op.cmd_addr, op.value);
|
|
}
|
|
|
|
out << "cheat" << cheat_index << "_desc = \"" << c.name << "\"\n";
|
|
out << "cheat" << cheat_index << "_code = \"" << code << "\"\n";
|
|
out << "cheat" << cheat_index << "_enable = " << (c.enabled ? "true" : "false") << "\n\n";
|
|
|
|
cheat_index++;
|
|
}
|
|
|
|
// -------------------------
|
|
// Gecko
|
|
// -------------------------
|
|
for (const auto& c : Libretro::g_gecko_codes)
|
|
{
|
|
std::string code;
|
|
|
|
for (const auto& line : c.codes)
|
|
{
|
|
if (!code.empty())
|
|
code += "+";
|
|
|
|
code += line.original_line;
|
|
}
|
|
|
|
out << "cheat" << cheat_index << "_desc = \"" << c.name << "\"\n";
|
|
out << "cheat" << cheat_index << "_code = \"" << code << "\"\n";
|
|
out << "cheat" << cheat_index << "_enable = " << (c.enabled ? "true" : "false") << "\n\n";
|
|
|
|
cheat_index++;
|
|
}
|
|
|
|
out.close();
|
|
|
|
INFO_LOG_FMT(BOOT, "Cheats: cht file created successfully at: {}", cheatFile);
|
|
}
|
|
|
|
// A region-free title runs in the fallback region, which follows the system locale and is PAL
|
|
// when there is none, as for Kodi on LibreELEC. A Japanese title can hang on a PAL console, so a
|
|
// region-free title runs in the region its game ID names.
|
|
static void SetRegionFreeFallback(const BootParameters& boot_params)
|
|
{
|
|
const DiscIO::Volume* volume = nullptr;
|
|
if (const auto* disc = std::get_if<BootParameters::Disc>(&boot_params.parameters))
|
|
volume = disc->volume.get();
|
|
else if (const auto* wad = std::get_if<DiscIO::VolumeWAD>(&boot_params.parameters))
|
|
volume = wad;
|
|
|
|
if (!volume || volume->GetRegion() != DiscIO::Region::Unknown)
|
|
return;
|
|
|
|
const std::string game_id = volume->GetGameID();
|
|
if (game_id.size() < 4)
|
|
return;
|
|
|
|
const DiscIO::Region region = DiscIO::CountryCodeToRegion(
|
|
game_id[3], volume->GetVolumeType(), Config::Get(Config::MAIN_FALLBACK_REGION));
|
|
if (region == DiscIO::Region::Unknown)
|
|
return;
|
|
|
|
NOTICE_LOG_FMT(BOOT, "Region-free title {}: running it as {}", game_id,
|
|
DiscIO::GetName(region, false));
|
|
Config::SetCurrent(Config::MAIN_FALLBACK_REGION, region);
|
|
}
|
|
|
|
} // namespace Libretro
|
|
|
|
bool retro_load_game(const struct retro_game_info* game)
|
|
{
|
|
Libretro::ResetDiskControlState();
|
|
|
|
const char* save_dir = NULL;
|
|
const char* gba_save_dir = NULL;
|
|
const char* system_dir = NULL;
|
|
const char* core_assets_dir = NULL;
|
|
std::string rebuild_save_dir;
|
|
std::string user_dir;
|
|
std::string sys_dir;
|
|
|
|
/*
|
|
* If the GBPlayer is active, we try to reconstruct the GC save dir location
|
|
* with regards to content sorting and core sorting. If we do not do this,
|
|
* there will be a User directory with GC saves in the GB/GBC/GBA rom save path
|
|
* when a user has content/core sorting enabled. Since the user is trying to avoid
|
|
* said behavior actively if those sorting options are enabled,
|
|
* we should seperate them accordingly.
|
|
*/
|
|
if (Libretro::GBPlayer_active)
|
|
{
|
|
std::filesystem::path gba_content_dir = std::filesystem::path("");
|
|
if (!Libretro::GBPlayer_rom_path.empty())
|
|
gba_content_dir = std::filesystem::path(Libretro::GBPlayer_rom_path).parent_path().filename().string();
|
|
|
|
Libretro::environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &gba_save_dir);
|
|
std::filesystem::path base = std::filesystem::path(gba_save_dir);
|
|
|
|
bool core_sort = false;
|
|
retro_system_info sys_info{};
|
|
retro_get_system_info(&sys_info);
|
|
const std::string library_name = sys_info.library_name;
|
|
|
|
if (!base.empty() && !library_name.empty())
|
|
core_sort = Common::CaseInsensitiveEquals(base.filename().string(), library_name);
|
|
if (core_sort)
|
|
base = base.parent_path();
|
|
|
|
bool content_sort = false;
|
|
std::string gc_content_dir;
|
|
if (!gba_content_dir.empty() && !base.empty())
|
|
content_sort = Common::CaseInsensitiveEquals(base.filename().string(), gba_content_dir.string());
|
|
if (content_sort)
|
|
{
|
|
gc_content_dir = std::filesystem::path(game->path).parent_path().filename().string();
|
|
base = base.parent_path();
|
|
}
|
|
|
|
rebuild_save_dir = base.string();
|
|
if (content_sort)
|
|
rebuild_save_dir = rebuild_save_dir + DIR_SEP + gc_content_dir;
|
|
if (core_sort)
|
|
rebuild_save_dir = rebuild_save_dir + DIR_SEP + library_name;
|
|
|
|
save_dir = rebuild_save_dir.c_str();
|
|
INFO_LOG_FMT(BOOT, "GBPlayer: GC save dir reconstructed to: '{}'", save_dir);
|
|
}
|
|
else
|
|
{
|
|
Libretro::environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_dir);
|
|
}
|
|
|
|
Libretro::environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir);
|
|
Libretro::environ_cb(RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY, &core_assets_dir);
|
|
Libretro::InitDiskControlInterface();
|
|
|
|
if (save_dir && *save_dir)
|
|
user_dir = std::string(save_dir) + DIR_SEP "User";
|
|
else if (system_dir && *system_dir)
|
|
user_dir = std::string(system_dir) + DIR_SEP "dolphin-emu" DIR_SEP "User";
|
|
|
|
if (system_dir && *system_dir)
|
|
sys_dir = std::string(system_dir) + DIR_SEP "dolphin-emu" DIR_SEP "Sys";
|
|
else if (core_assets_dir && *core_assets_dir)
|
|
sys_dir = std::string(core_assets_dir) + DIR_SEP "dolphin-emu" DIR_SEP "Sys";
|
|
else if (save_dir && *save_dir)
|
|
sys_dir = std::string(save_dir) + DIR_SEP "Sys";
|
|
else
|
|
sys_dir = "dolphin-emu" DIR_SEP "Sys";
|
|
|
|
using namespace Libretro::Options;
|
|
Libretro::Options::Init();
|
|
Libretro::VFile::Init();
|
|
|
|
#ifdef ANDROID
|
|
static bool sysdir_set = false;
|
|
|
|
if(!sysdir_set)
|
|
{
|
|
File::SetSysDirectory(sys_dir);
|
|
sysdir_set = true;
|
|
}
|
|
#else
|
|
File::SetSysDirectory(sys_dir);
|
|
#endif
|
|
|
|
UICommon::SetUserDirectory(user_dir);
|
|
/* Shader caches live in the system directory whatever the save directory is: they belong to
|
|
* the core and the host's GPU, not to a game's saves. */
|
|
if (system_dir && *system_dir)
|
|
{
|
|
const std::string shader_dir = std::string(system_dir) + DIR_SEP "dolphin-emu" DIR_SEP
|
|
"User" DIR_SEP "Cache" DIR_SEP "Shaders" DIR_SEP;
|
|
File::CreateFullPath(shader_dir);
|
|
File::SetUserPath(D_SHADERCACHE_IDX, shader_dir);
|
|
}
|
|
UICommon::CreateDirectories();
|
|
UICommon::Init();
|
|
Libretro::Log::Init();
|
|
|
|
if (Libretro::GBPlayer_active)
|
|
{
|
|
Config::SetBase(Config::MAIN_GBA_ROM_PATHS[Config::GBPLAYER_GBA_INDEX], Libretro::GBPlayer_rom_path);
|
|
INFO_LOG_FMT(BOOT, "GBPlayer: GBA ROM path applied from subsystem: '{}'", Libretro::GBPlayer_rom_path);
|
|
Config::SetBase(Config::MAIN_GBA_SAVES_PATH, std::string(gba_save_dir));
|
|
INFO_LOG_FMT(BOOT, "GBPlayer: ROM save set to: '{}'", gba_save_dir);
|
|
// In order to prevent confusion for the settings of the save location between RA and the
|
|
// emulator we just turn this off
|
|
Config::SetBase(Config::MAIN_GBA_SAVES_IN_ROM_PATH, false);
|
|
}
|
|
else
|
|
{
|
|
Config::SetBase(Config::MAIN_GBA_ROM_PATHS[Config::GBPLAYER_GBA_INDEX], std::string{});
|
|
}
|
|
|
|
Common::SetEnableAlert(false);
|
|
Common::SetAbortOnPanicAlert(false);
|
|
Common::RegisterMsgAlertHandler([](const char* caption, const char* text,
|
|
bool yes_no, Common::MsgType style) -> bool
|
|
{
|
|
// Log the message instead of showing a popup
|
|
WARN_LOG_FMT(COMMON, "Suppressed popup: {} - {}", caption, text);
|
|
|
|
if (Libretro::GetOption<bool>(retroarch_core::ENABLE_LOG_POPUPS,
|
|
/*def=*/false))
|
|
Libretro::Log::LogFrontEnd(style, caption, text, 2000);
|
|
return true; // Always "continue"
|
|
});
|
|
|
|
NOTICE_LOG_FMT(BOOT, "SCM Git revision: {}", Common::GetScmRevGitStr());
|
|
NOTICE_LOG_FMT(BOOT, "User Directory set to '{}'", user_dir);
|
|
NOTICE_LOG_FMT(BOOT, "System Directory set to '{}'", sys_dir);
|
|
|
|
const std::string codehandler = std::string(sys_dir) + DIR_SEP GECKO_CODE_HANDLER;
|
|
|
|
if (!File::Exists(codehandler))
|
|
{
|
|
#if defined(ANDROID) || defined(IPHONEOS)
|
|
// for reduced area to display the message..
|
|
const std::string missing_core_files_msg =
|
|
fmt::format(
|
|
"IMPORTANT - Open Online Updater ->\n"
|
|
"Core System Files Downloader -> Dolphin.zip.");
|
|
#else
|
|
const std::string missing_core_files_msg =
|
|
fmt::format(
|
|
"Core file {} missing! Open Online Updater ->\n"
|
|
"Core System Files Downloader -> Install Dolphin.zip. "
|
|
"Restart core to take effect.",
|
|
GECKO_CODE_HANDLER);
|
|
#endif
|
|
|
|
Libretro::Log::LogFrontEnd(
|
|
Common::Log::LogLevel::LERROR, missing_core_files_msg.c_str(), 10000);
|
|
|
|
ERROR_LOG_FMT(BOOT, "{}", missing_core_files_msg);
|
|
}
|
|
|
|
// Main.Core
|
|
Config::SetBase(Config::MAIN_CPU_CORE,
|
|
static_cast<PowerPC::CPUCore>(
|
|
Libretro::GetOption<int>(
|
|
core::CPU_CORE,
|
|
static_cast<int>(PowerPC::DefaultCPUCore()))));
|
|
|
|
#if defined(_DEBUG)
|
|
Config::SetBase(Config::MAIN_FASTMEM, false);
|
|
Config::SetBase(Config::MAIN_FASTMEM_ARENA, false);
|
|
#else
|
|
Config::SetBase(Config::MAIN_FASTMEM,
|
|
Libretro::GetOption<bool>(core::FASTMEM, /*def=*/true));
|
|
Config::SetBase(Config::MAIN_FASTMEM_ARENA,
|
|
Libretro::GetOption<bool>(core::FASTMEM_ARENA, /*def=*/true));
|
|
#endif
|
|
Config::SetBase(Config::MAIN_ACCURATE_CPU_CACHE,
|
|
Libretro::GetOption<bool>(core::MAIN_ACCURATE_CPU_CACHE, /*def=*/false));
|
|
|
|
Config::SetBase(Config::MAIN_DSP_HLE,
|
|
Libretro::GetOption<bool>(audio::DSP_HLE, /*def=*/true));
|
|
|
|
// dual core (true) or single core (false)
|
|
Config::SetBase(Config::MAIN_CPU_THREAD,
|
|
Libretro::GetOption<bool>(core::MAIN_CPU_THREAD, /*def=*/true));
|
|
|
|
// Dual core's GPU thread kept in step with the CPU thread (Dolphin's "Synchronize GPU Thread").
|
|
Config::SetBase(Config::MAIN_SYNC_GPU,
|
|
Libretro::GetOption<bool>(core::MAIN_SYNC_GPU, /*def=*/false));
|
|
|
|
Config::SetBase(Config::MAIN_LOAD_GAME_INTO_MEMORY,
|
|
Libretro::GetOption<bool>(core::MAIN_LOAD_GAME_INTO_MEMORY, /*def=*/false));
|
|
|
|
Config::SetBase(Config::MAIN_ENABLE_CHEATS,
|
|
Libretro::GetOption<bool>(core::CHEATS_ENABLED, /*def=*/false));
|
|
|
|
Config::SetBase(Config::MAIN_SKIP_IPL, Libretro::GetOption<bool>(core::SKIP_GC_BIOS, /*def=*/true));
|
|
|
|
const int language = Libretro::GetOption<int>(core::LANGUAGE, static_cast<int>(DiscIO::Language::English));
|
|
Config::SetBase(Config::SYSCONF_LANGUAGE, language);
|
|
Config::SetBase(Config::MAIN_GC_LANGUAGE, DiscIO::ToGameCubeLanguage(static_cast<DiscIO::Language>(language)));
|
|
|
|
{
|
|
// Overclock (cpu clock rate) — option values in option_defs used strings like "100%" etc.
|
|
double multiplier = Libretro::GetOption<double>(core::CPU_CLOCK_RATE, 1.0);
|
|
Config::SetBase(Config::MAIN_OVERCLOCK, multiplier);
|
|
Config::SetBase(Config::MAIN_OVERCLOCK_ENABLE, multiplier != 1.0);
|
|
}
|
|
|
|
Config::SetBase(Config::MAIN_WIIMOTE_CONTINUOUS_SCANNING,
|
|
Libretro::GetOption<bool>(sysconf::WIIMOTE_CONTINUOUS_SCANNING,
|
|
/*def=*/false) ||
|
|
Libretro::GetOption<std::string>(wiimote::DEFAULT_DEVICE, "frontend") == "real");
|
|
Config::SetBase(Config::MAIN_MMU,
|
|
Libretro::GetOption<bool>(core::MAIN_MMU, /*def=*/false));
|
|
|
|
Config::SetBase(Config::MAIN_FAST_DISC_SPEED,
|
|
Libretro::GetOption<bool>(core::FAST_DISC_SPEED, /*def=*/false));
|
|
|
|
// Main.Interface
|
|
Config::SetBase(Config::MAIN_ENABLE_DEBUGGING,
|
|
Libretro::GetOption<bool>(main_interface::ENABLE_DEBUGGING, /*def=*/false));
|
|
|
|
// Main.General
|
|
|
|
// Main.DSP
|
|
Config::SetBase(Config::MAIN_DSP_JIT,
|
|
Libretro::GetOption<bool>(audio::DSP_JIT, /*def=*/true));
|
|
|
|
// Main.BluetoothPassthrough
|
|
Config::SetBase(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED,
|
|
Libretro::GetOption<bool>(Libretro::Options::main_bluetooth::BLUETOOTH_PASSTHROUGH, /*def=*/false));
|
|
|
|
// SYSCONF (GC)
|
|
{
|
|
const std::string value =
|
|
Libretro::GetOption<std::string>(
|
|
sysconf_gc::SP1_DEVICE,
|
|
"255" // ExpansionInterface::EXIDeviceType::None
|
|
);
|
|
|
|
const int device_int = std::stoi(value);
|
|
const auto device = static_cast<ExpansionInterface::EXIDeviceType>(device_int);
|
|
|
|
Config::SetBase(Config::MAIN_SERIAL_PORT_1, device);
|
|
}
|
|
|
|
// SYSCONF.IPL
|
|
Config::SetBase(Config::SYSCONF_WIDESCREEN,
|
|
Libretro::GetOption<bool>(sysconf::WIDESCREEN, /*def=*/true));
|
|
Config::SetBase(Config::SYSCONF_PROGRESSIVE_SCAN,
|
|
Libretro::GetOption<bool>(sysconf::PROGRESSIVE_SCAN, /*def=*/true));
|
|
Config::SetBase(Config::SYSCONF_PAL60,
|
|
Libretro::GetOption<bool>(sysconf::PAL60, /*def=*/true));
|
|
|
|
// SYSCONF.BT
|
|
Config::SetBase(Config::SYSCONF_SENSOR_BAR_POSITION,
|
|
Libretro::GetOption<int>(sysconf::SENSOR_BAR_POSITION, 0));
|
|
Config::SetBase(Config::SYSCONF_WIIMOTE_MOTOR,
|
|
Libretro::GetOption<bool>(sysconf::ENABLE_RUMBLE, /*def=*/true));
|
|
|
|
// Graphics.Hardware
|
|
//Config::SetBase(Config::GFX_VSYNC,
|
|
// Libretro::GetOption<bool>(gfx_hardware::VSYNC, /*def=*/false));
|
|
|
|
// Graphics.Settings
|
|
Config::SetBase(Config::GFX_WIDESCREEN_HACK,
|
|
Libretro::GetOption<bool>(gfx_settings::WIDESCREEN_HACK, /*def=*/false));
|
|
// Dolphin stretches its picture over the frame; the Aspect Ratio option
|
|
// sets the aspect ratio the frontend shows it at (Main.cpp, FrameAspect)
|
|
Config::SetBase(Config::GFX_ASPECT_RATIO, AspectMode::Stretch);
|
|
Config::SetBase(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES,
|
|
Libretro::GetOption<int>(gfx_settings::TEXTURE_CACHE_ACCURACY, 128));
|
|
Config::SetBase(Config::GFX_HIRES_TEXTURES,
|
|
Libretro::GetOption<bool>(gfx_enhancements::LOAD_CUSTOM_TEXTURES, /*def=*/false));
|
|
Config::SetBase(Config::GFX_CACHE_HIRES_TEXTURES,
|
|
Libretro::GetOption<bool>(gfx_enhancements::CACHE_CUSTOM_TEXTURES, /*def=*/false));
|
|
Config::SetBase(Config::GFX_ENABLE_GPU_TEXTURE_DECODING,
|
|
Libretro::GetOption<bool>(gfx_settings::GPU_TEXTURE_DECODING, /*def=*/false));
|
|
Config::SetBase(Config::GFX_ENABLE_PIXEL_LIGHTING,
|
|
Libretro::GetOption<bool>(gfx_settings::ENABLE_PIXEL_LIGHTING, /*def=*/false));
|
|
Config::SetBase(Config::GFX_FAST_DEPTH_CALC,
|
|
Libretro::GetOption<bool>(gfx_settings::FAST_DEPTH_CALCULATION, /*def=*/true));
|
|
Config::SetBase(Config::GFX_DISABLE_FOG,
|
|
Libretro::GetOption<bool>(gfx_settings::DISABLE_FOG, /*def=*/false));
|
|
Config::SetBase(Config::GFX_EFB_SCALE,
|
|
Libretro::GetOption<int>(gfx_settings::EFB_SCALE, /*def=*/1));
|
|
Config::SetBase(Config::GFX_BACKEND_MULTITHREADING, false);
|
|
Config::SetBase(Config::GFX_SHADER_COMPILATION_MODE,
|
|
static_cast<ShaderCompilationMode>(
|
|
Libretro::GetOption<int>(
|
|
gfx_settings::SHADER_COMPILATION_MODE,
|
|
static_cast<int>(ShaderCompilationMode::AsynchronousUberShaders))));
|
|
/* as many compiler threads as the machine has to spare, for building
|
|
* pipelines both before the game starts and while it runs */
|
|
Config::SetBase(Config::GFX_SHADER_COMPILER_THREADS, -1);
|
|
Config::SetBase(Config::GFX_SHADER_PRECOMPILER_THREADS, -1);
|
|
|
|
Config::SetBase(Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING,
|
|
Libretro::GetOption<bool>(gfx_settings::WAIT_FOR_SHADERS, /*def=*/false));
|
|
|
|
if (Common::is_uwp())
|
|
Config::SetBase(Config::GFX_SHADER_CACHE, false);
|
|
|
|
Config::SetBase(Config::GFX_MODS_ENABLE,
|
|
Libretro::GetOption<bool>(gfx_settings::MODS_ENABLE, /*def=*/false));
|
|
|
|
// Graphics.Enhancements
|
|
Config::SetBase(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
|
static_cast<TextureFilteringMode>(
|
|
Libretro::GetOption<int>(
|
|
gfx_enhancements::FORCE_TEXTURE_FILTERING_MODE,
|
|
static_cast<int>(TextureFilteringMode::Default))));
|
|
|
|
Config::SetBase(Config::GFX_ENHANCE_MAX_ANISOTROPY,
|
|
static_cast<AnisotropicFilteringMode>(
|
|
Libretro::GetOption<int>(
|
|
gfx_enhancements::MAX_ANISOTROPY,
|
|
static_cast<int>(AnisotropicFilteringMode::Force1x))));
|
|
|
|
Config::SetBase(Config::GFX_ENHANCE_OUTPUT_RESAMPLING,
|
|
static_cast<OutputResamplingMode>(
|
|
Libretro::GetOption<int>(
|
|
gfx_enhancements::GFX_ENHANCE_OUTPUT_RESAMPLING,
|
|
static_cast<int>(OutputResamplingMode::Default))));
|
|
|
|
Config::SetBase(Config::GFX_ENHANCE_FORCE_TRUE_COLOR,
|
|
Libretro::GetOption<bool>(gfx_enhancements::FORCE_TRUE_COLOR,
|
|
/*def=*/true));
|
|
|
|
Config::SetBase(Config::GFX_ENHANCE_DISABLE_COPY_FILTER,
|
|
Libretro::GetOption<bool>(gfx_enhancements::GFX_ENHANCE_DISABLE_COPY_FILTER,
|
|
/*def=*/true));
|
|
Config::SetBase(Config::GFX_ENHANCE_HDR_OUTPUT,
|
|
Libretro::GetOption<bool>(gfx_enhancements::GFX_ENHANCE_HDR_OUTPUT,
|
|
/*def=*/false));
|
|
Config::SetBase(Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION,
|
|
Libretro::GetOption<bool>(gfx_enhancements::GFX_ARBITRARY_MIPMAP_DETECTION, /*def=*/false));
|
|
|
|
// Graphics.Hacks
|
|
Config::SetBase(Config::GFX_HACK_EFB_ACCESS_ENABLE,
|
|
Libretro::GetOption<bool>(gfx_hacks::EFB_ACCESS_ENABLE, /*def=*/false));
|
|
Config::SetBase(Config::GFX_HACK_EFB_DEFER_INVALIDATION,
|
|
Libretro::GetOption<bool>(gfx_hacks::EFB_ACCESS_DEFER_INVALIDATION, /*def=*/false));
|
|
Config::SetBase(Config::GFX_HACK_EFB_ACCESS_TILE_SIZE,
|
|
Libretro::GetOption<int>(gfx_hacks::EFB_ACCESS_TILE_SIZE, /*def=*/64));
|
|
Config::SetBase(Config::GFX_HACK_BBOX_ENABLE,
|
|
Libretro::GetOption<bool>(gfx_hacks::BBOX_ENABLED, /*def=*/false));
|
|
Config::SetBase(Config::GFX_HACK_FORCE_PROGRESSIVE,
|
|
Libretro::GetOption<bool>(gfx_hacks::FORCE_PROGRESSIVE, /*def=*/true));
|
|
Config::SetBase(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM,
|
|
Libretro::GetOption<bool>(gfx_hacks::EFB_TO_TEXTURE, /*def=*/true));
|
|
Config::SetBase(Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM,
|
|
Libretro::GetOption<bool>(gfx_hacks::XFB_TO_TEXTURE_ENABLE, /*def=*/true));
|
|
Config::SetBase(Config::GFX_HACK_DISABLE_COPY_TO_VRAM,
|
|
Libretro::GetOption<bool>(gfx_hacks::EFB_TO_VRAM, /*def=*/false));
|
|
Config::SetBase(Config::GFX_HACK_DEFER_EFB_COPIES,
|
|
Libretro::GetOption<bool>(gfx_hacks::DEFER_EFB_COPIES, /*def=*/true));
|
|
Config::SetBase(Config::GFX_HACK_IMMEDIATE_XFB,
|
|
Libretro::GetOption<bool>(gfx_hacks::IMMEDIATE_XFB, /*def=*/false));
|
|
Config::SetBase(Config::GFX_HACK_SKIP_DUPLICATE_XFBS,
|
|
Libretro::GetOption<bool>(gfx_hacks::SKIP_DUPE_FRAMES, /*def=*/true));
|
|
Config::SetBase(Config::GFX_HACK_EARLY_XFB_OUTPUT,
|
|
Libretro::GetOption<bool>(gfx_hacks::EARLY_XFB_OUTPUT, /*def=*/true));
|
|
Config::SetBase(Config::GFX_HACK_COPY_EFB_SCALED,
|
|
Libretro::GetOption<bool>(gfx_hacks::EFB_SCALED_COPY, /*def=*/true));
|
|
Config::SetBase(Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES,
|
|
Libretro::GetOption<bool>(gfx_hacks::EFB_EMULATE_FORMAT_CHANGES, /*def=*/false));
|
|
Config::SetBase(Config::GFX_HACK_VERTEX_ROUNDING,
|
|
Libretro::GetOption<bool>(gfx_hacks::VERTEX_ROUNDING, /*def=*/false));
|
|
Config::SetBase(Config::GFX_HACK_FAST_TEXTURE_SAMPLING,
|
|
Libretro::GetOption<bool>(gfx_hacks::FAST_TEXTURE_SAMPLING, /*def=*/true));
|
|
#ifdef __APPLE__
|
|
Config::SetBase(Config::GFX_HACK_NO_MIPMAPPING,
|
|
Libretro::GetOption<bool>(gfx_hacks::NO_MIPMAPPING, /*def=*/false));
|
|
#endif
|
|
|
|
switch (Libretro::GetOption<int>(gfx_settings::ANTI_ALIASING, 0))
|
|
{
|
|
case 1: // 2x MSAA
|
|
Config::SetBase(Config::GFX_MSAA, 2);
|
|
Config::SetBase(Config::GFX_SSAA, false);
|
|
break;
|
|
case 2: // 4x MSAA
|
|
Config::SetBase(Config::GFX_MSAA, 4);
|
|
Config::SetBase(Config::GFX_SSAA, false);
|
|
break;
|
|
case 3: // 8x MSAA
|
|
Config::SetBase(Config::GFX_MSAA, 8);
|
|
Config::SetBase(Config::GFX_SSAA, false);
|
|
break;
|
|
case 4: // 2x SSAA
|
|
Config::SetBase(Config::GFX_MSAA, 2);
|
|
Config::SetBase(Config::GFX_SSAA, true);
|
|
break;
|
|
case 5: // 4x SSAA
|
|
Config::SetBase(Config::GFX_MSAA, 4);
|
|
Config::SetBase(Config::GFX_SSAA, true);
|
|
break;
|
|
case 6: // 8x SSAA
|
|
Config::SetBase(Config::GFX_MSAA, 8);
|
|
Config::SetBase(Config::GFX_SSAA, true);
|
|
break;
|
|
default: // disabled
|
|
Config::SetBase(Config::GFX_MSAA, 1);
|
|
Config::SetBase(Config::GFX_SSAA, false);
|
|
break;
|
|
}
|
|
|
|
// Graphics.GameSpecific
|
|
Config::SetBase(Config::GFX_PERF_QUERIES_ENABLE,
|
|
Libretro::GetOption<bool>(gfx_gamespecific::GFX_PERF_QUERIES_ENABLE, /*def=*/false));
|
|
|
|
if (Libretro::VR::IsEnabled())
|
|
Libretro::VR::ApplyConfig();
|
|
|
|
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
|
|
// wiimote - alternative input server
|
|
{
|
|
const bool dsu_enabled = Libretro::GetOption<bool>(
|
|
Libretro::Options::wiimote_dsu::DSU_ENABLED, false);
|
|
|
|
Config::SetBase(ciface::DualShockUDPClient::Settings::SERVERS_ENABLED, dsu_enabled);
|
|
|
|
if (dsu_enabled)
|
|
{
|
|
const int o1 = Libretro::GetOption<int>(Libretro::Options::wiimote_dsu::DSU_IP_1, 127);
|
|
const int o2 = Libretro::GetOption<int>(Libretro::Options::wiimote_dsu::DSU_IP_2, 0);
|
|
const int o3 = Libretro::GetOption<int>(Libretro::Options::wiimote_dsu::DSU_IP_3, 0);
|
|
const int o4 = Libretro::GetOption<int>(Libretro::Options::wiimote_dsu::DSU_IP_4, 1);
|
|
const int port = Libretro::GetOption<int>(Libretro::Options::wiimote_dsu::DSU_PORT, 26760);
|
|
|
|
const std::string address = fmt::format("{}.{}.{}.{}", o1, o2, o3, o4);
|
|
const std::string servers_entry = fmt::format("DS4:{}:{};", address, port);
|
|
|
|
Config::SetBase(ciface::DualShockUDPClient::Settings::SERVERS, servers_entry);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SConfig::GetInstance().bBootToPause = true;
|
|
|
|
#ifdef IPHONEOS
|
|
bool can_jit = false;
|
|
{
|
|
struct retro_exec_mem_alloc probe = {};
|
|
probe.version = 1;
|
|
probe.size = 0;
|
|
if (Libretro::environ_cb(RETRO_ENVIRONMENT_EXEC_MEM_ALLOC, &probe))
|
|
{
|
|
if (probe.mode != RETRO_EXEC_MEM_MODE_UNAVAILABLE)
|
|
can_jit = true;
|
|
}
|
|
else if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_JIT_CAPABLE, &can_jit))
|
|
can_jit = false;
|
|
}
|
|
if (!can_jit)
|
|
{
|
|
auto current = Config::Get(Config::MAIN_CPU_CORE);
|
|
if (current == PowerPC::CPUCore::JIT64 ||
|
|
current == PowerPC::CPUCore::JITARM64)
|
|
{
|
|
Config::SetBase(Config::MAIN_CPU_CORE, PowerPC::CPUCore::CachedInterpreter);
|
|
}
|
|
|
|
Config::SetBase(Config::GFX_VERTEX_LOADER_TYPE, VertexLoaderType::Software);
|
|
|
|
Core::DisplayMessage("CPU: Just in time compiler disabled as unavailable on your system");
|
|
}
|
|
#endif
|
|
NOTICE_LOG_FMT(BOOT, "CPU Core: {}", Libretro::Options::CPUCoreToString(Config::Get(Config::MAIN_CPU_CORE)));
|
|
NOTICE_LOG_FMT(BOOT, "Fastmem enabled = {}", (Config::Get(Config::MAIN_FASTMEM)) ? "Yes" : "No");
|
|
NOTICE_LOG_FMT(BOOT, "JIT debug enabled = {}", Config::IsDebuggingEnabled() ? "Yes" : "No");
|
|
|
|
Libretro::Video::Init();
|
|
// The hardware renderer was asked for and no hardware context was granted (e.g. no GLES 3): fail the load so the
|
|
// frontend reports it, rather than run on the null backend behind a black screen.
|
|
if (Libretro::Options::GetCached<std::string>(Libretro::Options::gfx_settings::RENDERER) == "Hardware" &&
|
|
Libretro::Video::hw_render.context_type == RETRO_HW_CONTEXT_NONE)
|
|
{
|
|
ERROR_LOG_FMT(VIDEO, "No hardware render context was granted; refusing to load");
|
|
return false;
|
|
}
|
|
WindowSystemInfo wsi(WindowSystemType::Libretro, nullptr, nullptr, nullptr);
|
|
VideoBackendBase::PopulateBackendInfo(wsi);
|
|
NOTICE_LOG_FMT(VIDEO, "Using GFX backend: {}", Config::Get(Config::MAIN_GFX_BACKEND));
|
|
|
|
std::vector<std::string> normalized_game_paths;
|
|
normalized_game_paths.push_back(Libretro::VFile::NormalizePath(game->path));
|
|
std::string folder_path_str;
|
|
std::string filename_str;
|
|
std::string extension;
|
|
SplitPath(normalized_game_paths.front(), &folder_path_str, &filename_str, &extension);
|
|
fs::path folder_path(folder_path_str);
|
|
fs::path filename(filename_str);
|
|
std::transform(extension.begin(), extension.end(), extension.begin(),
|
|
[](unsigned char c){ return std::tolower(c); });
|
|
|
|
#ifdef _WIN32
|
|
// If SplitPath only gave us "D:", rebuild the real directory from the full path
|
|
if (folder_path_str.size() == 2 && folder_path_str[1] == ':')
|
|
{
|
|
// take everything up to the last backslash
|
|
size_t last_slash = normalized_game_paths.front().find_last_of("\\/");
|
|
if (last_slash != std::string::npos)
|
|
folder_path_str = normalized_game_paths.front().substr(0, last_slash + 1);
|
|
folder_path = fs::path(folder_path_str);
|
|
}
|
|
#endif
|
|
|
|
if (extension == ".m3u" || extension == ".m3u8")
|
|
{
|
|
normalized_game_paths = ReadM3UFile(normalized_game_paths.front(), folder_path_str);
|
|
if (normalized_game_paths.empty())
|
|
{
|
|
ERROR_LOG_FMT(BOOT, "Could not boot {}. M3U contains no paths", game->path);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (auto& normalized_game_path : normalized_game_paths)
|
|
Libretro::disk_paths.push_back(Libretro::VFile::DenormalizePath(normalized_game_path));
|
|
|
|
Libretro::Input::Init(wsi);
|
|
|
|
// for Wii
|
|
if (Libretro::Input::g_has_microphone_support)
|
|
{
|
|
Config::SetCurrent(Config::MAIN_EMULATE_WII_SPEAK,
|
|
Libretro::GetOption<bool>(sysconf::WII_SPEAK_ENABLE, /*def=*/false));
|
|
Config::SetCurrent(Config::MAIN_WII_SPEAK_MUTED,
|
|
Libretro::GetOption<bool>(sysconf::WII_SPEAK_MUTED, /*def=*/true));
|
|
|
|
// logitech
|
|
Config::SetCurrent(Config::MAIN_EMULATE_LOGITECH_MIC[0],
|
|
Libretro::GetOption<bool>(sysconf::WII_LOGI_MICROPHONE_ENABLE, /*def=*/false));
|
|
}
|
|
|
|
const bool disc_based_games_boot_to_wii_menu = Libretro::GetOption<bool>(Libretro::Options::core::DISC_BASED_GAMES_BOOT_TO_WII_MENU, false);
|
|
std::unique_ptr<BootParameters> boot_params = BootParameters::GenerateFromFile(normalized_game_paths);
|
|
|
|
if (disc_based_games_boot_to_wii_menu)
|
|
{
|
|
bool is_disc_title = false;
|
|
if (!normalized_game_paths.empty())
|
|
{
|
|
auto volume = DiscIO::CreateDisc(normalized_game_paths.front());
|
|
if (volume && (volume->GetVolumeType() == DiscIO::Platform::WiiDisc || volume->GetVolumeType() == DiscIO::Platform::GameCubeDisc))
|
|
is_disc_title = true;
|
|
else
|
|
WARN_LOG_FMT(BOOT, "'Disc Based Games Boot to Wii System Menu' enabled but content is not a disc based game, ignoring Wii Menu");
|
|
}
|
|
|
|
const std::string wii_menu_tmd_file_name = Common::GetTMDFileName(Titles::SYSTEM_MENU, Common::FromWhichRoot::Configured);
|
|
const bool wii_menu_installed = File::Exists(wii_menu_tmd_file_name);
|
|
if (is_disc_title && !wii_menu_installed)
|
|
WARN_LOG_FMT(BOOT,"'Disc Based Games Boot to Wii System Menu' enabled but Wii Menu not found at save location: {}, booting directly instead", wii_menu_tmd_file_name);
|
|
|
|
if (is_disc_title && wii_menu_installed)
|
|
{
|
|
Config::SetBase(Config::MAIN_DEFAULT_ISO, normalized_game_paths.front());
|
|
boot_params = std::make_unique<BootParameters>(BootParameters::NANDTitle{Titles::SYSTEM_MENU});
|
|
}
|
|
}
|
|
|
|
if (boot_params)
|
|
Libretro::SetRegionFreeFallback(*boot_params);
|
|
|
|
if (!BootManager::BootCore(Core::System::GetInstance(), std::move(boot_params), wsi))
|
|
{
|
|
ERROR_LOG_FMT(BOOT, "Could not boot {}", game->path);
|
|
return false;
|
|
}
|
|
|
|
Libretro::Input::InitStage2();
|
|
|
|
const bool importCheats = Libretro::GetOption<bool>(retroarch_core::CHEATS_IMPORT, true);
|
|
|
|
if (importCheats)
|
|
{
|
|
Libretro::reload_cheats_from_ini();
|
|
Libretro::generate_cht_from_ini(filename_str);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info)
|
|
{
|
|
if (game_type != Libretro::g_gbplayer_subsystem_id)
|
|
{
|
|
ERROR_LOG_FMT(BOOT, "retro_load_game_special: unknown game_type {}", game_type);
|
|
return false;
|
|
}
|
|
if (num_info < 1 || !info)
|
|
{
|
|
ERROR_LOG_FMT(BOOT, "retro_load_game_special: no content info");
|
|
return false;
|
|
}
|
|
|
|
const retro_game_info* gba_slot = (num_info >= 2 && info[0].path && *info[0].path) ? &info[0] : nullptr;
|
|
|
|
const retro_game_info* gc_slot = (info[1].path && *info[1].path) ? &info[1] : nullptr;
|
|
|
|
if (!gc_slot)
|
|
{
|
|
ERROR_LOG_FMT(BOOT, "GBPlayer: no GC disc path provided in slot 0");
|
|
return false;
|
|
}
|
|
|
|
INFO_LOG_FMT(BOOT, "GBPlayer: GC disc = '{}'", gc_slot->path);
|
|
if (gba_slot)
|
|
{
|
|
Libretro::GBPlayer_rom_path = gba_slot->path;
|
|
INFO_LOG_FMT(BOOT, "GBPlayer: ROM = '{}'", gba_slot->path);
|
|
}
|
|
else
|
|
{
|
|
Libretro::GBPlayer_rom_path.clear();
|
|
INFO_LOG_FMT(BOOT, "GBPlayer: no ROM");
|
|
}
|
|
|
|
Libretro::GBPlayer_active = true;
|
|
|
|
return retro_load_game(gc_slot);
|
|
}
|
|
|
|
void retro_unload_game(void)
|
|
{
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
/* the video backend's teardown uses the context */
|
|
Libretro::Video::D3D11Lock();
|
|
if (Core::IsRunning(system))
|
|
{
|
|
Libretro::FinishFieldInFlight();
|
|
Libretro::Video::GpuThreadStop();
|
|
Core::Stop(system);
|
|
#if defined(__LIBUSB__)
|
|
system.ShutdownUSBScanner();
|
|
#endif
|
|
|
|
Core::Shutdown(system);
|
|
}
|
|
|
|
if (!g_context_status.IsDestroyed() && g_video_backend)
|
|
g_video_backend->Shutdown();
|
|
Libretro::Video::D3D11Unlock();
|
|
|
|
// these are disabled in Shutdown on fullscreen/window toggle
|
|
system.GetFifo().Shutdown();
|
|
|
|
// Rest of shutdown
|
|
g_context_status.MarkUnitialized();
|
|
Libretro::Input::Shutdown();
|
|
Libretro::VR::Shutdown();
|
|
Libretro::Log::Shutdown();
|
|
UICommon::ShutdownControllers();
|
|
UICommon::Shutdown();
|
|
|
|
if (!system.IsDualCoreMode())
|
|
{
|
|
Core::SingleCorePostRunShutdown();
|
|
|
|
// otherwise it is still called CPU-GPU Thread
|
|
Common::SetCurrentThreadName("retroarch");
|
|
}
|
|
|
|
Core::UndeclareAsCPUThread();
|
|
Core::UndeclareAsGPUThread();
|
|
Libretro::ResetDiskControlState();
|
|
}
|
|
|
|
namespace Libretro
|
|
{
|
|
// Disk swapping
|
|
static bool retro_set_eject_state(bool ejected)
|
|
{
|
|
if (eject_state == ejected)
|
|
return false;
|
|
|
|
eject_state = ejected;
|
|
|
|
Libretro::FinishFieldInFlight();
|
|
Core::RunOnCPUThread(Core::System::GetInstance(), [ejected] {
|
|
auto& system = Core::System::GetInstance();
|
|
Core::CPUThreadGuard guard{system};
|
|
|
|
if (ejected)
|
|
system.GetDVDInterface().EjectDisc(guard, DVD::EjectCause::User);
|
|
else if (disk_index < disk_paths.size())
|
|
{
|
|
const std::string path = Libretro::VFile::NormalizePath(disk_paths[disk_index]);
|
|
system.GetDVDInterface().ChangeDisc(guard, path);
|
|
}
|
|
}, true); // wait_for_completion = true
|
|
Libretro::ResumeAfterField();
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool retro_get_eject_state()
|
|
{
|
|
return eject_state;
|
|
}
|
|
|
|
static unsigned retro_get_image_index()
|
|
{
|
|
return disk_index;
|
|
}
|
|
|
|
static bool retro_set_image_index(unsigned index)
|
|
{
|
|
if (eject_state)
|
|
disk_index = index;
|
|
|
|
return eject_state;
|
|
}
|
|
|
|
static unsigned retro_get_num_images()
|
|
{
|
|
return (unsigned)disk_paths.size();
|
|
}
|
|
|
|
static bool retro_add_image_index()
|
|
{
|
|
disk_paths.push_back("");
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool retro_replace_image_index(unsigned index, const struct retro_game_info* info)
|
|
{
|
|
if (index >= disk_paths.size())
|
|
return false;
|
|
|
|
if (!info->path)
|
|
{
|
|
disk_paths.erase(disk_paths.begin() + index);
|
|
if (!disk_paths.size())
|
|
disk_index = -1;
|
|
else if (disk_index > index)
|
|
disk_index--;
|
|
}
|
|
else
|
|
disk_paths[index] = info->path;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool RETRO_CALLCONV retro_set_initial_image(unsigned index, const char* path)
|
|
{
|
|
if (index >= disk_paths.size())
|
|
index = 0;
|
|
|
|
disk_index = index;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool RETRO_CALLCONV retro_get_image_path(unsigned index, char* path, size_t len)
|
|
{
|
|
if (index >= disk_paths.size())
|
|
return false;
|
|
|
|
if (disk_paths[index].empty())
|
|
return false;
|
|
|
|
strncpy(path, disk_paths[index].c_str(), len);
|
|
return true;
|
|
}
|
|
static bool RETRO_CALLCONV retro_get_image_label(unsigned index, char* label, size_t len)
|
|
{
|
|
if (index >= disk_paths.size())
|
|
return false;
|
|
|
|
if (disk_paths[index].empty())
|
|
return false;
|
|
|
|
strncpy(label, disk_paths[index].c_str(), len);
|
|
return true;
|
|
}
|
|
|
|
static void InitDiskControlInterface()
|
|
{
|
|
static retro_disk_control_ext_callback disk_control = {
|
|
retro_set_eject_state,
|
|
retro_get_eject_state,
|
|
retro_get_image_index,
|
|
retro_set_image_index,
|
|
retro_get_num_images,
|
|
retro_replace_image_index,
|
|
retro_add_image_index,
|
|
retro_set_initial_image,
|
|
retro_get_image_path,
|
|
retro_get_image_label,
|
|
};
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &disk_control);
|
|
}
|
|
} // namespace Libretro
|