mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-03-30 07:29:43 -06:00
update emulator_settings to latest
This commit is contained in:
parent
a9df50cc9b
commit
1cf22f2484
@ -164,6 +164,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
CLS(Input) \
|
||||
CLS(Tty) \
|
||||
CLS(KeyManager) \
|
||||
CLS(EmuSettings) \
|
||||
CLS(Loader)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
|
||||
@ -132,6 +132,7 @@ enum class Class : u8 {
|
||||
Input, ///< Input emulation
|
||||
Tty, ///< Debug output from emu
|
||||
KeyManager, ///< Key management system
|
||||
EmuSettings, /// Emulator settings system
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <common/path_util.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "emulator_settings.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
@ -29,12 +29,13 @@ struct adl_serializer<std::filesystem::path> {
|
||||
// --------------------
|
||||
void EmulatorSettings::PrintChangedSummary(const std::vector<std::string>& changed) {
|
||||
if (changed.empty()) {
|
||||
std::cout << "[Settings] No game-specific overrides applied\n";
|
||||
LOG_DEBUG(EmuSettings, "[Settings] No game-specific overrides applied");
|
||||
return;
|
||||
}
|
||||
std::cout << "[Settings] Game-specific overrides applied:\n";
|
||||
for (const auto& k : changed)
|
||||
std::cout << " * " << k << "\n";
|
||||
LOG_DEBUG(EmuSettings, "[Settings] Game-specific overrides applied:");
|
||||
for (const auto& k : changed) {
|
||||
LOG_DEBUG(EmuSettings, " * {}", k);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
@ -141,6 +142,17 @@ void EmulatorSettings::SetSysModulesDir(const std::filesystem::path& dir) {
|
||||
m_general.sys_modules_dir.value = dir;
|
||||
}
|
||||
|
||||
std::filesystem::path EmulatorSettings::GetFontsDir() {
|
||||
if (m_general.font_dir.value.empty()) {
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::FontsDir);
|
||||
}
|
||||
return m_general.font_dir.value;
|
||||
}
|
||||
|
||||
void EmulatorSettings::SetFontsDir(const std::filesystem::path& dir) {
|
||||
m_general.font_dir.value = dir;
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Save
|
||||
// --------------------
|
||||
@ -210,13 +222,13 @@ bool EmulatorSettings::Save(const std::string& serial) const {
|
||||
|
||||
std::ofstream out(path);
|
||||
if (!out.is_open()) {
|
||||
std::cerr << "Failed to open file for writing: " << path << std::endl;
|
||||
LOG_ERROR(EmuSettings, "Failed to open file for writing: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
out << std::setw(4) << j;
|
||||
out.flush();
|
||||
if (out.fail()) {
|
||||
std::cerr << "Failed to write settings to: " << path << std::endl;
|
||||
LOG_ERROR(EmuSettings, "Failed to write settings to: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -234,19 +246,19 @@ bool EmulatorSettings::Save(const std::string& serial) const {
|
||||
|
||||
std::ofstream out(path);
|
||||
if (!out.is_open()) {
|
||||
std::cerr << "Failed to open file for writing: " << path << std::endl;
|
||||
LOG_ERROR(EmuSettings, "Failed to open file for writing: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
out << std::setw(4) << j;
|
||||
out.flush();
|
||||
if (out.fail()) {
|
||||
std::cerr << "Failed to write settings to: " << path << std::endl;
|
||||
LOG_ERROR(EmuSettings, "Failed to write settings to: {}", path.string());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error saving settings: " << e.what() << std::endl;
|
||||
LOG_ERROR(EmuSettings, "Error saving settings: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -256,96 +268,145 @@ bool EmulatorSettings::Save(const std::string& serial) const {
|
||||
// --------------------
|
||||
bool EmulatorSettings::Load(const std::string& serial) {
|
||||
try {
|
||||
const std::filesystem::path userDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
const std::filesystem::path configPath = userDir / "config.json";
|
||||
// If serial is empty, load ONLY global settings
|
||||
if (serial.empty()) {
|
||||
const std::filesystem::path userDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
const std::filesystem::path configPath = userDir / "config.json";
|
||||
|
||||
// Load global config if exists
|
||||
if (std::ifstream globalIn{configPath}; globalIn.good()) {
|
||||
json gj;
|
||||
globalIn >> gj;
|
||||
if (gj.contains("General")) {
|
||||
json current = m_general; // JSON from existing struct with all defaults
|
||||
current.update(gj.at("General")); // merge only fields present in file
|
||||
m_general = current.get<GeneralSettings>(); // convert back
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loading global settings from: {}",
|
||||
configPath.string());
|
||||
|
||||
// Load global config if exists
|
||||
if (std::ifstream globalIn{configPath}; globalIn.good()) {
|
||||
json gj;
|
||||
globalIn >> gj;
|
||||
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Global config JSON size: {}", gj.size());
|
||||
|
||||
if (gj.contains("General")) {
|
||||
json current = m_general;
|
||||
current.update(gj.at("General"));
|
||||
m_general = current.get<GeneralSettings>();
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded General settings");
|
||||
}
|
||||
if (gj.contains("Debug")) {
|
||||
json current = m_debug;
|
||||
current.update(gj.at("Debug"));
|
||||
m_debug = current.get<DebugSettings>();
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Debug settings");
|
||||
}
|
||||
if (gj.contains("Input")) {
|
||||
json current = m_input;
|
||||
current.update(gj.at("Input"));
|
||||
m_input = current.get<InputSettings>();
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Input settings");
|
||||
}
|
||||
if (gj.contains("Audio")) {
|
||||
json current = m_audio;
|
||||
current.update(gj.at("Audio"));
|
||||
m_audio = current.get<AudioSettings>();
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Audio settings");
|
||||
}
|
||||
if (gj.contains("GPU")) {
|
||||
json current = m_gpu;
|
||||
current.update(gj.at("GPU"));
|
||||
m_gpu = current.get<GPUSettings>();
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded GPU settings");
|
||||
}
|
||||
if (gj.contains("Vulkan")) {
|
||||
json current = m_vulkan;
|
||||
current.update(gj.at("Vulkan"));
|
||||
m_vulkan = current.get<VulkanSettings>();
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Vulkan settings");
|
||||
}
|
||||
if (gj.contains("Users")) {
|
||||
m_userManager.GetUsers() = gj.at("Users").get<Users>();
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Users");
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(EmuSettings,
|
||||
"[EmulatorSettings] Global config not found, setting defaults");
|
||||
SetDefaultValues();
|
||||
// ensure a default user exists
|
||||
if (m_userManager.GetUsers().user.empty())
|
||||
m_userManager.GetUsers().user = m_userManager.CreateDefaultUser();
|
||||
Save();
|
||||
}
|
||||
if (gj.contains("Debug")) {
|
||||
json current = m_debug;
|
||||
current.update(gj.at("Debug"));
|
||||
m_debug = current.get<DebugSettings>();
|
||||
}
|
||||
if (gj.contains("Input")) {
|
||||
json current = m_input;
|
||||
current.update(gj.at("Input"));
|
||||
m_input = current.get<InputSettings>();
|
||||
}
|
||||
if (gj.contains("Audio")) {
|
||||
json current = m_audio;
|
||||
current.update(gj.at("Audio"));
|
||||
m_audio = current.get<AudioSettings>();
|
||||
}
|
||||
if (gj.contains("GPU")) {
|
||||
json current = m_gpu;
|
||||
current.update(gj.at("GPU"));
|
||||
m_gpu = current.get<GPUSettings>();
|
||||
}
|
||||
if (gj.contains("Vulkan")) {
|
||||
json current = m_vulkan;
|
||||
current.update(gj.at("Vulkan"));
|
||||
m_vulkan = current.get<VulkanSettings>();
|
||||
}
|
||||
if (gj.contains("Users"))
|
||||
m_userManager.GetUsers() = gj.at("Users").get<Users>();
|
||||
} else {
|
||||
SetDefaultValues();
|
||||
// ensure a default user exists
|
||||
if (m_userManager.GetUsers().user.empty())
|
||||
m_userManager.GetUsers().user = m_userManager.CreateDefaultUser();
|
||||
Save();
|
||||
|
||||
return true;
|
||||
}
|
||||
// If serial is provided, ONLY apply game-specific overrides
|
||||
// WITHOUT reloading global settings!
|
||||
else {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying game-specific overrides for: {}",
|
||||
serial);
|
||||
|
||||
// Load per-game overrides and apply
|
||||
if (!serial.empty()) {
|
||||
const std::filesystem::path gamePath =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (serial + ".json");
|
||||
if (!std::filesystem::exists(gamePath))
|
||||
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Game config path: {}", gamePath.string());
|
||||
|
||||
if (!std::filesystem::exists(gamePath)) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] No game-specific config found");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream in(gamePath);
|
||||
if (!in.is_open())
|
||||
if (!in.is_open()) {
|
||||
LOG_ERROR(EmuSettings, "[EmulatorSettings] Failed to open game config file");
|
||||
return false;
|
||||
}
|
||||
|
||||
json gj;
|
||||
in >> gj;
|
||||
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Game config JSON: {}", gj.dump(2));
|
||||
|
||||
std::vector<std::string> changed;
|
||||
|
||||
if (gj.contains("General")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying General overrides");
|
||||
ApplyGroupOverrides<GeneralSettings>(m_general, gj.at("General"), changed);
|
||||
}
|
||||
if (gj.contains("Debug")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Debug overrides");
|
||||
ApplyGroupOverrides<DebugSettings>(m_debug, gj.at("Debug"), changed);
|
||||
}
|
||||
if (gj.contains("Input")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Input overrides");
|
||||
ApplyGroupOverrides<InputSettings>(m_input, gj.at("Input"), changed);
|
||||
}
|
||||
if (gj.contains("Audio")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Audio overrides");
|
||||
ApplyGroupOverrides<AudioSettings>(m_audio, gj.at("Audio"), changed);
|
||||
}
|
||||
if (gj.contains("GPU")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying GPU overrides");
|
||||
ApplyGroupOverrides<GPUSettings>(m_gpu, gj.at("GPU"), changed);
|
||||
|
||||
// Debug: Print specific GPU values
|
||||
auto gpuJson = gj["GPU"];
|
||||
if (gpuJson.contains("fsr_enabled")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] GPU/fsr_enabled JSON: {}",
|
||||
gpuJson["fsr_enabled"].dump());
|
||||
}
|
||||
if (gpuJson.contains("rcas_enabled")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] GPU/rcas_enabled JSON: {}",
|
||||
gpuJson["rcas_enabled"].dump());
|
||||
}
|
||||
}
|
||||
if (gj.contains("Vulkan")) {
|
||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Vulkan overrides");
|
||||
ApplyGroupOverrides<VulkanSettings>(m_vulkan, gj.at("Vulkan"), changed);
|
||||
}
|
||||
|
||||
PrintChangedSummary(changed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error loading settings: " << e.what() << std::endl;
|
||||
LOG_ERROR(EmuSettings, "Error loading settings: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -358,3 +419,22 @@ void EmulatorSettings::SetDefaultValues() {
|
||||
m_gpu = GPUSettings{};
|
||||
m_vulkan = VulkanSettings{};
|
||||
}
|
||||
|
||||
std::vector<std::string> EmulatorSettings::GetAllOverrideableKeys() const {
|
||||
std::vector<std::string> keys;
|
||||
|
||||
auto addKeys = [&keys](const std::vector<OverrideItem>& items) {
|
||||
for (const auto& item : items) {
|
||||
keys.push_back(item.key);
|
||||
}
|
||||
};
|
||||
|
||||
addKeys(m_general.GetOverrideableFields());
|
||||
addKeys(m_debug.GetOverrideableFields());
|
||||
addKeys(m_input.GetOverrideableFields());
|
||||
addKeys(m_audio.GetOverrideableFields());
|
||||
addKeys(m_gpu.GetOverrideableFields());
|
||||
addKeys(m_vulkan.GetOverrideableFields());
|
||||
|
||||
return keys;
|
||||
}
|
||||
@ -10,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/types.h"
|
||||
#include "core/user_manager.h"
|
||||
|
||||
@ -43,24 +44,37 @@ struct OverrideItem {
|
||||
// Helper factory: create an OverrideItem binding a pointer-to-member
|
||||
template <typename Struct, typename T>
|
||||
inline OverrideItem make_override(const char* key, Setting<T> Struct::* member) {
|
||||
return OverrideItem{key, [member, key](void* base, const nlohmann::json& entry,
|
||||
std::vector<std::string>& changed) {
|
||||
if (!entry.is_object())
|
||||
return;
|
||||
return OverrideItem{
|
||||
key,
|
||||
[member, key](void* base, const nlohmann::json& entry, std::vector<std::string>& changed) {
|
||||
LOG_DEBUG(EmuSettings, "[make_override] Processing key: {}", key);
|
||||
LOG_DEBUG(EmuSettings, "[make_override] Entry JSON: {}", entry.dump());
|
||||
|
||||
Struct* obj = reinterpret_cast<Struct*>(base);
|
||||
Setting<T>& dst = obj->*member;
|
||||
Struct* obj = reinterpret_cast<Struct*>(base);
|
||||
Setting<T>& dst = obj->*member;
|
||||
|
||||
Setting<T> tmp = entry.get<Setting<T>>();
|
||||
try {
|
||||
// Parse the value from JSON
|
||||
T newValue = entry.get<T>();
|
||||
|
||||
if (dst.value != tmp.value) {
|
||||
changed.push_back(std::string(key) + " ( " +
|
||||
nlohmann::json(dst.value).dump() + " → " +
|
||||
nlohmann::json(tmp.value).dump() + " )");
|
||||
}
|
||||
LOG_DEBUG(EmuSettings, "[make_override] Parsed value: {}", newValue);
|
||||
LOG_DEBUG(EmuSettings, "[make_override] Current value: {}", dst.value);
|
||||
|
||||
dst.value = tmp.value;
|
||||
}};
|
||||
if (dst.value != newValue) {
|
||||
std::ostringstream oss;
|
||||
oss << key << " ( " << dst.value << " → " << newValue << " )";
|
||||
changed.push_back(oss.str());
|
||||
LOG_DEBUG(EmuSettings, "[make_override] Recorded change: {}", oss.str());
|
||||
}
|
||||
|
||||
dst.value = newValue;
|
||||
LOG_DEBUG(EmuSettings, "[make_override] Successfully updated {}", key);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(EmuSettings, "[make_override] ERROR parsing {}: {}", key, e.what());
|
||||
LOG_ERROR(EmuSettings, "[make_override] Entry was: {}", entry.dump());
|
||||
LOG_ERROR(EmuSettings, "[make_override] Type name: {}", entry.type_name());
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
@ -80,6 +94,7 @@ struct GeneralSettings {
|
||||
Setting<std::filesystem::path> addon_install_dir;
|
||||
Setting<std::filesystem::path> home_dir;
|
||||
Setting<std::filesystem::path> sys_modules_dir;
|
||||
Setting<std::filesystem::path> font_dir;
|
||||
|
||||
Setting<int> volume_slider{100};
|
||||
Setting<bool> neo_mode{false};
|
||||
@ -92,6 +107,7 @@ struct GeneralSettings {
|
||||
Setting<std::string> log_filter{""};
|
||||
Setting<std::string> log_type{"sync"};
|
||||
Setting<bool> show_splash{false};
|
||||
Setting<bool> identical_log_grouped{true};
|
||||
Setting<bool> connected_to_network{false};
|
||||
Setting<bool> discord_rpc_enabled{false};
|
||||
Setting<bool> show_fps_counter{false};
|
||||
@ -112,6 +128,8 @@ struct GeneralSettings {
|
||||
&GeneralSettings::trophy_notification_duration),
|
||||
make_override<GeneralSettings>("log_filter", &GeneralSettings::log_filter),
|
||||
make_override<GeneralSettings>("log_type", &GeneralSettings::log_type),
|
||||
make_override<GeneralSettings>("identical_log_grouped",
|
||||
&GeneralSettings::identical_log_grouped),
|
||||
make_override<GeneralSettings>("show_splash", &GeneralSettings::show_splash),
|
||||
make_override<GeneralSettings>("trophy_notification_side",
|
||||
&GeneralSettings::trophy_notification_side),
|
||||
@ -120,11 +138,12 @@ struct GeneralSettings {
|
||||
}
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir,
|
||||
sys_modules_dir, volume_slider, neo_mode, dev_kit_mode,
|
||||
sys_modules_dir, font_dir, volume_slider, neo_mode, dev_kit_mode,
|
||||
extra_dmem_in_mbytes, psn_signed_in, trophy_popup_disabled,
|
||||
trophy_notification_duration, log_filter, log_type, show_splash,
|
||||
trophy_notification_side, connected_to_network,
|
||||
discord_rpc_enabled, show_fps_counter, console_language)
|
||||
identical_log_grouped, trophy_notification_side,
|
||||
connected_to_network, discord_rpc_enabled, show_fps_counter,
|
||||
console_language)
|
||||
|
||||
// -------------------------------
|
||||
// Debug settings
|
||||
@ -245,6 +264,7 @@ struct GPUSettings {
|
||||
&GPUSettings::readback_linear_images_enabled),
|
||||
make_override<GPUSettings>("direct_memory_access_enabled",
|
||||
&GPUSettings::direct_memory_access_enabled),
|
||||
make_override<GPUSettings>("vblank_frequency", &GPUSettings::vblank_frequency),
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -332,6 +352,8 @@ public:
|
||||
void SetHomeDir(const std::filesystem::path& dir);
|
||||
std::filesystem::path GetSysModulesDir();
|
||||
void SetSysModulesDir(const std::filesystem::path& dir);
|
||||
std::filesystem::path GetFontsDir();
|
||||
void SetFontsDir(const std::filesystem::path& dir);
|
||||
|
||||
// user helpers
|
||||
UserManager& GetUserManager() {
|
||||
@ -364,6 +386,26 @@ private:
|
||||
static void PrintChangedSummary(const std::vector<std::string>& changed);
|
||||
|
||||
public:
|
||||
// Add these getters to access overrideable fields
|
||||
std::vector<OverrideItem> GetGeneralOverrideableFields() const {
|
||||
return m_general.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetDebugOverrideableFields() const {
|
||||
return m_debug.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetInputOverrideableFields() const {
|
||||
return m_input.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetAudioOverrideableFields() const {
|
||||
return m_audio.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetGPUOverrideableFields() const {
|
||||
return m_gpu.GetOverrideableFields();
|
||||
}
|
||||
std::vector<OverrideItem> GetVulkanOverrideableFields() const {
|
||||
return m_vulkan.GetOverrideableFields();
|
||||
}
|
||||
std::vector<std::string> GetAllOverrideableKeys() const;
|
||||
#define SETTING_FORWARD(group, Name, field) \
|
||||
auto Get##Name() const { \
|
||||
return group.field.value; \
|
||||
@ -393,6 +435,7 @@ public:
|
||||
SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration)
|
||||
SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side)
|
||||
SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash)
|
||||
SETTING_FORWARD_BOOL(m_general, IdenticalLogGrouped, identical_log_grouped)
|
||||
SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir)
|
||||
SETTING_FORWARD(m_general, LogFilter, log_filter)
|
||||
SETTING_FORWARD(m_general, LogType, log_type)
|
||||
@ -455,6 +498,7 @@ public:
|
||||
SETTING_FORWARD(m_input, DefaultControllerId, default_controller_id)
|
||||
SETTING_FORWARD_BOOL(m_input, UsingSpecialPad, use_special_pad)
|
||||
SETTING_FORWARD(m_input, SpecialPadClass, special_pad_class)
|
||||
SETTING_FORWARD_BOOL(m_input, UseUnifiedInputConfig, use_unified_Input_Config)
|
||||
|
||||
// Vulkan settings
|
||||
SETTING_FORWARD(m_vulkan, GpuId, gpu_id)
|
||||
@ -471,4 +515,4 @@ public:
|
||||
|
||||
#undef SETTING_FORWARD
|
||||
#undef SETTING_FORWARD_BOOL
|
||||
};
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user