Cxbx-Reloaded/src/common/Settings.cpp

1050 lines
39 KiB
C++

// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// ******************************************************************
// *
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them 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.
// *
// * This program 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 recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2018 wutno (#/g/punk - Rizon)
// * (c) 2018 RadWolfie
// * (c) 2019 ergo720
// *
// * All rights reserved
// *
// ******************************************************************
#define LOG_PREFIX CXBXR_MODULE::CXBXR
#include "Settings.hpp"
#include "core\kernel\support\Emu.h"
#include "EmuShared.h"
#include <filesystem>
#include "common\input\layout_xbox_device.h"
#include <fstream>
#include "common/util/cliConfig.hpp"
// TODO: Implement Qt support when real CPU emulation is available.
#ifndef QT_VERSION // NOTE: Non-Qt will be using current directory for data
#include <ShlObj.h> // For SHGetSpecialFolderPath and CSIDL_APPDATA
#else
static_assert(false, "Please implement support for cross-platform's user profile data.");
#include <QDir> // for create directory
#include <QFile> // for check file existance
#include <QStandardPaths> // for cross-platform's user profile support
#endif
// Individual library version
uint16_t g_LibVersion_D3D8 = 0;
uint16_t g_LibVersion_DSOUND = 0;
// NOTE: Update settings_version when conversion to setting's structure is required.
// UPDATE: When settings are removed, use "if (use false && settings_version < {next_version}) {" statement
// until existing settings require replacement or conversion. next_version input is a hardcode number.
// Settings version 10 and later should consider as not backward compatible.
// TODO: Add read-only state when using an older build and add a notification for will not be able save to file.
// The sooner we do this, the better before version upgrade.
///////////////////////////
// * History:
// * 2: (RadWolfie), initial version
// * 3: (ergo720), added logging settings
// * 4: (LukeUsher), added network settings
// * 5: (ergo720), added new input gui settings and revision to core
// * 6: (RadWolfie), added loader executable member to core, only for clean up loader expertimental setting
// * 7: (RadWolfie), fix allowAdminPrivilege not align with other boolean members
// * 8: (ergo720), added general input settings
// * 9: (LukeUsher), replaced HardwareYUV with MaintainAspect
///////////////////////////
const unsigned int settings_version = 9;
Settings* g_Settings = nullptr;
#define szSettings_setup_error "ERROR: Either setup have a problem or do not have write permission to directory."
#define szSettings_init_error "ERROR: Unable to initialize Settings class."
#define szSettings_save_user_option_message "Use Cxbx-Reloaded in Portable Mode?" \
"\n\nClicking 'Yes' will save your setting in the current directory, rather than your User Profile."
#define szSettings_settings_file "/settings.ini"
#define szSettings_cxbx_reloaded_directory "/Cxbx-Reloaded"
static const char* section_gui = "gui";
static struct {
const char* CxbxDebugMode = "CxbxDebugMode";
const char* CxbxDebugLogFile = "CxbxDebugLogFile";
const char* RecentXbeFiles = "RecentXbeFiles";
const char* DataStorageToggle = "DataStorageToggle";
const char* DataCustomLocation = "DataCustomLocation";
const char* IgnoreInvalidXbeSig = "IgnoreInvalidXbeSig";
const char *IgnoreInvalidXbeSec = "IgnoreInvalidXbeSec";
const char* ConsoleTypeToggle = "ConsoleTypeToggle";
} sect_gui_keys;
static const char* section_core = "core";
static struct {
const char* Revision = "Revision";
const char* FlagsLLE = "FlagsLLE";
const char* KrnlDebugMode = "KrnlDebugMode";
const char* KrnlDebugLogFile = "KrnlDebugLogFile";
const char* AllowAdminPrivilege = "AllowAdminPrivilege";
const char* LoggedModules = "LoggedModules";
const char* LogLevel = "LogLevel";
const char* LogPopupTestCase = "LogPopupTestCase";
} sect_core_keys;
static const char* section_video = "video";
static struct {
const char* VideoResolution = "VideoResolution";
const char* adapter = "adapter";
const char* Direct3DDevice = "Direct3DDevice";
const char* VSync = "VSync";
const char* FullScreen = "FullScreen";
const char* MaintainAspect = "MaintainAspect";
const char* RenderResolution = "RenderResolution";
} sect_video_keys;
static const char* section_overlay = "overlay";
static struct {
const char* build_hash = "Build Hash";
const char* FPS = "FPS";
const char* hle_lle_stats = "HLE/LLE Stats";
const char* title_name = "Title Name";
const char* file_name = "File Name";
} sect_overlay_keys;
static const char* section_audio = "audio";
static struct {
const char* adapter = "adapter";
const char* adapter_value = "%08X %04X %04X %02X%02X %02X%02X%02X%02X%02X%02X";
const char* codec_pcm = "PCM";
const char* codec_xadpcm = "XADPCM";
const char* codec_unknown = "UnknownCodec";
const char* mute_on_unfocus = "MuteOnUnfocus";
} sect_audio_keys;
static const char* section_network = "network";
static struct {
const char* adapter_name = "adapter_name";
} sect_network_keys;
static const char *section_input_general = "input-general";
static struct {
const char *mo_axis_range = "MouseAxisRange";
const char *mo_wheel_range = "MouseWheelRange";
const char *ignore_kbmo_unfocus = "IgnoreKbMoUnfocus";
} sect_input_general;
static const char* section_controller_dinput = "controller-dinput";
static const char* section_controller_port = "controller-port";
static const char* section_input_port = "input-port-";
static struct {
const char* type = "Type";
const char* device = "DeviceName";
const char* config = "ProfileName";
const char *top_slot = "TopSlot";
const char *bottom_slot = "BottomSlot";
} sect_input_port;
static const char* section_input_profiles = "input-profile-";
static struct {
const char* type = "Type";
const char* config = "ProfileName";
const char* device = "DeviceName";
const char* control = "%s";
} sect_input_profiles;
static const char* section_hack = "hack";
static struct {
const char* DisablePixelShaders = "DisablePixelShaders";
const char* UseAllCores = "UseAllCores";
const char* SkipRdtscPatching = "SkipRdtscPatching";
} sect_hack_keys;
std::string GenerateExecDirectoryStr()
{
std::string exec_path;
(void)cli_config::GetValue(cli_config::exec, &exec_path);
return exec_path.substr(0, exec_path.find_last_of("\\/"));
}
// NOTE: This function will be only have Qt support, std::filesystem doesn't have generic support.
// Plus appending support for each OSes are not worthy to work on.
std::string GenerateUserProfileDirectoryStr()
{
// ========================================================
// TODO: Use QT's QDir and QFile classes for cross-platform
// with QStandardPaths::GenericDataLocation for generic User Profile location.
// NOTE: LibRetro compile build will not have user profile option support.
// ========================================================
char folderOption[MAX_PATH];
std::string genDirectory;
// TODO: Use QDir and QStandardPaths::GenericDataLocation for get user profile directory to support cross-platform
BOOL bRet = SHGetSpecialFolderPathA(NULL, folderOption, CSIDL_APPDATA, TRUE); // NOTE: Windows only support
if (!bRet) {
return "";
}
genDirectory = folderOption;
genDirectory.append(szSettings_cxbx_reloaded_directory);
return genDirectory;
}
std::string TrimQuoteFromString(const char* data)
{
// Safeguard before continue on.
if (data == nullptr) {
return "";
}
std::string trim_str = data;
size_t len = trim_str.size();
if (len > 0) {
// New method, in order to support spaces inside DeviceName value.
if (data[0] == '"') {
len--; // Let's decrement since it also can act as offset too.
if (trim_str.at(len) == '"') {
len--;
}
trim_str = trim_str.substr(1, len);
}
// Old method, don't do anything special.
}
return trim_str;
}
#define AppendQuoteToString(d) "\"" + std::string(d) + "\""
bool Settings::Init()
{
m_si.SetMultiKey(true);
bool bRet = LoadUserConfig();
// Enter setup installer process
if (!bRet) {
std::string setupFile;
m_gui.DataStorageToggle = SetupFile(setupFile);
if (m_gui.DataStorageToggle == CXBX_DATA_INVALID) {
return false;
}
// Call LoadConfig, this will load the config, applying defaults for any missing fields
bRet = LoadConfig();
if (!bRet) {
PopupError(nullptr, szSettings_setup_error);
return false;
}
bRet = Save(setupFile);
}
return bRet;
}
bool Settings::LoadUserConfig()
{
std::string fileSearch;
m_gui.DataStorageToggle = FindSettingsLocation(fileSearch);
if (m_gui.DataStorageToggle == CXBX_DATA_INVALID) {
return false;
}
return LoadFile(fileSearch);
}
bool Settings::LoadFile(std::string file_path)
{
std::list<CSimpleIniA::Entry> si_list;
std::list<CSimpleIniA::Entry>::iterator si_list_iterator;
SI_Error siError = m_si.LoadFile(file_path.c_str());
if (siError != SI_OK) {
return false;
}
m_file_path = file_path;
return LoadConfig();
}
bool Settings::LoadConfig()
{
bool bRet;
const char* si_data;
int iStatus;
std::list<CSimpleIniA::Entry> si_list;
std::list<CSimpleIniA::Entry>::iterator si_list_iterator;
std::string trim_str;
// ==== GUI Begin ===========
m_gui.CxbxDebugMode = (DebugMode)m_si.GetLongValue(section_gui, sect_gui_keys.CxbxDebugMode, /*Default=*/DM_NONE);
si_data = m_si.GetValue(section_gui, sect_gui_keys.CxbxDebugLogFile, /*Default=*/nullptr);
// Fallback to null string if value is empty or contain bigger string.
if (si_data == nullptr || std::strlen(si_data) >= MAX_PATH) {
m_gui.szCxbxDebugFile = "";
}
else {
m_gui.szCxbxDebugFile = si_data;
}
m_gui.DataStorageToggle = m_si.GetLongValue(section_gui, sect_gui_keys.DataStorageToggle, /*Default=*/m_gui.DataStorageToggle);
si_data = m_si.GetValue(section_gui, sect_gui_keys.DataCustomLocation, /*Default=*/nullptr);
// Fallback to null string if value is empty or contains a bigger string.
if (si_data == nullptr || std::strlen(si_data) >= MAX_PATH) {
m_gui.szCustomLocation = "";
}
else {
m_gui.szCustomLocation = si_data;
}
// GUI - Recent xbe file paths
unsigned int index = 0;
unsigned int list_max = std::size(m_gui.szRecentXbeFiles);
bRet = m_si.GetAllValues(section_gui, sect_gui_keys.RecentXbeFiles, si_list);/*Default=empty list*/
if (bRet) {
si_list_iterator = si_list.begin();
for (si_list_iterator; si_list_iterator != si_list.end(); si_list_iterator++) {
// Exit loop when the list has reached the limit.
if (index == list_max) {
break;
}
// Do not accept any file path greater or equal to MAX_PATH
if (std::strlen(si_list_iterator->pItem) >= MAX_PATH) {
continue;
}
m_gui.szRecentXbeFiles[index] = si_list_iterator->pItem;
index++;
}
}
// Set all or remaining recent xbe file paths to null string
while (index < list_max) {
m_gui.szRecentXbeFiles[index] = "";
index++;
}
m_gui.bIgnoreInvalidXbeSig = m_si.GetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSig, /*Default=*/false);
m_gui.bIgnoreInvalidXbeSec = m_si.GetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSec, /*Default=*/false);
m_gui.ConsoleTypeToggle = (EMU_CONSOLE_TYPE)m_si.GetLongValue(section_gui, sect_gui_keys.ConsoleTypeToggle, /*Default=*/EMU_CONSOLE_TYPE_AUTO);
// ==== GUI End =============
// ==== Core Begin ==========
m_core.Revision = m_si.GetLongValue(section_core, sect_core_keys.Revision, 4);
m_core.FlagsLLE = m_si.GetLongValue(section_core, sect_core_keys.FlagsLLE, /*Default=*/LLE_NONE);
m_core.KrnlDebugMode = (DebugMode)m_si.GetLongValue(section_core, sect_core_keys.KrnlDebugMode, /*Default=*/DM_NONE);
si_data = m_si.GetValue(section_core, sect_core_keys.KrnlDebugLogFile, /*Default=*/nullptr);
// Fallback to null string if value is empty or contain bigger string.
if (si_data == nullptr || std::strlen(si_data) >= MAX_PATH) {
m_core.szKrnlDebug[0] = '\0';
}
else {
std::strncpy(m_core.szKrnlDebug, si_data, MAX_PATH);
}
m_core.allowAdminPrivilege = m_si.GetBoolValue(section_core, sect_core_keys.AllowAdminPrivilege, /*Default=*/false);
m_core.LogLevel = m_si.GetLongValue(section_core, sect_core_keys.LogLevel, 1);
si_list.clear();
index = 0;
list_max = std::size(m_core.LoggedModules);
bRet = m_si.GetAllValues(section_core, sect_core_keys.LoggedModules, si_list);
if (bRet) {
si_list_iterator = si_list.begin();
for (si_list_iterator; si_list_iterator != si_list.end(); si_list_iterator++) {
// Exit loop when the list has reached the limit.
if (index == list_max) {
break;
}
if (std::strncmp(si_list_iterator->pItem, "0x", 2) == 0) {
si_list_iterator->pItem += 2;
}
m_core.LoggedModules[index] = std::strtoul(si_list_iterator->pItem, nullptr, 16);
index++;
}
}
while (index < list_max) {
m_core.LoggedModules[index] = 0;
index++;
}
m_core.bLogPopupTestCase = m_si.GetBoolValue(section_core, sect_core_keys.LogPopupTestCase, /*Default=*/true);
// ==== Core End ============
// Delete/update legacy configs from previous revisions
RemoveLegacyConfigs(m_core.Revision);
m_core.Revision = settings_version;
// ==== Hack Begin ==========
m_hacks.DisablePixelShaders = m_si.GetBoolValue(section_hack, sect_hack_keys.DisablePixelShaders, /*Default=*/false);
m_hacks.UseAllCores = m_si.GetBoolValue(section_hack, sect_hack_keys.UseAllCores, /*Default=*/false);
m_hacks.SkipRdtscPatching = m_si.GetBoolValue(section_hack, sect_hack_keys.SkipRdtscPatching, /*Default=*/false);
// ==== Hack End ============
// ==== Video Begin =========
// Video - Resolution config
si_data = m_si.GetValue(section_video, sect_video_keys.VideoResolution, /*Default=*/nullptr);
// Fallback to null string if value is empty or contain bigger string.
if (si_data == nullptr || std::strlen(si_data) >= std::size(m_video.szVideoResolution)) {
m_video.szVideoResolution[0] = '\0';
}
else {
std::strncpy(m_video.szVideoResolution, si_data, std::size(m_video.szVideoResolution));
}
m_video.adapter = m_si.GetLongValue(section_video, sect_video_keys.adapter, /*Default=*/0);
m_video.direct3DDevice = m_si.GetLongValue(section_video, sect_video_keys.Direct3DDevice, /*Default=*/0);
m_video.bVSync = m_si.GetBoolValue(section_video, sect_video_keys.VSync, /*Default=*/false);
m_video.bFullScreen = m_si.GetBoolValue(section_video, sect_video_keys.FullScreen, /*Default=*/false);
m_video.bMaintainAspect = m_si.GetBoolValue(section_video, sect_video_keys.MaintainAspect, /*Default=*/true);
m_video.renderScaleFactor = m_si.GetLongValue(section_video, sect_video_keys.RenderResolution, /*Default=*/1);
// ==== Video End ===========
// ==== Audio Begin =========
// Audio - Adapter config
si_data = m_si.GetValue(section_audio, sect_audio_keys.adapter, /*Default=*/nullptr);
if (si_data == nullptr) {
// Default to primary audio device
m_audio.adapterGUID = { 0 };
}
else {
iStatus = std::sscanf(si_data, sect_audio_keys.adapter_value,
&m_audio.adapterGUID.Data1, &m_audio.adapterGUID.Data2, &m_audio.adapterGUID.Data3,
&m_audio.adapterGUID.Data4[0], &m_audio.adapterGUID.Data4[1], &m_audio.adapterGUID.Data4[2], &m_audio.adapterGUID.Data4[3],
&m_audio.adapterGUID.Data4[4], &m_audio.adapterGUID.Data4[5], &m_audio.adapterGUID.Data4[6], &m_audio.adapterGUID.Data4[7]);
// Fallback to primary audio device if file contain invalid value.
if (iStatus != 11 /*= total arguments*/) {
m_audio.adapterGUID = { 0 };
}
}
m_audio.codec_pcm = m_si.GetBoolValue(section_audio, sect_audio_keys.codec_pcm, /*Default=*/true, nullptr);
m_audio.codec_xadpcm = m_si.GetBoolValue(section_audio, sect_audio_keys.codec_xadpcm, /*Default=*/true, nullptr);
m_audio.codec_unknown = m_si.GetBoolValue(section_audio, sect_audio_keys.codec_unknown, /*Default=*/true, nullptr);
m_audio.mute_on_unfocus = m_si.GetBoolValue(section_audio, sect_audio_keys.mute_on_unfocus, /*Default=*/true, nullptr);
// ==== Audio End ===========
// ==== Network Begin =======
si_data = m_si.GetValue(section_network, sect_network_keys.adapter_name, /*Default=*/nullptr);
// Fallback to null string if value is empty or contains a bigger string.
if (si_data == nullptr || std::strlen(si_data) >= std::size(m_network.adapter_name)) {
m_network.adapter_name[0] = '\0';
} else {
std::strncpy(m_network.adapter_name, si_data, std::size(m_network.adapter_name));
}
// ==== Network End =========
// ==== Input General Begin ====
m_input_general.MoAxisRange = m_si.GetLongValue(section_input_general, sect_input_general.mo_axis_range, MO_AXIS_DEFAULT_RANGE);
m_input_general.MoWheelRange = m_si.GetLongValue(section_input_general, sect_input_general.mo_wheel_range, MO_WHEEL_DEFAULT_RANGE);
m_input_general.IgnoreKbMoUnfocus = m_si.GetBoolValue(section_input_general, sect_input_general.ignore_kbmo_unfocus, true);
// ==== Input General End ==============
// ==== Input Port Begin ====
for (int port_num = 0; port_num < 4; port_num++) {
for (int slot = 0; slot < XBOX_CTRL_NUM_SLOTS; ++slot) {
m_input_port[port_num].Type = to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID);
m_input_port[port_num].SlotType[slot] = to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID);
std::string current_section = std::string(section_input_port) + std::to_string(port_num);
int ret = m_si.GetLongValue(current_section.c_str(), sect_input_port.type, -2);
if (ret == -2) {
continue;
}
m_input_port[port_num].Type = ret;
m_input_port[port_num].DeviceName = m_si.GetValue(current_section.c_str(), sect_input_port.device);
m_input_port[port_num].ProfileName = TrimQuoteFromString(m_si.GetValue(current_section.c_str(), sect_input_port.config));
ret = m_si.GetLongValue(current_section.c_str(), slot == 0 ? sect_input_port.top_slot : sect_input_port.bottom_slot, -2);
if (ret == -2) {
continue;
}
m_input_port[port_num].SlotType[slot] = ret;
}
}
// ==== Input Port End ==============
// ==== Input Profile Begin ====
std::array<std::vector<std::string>, to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX)> control_names;
for (int device = 0; device < to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX); device++) {
if (dev_num_buttons[device] == 0) {
continue;
}
const auto &lambda = [&control_names, &device](int num_buttons, const char *const ctrl_names[]) {
for (int i = 0; i < num_buttons; i++) {
char control_name[XBOX_BUTTON_NAME_LENGTH];
std::sprintf(control_name, sect_input_profiles.control, ctrl_names[i]);
control_names[device].push_back(control_name);
}
};
switch (device)
{
case to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE):
case to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_S):
case to_underlying(XBOX_INPUT_DEVICE::ARCADE_STICK):
lambda(dev_num_buttons[device], button_xbox_ctrl_names);
break;
case to_underlying(XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER):
lambda(dev_num_buttons[device], button_sbc_names);
break;
case to_underlying(XBOX_INPUT_DEVICE::LIGHTGUN):
lambda(dev_num_buttons[device], button_lightgun_names);
break;
}
}
index = 0;
while (true) {
std::string current_section = std::string(section_input_profiles) + std::to_string(index);
if (m_si.GetSectionSize(current_section.c_str()) == -1) {
break;
}
s_input_profiles local_profile;
local_profile.Type = m_si.GetLongValue(current_section.c_str(), sect_input_profiles.type);
local_profile.ProfileName = TrimQuoteFromString(m_si.GetValue(current_section.c_str(), sect_input_profiles.config));
local_profile.DeviceName = m_si.GetValue(current_section.c_str(), sect_input_profiles.device);
for (int vec_control_index = 0; vec_control_index < dev_num_buttons[local_profile.Type]; vec_control_index++) {
local_profile.ControlList.push_back(m_si.GetValue(current_section.c_str(),
control_names[local_profile.Type][vec_control_index].c_str()));
}
m_input_profiles[local_profile.Type].push_back(std::move(local_profile));
index++;
}
// ==== Input Profile End ======
// ==== Overlay Begin =========
m_overlay.build_hash = m_si.GetBoolValue(section_overlay, sect_overlay_keys.build_hash, false);
m_overlay.fps = m_si.GetBoolValue(section_overlay, sect_overlay_keys.FPS, false);
m_overlay.hle_lle_stats = m_si.GetBoolValue(section_overlay, sect_overlay_keys.hle_lle_stats, false);
m_overlay.title_name = m_si.GetBoolValue(section_overlay, sect_overlay_keys.title_name, false);
m_overlay.file_name = m_si.GetBoolValue(section_overlay, sect_overlay_keys.file_name, false);
// ==== Overlay End ===========
return true;
}
bool Settings::Save(std::string file_path)
{
if (m_file_path.empty() && file_path.empty()) {
return false;
}
// Minimal need is 25, 0x37 for GUID.
char si_value[64];
std::string quote_str;
// ==== GUI Begin ===========
m_si.SetLongValue(section_gui, sect_gui_keys.CxbxDebugMode, m_gui.CxbxDebugMode, nullptr, true, true);
m_si.SetValue(section_gui, sect_gui_keys.CxbxDebugLogFile, m_gui.szCxbxDebugFile.c_str(), nullptr, true);
m_si.SetLongValue(section_gui, sect_gui_keys.DataStorageToggle, m_gui.DataStorageToggle, nullptr, true, true);
m_si.SetValue(section_gui, sect_gui_keys.DataCustomLocation, m_gui.szCustomLocation.c_str(), nullptr, true);
// First force overwrite existing value
m_si.SetValue(section_gui, sect_gui_keys.RecentXbeFiles, m_gui.szRecentXbeFiles[0].c_str(), nullptr, true);
// Then append recent xbe file strings
for (unsigned int i = 1; i < 10; i++) {
m_si.SetValue(section_gui, sect_gui_keys.RecentXbeFiles, m_gui.szRecentXbeFiles[i].c_str(), nullptr, false);
}
m_si.SetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSig, m_gui.bIgnoreInvalidXbeSig, nullptr, true);
m_si.SetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSec, m_gui.bIgnoreInvalidXbeSec, nullptr, true);
m_si.SetLongValue(section_gui, sect_gui_keys.ConsoleTypeToggle, m_gui.ConsoleTypeToggle, nullptr, true, true);
// ==== GUI End =============
// ==== Core Begin ==========
m_si.SetLongValue(section_core, sect_core_keys.Revision, m_core.Revision, nullptr, false, true);
m_si.SetLongValue(section_core, sect_core_keys.FlagsLLE, m_core.FlagsLLE, nullptr, true, true);
m_si.SetLongValue(section_core, sect_core_keys.KrnlDebugMode, m_core.KrnlDebugMode, nullptr, true, true);
m_si.SetValue(section_core, sect_core_keys.KrnlDebugLogFile, m_core.szKrnlDebug, nullptr, true);
m_si.SetBoolValue(section_core, sect_core_keys.AllowAdminPrivilege, m_core.allowAdminPrivilege, nullptr, true);
m_si.SetLongValue(section_core, sect_core_keys.LogLevel, m_core.LogLevel, nullptr, false, true);
std::stringstream stream;
stream << "0x" << std::hex << m_core.LoggedModules[0];
m_si.SetValue(section_core, sect_core_keys.LoggedModules, stream.str().c_str(), nullptr, true);
for (int i = 1; i < NUM_INTEGERS_LOG; i++) {
stream.str("");
stream << "0x" << std::hex << m_core.LoggedModules[i];
m_si.SetValue(section_core, sect_core_keys.LoggedModules, stream.str().c_str(), nullptr, false);
}
m_si.SetBoolValue(section_core, sect_core_keys.LogPopupTestCase, m_core.bLogPopupTestCase, nullptr, true);
// ==== Core End ============
// ==== Video Begin =========
m_si.SetValue(section_video, sect_video_keys.VideoResolution, m_video.szVideoResolution, nullptr, true);
m_si.SetLongValue(section_video, sect_video_keys.adapter, m_video.adapter, nullptr, true, true);
m_si.SetLongValue(section_video, sect_video_keys.Direct3DDevice, m_video.direct3DDevice, nullptr, true, true);
m_si.SetBoolValue(section_video, sect_video_keys.VSync, m_video.bVSync, nullptr, true);
m_si.SetBoolValue(section_video, sect_video_keys.FullScreen, m_video.bFullScreen, nullptr, true);
m_si.SetBoolValue(section_video, sect_video_keys.MaintainAspect, m_video.bMaintainAspect, nullptr, true);
m_si.SetLongValue(section_video, sect_video_keys.RenderResolution, m_video.renderScaleFactor, nullptr, false, true);
// ==== Video End ===========
// ==== Audio Begin =========
// Audio - Adapter config
std::sprintf(si_value, sect_audio_keys.adapter_value,
m_audio.adapterGUID.Data1, m_audio.adapterGUID.Data2, m_audio.adapterGUID.Data3,
m_audio.adapterGUID.Data4[0], m_audio.adapterGUID.Data4[1], m_audio.adapterGUID.Data4[2], m_audio.adapterGUID.Data4[3],
m_audio.adapterGUID.Data4[4], m_audio.adapterGUID.Data4[5], m_audio.adapterGUID.Data4[6], m_audio.adapterGUID.Data4[7]);
m_si.SetValue(section_audio, sect_audio_keys.adapter, si_value, nullptr, true);
m_si.SetBoolValue(section_audio, sect_audio_keys.codec_pcm, m_audio.codec_pcm, nullptr, true);
m_si.SetBoolValue(section_audio, sect_audio_keys.codec_xadpcm, m_audio.codec_xadpcm, nullptr, true);
m_si.SetBoolValue(section_audio, sect_audio_keys.codec_unknown, m_audio.codec_unknown, nullptr, true);
m_si.SetBoolValue(section_audio, sect_audio_keys.mute_on_unfocus, m_audio.mute_on_unfocus, nullptr, true);
// ==== Audio End ===========
// ==== Network Begin =======
m_si.SetValue(section_network, sect_network_keys.adapter_name, m_network.adapter_name, nullptr, true);
// ==== Network End =========
// ==== Input General Begin =======
m_si.SetLongValue(section_input_general, sect_input_general.mo_axis_range, m_input_general.MoAxisRange, nullptr, false, true);
m_si.SetLongValue(section_input_general, sect_input_general.mo_wheel_range, m_input_general.MoWheelRange, nullptr, false, true);
m_si.SetBoolValue(section_input_general, sect_input_general.ignore_kbmo_unfocus, m_input_general.IgnoreKbMoUnfocus, nullptr, true);
// ==== Input General End =========
// ==== Input Port Begin ====
for (int port_num = 0; port_num < 4; port_num++) {
std::string current_section = std::string(section_input_port) + std::to_string(port_num);
std::string quoted_prf_str = m_input_port[port_num].ProfileName.insert(0, "\"");
quoted_prf_str += "\"";
m_si.SetLongValue(current_section.c_str(), sect_input_port.type, m_input_port[port_num].Type, nullptr, false, true);
m_si.SetValue(current_section.c_str(), sect_input_port.device, m_input_port[port_num].DeviceName.c_str(), nullptr, true);
m_si.SetValue(current_section.c_str(), sect_input_port.config, quoted_prf_str.c_str(), nullptr, true);
m_si.SetLongValue(current_section.c_str(), sect_input_port.top_slot, m_input_port[port_num].SlotType[SLOT_TOP], nullptr, false, true);
m_si.SetLongValue(current_section.c_str(), sect_input_port.bottom_slot, m_input_port[port_num].SlotType[SLOT_BOTTOM], nullptr, false, true);
}
// ==== Input Port End ==============
// ==== Input Profile Begin ====
std::array<std::vector<std::string>, to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX)> control_names;
for (int device = 0; device < to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX); device++) {
if (dev_num_buttons[device] == 0) {
continue;
}
const auto &lambda = [&control_names, &device](int num_buttons, const char *const ctrl_names[]) {
for (int i = 0; i < num_buttons; i++) {
char control_name[XBOX_BUTTON_NAME_LENGTH];
std::sprintf(control_name, sect_input_profiles.control, ctrl_names[i]);
control_names[device].push_back(control_name);
}
};
switch (device)
{
case to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE):
case to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_S):
case to_underlying(XBOX_INPUT_DEVICE::ARCADE_STICK):
lambda(dev_num_buttons[device], button_xbox_ctrl_names);
break;
case to_underlying(XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER):
lambda(dev_num_buttons[device], button_sbc_names);
break;
case to_underlying(XBOX_INPUT_DEVICE::LIGHTGUN):
lambda(dev_num_buttons[device], button_lightgun_names);
break;
}
}
int profile_num = 0;
for (int i = 0; i < to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX); i++) {
size_t vec_size = m_input_profiles[i].size();
if (vec_size == 0) {
continue;
}
for (unsigned int vec_index = 0; vec_index < vec_size; vec_index++, profile_num++) {
std::string current_section = std::string(section_input_profiles) + std::to_string(profile_num);
std::string quoted_prf_str = m_input_profiles[i][vec_index].ProfileName.insert(0, "\"");
quoted_prf_str += "\"";
m_si.SetLongValue(current_section.c_str(), sect_input_profiles.type, m_input_profiles[i][vec_index].Type, nullptr, false, true);
m_si.SetValue(current_section.c_str(), sect_input_profiles.config, quoted_prf_str.c_str(), nullptr, true);
m_si.SetValue(current_section.c_str(), sect_input_profiles.device, m_input_profiles[i][vec_index].DeviceName.c_str(), nullptr, true);
size_t vec_control_size = m_input_profiles[i][vec_index].ControlList.size();
if (vec_control_size == 0) {
continue;
}
m_si.SetValue(current_section.c_str(), control_names[i][0].c_str(), m_input_profiles[i][vec_index].ControlList[0].c_str(), nullptr, true);
for (unsigned int vec_control_index = 1; vec_control_index < vec_control_size; vec_control_index++) {
m_si.SetValue(current_section.c_str(), control_names[i][vec_control_index].c_str(),
m_input_profiles[i][vec_index].ControlList[vec_control_index].c_str(), nullptr, true);
}
}
}
while (true) {
std::string current_section = std::string(section_input_profiles) + std::to_string(profile_num);
if (m_si.GetSectionSize(current_section.c_str()) == -1) {
break;
}
m_si.Delete(current_section.c_str(), nullptr, true);
profile_num++;
}
// ==== Input Profile End ======
// ==== Overlay Begin =======
m_si.SetBoolValue(section_overlay, sect_overlay_keys.build_hash, m_overlay.build_hash, nullptr, true);
m_si.SetBoolValue(section_overlay, sect_overlay_keys.FPS, m_overlay.fps, nullptr, true);
m_si.SetBoolValue(section_overlay, sect_overlay_keys.hle_lle_stats, m_overlay.hle_lle_stats, nullptr, true);
m_si.SetBoolValue(section_overlay, sect_overlay_keys.title_name, m_overlay.title_name, nullptr, true);
m_si.SetBoolValue(section_overlay, sect_overlay_keys.file_name, m_overlay.file_name, nullptr, true);
// ==== Overlay End =========
// ==== Hack Begin ==========
m_si.SetBoolValue(section_hack, sect_hack_keys.DisablePixelShaders, m_hacks.DisablePixelShaders, nullptr, true);
m_si.SetBoolValue(section_hack, sect_hack_keys.UseAllCores, m_hacks.UseAllCores, nullptr, true);
m_si.SetBoolValue(section_hack, sect_hack_keys.SkipRdtscPatching, m_hacks.SkipRdtscPatching, nullptr, true);
// ==== Hack End ============
SI_Error siError;
if (!file_path.empty()) {
siError = m_si.SaveFile(file_path.c_str(), true);
if (siError == SI_OK) {
m_file_path = file_path;
}
}
else {
siError = m_si.SaveFile(m_file_path.c_str(), true);
}
return (siError == SI_OK);
}
void Settings::Delete()
{
std::filesystem::remove(m_file_path);
}
// Universal update to EmuShared from both standalone kernel, and GUI process.
void Settings::SyncToEmulator()
{
// register Core settings
g_EmuShared->SetCoreSettings(&m_core);
g_EmuShared->SetIsKrnlLogEnabled(m_core.KrnlDebugMode != DebugMode::DM_NONE);
// register Video settings
g_EmuShared->SetVideoSettings(&m_video);
g_EmuShared->SetOverlaySettings(&m_overlay);
// register Audio settings
g_EmuShared->SetAudioSettings(&m_audio);
// register Network settings
g_EmuShared->SetNetworkSettings(&m_network);
// register xbox device input settings
for (int i = 0; i < 4; i++) {
g_EmuShared->SetInputDevTypeSettings(&m_input_port[i].Type, i);
g_EmuShared->SetInputSlotTypeSettings(&m_input_port[i].SlotType[SLOT_TOP], i, SLOT_TOP);
g_EmuShared->SetInputSlotTypeSettings(&m_input_port[i].SlotType[SLOT_BOTTOM], i, SLOT_BOTTOM);
if (m_input_port[i].Type != to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID)) {
g_EmuShared->SetInputDevNameSettings(m_input_port[i].DeviceName.c_str(), i);
if (m_input_port[i].Type < to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX)) {
auto it = std::find_if(m_input_profiles[m_input_port[i].Type].begin(),
m_input_profiles[m_input_port[i].Type].end(), [this, i](const auto &profile) {
if (profile.ProfileName == m_input_port[i].ProfileName) {
return true;
}
return false;
});
if (it != m_input_profiles[m_input_port[i].Type].end()) {
char controls_name[HIGHEST_NUM_BUTTONS][HOST_BUTTON_NAME_LENGTH];
for (int index = 0; index < dev_num_buttons[m_input_port[i].Type]; index++) {
strncpy(controls_name[index], it->ControlList[index].c_str(), 30);
}
g_EmuShared->SetInputBindingsSettings(controls_name, dev_num_buttons[m_input_port[i].Type], i);
}
}
}
}
// register Input general settings
g_EmuShared->SetInputGeneralSettings(&m_input_general);
// register Hacks settings
g_EmuShared->SetHackSettings(&m_hacks);
// register data location setting
g_EmuShared->SetDataLocation(GetDataLocation().c_str());
// reset title mount path
g_EmuShared->SetTitleMountPath("");
}
void verifyDebugFilePath(DebugMode& debug_mode, std::string& file_path)
{
// Prevent using an incorrect path from the registry if the debug folders have been moved
std::string szDebugPath;
std::string szDebugName;
if (debug_mode == DM_FILE) {
if(file_path.size() == 0 || file_path.size() >= MAX_PATH) {
debug_mode = DM_NONE;
}
else {
szDebugName = file_path.substr(file_path.find_last_of("\\/"), std::string::npos);
if(file_path.size() < szDebugName.size()) {
file_path = "";
debug_mode = DM_NONE;
}
else {
szDebugPath = file_path.substr(0, file_path.size() - szDebugName.size());
if(std::filesystem::exists(szDebugPath) == false) {
file_path = "";
debug_mode = DM_NONE;
}
}
}
}
}
void Settings::Verify()
{
std::string szKrnlDebug = m_core.szKrnlDebug; // Temporary placeholder until m_core.szKrnlDebug is replace to std::string.
verifyDebugFilePath(m_gui.CxbxDebugMode, m_gui.szCxbxDebugFile);
verifyDebugFilePath(m_core.KrnlDebugMode, szKrnlDebug);
// Set to null string once if contain invalid path.
if (m_core.szKrnlDebug[0] != '\0' && szKrnlDebug.size() == 0) {
std::memset(m_core.szKrnlDebug, 0, MAX_PATH);
}
}
std::string Settings::GetDataLocation()
{
// Optimization purpose for not require to re-process when toggle state has not changed.
if (m_current_DataStorageToggle == m_gui.DataStorageToggle) {
return m_current_data_location;
}
switch (m_gui.DataStorageToggle) {
default:
#ifdef RETRO_API_VERSION // TODO: Change me to #ifndef QT_VERSION
m_gui.DataStorageToggle = CXBX_DATA_EXECDIR;
#else // Only support for Qt compile build.
m_gui.DataStorageToggle = CXBX_DATA_APPDATA;
// If unknown value, default to CXBX_DATA_APPDATA (below, don't use break)
case CXBX_DATA_APPDATA:
m_current_data_location = GenerateUserProfileDirectoryStr();
break;
#endif
case CXBX_DATA_EXECDIR:
m_current_data_location = GenerateExecDirectoryStr();
break;
case CXBX_DATA_CUSTOM:
m_current_data_location = m_gui.szCustomLocation;
break;
}
m_current_DataStorageToggle = m_gui.DataStorageToggle;
return m_current_data_location;
}
// Detect where settings file is located and return default data mode.
CXBX_DATA Settings::FindSettingsLocation(std::string& file_path_out)
{
std::string fileSearch = GenerateExecDirectoryStr();
CXBX_DATA ret = CXBX_DATA_EXECDIR;
fileSearch.append(szSettings_settings_file);
// Check and see if file exists from portable, current, directory.
if (std::filesystem::exists(fileSearch) == false) {
fileSearch = GenerateUserProfileDirectoryStr();
if (fileSearch.size() == 0) {
return CXBX_DATA_INVALID;
}
CXBX_DATA ret = CXBX_DATA_APPDATA;
fileSearch.append(szSettings_settings_file);
// Check if the user profile directory settings file exists.
if (std::filesystem::exists(fileSearch) == false) {
return CXBX_DATA_INVALID;
}
}
file_path_out = fileSearch;
return ret;
}
// Enter setup installer process
CXBX_DATA Settings::SetupFile(std::string& file_path_out)
{
std::string setupFile;
CXBX_DATA data_ret = CXBX_DATA_INVALID;
#ifdef RETRO_API_VERSION // TODO: Change me to #ifndef QT_VERSION
// Can only have one option without Qt.
setupFile = GenerateExecDirectoryStr();
#else // Only support for Qt compile build.
PopupReturn eRet = PopupQuestion(nullptr, szSettings_save_user_option_message);
if (eRet == PopupReturn::Yes) {
setupFile = GenerateExecDirectoryStr();
data_ret = CXBX_DATA_EXECDIR;
}
else if (eRet == PopupReturn::No) {
setupFile = GenerateUserProfileDirectoryStr();
data_ret = CXBX_DATA_APPDATA;
if (setupFile.size() != 0) {
// Check if data directory exists.
if (!std::filesystem::exists(setupFile)) {
// Then try create data directory.
if (!std::filesystem::create_directory(setupFile)) {
// Unable to create a data directory
data_ret = CXBX_DATA_INVALID;
}
}
}
}
#endif
if (data_ret == CXBX_DATA_INVALID) {
PopupError(nullptr, szSettings_setup_error);
}
else {
setupFile.append(szSettings_settings_file);
// Create the file, that's it. Load the default configuration later on;
std::ofstream createFile(setupFile);
if (createFile.is_open()) {
createFile.close();
}
file_path_out = setupFile;
}
return data_ret;
}
void Settings::RemoveLegacyConfigs(unsigned int CurrentRevision)
{
if (CurrentRevision < 5) {
m_si.Delete(section_controller_dinput, nullptr, true);
m_si.Delete(section_controller_port, nullptr, true);
}
if (CurrentRevision == 5) {
m_si.Delete(section_core, "LoaderExperiment", true);
}
if (CurrentRevision < 8) {
const std::string kb_str = "Keyboard";
for (unsigned port_num = 0; port_num < 4; ++port_num) {
std::string current_section = std::string(section_input_port) + std::to_string(port_num);
std::string device_name = m_si.GetValue(current_section.c_str(), sect_input_port.device, "");
if (device_name.ends_with(kb_str)) {
device_name += "Mouse";
m_si.SetValue(current_section.c_str(), sect_input_port.device, device_name.c_str(), nullptr, true);
}
}
for (unsigned index = 0; ; ++index) {
std::string current_section = std::string(section_input_profiles) + std::to_string(index);
if (m_si.GetSectionSize(current_section.c_str()) == -1) {
break;
}
std::string device_name = m_si.GetValue(current_section.c_str(), sect_input_profiles.device, "");
// NOTE: with C++20, this can be simplified by simply calling device_name.ends_with()
if (device_name.length() >= kb_str.length()) {
if (device_name.compare(device_name.length() - kb_str.length(), kb_str.length(), kb_str) == 0) {
device_name += "Mouse";
m_si.SetValue(current_section.c_str(), sect_input_profiles.device, device_name.c_str(), nullptr, true);
}
}
}
}
if(CurrentRevision < 9) {
m_si.Delete(section_video, "HardwareYUV", true);
}
// see settings_version for details.
if(false && CurrentRevision < 10) {
m_si.Delete(section_core, "LoaderExecutable", true);
}
}