24 Commits

Author SHA1 Message Date
georgemoralis
a5af64c258 misc 2025-12-09 19:28:46 +02:00
georgemoralis
868ad608b2 GetValidUsers function to check users with valid home directories 2025-12-09 18:24:22 +02:00
georgemoralis
fca94497b1 savedata path is not homedir / userid / savedata / gameid 2025-12-09 18:22:01 +02:00
georgemoralis
34ef7fd0bf Merge branch 'main' into user_and_settings 2025-12-09 14:43:15 +02:00
kalaposfos13
88c30ab9d6 copyright 2025 2025-12-02 10:28:13 +01:00
georgemoralis
80be4b391c Merge branch 'main' into user_and_settings 2025-12-02 11:15:45 +02:00
kalaposfos13
f50d842f80 Merge remote-tracking branch 'origin/main' into user_and_settings 2025-12-02 09:49:07 +01:00
georgemoralis
cfa6317836 Merge branch 'main' into user_and_settings 2025-12-01 11:17:21 +02:00
georgemoralis
073194b324 Merge branch 'user_and_settings' of https://github.com/shadps4-emu/shadPS4 into user_and_settings 2025-11-29 20:10:21 +02:00
georgemoralis
85d4fef6c9 fixed linux? 2025-11-29 20:10:12 +02:00
georgemoralis
149c613834 Merge branch 'main' into user_and_settings 2025-11-29 16:13:54 +02:00
georgemoralis
44b78612f8 fixed creating new default json file if not exists 2025-11-29 15:56:44 +02:00
georgemoralis
4977b99d9a save after adding a game dir via command line 2025-11-29 15:40:28 +02:00
kalaposfos13
ad1e58ed16 Squashed multiple controllers PR 2025-11-29 14:08:24 +01:00
georgemoralis
d1e9b47fc3 removed load before init singleton 2025-11-29 15:07:26 +02:00
georgemoralis
abc456b62f even more settings 2025-11-28 20:04:20 +02:00
georgemoralis
039b145159 more settings 2025-11-28 15:11:41 +02:00
georgemoralis
6ee0ee65ad more settings conversion 2025-11-27 21:04:46 +02:00
georgemoralis
d39ff10747 More settings 2025-11-27 19:26:10 +02:00
georgemoralis
6bb8ad8968 clang fix (of course) 2025-11-27 17:11:21 +02:00
georgemoralis
f952d375a4 more settings porting 2025-11-27 17:08:55 +02:00
georgemoralis
f54367734d more settings porting 2025-11-27 15:25:21 +02:00
georgemoralis
761306188d more settings conversion 2025-11-27 13:01:29 +02:00
georgemoralis
b6d2748ed7 initial 2025-11-26 20:44:20 +02:00
42 changed files with 1712 additions and 1182 deletions

View File

@@ -836,6 +836,10 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/thread.h
src/core/tls.cpp
src/core/tls.h
src/core/emulator_settings.cpp
src/core/emulator_settings.h
src/core/user_manager.cpp
src/core/user_manager.h
)
if (ARCHITECTURE STREQUAL "x86_64")

View File

@@ -46,7 +46,7 @@ std::optional<T> get_optional(const toml::value& v, const std::string& key) {
if constexpr (std::is_same_v<T, int>) {
if (it->second.is_integer()) {
return static_cast<int>(toml::get<int>(it->second));
return static_cast<s32>(toml::get<int>(it->second));
}
} else if constexpr (std::is_same_v<T, unsigned int>) {
if (it->second.is_integer()) {
@@ -54,15 +54,19 @@ std::optional<T> get_optional(const toml::value& v, const std::string& key) {
}
} else if constexpr (std::is_same_v<T, double>) {
if (it->second.is_floating()) {
return toml::get<double>(it->second);
return toml::get<T>(it->second);
}
} else if constexpr (std::is_same_v<T, std::string>) {
if (it->second.is_string()) {
return toml::get<std::string>(it->second);
return toml::get<T>(it->second);
}
} else if constexpr (std::is_same_v<T, bool>) {
if (it->second.is_boolean()) {
return toml::get<bool>(it->second);
return toml::get<T>(it->second);
}
} else if constexpr (std::is_same_v<T, std::array<string, 4>>) {
if (it->second.is_array()) {
return toml::get<T>(it->second);
}
} else {
static_assert([] { return false; }(), "Unsupported type in get_optional<T>");
@@ -114,6 +118,9 @@ public:
void set(const T value, bool is_game_specific = false) {
is_game_specific ? game_specific_value = value : base_value = value;
}
void setDefault(bool is_game_specific = false) {
is_game_specific ? game_specific_value = default_value : base_value = default_value;
}
void setTomlValue(toml::ordered_value& data, const std::string& header, const std::string& key,
bool is_game_specific = false) {
if (is_game_specific) {
@@ -130,20 +137,12 @@ public:
// General
static ConfigEntry<int> volumeSlider(100);
static ConfigEntry<bool> isNeo(false);
static ConfigEntry<bool> isDevKit(false);
static ConfigEntry<int> extraDmemInMbytes(0);
static ConfigEntry<bool> isPSNSignedIn(false);
static ConfigEntry<bool> isTrophyPopupDisabled(false);
static ConfigEntry<double> trophyNotificationDuration(6.0);
static ConfigEntry<string> logFilter("");
static ConfigEntry<string> logType("sync");
static ConfigEntry<string> userName("shadPS4");
static ConfigEntry<bool> isShowSplash(false);
static ConfigEntry<string> isSideTrophy("right");
static ConfigEntry<bool> isConnectedToNetwork(false);
static bool enableDiscordRPC = false;
static std::filesystem::path sys_modules_path = {};
static ConfigEntry<std::array<std::string, 4>> userNames({
"shadPS4",
"shadps4-2",
"shadPS4-3",
"shadPS4-4",
});
// Input
static ConfigEntry<int> cursorState(HideCursorState::Idle);
@@ -165,7 +164,6 @@ static ConfigEntry<u32> windowWidth(1280);
static ConfigEntry<u32> windowHeight(720);
static ConfigEntry<u32> internalScreenWidth(1280);
static ConfigEntry<u32> internalScreenHeight(720);
static ConfigEntry<bool> isNullGpu(false);
static ConfigEntry<bool> shouldCopyGPUBuffers(false);
static ConfigEntry<bool> readbacksEnabled(false);
static ConfigEntry<bool> readbackLinearImagesEnabled(false);
@@ -173,16 +171,12 @@ static ConfigEntry<bool> directMemoryAccessEnabled(false);
static ConfigEntry<bool> shouldDumpShaders(false);
static ConfigEntry<bool> shouldPatchShaders(false);
static ConfigEntry<u32> vblankFrequency(60);
static ConfigEntry<bool> isFullscreen(false);
static ConfigEntry<string> fullscreenMode("Windowed");
static ConfigEntry<string> presentMode("Mailbox");
static ConfigEntry<bool> isHDRAllowed(false);
static ConfigEntry<bool> fsrEnabled(false);
static ConfigEntry<bool> rcasEnabled(true);
static ConfigEntry<int> rcasAttenuation(250);
// Vulkan
static ConfigEntry<s32> gpuId(-1);
static ConfigEntry<bool> vkValidation(false);
static ConfigEntry<bool> vkValidationCore(true);
static ConfigEntry<bool> vkValidationSync(false);
@@ -195,18 +189,8 @@ static ConfigEntry<bool> pipelineCacheEnable(false);
static ConfigEntry<bool> pipelineCacheArchive(false);
// Debug
static ConfigEntry<bool> isDebugDump(false);
static ConfigEntry<bool> isShaderDebug(false);
static ConfigEntry<bool> isSeparateLogFilesEnabled(false);
static ConfigEntry<bool> isFpsColor(true);
static ConfigEntry<bool> showFpsCounter(false);
static ConfigEntry<bool> logEnabled(true);
// GUI
static std::vector<GameInstallDir> settings_install_dirs = {};
std::vector<bool> install_dirs_enabled = {};
std::filesystem::path settings_addon_install_dir = {};
std::filesystem::path save_data_path = {};
// Settings
ConfigEntry<u32> m_language(1); // english
@@ -234,17 +218,6 @@ void setGameRunning(bool running) {
isGameRunning = running;
}
std::filesystem::path getSysModulesPath() {
if (sys_modules_path.empty()) {
return Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir);
}
return sys_modules_path;
}
void setSysModulesPath(const std::filesystem::path& path) {
sys_modules_path = path;
}
int getVolumeSlider() {
return volumeSlider.get();
}
@@ -272,10 +245,6 @@ int* GetControllerCustomColor() {
return controllerCustomColorRGB;
}
bool getLoggingEnabled() {
return logEnabled.get();
}
void SetControllerCustomColor(int r, int b, int g) {
controllerCustomColorRGB[0] = r;
controllerCustomColorRGB[1] = b;
@@ -290,55 +259,10 @@ void setTrophyKey(string key) {
trophyKey = key;
}
std::filesystem::path GetSaveDataPath() {
if (save_data_path.empty()) {
return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "savedata";
}
return save_data_path;
}
void setVolumeSlider(int volumeValue, bool is_game_specific) {
volumeSlider.set(volumeValue, is_game_specific);
}
bool isNeoModeConsole() {
return isNeo.get();
}
bool isDevKitConsole() {
return isDevKit.get();
}
int getExtraDmemInMbytes() {
return extraDmemInMbytes.get();
}
void setExtraDmemInMbytes(int value, bool is_game_specific) {
// Disable setting in global config
is_game_specific ? extraDmemInMbytes.game_specific_value = value
: extraDmemInMbytes.base_value = 0;
}
bool getIsFullscreen() {
return isFullscreen.get();
}
string getFullscreenMode() {
return fullscreenMode.get();
}
std::string getPresentMode() {
return presentMode.get();
}
bool getisTrophyPopupDisabled() {
return isTrophyPopupDisabled.get();
}
bool getEnableDiscordRPC() {
return enableDiscordRPC;
}
s16 getCursorState() {
return cursorState.get();
}
@@ -359,10 +283,6 @@ std::string getPadSpkOutputDevice() {
return padSpkOutputDevice.get();
}
double getTrophyNotificationDuration() {
return trophyNotificationDuration.get();
}
u32 getWindowWidth() {
return windowWidth.get();
}
@@ -379,20 +299,18 @@ u32 getInternalScreenHeight() {
return internalScreenHeight.get();
}
s32 getGpuId() {
return gpuId.get();
void setUserName(int id, string name) {
auto temp = userNames.get();
temp[id] = name;
userNames.set(temp);
}
string getLogFilter() {
return logFilter.get();
std::array<string, 4> const getUserNames() {
return userNames.get();
}
string getLogType() {
return logType.get();
}
string getUserName() {
return userName.get();
std::string getUserName(int id) {
return userNames.get()[id];
}
bool getUseSpecialPad() {
@@ -407,26 +325,6 @@ bool getIsMotionControlsEnabled() {
return isMotionControlsEnabled.get();
}
bool debugDump() {
return isDebugDump.get();
}
bool collectShadersForDebug() {
return isShaderDebug.get();
}
bool showSplash() {
return isShowSplash.get();
}
string sideTrophy() {
return isSideTrophy.get();
}
bool nullGpu() {
return isNullGpu.get();
}
bool copyGPUCmdBuffers() {
return shouldCopyGPUBuffers.get();
}
@@ -475,10 +373,6 @@ void setShowFpsCounter(bool enable, bool is_game_specific) {
showFpsCounter.set(enable, is_game_specific);
}
bool isLoggingEnabled() {
return logEnabled.get();
}
u32 vblankFreq() {
if (vblankFrequency.get() < 60) {
vblankFrequency = 60;
@@ -526,18 +420,6 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific) {
vkGuestMarkers.set(enable, is_game_specific);
}
bool getIsConnectedToNetwork() {
return isConnectedToNetwork.get();
}
void setConnectedToNetwork(bool enable, bool is_game_specific) {
isConnectedToNetwork.set(enable, is_game_specific);
}
void setGpuId(s32 selectedGpuId, bool is_game_specific) {
gpuId.set(selectedGpuId, is_game_specific);
}
void setWindowWidth(u32 width, bool is_game_specific) {
windowWidth.set(width, is_game_specific);
}
@@ -554,30 +436,6 @@ void setInternalScreenHeight(u32 height) {
internalScreenHeight.base_value = height;
}
void setDebugDump(bool enable, bool is_game_specific) {
isDebugDump.set(enable, is_game_specific);
}
void setLoggingEnabled(bool enable, bool is_game_specific) {
logEnabled.set(enable, is_game_specific);
}
void setCollectShaderForDebug(bool enable, bool is_game_specific) {
isShaderDebug.set(enable, is_game_specific);
}
void setShowSplash(bool enable, bool is_game_specific) {
isShowSplash.set(enable, is_game_specific);
}
void setSideTrophy(string side, bool is_game_specific) {
isSideTrophy.set(side, is_game_specific);
}
void setNullGpu(bool enable, bool is_game_specific) {
isNullGpu.set(enable, is_game_specific);
}
void setAllowHDR(bool enable, bool is_game_specific) {
isHDRAllowed.set(enable, is_game_specific);
}
@@ -634,26 +492,6 @@ void setVblankFreq(u32 value, bool is_game_specific) {
vblankFrequency.set(value, is_game_specific);
}
void setIsFullscreen(bool enable, bool is_game_specific) {
isFullscreen.set(enable, is_game_specific);
}
void setFullscreenMode(string mode, bool is_game_specific) {
fullscreenMode.set(mode, is_game_specific);
}
void setPresentMode(std::string mode, bool is_game_specific) {
presentMode.set(mode, is_game_specific);
}
void setisTrophyPopupDisabled(bool disable, bool is_game_specific) {
isTrophyPopupDisabled.set(disable, is_game_specific);
}
void setEnableDiscordRPC(bool enable) {
enableDiscordRPC = enable;
}
void setCursorState(s16 newCursorState, bool is_game_specific) {
cursorState.set(newCursorState, is_game_specific);
}
@@ -674,38 +512,10 @@ void setPadSpkOutputDevice(std::string device, bool is_game_specific) {
padSpkOutputDevice.set(device, is_game_specific);
}
void setTrophyNotificationDuration(double newTrophyNotificationDuration, bool is_game_specific) {
trophyNotificationDuration.set(newTrophyNotificationDuration, is_game_specific);
}
void setLanguage(u32 language, bool is_game_specific) {
m_language.set(language, is_game_specific);
}
void setNeoMode(bool enable, bool is_game_specific) {
isNeo.set(enable, is_game_specific);
}
void setDevKitConsole(bool enable, bool is_game_specific) {
isDevKit.set(enable, is_game_specific);
}
void setLogType(const string& type, bool is_game_specific) {
logType.set(type, is_game_specific);
}
void setLogFilter(const string& type, bool is_game_specific) {
logFilter.set(type, is_game_specific);
}
void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) {
isSeparateLogFilesEnabled.set(enabled, is_game_specific);
}
void setUserName(const string& name, bool is_game_specific) {
userName.set(name, is_game_specific);
}
void setUseSpecialPad(bool use) {
useSpecialPad.base_value = use;
}
@@ -718,95 +528,10 @@ void setIsMotionControlsEnabled(bool use, bool is_game_specific) {
isMotionControlsEnabled.set(use, is_game_specific);
}
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) {
for (const auto& install_dir : settings_install_dirs) {
if (install_dir.path == dir) {
return false;
}
}
settings_install_dirs.push_back({dir, enabled});
return true;
}
void removeGameInstallDir(const std::filesystem::path& dir) {
auto iterator =
std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(),
[&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; });
if (iterator != settings_install_dirs.end()) {
settings_install_dirs.erase(iterator);
}
}
void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled) {
auto iterator =
std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(),
[&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; });
if (iterator != settings_install_dirs.end()) {
iterator->enabled = enabled;
}
}
void setAddonInstallDir(const std::filesystem::path& dir) {
settings_addon_install_dir = dir;
}
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config) {
settings_install_dirs.clear();
for (const auto& dir : dirs_config) {
settings_install_dirs.push_back({dir, true});
}
}
void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config) {
settings_install_dirs = dirs_config;
}
void setSaveDataPath(const std::filesystem::path& path) {
save_data_path = path;
}
const std::vector<std::filesystem::path> getGameInstallDirs() {
std::vector<std::filesystem::path> enabled_dirs;
for (const auto& dir : settings_install_dirs) {
if (dir.enabled) {
enabled_dirs.push_back(dir.path);
}
}
return enabled_dirs;
}
const std::vector<bool> getGameInstallDirsEnabled() {
std::vector<bool> enabled_dirs;
for (const auto& dir : settings_install_dirs) {
enabled_dirs.push_back(dir.enabled);
}
return enabled_dirs;
}
std::filesystem::path getAddonInstallDir() {
if (settings_addon_install_dir.empty()) {
// Default for users without a config file or a config file from before this option existed
return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "addcont";
}
return settings_addon_install_dir;
}
u32 GetLanguage() {
return m_language.get();
}
bool getSeparateLogFilesEnabled() {
return isSeparateLogFilesEnabled.get();
}
bool getPSNSignedIn() {
return isPSNSignedIn.get();
}
void setPSNSignedIn(bool sign, bool is_game_specific) {
isPSNSignedIn.set(sign, is_game_specific);
}
string getDefaultControllerID() {
return defaultControllerID.get();
}
@@ -888,25 +613,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
const toml::value& general = data.at("General");
volumeSlider.setFromToml(general, "volumeSlider", is_game_specific);
isNeo.setFromToml(general, "isPS4Pro", is_game_specific);
isDevKit.setFromToml(general, "isDevKit", is_game_specific);
if (is_game_specific) { // do not get this value from the base config
extraDmemInMbytes.setFromToml(general, "extraDmemInMbytes", is_game_specific);
}
isPSNSignedIn.setFromToml(general, "isPSNSignedIn", is_game_specific);
isTrophyPopupDisabled.setFromToml(general, "isTrophyPopupDisabled", is_game_specific);
trophyNotificationDuration.setFromToml(general, "trophyNotificationDuration",
is_game_specific);
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", enableDiscordRPC);
logFilter.setFromToml(general, "logFilter", is_game_specific);
logType.setFromToml(general, "logType", is_game_specific);
userName.setFromToml(general, "userName", is_game_specific);
isShowSplash.setFromToml(general, "showSplash", is_game_specific);
isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific);
isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific);
userNames.setFromToml(general, "userNames", is_game_specific);
defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific);
sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path);
}
if (data.contains("Input")) {
@@ -937,7 +645,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
windowHeight.setFromToml(gpu, "screenHeight", is_game_specific);
internalScreenWidth.setFromToml(gpu, "internalScreenWidth", is_game_specific);
internalScreenHeight.setFromToml(gpu, "internalScreenHeight", is_game_specific);
isNullGpu.setFromToml(gpu, "nullGpu", is_game_specific);
shouldCopyGPUBuffers.setFromToml(gpu, "copyGPUBuffers", is_game_specific);
readbacksEnabled.setFromToml(gpu, "readbacks", is_game_specific);
readbackLinearImagesEnabled.setFromToml(gpu, "readbackLinearImages", is_game_specific);
@@ -945,9 +652,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
shouldDumpShaders.setFromToml(gpu, "dumpShaders", is_game_specific);
shouldPatchShaders.setFromToml(gpu, "patchShaders", is_game_specific);
vblankFrequency.setFromToml(gpu, "vblankFrequency", is_game_specific);
isFullscreen.setFromToml(gpu, "Fullscreen", is_game_specific);
fullscreenMode.setFromToml(gpu, "FullscreenMode", is_game_specific);
presentMode.setFromToml(gpu, "presentMode", is_game_specific);
isHDRAllowed.setFromToml(gpu, "allowHDR", is_game_specific);
fsrEnabled.setFromToml(gpu, "fsrEnabled", is_game_specific);
rcasEnabled.setFromToml(gpu, "rcasEnabled", is_game_specific);
@@ -957,7 +661,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
if (data.contains("Vulkan")) {
const toml::value& vk = data.at("Vulkan");
gpuId.setFromToml(vk, "gpuId", is_game_specific);
vkValidation.setFromToml(vk, "validation", is_game_specific);
vkValidationCore.setFromToml(vk, "validation_core", is_game_specific);
vkValidationSync.setFromToml(vk, "validation_sync", is_game_specific);
@@ -973,45 +676,11 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
string current_version = {};
if (data.contains("Debug")) {
const toml::value& debug = data.at("Debug");
isDebugDump.setFromToml(debug, "DebugDump", is_game_specific);
isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific);
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
isFpsColor.setFromToml(debug, "FPSColor", is_game_specific);
showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific);
logEnabled.setFromToml(debug, "logEnabled", is_game_specific);
current_version = toml::find_or<std::string>(debug, "ConfigVersion", current_version);
}
if (data.contains("GUI")) {
const toml::value& gui = data.at("GUI");
const auto install_dir_array =
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
try {
install_dirs_enabled = toml::find<std::vector<bool>>(gui, "installDirsEnabled");
} catch (...) {
// If it does not exist, assume that all are enabled.
install_dirs_enabled.resize(install_dir_array.size(), true);
}
if (install_dirs_enabled.size() < install_dir_array.size()) {
install_dirs_enabled.resize(install_dir_array.size(), true);
}
settings_install_dirs.clear();
for (size_t i = 0; i < install_dir_array.size(); i++) {
settings_install_dirs.push_back(
{std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]});
}
save_data_path = toml::find_fs_path_or(gui, "saveDataPath", save_data_path);
settings_addon_install_dir =
toml::find_fs_path_or(gui, "addonInstallDir", settings_addon_install_dir);
}
if (data.contains("Settings")) {
const toml::value& settings = data.at("Settings");
m_language.setFromToml(settings, "consoleLanguage", is_game_specific);
@@ -1080,24 +749,9 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
}
fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string()));
}
// Entries saved by the game-specific settings GUI
volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific);
isTrophyPopupDisabled.setTomlValue(data, "General", "isTrophyPopupDisabled", is_game_specific);
trophyNotificationDuration.setTomlValue(data, "General", "trophyNotificationDuration",
is_game_specific);
logFilter.setTomlValue(data, "General", "logFilter", is_game_specific);
logType.setTomlValue(data, "General", "logType", is_game_specific);
userName.setTomlValue(data, "General", "userName", is_game_specific);
isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific);
isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific);
isNeo.setTomlValue(data, "General", "isPS4Pro", is_game_specific);
isDevKit.setTomlValue(data, "General", "isDevKit", is_game_specific);
if (is_game_specific) {
extraDmemInMbytes.setTomlValue(data, "General", "extraDmemInMbytes", is_game_specific);
}
isPSNSignedIn.setTomlValue(data, "General", "isPSNSignedIn", is_game_specific);
isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific);
userNames.setTomlValue(data, "General", "userNames", is_game_specific);
cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific);
cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific);
@@ -1113,22 +767,17 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
windowWidth.setTomlValue(data, "GPU", "screenWidth", is_game_specific);
windowHeight.setTomlValue(data, "GPU", "screenHeight", is_game_specific);
isNullGpu.setTomlValue(data, "GPU", "nullGpu", is_game_specific);
shouldCopyGPUBuffers.setTomlValue(data, "GPU", "copyGPUBuffers", is_game_specific);
readbacksEnabled.setTomlValue(data, "GPU", "readbacks", is_game_specific);
readbackLinearImagesEnabled.setTomlValue(data, "GPU", "readbackLinearImages", is_game_specific);
shouldDumpShaders.setTomlValue(data, "GPU", "dumpShaders", is_game_specific);
vblankFrequency.setTomlValue(data, "GPU", "vblankFrequency", is_game_specific);
isFullscreen.setTomlValue(data, "GPU", "Fullscreen", is_game_specific);
fullscreenMode.setTomlValue(data, "GPU", "FullscreenMode", is_game_specific);
presentMode.setTomlValue(data, "GPU", "presentMode", is_game_specific);
isHDRAllowed.setTomlValue(data, "GPU", "allowHDR", is_game_specific);
fsrEnabled.setTomlValue(data, "GPU", "fsrEnabled", is_game_specific);
rcasEnabled.setTomlValue(data, "GPU", "rcasEnabled", is_game_specific);
rcasAttenuation.setTomlValue(data, "GPU", "rcasAttenuation", is_game_specific);
directMemoryAccessEnabled.setTomlValue(data, "GPU", "directMemoryAccess", is_game_specific);
gpuId.setTomlValue(data, "Vulkan", "gpuId", is_game_specific);
vkValidation.setTomlValue(data, "Vulkan", "validation", is_game_specific);
vkValidationSync.setTomlValue(data, "Vulkan", "validation_sync", is_game_specific);
vkValidationCore.setTomlValue(data, "Vulkan", "validation_core", is_game_specific);
@@ -1140,52 +789,10 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
pipelineCacheEnable.setTomlValue(data, "Vulkan", "pipelineCacheEnable", is_game_specific);
pipelineCacheArchive.setTomlValue(data, "Vulkan", "pipelineCacheArchive", is_game_specific);
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
isSeparateLogFilesEnabled.setTomlValue(data, "Debug", "isSeparateLogFilesEnabled",
is_game_specific);
logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific);
m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific);
if (!is_game_specific) {
std::vector<std::string> install_dirs;
std::vector<bool> install_dirs_enabled;
// temporary structure for ordering
struct DirEntry {
string path_str;
bool enabled;
};
std::vector<DirEntry> sorted_dirs;
for (const auto& dirInfo : settings_install_dirs) {
sorted_dirs.push_back(
{string{fmt::UTF(dirInfo.path.u8string()).data}, dirInfo.enabled});
}
// Sort directories alphabetically
std::sort(sorted_dirs.begin(), sorted_dirs.end(), [](const DirEntry& a, const DirEntry& b) {
return std::lexicographical_compare(
a.path_str.begin(), a.path_str.end(), b.path_str.begin(), b.path_str.end(),
[](char a_char, char b_char) {
return std::tolower(a_char) < std::tolower(b_char);
});
});
for (const auto& entry : sorted_dirs) {
install_dirs.push_back(entry.path_str);
install_dirs_enabled.push_back(entry.enabled);
}
// Non game-specific entries
data["General"]["enableDiscordRPC"] = enableDiscordRPC;
data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data};
data["GUI"]["installDirs"] = install_dirs;
data["GUI"]["installDirsEnabled"] = install_dirs_enabled;
data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data};
data["GUI"]["addonInstallDir"] =
string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["Debug"]["ConfigVersion"] = config_version;
data["Keys"]["TrophyKey"] = trophyKey;
@@ -1214,54 +821,38 @@ void setDefaultValues(bool is_game_specific) {
// Entries with game-specific settings that are in the game-specific setings GUI but not in
// the global settings GUI
if (is_game_specific) {
readbacksEnabled.set(false, is_game_specific);
readbackLinearImagesEnabled.set(false, is_game_specific);
isNeo.set(false, is_game_specific);
isDevKit.set(false, is_game_specific);
isPSNSignedIn.set(false, is_game_specific);
isConnectedToNetwork.set(false, is_game_specific);
directMemoryAccessEnabled.set(false, is_game_specific);
extraDmemInMbytes.set(0, is_game_specific);
readbacksEnabled.setDefault(is_game_specific);
readbackLinearImagesEnabled.setDefault(is_game_specific);
directMemoryAccessEnabled.setDefault(is_game_specific);
}
// Entries with game-specific settings that are in both the game-specific and global GUI
// GS - General
volumeSlider.set(100, is_game_specific);
isTrophyPopupDisabled.set(false, is_game_specific);
trophyNotificationDuration.set(6.0, is_game_specific);
logFilter.set("", is_game_specific);
logType.set("sync", is_game_specific);
userName.set("shadPS4", is_game_specific);
isShowSplash.set(false, is_game_specific);
isSideTrophy.set("right", is_game_specific);
volumeSlider.setDefault(is_game_specific);
userNames.setDefault(is_game_specific);
// GS - Input
cursorState.set(HideCursorState::Idle, is_game_specific);
cursorHideTimeout.set(5, is_game_specific);
isMotionControlsEnabled.set(true, is_game_specific);
backgroundControllerInput.set(false, is_game_specific);
usbDeviceBackend.set(UsbBackendType::Real, is_game_specific);
cursorState.setDefault(is_game_specific);
cursorHideTimeout.setDefault(is_game_specific);
isMotionControlsEnabled.setDefault(is_game_specific);
backgroundControllerInput.setDefault(is_game_specific);
usbDeviceBackend.setDefault(is_game_specific);
// GS - Audio
micDevice.set("Default Device", is_game_specific);
micDevice.setDefault(is_game_specific);
// GS - GPU
windowWidth.set(1280, is_game_specific);
windowHeight.set(720, is_game_specific);
isNullGpu.set(false, is_game_specific);
shouldCopyGPUBuffers.set(false, is_game_specific);
shouldDumpShaders.set(false, is_game_specific);
vblankFrequency.set(60, is_game_specific);
isFullscreen.set(false, is_game_specific);
fullscreenMode.set("Windowed", is_game_specific);
presentMode.set("Mailbox", is_game_specific);
isHDRAllowed.set(false, is_game_specific);
fsrEnabled.set(true, is_game_specific);
rcasEnabled.set(true, is_game_specific);
rcasAttenuation.set(250, is_game_specific);
windowWidth.setDefault(is_game_specific);
windowHeight.setDefault(is_game_specific);
shouldCopyGPUBuffers.setDefault(is_game_specific);
shouldDumpShaders.setDefault(is_game_specific);
vblankFrequency.setDefault(is_game_specific);
isHDRAllowed.setDefault(is_game_specific);
fsrEnabled.setDefault(is_game_specific);
rcasEnabled.setDefault(is_game_specific);
rcasAttenuation.setDefault(is_game_specific);
// GS - Vulkan
gpuId.set(-1, is_game_specific);
vkValidation.set(false, is_game_specific);
vkValidationCore.set(true, is_game_specific);
vkValidationSync.set(false, is_game_specific);
@@ -1273,21 +864,11 @@ void setDefaultValues(bool is_game_specific) {
pipelineCacheEnable.set(false, is_game_specific);
pipelineCacheArchive.set(false, is_game_specific);
// GS - Debug
isDebugDump.set(false, is_game_specific);
isShaderDebug.set(false, is_game_specific);
isSeparateLogFilesEnabled.set(false, is_game_specific);
logEnabled.set(true, is_game_specific);
// GS - Settings
m_language.set(1, is_game_specific);
m_language.setDefault(is_game_specific);
// All other entries
if (!is_game_specific) {
// General
enableDiscordRPC = false;
// Input
useSpecialPad.base_value = false;
specialPadClass.base_value = 1;
@@ -1322,6 +903,8 @@ hotkey_pause = f9
hotkey_reload_inputs = f8
hotkey_toggle_mouse_to_joystick = f7
hotkey_toggle_mouse_to_gyro = f6
hotkey_add_virtual_user = f5
hotkey_remove_virtual_user = f4
hotkey_toggle_mouse_to_touchpad = delete
hotkey_quit = lctrl, lshift, end
)";

View File

@@ -33,12 +33,6 @@ int getVolumeSlider();
void setVolumeSlider(int volumeValue, bool is_game_specific = false);
std::string getTrophyKey();
void setTrophyKey(std::string key);
bool getIsFullscreen();
void setIsFullscreen(bool enable, bool is_game_specific = false);
std::string getFullscreenMode();
void setFullscreenMode(std::string mode, bool is_game_specific = false);
std::string getPresentMode();
void setPresentMode(std::string mode, bool is_game_specific = false);
u32 getWindowWidth();
u32 getWindowHeight();
void setWindowWidth(u32 width, bool is_game_specific = false);
@@ -47,20 +41,8 @@ u32 getInternalScreenWidth();
u32 getInternalScreenHeight();
void setInternalScreenWidth(u32 width);
void setInternalScreenHeight(u32 height);
bool debugDump();
void setDebugDump(bool enable, bool is_game_specific = false);
s32 getGpuId();
void setGpuId(s32 selectedGpuId, bool is_game_specific = false);
bool allowHDR();
void setAllowHDR(bool enable, bool is_game_specific = false);
bool collectShadersForDebug();
void setCollectShaderForDebug(bool enable, bool is_game_specific = false);
bool showSplash();
void setShowSplash(bool enable, bool is_game_specific = false);
std::string sideTrophy();
void setSideTrophy(std::string side, bool is_game_specific = false);
bool nullGpu();
void setNullGpu(bool enable, bool is_game_specific = false);
bool copyGPUCmdBuffers();
void setCopyGPUCmdBuffers(bool enable, bool is_game_specific = false);
bool readbacks();
@@ -73,8 +55,6 @@ bool dumpShaders();
void setDumpShaders(bool enable, bool is_game_specific = false);
u32 vblankFreq();
void setVblankFreq(u32 value, bool is_game_specific = false);
bool getisTrophyPopupDisabled();
void setisTrophyPopupDisabled(bool disable, bool is_game_specific = false);
s16 getCursorState();
void setCursorState(s16 cursorState, bool is_game_specific = false);
bool vkValidationEnabled();
@@ -91,21 +71,12 @@ bool getVkHostMarkersEnabled();
void setVkHostMarkersEnabled(bool enable, bool is_game_specific = false);
bool getVkGuestMarkersEnabled();
void setVkGuestMarkersEnabled(bool enable, bool is_game_specific = false);
bool getEnableDiscordRPC();
void setEnableDiscordRPC(bool enable);
bool isRdocEnabled();
bool isPipelineCacheEnabled();
bool isPipelineCacheArchived();
void setRdocEnabled(bool enable, bool is_game_specific = false);
void setPipelineCacheEnabled(bool enable, bool is_game_specific = false);
void setPipelineCacheArchived(bool enable, bool is_game_specific = false);
std::string getLogType();
void setLogType(const std::string& type, bool is_game_specific = false);
std::string getLogFilter();
void setLogFilter(const std::string& type, bool is_game_specific = false);
double getTrophyNotificationDuration();
void setTrophyNotificationDuration(double newTrophyNotificationDuration,
bool is_game_specific = false);
int getCursorHideTimeout();
std::string getMainOutputDevice();
void setMainOutputDevice(std::string device, bool is_game_specific = false);
@@ -114,46 +85,29 @@ void setPadSpkOutputDevice(std::string device, bool is_game_specific = false);
std::string getMicDevice();
void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false);
void setMicDevice(std::string device, bool is_game_specific = false);
void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific = false);
bool getSeparateLogFilesEnabled();
u32 GetLanguage();
void setLanguage(u32 language, bool is_game_specific = false);
void setUseSpecialPad(bool use);
bool getUseSpecialPad();
void setSpecialPadClass(int type);
int getSpecialPadClass();
bool getPSNSignedIn();
void setPSNSignedIn(bool sign, bool is_game_specific = false);
bool patchShaders(); // no set
bool fpsColor(); // no set
bool getShowFpsCounter();
void setShowFpsCounter(bool enable, bool is_game_specific = false);
bool isNeoModeConsole();
void setNeoMode(bool enable, bool is_game_specific = false);
bool isDevKitConsole();
void setDevKitConsole(bool enable, bool is_game_specific = false);
int getExtraDmemInMbytes();
void setExtraDmemInMbytes(int value, bool is_game_specific = false);
bool getIsMotionControlsEnabled();
void setIsMotionControlsEnabled(bool use, bool is_game_specific = false);
std::string getDefaultControllerID();
void setDefaultControllerID(std::string id);
bool getBackgroundControllerInput();
void setBackgroundControllerInput(bool enable, bool is_game_specific = false);
bool getLoggingEnabled();
void setLoggingEnabled(bool enable, bool is_game_specific = false);
bool getFsrEnabled();
void setFsrEnabled(bool enable, bool is_game_specific = false);
bool getRcasEnabled();
void setRcasEnabled(bool enable, bool is_game_specific = false);
int getRcasAttenuation();
void setRcasAttenuation(int value, bool is_game_specific = false);
bool getIsConnectedToNetwork();
void setConnectedToNetwork(bool enable, bool is_game_specific = false);
void setUserName(const std::string& name, bool is_game_specific = false);
std::filesystem::path getSysModulesPath();
void setSysModulesPath(const std::filesystem::path& path);
bool getLoadAutoPatches();
void setLoadAutoPatches(bool enable);
@@ -162,26 +116,14 @@ int getUsbDeviceBackend();
void setUsbDeviceBackend(int value, bool is_game_specific = false);
// TODO
std::filesystem::path GetSaveDataPath();
std::string getUserName();
std::string getUserName(int id);
std::array<std::string, 4> const getUserNames();
bool GetUseUnifiedInputConfig();
void SetUseUnifiedInputConfig(bool use);
bool GetOverrideControllerColor();
void SetOverrideControllerColor(bool enable);
int* GetControllerCustomColor();
void SetControllerCustomColor(int r, int b, int g);
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config);
void setSaveDataPath(const std::filesystem::path& path);
// Gui
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
void removeGameInstallDir(const std::filesystem::path& dir);
void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
void setAddonInstallDir(const std::filesystem::path& dir);
const std::vector<std::filesystem::path> getGameInstallDirs();
const std::vector<bool> getGameInstallDirsEnabled();
std::filesystem::path getAddonInstallDir();
void setDefaultValues(bool is_game_specific = false);

View File

@@ -22,6 +22,7 @@
#include "common/path_util.h"
#include "common/string_util.h"
#include "common/thread.h"
#include "core/emulator_settings.h"
namespace Common::Log {
@@ -139,7 +140,7 @@ public:
const auto& log_dir = GetUserPath(PathType::LogDir);
std::filesystem::create_directory(log_dir);
Filter filter;
filter.ParseFilterString(Config::getLogFilter());
filter.ParseFilterString(EmulatorSettings::GetInstance()->GetLogFilter());
const auto& log_file_path = log_file.empty() ? LOG_FILE : log_file;
instance = std::unique_ptr<Impl, decltype(&Deleter)>(
new Impl(log_dir / log_file_path, filter), Deleter);
@@ -183,7 +184,8 @@ public:
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, const char* format, const fmt::format_args& args) {
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
if (!filter.CheckMessage(log_class, log_level) ||
!EmulatorSettings::GetInstance()->IsLogEnabled()) {
return;
}
@@ -220,7 +222,7 @@ public:
.function = function,
.message = std::move(message),
};
if (Config::getLogType() == "async") {
if (EmulatorSettings::GetInstance()->GetLogType() == "async") {
message_queue.EmplaceWait(entry);
} else {
ForEachBackend([&entry](auto& backend) { backend.Write(entry); });

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
@@ -127,6 +127,7 @@ static auto UserPaths = [] {
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
create_path(PathType::HomeDir, user_dir / HOME_DIR);
create_path(PathType::CacheDir, user_dir / CACHE_DIR);
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");

View File

@@ -24,6 +24,7 @@ enum class PathType {
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
CustomTrophy, // Where custom files for trophies are stored.
CustomConfigs, // Where custom files for different games are stored.
HomeDir, // PS4 home directory
CacheDir, // Where pipeline and shader cache is stored.
};
@@ -43,6 +44,7 @@ constexpr auto PATCHES_DIR = "patches";
constexpr auto METADATA_DIR = "game_data";
constexpr auto CUSTOM_TROPHY = "custom_trophy";
constexpr auto CUSTOM_CONFIGS = "custom_configs";
constexpr auto HOME_DIR = "home";
constexpr auto CACHE_DIR = "cache";
// Filenames

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
@@ -9,6 +9,7 @@
#include "common/elf_info.h"
#include "common/error.h"
#include "core/address_space.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/memory.h"
#include "core/memory.h"
#include "libraries/error_codes.h"
@@ -183,7 +184,7 @@ struct AddressSpace::Impl {
user_size = supported_user_max - USER_MIN - 1;
// Increase BackingSize to account for config options.
BackingSize += Config::getExtraDmemInMbytes() * 1_MB;
BackingSize += EmulatorSettings::GetInstance()->GetExtraDmemInMBytes() * 1_MB;
// Allocate backing file that represents the total physical memory.
backing_handle = CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_ALL_ACCESS,
@@ -505,7 +506,7 @@ enum PosixPageProtection {
struct AddressSpace::Impl {
Impl() {
BackingSize += Config::getExtraDmemInMbytes() * 1_MB;
BackingSize += EmulatorSettings::GetInstance()->GetExtraDmemInMBytes() * 1_MB;
// Allocate virtual address placeholder for our address space.
system_managed_size = SystemManagedSize;
system_reserved_size = SystemReservedSize;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -11,6 +11,7 @@
#include "common/config.h"
#include "common/elf_info.h"
#include "common/path_util.h"
#include "core/emulator_settings.h"
namespace Core::Devtools::Widget {
@@ -23,7 +24,7 @@ public:
bool open = false;
static bool IsSystemModule(const std::filesystem::path& path) {
const auto sys_modules_path = Config::getSysModulesPath();
const auto sys_modules_path = EmulatorSettings::GetInstance()->GetSysModulesDir();
const auto abs_path = std::filesystem::absolute(path).lexically_normal();
const auto abs_sys_path = std::filesystem::absolute(sys_modules_path).lexically_normal();

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
@@ -13,6 +13,7 @@
#include "common/string_util.h"
#include "core/debug_state.h"
#include "core/devtools/options.h"
#include "core/emulator_settings.h"
#include "imgui/imgui_std.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/vk_presenter.h"
@@ -244,8 +245,8 @@ void ShaderList::Draw() {
return;
}
if (!Config::collectShadersForDebug()) {
DrawCenteredText("Enable 'CollectShader' in config to see shaders");
if (!EmulatorSettings::GetInstance()->IsShaderDump()) {
DrawCenteredText("Enable 'shader_dump' in config to see shaders");
End();
return;
}

View File

@@ -0,0 +1,360 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include <iomanip>
#include <iostream>
#include <common/path_util.h>
#include "emulator_settings.h"
using json = nlohmann::json;
std::shared_ptr<EmulatorSettings> EmulatorSettings::s_instance = nullptr;
std::mutex EmulatorSettings::s_mutex;
namespace nlohmann {
template <>
struct adl_serializer<std::filesystem::path> {
static void to_json(json& j, const std::filesystem::path& p) {
j = p.string();
}
static void from_json(const json& j, std::filesystem::path& p) {
p = j.get<std::string>();
}
};
} // namespace nlohmann
// --------------------
// Print summary
// --------------------
void EmulatorSettings::PrintChangedSummary(const std::vector<std::string>& changed) {
if (changed.empty()) {
std::cout << "[Settings] No game-specific overrides applied\n";
return;
}
std::cout << "[Settings] Game-specific overrides applied:\n";
for (const auto& k : changed)
std::cout << " * " << k << "\n";
}
// --------------------
// ctor/dtor + singleton
// --------------------
EmulatorSettings::EmulatorSettings() {
// Load();
}
EmulatorSettings::~EmulatorSettings() {
Save();
}
std::shared_ptr<EmulatorSettings> EmulatorSettings::GetInstance() {
std::lock_guard<std::mutex> lock(s_mutex);
if (!s_instance)
s_instance = std::make_shared<EmulatorSettings>();
return s_instance;
}
void EmulatorSettings::SetInstance(std::shared_ptr<EmulatorSettings> instance) {
std::lock_guard<std::mutex> lock(s_mutex);
s_instance = instance;
}
// --------------------
// General helpers
// --------------------
bool EmulatorSettings::AddGameInstallDir(const std::filesystem::path& dir, bool enabled) {
for (const auto& d : m_general.install_dirs.value)
if (d.path == dir)
return false;
m_general.install_dirs.value.push_back({dir, enabled});
return true;
}
std::vector<std::filesystem::path> EmulatorSettings::GetGameInstallDirs() const {
std::vector<std::filesystem::path> out;
for (const auto& d : m_general.install_dirs.value)
if (d.enabled)
out.push_back(d.path);
return out;
}
const std::vector<GameInstallDir>& EmulatorSettings::GetAllGameInstallDirs() const {
return m_general.install_dirs.value;
}
void EmulatorSettings::SetAllGameInstallDirs(const std::vector<GameInstallDir>& dirs) {
m_general.install_dirs.value = dirs;
}
void EmulatorSettings::RemoveGameInstallDir(const std::filesystem::path& dir) {
auto iterator =
std::find_if(m_general.install_dirs.value.begin(), m_general.install_dirs.value.end(),
[&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; });
if (iterator != m_general.install_dirs.value.end()) {
m_general.install_dirs.value.erase(iterator);
}
}
void EmulatorSettings::SetGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled) {
auto iterator =
std::find_if(m_general.install_dirs.value.begin(), m_general.install_dirs.value.end(),
[&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; });
if (iterator != m_general.install_dirs.value.end()) {
iterator->enabled = enabled;
}
}
void EmulatorSettings::SetGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config) {
m_general.install_dirs.value.clear();
for (const auto& dir : dirs_config) {
m_general.install_dirs.value.push_back({dir, true});
}
}
const std::vector<bool> EmulatorSettings::GetGameInstallDirsEnabled() {
std::vector<bool> enabled_dirs;
for (const auto& dir : m_general.install_dirs.value) {
enabled_dirs.push_back(dir.enabled);
}
return enabled_dirs;
}
std::filesystem::path EmulatorSettings::GetHomeDir() {
if (m_general.home_dir.value.empty()) {
return Common::FS::GetUserPath(Common::FS::PathType::HomeDir);
}
return m_general.home_dir.value;
}
void EmulatorSettings::SetHomeDir(const std::filesystem::path& dir) {
m_general.home_dir.value = dir;
}
std::filesystem::path EmulatorSettings::GetSysModulesDir() {
if (m_general.sys_modules_dir.value.empty()) {
return Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir);
}
return m_general.sys_modules_dir.value;
}
void EmulatorSettings::SetSysModulesDir(const std::filesystem::path& dir) {
m_general.sys_modules_dir.value = dir;
}
// --------------------
// Save
// --------------------
bool EmulatorSettings::Save(const std::string& serial) const {
try {
if (!serial.empty()) {
const std::filesystem::path cfgDir =
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs);
std::filesystem::create_directories(cfgDir);
const std::filesystem::path path = cfgDir / (serial + ".json");
json j = json::object();
// Only write overrideable fields for each group
json generalObj = json::object();
for (auto& item : m_general.GetOverrideableFields()) {
json whole = m_general;
if (whole.contains(item.key))
generalObj[item.key] = whole[item.key];
}
j["General"] = generalObj;
// Debug
json debugObj = json::object();
for (auto& item : m_debug.GetOverrideableFields()) {
json whole = m_debug;
if (whole.contains(item.key))
debugObj[item.key] = whole[item.key];
}
j["Debug"] = debugObj;
// Input
json inputObj = json::object();
for (auto& item : m_input.GetOverrideableFields()) {
json whole = m_input;
if (whole.contains(item.key))
inputObj[item.key] = whole[item.key];
}
j["Input"] = inputObj;
// Audio
json audioObj = json::object();
for (auto& item : m_audio.GetOverrideableFields()) {
json whole = m_audio;
if (whole.contains(item.key))
audioObj[item.key] = whole[item.key];
}
j["Audio"] = audioObj;
// GPU
json gpuObj = json::object();
for (auto& item : m_gpu.GetOverrideableFields()) {
json whole = m_gpu;
if (whole.contains(item.key))
gpuObj[item.key] = whole[item.key];
}
j["GPU"] = gpuObj;
// Vulkan
json vulkanObj = json::object();
for (auto& item : m_vulkan.GetOverrideableFields()) {
json whole = m_vulkan;
if (whole.contains(item.key))
vulkanObj[item.key] = whole[item.key];
}
j["Vulkan"] = vulkanObj;
std::ofstream out(path);
if (!out.is_open()) {
std::cerr << "Failed to open file for writing: " << path << std::endl;
return false;
}
out << std::setw(4) << j;
out.flush();
if (out.fail()) {
std::cerr << "Failed to write settings to: " << path << std::endl;
return false;
}
return true;
} else {
const std::filesystem::path path =
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.json";
json j;
j["General"] = m_general;
j["Debug"] = m_debug;
j["Input"] = m_input;
j["Audio"] = m_audio;
j["GPU"] = m_gpu;
j["Vulkan"] = m_vulkan;
j["Users"] = m_userManager.GetUsers();
std::ofstream out(path);
if (!out.is_open()) {
std::cerr << "Failed to open file for writing: " << path << std::endl;
return false;
}
out << std::setw(4) << j;
out.flush();
if (out.fail()) {
std::cerr << "Failed to write settings to: " << path << std::endl;
return false;
}
return true;
}
} catch (const std::exception& e) {
std::cerr << "Error saving settings: " << e.what() << std::endl;
return false;
}
}
// --------------------
// Load
// --------------------
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";
// 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
}
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();
}
// 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))
return false;
std::ifstream in(gamePath);
if (!in.is_open())
return false;
json gj;
in >> gj;
std::vector<std::string> changed;
if (gj.contains("General")) {
ApplyGroupOverrides<GeneralSettings>(m_general, gj.at("General"), changed);
}
if (gj.contains("Debug")) {
ApplyGroupOverrides<DebugSettings>(m_debug, gj.at("Debug"), changed);
}
if (gj.contains("Input")) {
ApplyGroupOverrides<InputSettings>(m_input, gj.at("Input"), changed);
}
if (gj.contains("Audio")) {
ApplyGroupOverrides<AudioSettings>(m_audio, gj.at("Audio"), changed);
}
if (gj.contains("GPU")) {
ApplyGroupOverrides<GPUSettings>(m_gpu, gj.at("GPU"), changed);
}
if (gj.contains("Vulkan")) {
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;
return false;
}
}
void EmulatorSettings::SetDefaultValues() {
m_general = GeneralSettings{};
m_debug = DebugSettings{};
m_input = InputSettings{};
m_audio = AudioSettings{};
m_gpu = GPUSettings{};
m_vulkan = VulkanSettings{};
}

View File

@@ -0,0 +1,362 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "common/types.h"
#include "core/user_manager.h"
// -------------------------------
// Generic Setting wrapper
// -------------------------------
template <typename T>
struct Setting {
T value{};
};
template <typename T>
void to_json(nlohmann::json& j, const Setting<T>& s) {
j = s.value;
}
template <typename T>
void from_json(const nlohmann::json& j, Setting<T>& s) {
s.value = j.get<T>();
}
// -------------------------------
// Helper to describe a per-field override action
// -------------------------------
struct OverrideItem {
const char* key;
// apply(basePtrToStruct, jsonEntry, changedFields)
std::function<void(void*, const nlohmann::json&, std::vector<std::string>&)> apply;
};
// 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;
Struct* obj = reinterpret_cast<Struct*>(base);
Setting<T>& dst = obj->*member;
Setting<T> tmp = entry.get<Setting<T>>();
if (dst.value != tmp.value) {
changed.push_back(std::string(key) + " ( " +
nlohmann::json(dst.value).dump() + "" +
nlohmann::json(tmp.value).dump() + " )");
}
dst.value = tmp.value;
}};
}
// -------------------------------
// Support types
// -------------------------------
struct GameInstallDir {
std::filesystem::path path;
bool enabled;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GameInstallDir, path, enabled)
// -------------------------------
// General settings
// -------------------------------
struct GeneralSettings {
Setting<std::vector<GameInstallDir>> install_dirs;
Setting<std::filesystem::path> addon_install_dir;
Setting<std::filesystem::path> home_dir;
Setting<std::filesystem::path> sys_modules_dir;
Setting<int> volume_slider{100};
Setting<bool> neo_mode{false};
Setting<bool> dev_kit_mode{false};
Setting<int> extra_dmem_in_mbytes{0};
Setting<bool> psn_signed_in{false};
Setting<bool> trophy_popup_disabled{false};
Setting<double> trophy_notification_duration{6.0};
Setting<std::string> log_filter{""};
Setting<std::string> log_type{"sync"};
Setting<bool> show_splash{false};
Setting<std::string> trophy_notification_side{"right"};
Setting<bool> connected_to_network{false};
Setting<bool> discord_rpc_enabled{false};
// return a vector of override descriptors (runtime, but tiny)
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
make_override<GeneralSettings>("volume_slider", &GeneralSettings::volume_slider),
make_override<GeneralSettings>("neo_mode", &GeneralSettings::neo_mode),
make_override<GeneralSettings>("dev_kit_mode", &GeneralSettings::dev_kit_mode),
make_override<GeneralSettings>("extra_dmem_in_mbytes",
&GeneralSettings::extra_dmem_in_mbytes),
make_override<GeneralSettings>("psn_signed_in", &GeneralSettings::psn_signed_in),
make_override<GeneralSettings>("trophy_popup_disabled",
&GeneralSettings::trophy_popup_disabled),
make_override<GeneralSettings>("trophy_notification_duration",
&GeneralSettings::trophy_notification_duration),
make_override<GeneralSettings>("log_filter", &GeneralSettings::log_filter),
make_override<GeneralSettings>("log_type", &GeneralSettings::log_type),
make_override<GeneralSettings>("show_splash", &GeneralSettings::show_splash),
make_override<GeneralSettings>("trophy_notification_side",
&GeneralSettings::trophy_notification_side),
make_override<GeneralSettings>("connected_to_network",
&GeneralSettings::connected_to_network)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir,
sys_modules_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)
// -------------------------------
// Debug settings
// -------------------------------
struct DebugSettings {
Setting<bool> separate_logging_enabled{false}; // specific
Setting<bool> debug_dump{false}; // specific
Setting<bool> shader_dump{false}; // specific
Setting<bool> fps_color{true};
Setting<bool> log_enabled{true}; // specific
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
make_override<DebugSettings>("debug_dump", &DebugSettings::debug_dump),
make_override<DebugSettings>("shader_dump", &DebugSettings::shader_dump),
make_override<DebugSettings>("separate_logging_enabled",
&DebugSettings::separate_logging_enabled),
make_override<DebugSettings>("log_enabled", &DebugSettings::log_enabled)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump, shader_dump,
fps_color, log_enabled)
// -------------------------------
// Input settings
// -------------------------------
enum HideCursorState : int { Never, Idle, Always };
struct InputSettings {
Setting<int> cursor_state{HideCursorState::Idle}; // specific
Setting<int> cursor_hide_timeout{5}; // specific
Setting<bool> use_special_pad{false};
Setting<int> special_pad_class{1};
Setting<bool> motion_controls_enabled{true}; // specific
Setting<bool> use_unified_Input_Config{true};
Setting<std::string> default_controller_id{""};
Setting<bool> background_controller_input{false}; // specific
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
make_override<InputSettings>("cursor_state", &InputSettings::cursor_state),
make_override<InputSettings>("cursor_hide_timeout",
&InputSettings::cursor_hide_timeout),
make_override<InputSettings>("motion_controls_enabled",
&InputSettings::motion_controls_enabled),
make_override<InputSettings>("background_controller_input",
&InputSettings::background_controller_input)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings, cursor_state, cursor_hide_timeout,
use_special_pad, special_pad_class, motion_controls_enabled,
use_unified_Input_Config, default_controller_id,
background_controller_input)
// -------------------------------
// Audio settings
// -------------------------------
struct AudioSettings {
Setting<std::string> mic_device{"Default Device"};
Setting<std::string> main_output_device{"Default Device"};
Setting<std::string> padSpk_output_device{"Default Device"};
// TODO add overrides
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AudioSettings, mic_device, main_output_device,
padSpk_output_device)
// -------------------------------
// GPU settings
// -------------------------------
struct GPUSettings {
Setting<u32> window_width{1280};
Setting<u32> window_height{720};
Setting<u32> internal_screen_width{1280};
Setting<u32> internal_screen_height{720};
Setting<bool> null_gpu{false};
Setting<bool> should_copy_gpu_buffers{false};
Setting<bool> readbacks_enabled{false};
Setting<bool> readback_linear_images_enabled{false};
Setting<bool> direct_memory_access_enabled{false};
Setting<bool> should_dump_shaders{false};
Setting<bool> should_patch_shaders{false};
Setting<u32> vblank_frequency{60};
Setting<bool> full_screen{false};
Setting<std::string> full_screen_mode{"Windowed"};
Setting<std::string> present_mode{"Mailbox"};
Setting<bool> hdr_allowed{false};
Setting<bool> fsr_enabled{false};
Setting<bool> rcas_enabled{true};
Setting<int> rcas_attenuation{250};
// TODO add overrides
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
make_override<GPUSettings>("null_gpu", &GPUSettings::null_gpu),
make_override<GPUSettings>("full_screen", &GPUSettings::full_screen),
make_override<GPUSettings>("present_mode", &GPUSettings::present_mode)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings, window_width, window_height, internal_screen_width,
internal_screen_height, null_gpu, should_copy_gpu_buffers,
readbacks_enabled, readback_linear_images_enabled,
direct_memory_access_enabled, should_dump_shaders,
should_patch_shaders, vblank_frequency, full_screen,
full_screen_mode, present_mode, hdr_allowed, fsr_enabled,
rcas_enabled, rcas_attenuation)
// -------------------------------
// Vulkan settings
// -------------------------------
struct VulkanSettings {
Setting<s32> gpu_id{-1};
Setting<bool> full_screen{false};
// TODO
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
make_override<VulkanSettings>("gpu_id", &VulkanSettings::gpu_id),
make_override<VulkanSettings>("full_screen", &VulkanSettings::full_screen)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id, full_screen)
// -------------------------------
// User settings
// -------------------------------
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, user_id, user_color, user_name, controller_port)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Users, default_user_id, user)
// -------------------------------
// Main manager
// -------------------------------
class EmulatorSettings {
public:
EmulatorSettings();
~EmulatorSettings();
static std::shared_ptr<EmulatorSettings> GetInstance();
static void SetInstance(std::shared_ptr<EmulatorSettings> instance);
bool Save(const std::string& serial = "") const;
bool Load(const std::string& serial = "");
void SetDefaultValues();
// general accessors
bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
std::vector<std::filesystem::path> GetGameInstallDirs() const;
void SetAllGameInstallDirs(const std::vector<GameInstallDir>& dirs);
void RemoveGameInstallDir(const std::filesystem::path& dir);
void SetGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
void SetGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
const std::vector<bool> GetGameInstallDirsEnabled();
const std::vector<GameInstallDir>& GetAllGameInstallDirs() const;
std::filesystem::path GetHomeDir();
void SetHomeDir(const std::filesystem::path& dir);
std::filesystem::path GetSysModulesDir();
void SetSysModulesDir(const std::filesystem::path& dir);
// user helpers
UserManager& GetUserManager() {
return m_userManager;
}
private:
GeneralSettings m_general{};
DebugSettings m_debug{};
InputSettings m_input{};
AudioSettings m_audio{};
GPUSettings m_gpu{};
VulkanSettings m_vulkan{};
UserManager m_userManager;
static std::shared_ptr<EmulatorSettings> s_instance;
static std::mutex s_mutex;
// Generic helper that applies override descriptors for a specific group
template <typename Group>
void ApplyGroupOverrides(Group& group, const nlohmann::json& groupJson,
std::vector<std::string>& changed) {
for (auto& item : group.GetOverrideableFields()) {
if (!groupJson.contains(item.key))
continue;
item.apply(&group, groupJson.at(item.key), changed);
}
}
static void PrintChangedSummary(const std::vector<std::string>& changed);
public:
#define SETTING_FORWARD(group, Name, field) \
auto Get##Name() const { \
return group.field.value; \
} \
void Set##Name(const decltype(group.field.value)& v) { \
group.field.value = v; \
}
#define SETTING_FORWARD_BOOL(group, Name, field) \
auto Is##Name() const { \
return group.field.value; \
} \
void Set##Name(const decltype(group.field.value)& v) { \
group.field.value = v; \
}
// General settings
SETTING_FORWARD(m_general, VolumeSlider, volume_slider)
SETTING_FORWARD_BOOL(m_general, Neo, neo_mode)
SETTING_FORWARD_BOOL(m_general, DevKit, dev_kit_mode)
SETTING_FORWARD(m_general, ExtraDmemInMBytes, extra_dmem_in_mbytes)
SETTING_FORWARD_BOOL(m_general, PSNSignedIn, psn_signed_in)
SETTING_FORWARD_BOOL(m_general, TrophyPopupDisabled, trophy_popup_disabled)
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(m_general, AddonInstallDir, addon_install_dir)
SETTING_FORWARD(m_general, LogFilter, log_filter)
SETTING_FORWARD(m_general, LogType, log_type)
SETTING_FORWARD_BOOL(m_general, ConnectedToNetwork, connected_to_network)
SETTING_FORWARD_BOOL(m_general, DiscorRPCEnabled, discord_rpc_enabled)
// Debug settings
SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled)
SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump)
SETTING_FORWARD_BOOL(m_debug, ShaderDump, shader_dump)
SETTING_FORWARD_BOOL(m_debug, LogEnabled, log_enabled)
// GPU Settings
SETTING_FORWARD_BOOL(m_gpu, NullGPU, null_gpu)
SETTING_FORWARD(m_gpu, FullScreenMode, full_screen_mode)
SETTING_FORWARD(m_gpu, PresentMode, present_mode)
// Vulkan settings
SETTING_FORWARD(m_vulkan, GpuId, gpu_id)
SETTING_FORWARD_BOOL(m_vulkan, FullScreen, full_screen)
#undef SETTING_FORWARD
#undef SETTING_FORWARD_BOOL
};

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
@@ -8,6 +8,7 @@
#include "common/config.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/file_format/psf.h"
#include "core/file_sys/fs.h"
#include "core/libraries/app_content/app_content_error.h"
@@ -57,7 +58,7 @@ int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
OrbisAppContentMountPoint* mount_point) {
LOG_INFO(Lib_AppContent, "called");
const auto& addon_path = Config::getAddonInstallDir() / title_id;
const auto& addon_path = EmulatorSettings::GetInstance()->GetAddonInstallDir() / title_id;
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
// Determine which loaded additional content this entitlement label is for.
@@ -282,7 +283,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
LOG_ERROR(Lib_AppContent, "(DUMMY) called");
auto* param_sfo = Common::Singleton<PSF>::Instance();
const auto addons_dir = Config::getAddonInstallDir();
const auto addons_dir = EmulatorSettings::GetInstance()->GetAddonInstallDir();
if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) {
title_id = *value;
} else {

View File

@@ -4,6 +4,7 @@
#include "common/config.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/process.h"
@@ -17,19 +18,19 @@ s32 PS4_SYSV_ABI sceKernelIsInSandbox() {
}
s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
return Config::isNeoModeConsole() &&
return EmulatorSettings::GetInstance()->IsNeo() &&
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
}
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
return Config::isNeoModeConsole();
return EmulatorSettings::GetInstance()->IsNeo();
}
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
// These hardcoded values are based on hardware observations.
// Different models of PS4/PS4 Pro likely return slightly different values.
LOG_DEBUG(Lib_Kernel, "called");
if (Config::isNeoModeConsole()) {
if (EmulatorSettings::GetInstance()->IsNeo()) {
return 0x740f30;
}
return 0x710f10;

View File

@@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/config.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/network/net_ctl_codes.h"
#include "core/libraries/network/net_ctl_obj.h"
#include "core/tls.h"
@@ -46,8 +47,9 @@ s32 NetCtlInternal::RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit fu
void NetCtlInternal::CheckCallback() {
std::scoped_lock lock{m_mutex};
const auto event = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
const auto event = EmulatorSettings::GetInstance()->IsConnectedToNetwork()
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
for (const auto [func, arg] : callbacks) {
if (func != nullptr) {
Core::ExecuteGuest(func, event, arg);
@@ -57,8 +59,9 @@ void NetCtlInternal::CheckCallback() {
void NetCtlInternal::CheckNpToolkitCallback() {
std::scoped_lock lock{m_mutex};
const auto event = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
const auto event = EmulatorSettings::GetInstance()->IsConnectedToNetwork()
? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED
: ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED;
for (const auto [func, arg] : nptool_callbacks) {
if (func != nullptr) {
Core::ExecuteGuest(func, event, arg);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef WIN32
@@ -15,6 +15,7 @@
#include <common/singleton.h>
#include "common/config.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/net_ctl_codes.h"
@@ -162,7 +163,7 @@ int PS4_SYSV_ABI sceNetCtlGetIfStat() {
int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) {
LOG_DEBUG(Lib_NetCtl, "code = {}", code);
if (!Config::getIsConnectedToNetwork()) {
if (!EmulatorSettings::GetInstance()->IsConnectedToNetwork()) {
return ORBIS_NET_CTL_ERROR_NOT_CONNECTED;
}
@@ -180,8 +181,9 @@ int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) {
info->mtu = 1500; // default value
break;
case ORBIS_NET_CTL_INFO_LINK:
info->link = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_LINK_CONNECTED
: ORBIS_NET_CTL_LINK_DISCONNECTED;
info->link = EmulatorSettings::GetInstance()->IsConnectedToNetwork()
? ORBIS_NET_CTL_LINK_CONNECTED
: ORBIS_NET_CTL_LINK_DISCONNECTED;
break;
case ORBIS_NET_CTL_INFO_IP_ADDRESS: {
strcpy(info->ip_address,
@@ -318,7 +320,7 @@ int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt() {
}
int PS4_SYSV_ABI sceNetCtlGetState(int* state) {
const auto connected = Config::getIsConnectedToNetwork();
const auto connected = EmulatorSettings::GetInstance()->IsConnectedToNetwork();
LOG_DEBUG(Lib_NetCtl, "connected = {}", connected);
const auto current_state =
connected ? ORBIS_NET_CTL_STATE_IPOBTAINED : ORBIS_NET_CTL_STATE_DISCONNECTED;

View File

@@ -4,6 +4,7 @@
#include <mutex>
#include "common/config.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_auth.h"
@@ -361,7 +362,7 @@ s32 PS4_SYSV_ABI sceNpAuthDeleteRequest(s32 req_id) {
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
g_signed_in = Config::getPSNSignedIn();
g_signed_in = EmulatorSettings::GetInstance()->IsPSNSignedIn();
LIB_FUNCTION("6bwFkosYRQg", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateRequest);
LIB_FUNCTION("N+mr7GjTvr8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateAsyncRequest);

View File

@@ -5,6 +5,7 @@
#include "common/config.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
@@ -626,7 +627,8 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use
return ORBIS_NP_ERROR_SIGNED_OUT;
}
memset(np_id, 0, sizeof(OrbisNpId));
strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data));
strncpy(np_id->handle.data, Config::getUserName(user_id - 1).c_str(),
sizeof(np_id->handle.data));
return ORBIS_OK;
}
@@ -640,7 +642,7 @@ s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId
return ORBIS_NP_ERROR_SIGNED_OUT;
}
memset(online_id, 0, sizeof(OrbisNpOnlineId));
strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data));
strncpy(online_id->data, Config::getUserName(user_id - 1).c_str(), sizeof(online_id->data));
return ORBIS_OK;
}
@@ -702,7 +704,7 @@ s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpT
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
g_signed_in = Config::getPSNSignedIn();
g_signed_in = EmulatorSettings::GetInstance()->IsPSNSignedIn();
LIB_FUNCTION("GpLQDNKICac", "libSceNpManager", 1, "libSceNpManager", sceNpCreateRequest);
LIB_FUNCTION("eiqMCt9UshI", "libSceNpManager", 1, "libSceNpManager", sceNpCreateAsyncRequest);

View File

@@ -7,15 +7,11 @@
#include <mutex>
#include <cmrc/cmrc.hpp>
#include <imgui.h>
#ifdef ENABLE_QT_GUI
#include <qt_gui/background_music_player.h>
#endif
#include "common/assert.h"
#include "common/config.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/libraries/np/trophy_ui.h"
#include "imgui/imgui_std.h"
@@ -36,9 +32,9 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin
const std::string_view& rarity)
: trophy_name(trophyName), trophy_type(rarity) {
side = Config::sideTrophy();
side = EmulatorSettings::GetInstance()->GetTrophyNotificationSide();
trophy_timer = Config::getTrophyNotificationDuration();
trophy_timer = EmulatorSettings::GetInstance()->GetTrophyNotificationDuration();
if (std::filesystem::exists(trophyIconPath)) {
trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath);
@@ -284,7 +280,7 @@ void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::st
const std::string_view& rarity) {
std::lock_guard<std::mutex> lock(queueMtx);
if (Config::getisTrophyPopupDisabled()) {
if (EmulatorSettings::GetInstance()->IsTrophyPopupDisabled()) {
return;
} else if (current_trophy_ui.has_value()) {
current_trophy_ui.reset();

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
@@ -274,7 +274,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
g_opened = true;
scePadResetLightBar(userId);
scePadResetOrientation(userId);
return 1; // dummy
return userId; // TODO: userId shouldn't be used as the handle too
}
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
@@ -287,7 +287,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
}
return 1; // dummy
return userId; // dummy
}
int PS4_SYSV_ABI scePadOpenExt2() {
@@ -301,12 +301,16 @@ int PS4_SYSV_ABI scePadOutputReport() {
}
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
LOG_TRACE(Lib_Pad, "called");
LOG_TRACE(Lib_Pad, "handle: {}", handle);
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
int connected_count = 0;
bool connected = false;
Input::State states[64];
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
auto const& controller = controllers[*controller_id];
int ret_num = controller->ReadStates(states, num, &connected, &connected_count);
if (!connected) {
@@ -321,9 +325,6 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].rightStick.y = states[i].axes[static_cast<int>(Input::Axis::RightY)];
pData[i].analogButtons.l2 = states[i].axes[static_cast<int>(Input::Axis::TriggerLeft)];
pData[i].analogButtons.r2 = states[i].axes[static_cast<int>(Input::Axis::TriggerRight)];
pData[i].acceleration.x = states[i].acceleration.x;
pData[i].acceleration.y = states[i].acceleration.y;
pData[i].acceleration.z = states[i].acceleration.z;
pData[i].angularVelocity.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
@@ -335,8 +336,8 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (handle == 1) {
const auto gyro_poll_rate = controller->accel_poll_rate;
if (gyro_poll_rate != 0.0f) {
auto now = std::chrono::steady_clock::now();
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
@@ -431,24 +432,34 @@ int PS4_SYSV_ABI scePadReadHistory() {
}
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
LOG_TRACE(Lib_Pad, "called");
if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE) {
LOG_TRACE(Lib_Pad, "handle: {}", handle);
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int connectedCount = 0;
bool isConnected = false;
Input::State state;
auto const& controller = controllers[*controller_id];
controller->ReadState(&state, &isConnected, &connectedCount);
pData->buttons = state.buttonsState;
pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)];
pData->leftStick.y = state.axes[static_cast<int>(Input::Axis::LeftY)];
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
pData->rightStick.y = state.axes[static_cast<int>(Input::Axis::RightY)];
pData->analogButtons.l2 = state.axes[static_cast<int>(Input::Axis::TriggerLeft)];
pData->analogButtons.r2 = state.axes[static_cast<int>(Input::Axis::TriggerRight)];
auto getAxisValue = [&state, &controller](Input::Axis a) {
auto i = static_cast<int>(a);
if (controller->axis_smoothing_ticks[i] > 0) {
--controller->axis_smoothing_ticks[i];
return (state.axes[i] + controller->axis_smoothing_values[i]) / 2;
}
return state.axes[i];
};
pData->leftStick.x = getAxisValue(Input::Axis::LeftX);
pData->leftStick.y = getAxisValue(Input::Axis::LeftY);
pData->rightStick.x = getAxisValue(Input::Axis::RightX);
pData->rightStick.y = getAxisValue(Input::Axis::RightY);
pData->analogButtons.l2 = getAxisValue(Input::Axis::TriggerLeft);
pData->analogButtons.r2 = getAxisValue(Input::Axis::TriggerRight);
pData->acceleration.x = state.acceleration.x * 0.098;
pData->acceleration.y = state.acceleration.y * 0.098;
pData->acceleration.z = state.acceleration.z * 0.098;
@@ -457,21 +468,19 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
pData->angularVelocity.z = state.angularVelocity.z;
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f};
// Only do this on handle 1 for now
if (engine && handle == 1) {
auto now = std::chrono::steady_clock::now();
float deltaTime =
std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
.count() /
1000000.0f;
controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
lastOrientation, outputOrientation);
pData->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
}
auto now = std::chrono::steady_clock::now();
float deltaTime =
std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
.count() /
1000000.0f;
controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
lastOrientation, outputOrientation);
pData->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
@@ -536,12 +545,13 @@ int PS4_SYSV_ABI scePadReadStateExt() {
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
LOG_INFO(Lib_Pad, "(DUMMY) called");
if (handle != 1) {
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto* controller = Common::Singleton<GameController>::Instance();
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int* rgb = Config::GetControllerCustomColor();
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
controllers[*controller_id]->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
return ORBIS_OK;
}
@@ -558,7 +568,8 @@ int PS4_SYSV_ABI scePadResetLightBarAllByPortType() {
int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle);
if (handle != 1) {
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
@@ -614,6 +625,10 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
if (Config::GetOverrideControllerColor()) {
return ORBIS_OK;
}
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
if (pParam != nullptr) {
LOG_DEBUG(Lib_Pad, "called handle = {} rgb = {} {} {}", handle, pParam->r, pParam->g,
pParam->b);
@@ -623,7 +638,7 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
}
auto* controller = Common::Singleton<GameController>::Instance();
auto* controller = Common::Singleton<Input::GameController>::Instance();
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
return ORBIS_OK;
}
@@ -691,7 +706,7 @@ int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pP
if (pParam != nullptr) {
LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
pParam->smallMotor, pParam->largeMotor);
auto* controller = Common::Singleton<GameController>::Instance();
auto* controller = Common::Singleton<Input::GameController>::Instance();
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
return ORBIS_OK;
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <iostream>
@@ -9,6 +9,7 @@
#include "common/config.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/file_sys/fs.h"
#include "save_backup.h"
#include "save_instance.h"
@@ -48,12 +49,14 @@ namespace Libraries::SaveData {
fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial;
return EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id) / "savedata" /
game_serial;
}
fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial,
std::string_view dir_name) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name;
return EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id) / "savedata" /
game_serial / dir_name;
}
uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) {

View File

@@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include <thread>
#include <vector>
#include <cstring>
#include <magic_enum/magic_enum.hpp>
@@ -15,6 +16,7 @@
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/string_util.h"
#include "core/emulator_settings.h"
#include "core/file_format/psf.h"
#include "core/file_sys/fs.h"
#include "core/libraries/error_codes.h"
@@ -439,7 +441,8 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
LOG_INFO(Lib_SaveData, "called with invalid block size");
}
const auto root_save = Config::GetSaveDataPath();
const auto root_save = EmulatorSettings::GetInstance()->GetHomeDir() /
std::to_string(mount_info->userId) / "savedata";
fs::create_directories(root_save);
const auto available = fs::space(root_save).available;
@@ -487,7 +490,9 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
return Error::PARAMETER;
}
LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view());
const std::string_view mount_point_str{mountPoint->data};
std::string mount_point_str = mountPoint->data.to_string();
for (auto& instance : g_mount_slots) {
if (instance.has_value()) {
const auto& slot_name = instance->GetMountPoint();

View File

@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include "common/config.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/file_sys/fs.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/systemservice.h"
@@ -18,7 +19,7 @@ std::queue<OrbisSystemServiceEvent> g_event_queue;
std::mutex g_event_queue_mutex;
bool IsSplashVisible() {
return Config::showSplash() && g_splash_status;
return EmulatorSettings::GetInstance()->IsShowSplash() && g_splash_status;
}
int PS4_SYSV_ABI sceAppMessagingClearEventFlag() {

View File

@@ -1,12 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <queue>
#include "common/config.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/system/userservice_error.h"
#include "core/tls.h"
#include "input/controller.h"
namespace Libraries::UserService {
@@ -105,15 +110,23 @@ int PS4_SYSV_ABI sceUserServiceGetDiscPlayerFlag() {
return ORBIS_OK;
}
std::queue<OrbisUserServiceEvent> user_service_event_queue = {};
void AddUserServiceEvent(const OrbisUserServiceEvent e) {
LOG_DEBUG(Lib_UserService, "Event added to queue: {} {}", (u8)e.event, e.userId);
user_service_event_queue.push(e);
}
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
LOG_TRACE(Lib_UserService, "(DUMMY) called");
// fake a loggin event
static bool logged_in = false;
if (!logged_in) {
logged_in = true;
event->event = OrbisUserServiceEventType::Login;
event->userId = 1;
if (!user_service_event_queue.empty()) {
OrbisUserServiceEvent& temp = user_service_event_queue.front();
event->event = temp.event;
event->userId = temp.userId;
user_service_event_queue.pop();
LOG_INFO(Lib_UserService, "Event processed by the game: {} {}", (u8)temp.event,
temp.userId);
return ORBIS_OK;
}
@@ -567,17 +580,22 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
}
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
LOG_DEBUG(Lib_UserService, "called");
// LOG_DEBUG(Lib_UserService, "called");
if (userIdList == nullptr) {
LOG_ERROR(Lib_UserService, "user_id is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
// TODO only first user, do the others as well
userIdList->user_id[0] = 1;
userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID;
userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID;
userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID;
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int li = 0;
for (int ci = 0; ci < 4; ci++) {
if (controllers[ci]->user_id != -1) {
userIdList->user_id[li++] = controllers[ci]->user_id;
}
}
for (; li < 4; li++) {
userIdList->user_id[li] = -1;
}
return ORBIS_OK;
}
@@ -1048,7 +1066,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserCol
LOG_ERROR(Lib_UserService, "color is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
*color = OrbisUserServiceUserColor::Blue;
*color = (OrbisUserServiceUserColor)(user_id - 1);
return ORBIS_OK;
}
@@ -1068,12 +1086,12 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() {
}
s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) {
LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size);
LOG_DEBUG(Lib_UserService, "called user_id = {}, size = {} ", user_id, size);
if (user_name == nullptr) {
LOG_ERROR(Lib_UserService, "user_name is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
std::string name = Config::getUserName();
std::string name = Config::getUserName(user_id - 1);
if (size < name.length()) {
LOG_ERROR(Lib_UserService, "buffer is too short");
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// reference :
// https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/user.h
@@ -57,6 +57,8 @@ struct OrbisUserServiceEvent {
OrbisUserServiceUserId userId;
};
void AddUserServiceEvent(const OrbisUserServiceEvent e);
int PS4_SYSV_ABI sceUserServiceInitializeForShellCore();
int PS4_SYSV_ABI sceUserServiceTerminateForShellCore();
int PS4_SYSV_ABI sceUserServiceDestroyUser();

View File

@@ -13,6 +13,7 @@
#include "core/aerolib/aerolib.h"
#include "core/aerolib/stubs.h"
#include "core/devtools/widget/module_list.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/threads.h"
@@ -56,7 +57,7 @@ Linker::Linker() : memory{Memory::Instance()} {}
Linker::~Linker() = default;
void Linker::Execute(const std::vector<std::string>& args) {
if (Config::debugDump()) {
if (EmulatorSettings::GetInstance()->IsDebugDump()) {
DebugDump();
}

View File

@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/config.h"
#include "common/debug.h"
#include "core/emulator_settings.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/orbis_error.h"
@@ -33,11 +34,11 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1
bool use_extended_mem2) {
const bool is_neo = ::Libraries::Kernel::sceKernelIsNeoMode();
auto total_size = is_neo ? ORBIS_KERNEL_TOTAL_MEM_PRO : ORBIS_KERNEL_TOTAL_MEM;
if (Config::isDevKitConsole()) {
if (EmulatorSettings::GetInstance()->IsDevKit()) {
total_size = is_neo ? ORBIS_KERNEL_TOTAL_MEM_DEV_PRO : ORBIS_KERNEL_TOTAL_MEM_DEV;
}
s32 extra_dmem = Config::getExtraDmemInMbytes();
if (Config::getExtraDmemInMbytes() != 0) {
s32 extra_dmem = EmulatorSettings::GetInstance()->GetExtraDmemInMBytes();
if (extra_dmem != 0) {
LOG_WARNING(Kernel_Vmm,
"extraDmemInMbytes is {} MB! Old Direct Size: {:#x} -> New Direct Size: {:#x}",
extra_dmem, total_size, total_size + extra_dmem * 1_MB);

130
src/core/user_manager.cpp Normal file
View File

@@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <filesystem>
#include <iostream>
#include <common/path_util.h>
#include "emulator_settings.h"
#include "user_manager.h"
bool UserManager::AddUser(const User& user) {
for (const auto& u : m_users.user) {
if (u.user_id == user.user_id)
return false; // already exists
}
m_users.user.push_back(user);
// Create user home directory and subfolders
const auto user_dir =
EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user.user_id);
std::error_code ec;
if (!std::filesystem::exists(user_dir)) {
std::filesystem::create_directory(user_dir, ec);
std::filesystem::create_directory(user_dir / "savedata", ec);
std::filesystem::create_directory(user_dir / "trophy", ec);
std::filesystem::create_directory(user_dir / "trophy/data", ec);
}
return true;
}
bool UserManager::RemoveUser(s32 user_id) {
auto it = std::remove_if(m_users.user.begin(), m_users.user.end(),
[user_id](const User& u) { return u.user_id == user_id; });
if (it == m_users.user.end())
return false; // not found
const auto user_dir = EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id);
if (std::filesystem::exists(user_dir)) {
std::error_code ec;
std::filesystem::remove_all(user_dir, ec);
}
m_users.user.erase(it, m_users.user.end());
return true;
}
bool UserManager::RenameUser(s32 user_id, const std::string& new_name) {
// Find user in the internal list
for (auto& user : m_users.user) {
if (user.user_id == user_id) {
if (user.user_name == new_name)
return true; // no change
user.user_name = new_name;
return true;
}
}
return false;
}
User* UserManager::GetUserByID(s32 user_id) {
for (auto& u : m_users.user) {
if (u.user_id == user_id)
return &u;
}
return nullptr;
}
const std::vector<User>& UserManager::GetAllUsers() const {
return m_users.user;
}
std::vector<User> UserManager::CreateDefaultUser() {
User default_user;
default_user.user_id = 1;
default_user.user_color = 0; // BLUE
default_user.user_name = "shadPS4";
default_user.controller_port = 1;
const auto user_dir =
EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(default_user.user_id);
if (!std::filesystem::exists(user_dir)) {
std::filesystem::create_directory(user_dir);
std::filesystem::create_directory(user_dir / "savedata");
std::filesystem::create_directory(user_dir / "trophy");
std::filesystem::create_directory(user_dir / "trophy/data");
}
return {default_user};
}
bool UserManager::SetDefaultUser(u32 user_id) {
auto it = std::find_if(m_users.user.begin(), m_users.user.end(),
[user_id](const User& u) { return u.user_id == user_id; });
if (it == m_users.user.end())
return false;
m_users.default_user_id = user_id;
SetControllerPort(user_id, 1); // Set default user to port 1
return true;
}
void UserManager::SetControllerPort(u32 user_id, int port) {
for (auto& u : m_users.user) {
if (u.user_id != user_id && u.controller_port == port)
u.controller_port = -1;
if (u.user_id == user_id)
u.controller_port = port;
}
}
// Returns a list of users that have valid home directories
std::vector<User> UserManager::GetValidUsers() const {
std::vector<User> result;
result.reserve(m_users.user.size());
const auto home_dir = EmulatorSettings::GetInstance()->GetHomeDir();
for (const auto& user : m_users.user) {
const auto user_dir = home_dir / std::to_string(user.user_id);
if (std::filesystem::exists(user_dir)) {
result.push_back(user);
}
}
return result;
}

45
src/core/user_manager.h Normal file
View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include "common/types.h"
struct User {
s32 user_id;
u32 user_color;
std::string user_name;
int controller_port; // 1<>4
};
struct Users {
int default_user_id = 1;
std::vector<User> user;
};
class UserManager {
public:
UserManager() = default;
bool AddUser(const User& user);
bool RemoveUser(s32 user_id);
bool RenameUser(s32 user_id, const std::string& new_name);
User* GetUserByID(s32 user_id);
const std::vector<User>& GetAllUsers() const;
std::vector<User> CreateDefaultUser();
bool SetDefaultUser(u32 user_id);
void SetControllerPort(u32 user_id, int port);
std::vector<User> GetValidUsers() const;
Users& GetUsers() {
return m_users;
}
const Users& GetUsers() const {
return m_users;
}
private:
Users m_users;
};

View File

@@ -27,6 +27,7 @@
#include "common/singleton.h"
#include "core/debugger.h"
#include "core/devtools/widget/module_list.h"
#include "core/emulator_settings.h"
#include "core/file_format/psf.h"
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
@@ -181,7 +182,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
true);
// Initialize logging as soon as possible
if (!id.empty() && Config::getSeparateLogFilesEnabled()) {
if (!id.empty() && EmulatorSettings::GetInstance()->IsSeparateLoggingEnabled()) {
Common::Log::Initialize(id + ".log");
} else {
Common::Log::Initialize();
@@ -203,19 +204,20 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"));
LOG_INFO(Config, "Game-specific config exists: {}", has_game_config);
LOG_INFO(Config, "General LogType: {}", Config::getLogType());
LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole());
LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole());
LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork());
LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn());
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
LOG_INFO(Config, "General LogType: {}", EmulatorSettings::GetInstance()->GetLogType());
LOG_INFO(Config, "General isNeo: {}", EmulatorSettings::GetInstance()->IsNeo());
LOG_INFO(Config, "General isDevKit: {}", EmulatorSettings::GetInstance()->IsDevKit());
LOG_INFO(Config, "General isConnectedToNetwork: {}",
EmulatorSettings::GetInstance()->IsConnectedToNetwork());
LOG_INFO(Config, "General isPsnSignedIn: {}", EmulatorSettings::GetInstance()->IsPSNSignedIn());
LOG_INFO(Config, "GPU isNullGpu: {}", EmulatorSettings::GetInstance()->IsNullGPU());
LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks());
LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages());
LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess());
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
LOG_INFO(Config, "GPU vblankFrequency: {}", Config::vblankFreq());
LOG_INFO(Config, "GPU shouldCopyGPUBuffers: {}", Config::copyGPUCmdBuffers());
LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId());
LOG_INFO(Config, "Vulkan gpuId: {}", EmulatorSettings::GetInstance()->GetGpuId());
LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled());
LOG_INFO(Config, "Vulkan vkValidationCore: {}", Config::vkValidationCoreEnabled());
LOG_INFO(Config, "Vulkan vkValidationSync: {}", Config::vkValidationSyncEnabled());
@@ -258,7 +260,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
// Initialize components
memory = Core::Memory::Instance();
controller = Common::Singleton<Input::GameController>::Instance();
controllers = Common::Singleton<Input::GameControllers>::Instance();
linker = Common::Singleton<Core::Linker>::Instance();
// Load renderdoc module
@@ -300,7 +302,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
window = std::make_unique<Frontend::WindowSDL>(
Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title);
Config::getWindowWidth(), Config::getWindowHeight(), controllers, window_title);
g_window = window.get();
@@ -358,7 +360,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
#ifdef ENABLE_DISCORD_RPC
// Discord RPC
if (Config::getEnableDiscordRPC()) {
if (EmulatorSettings::GetInstance()->IsDiscorRPCEnabled()) {
auto* rpc = Common::Singleton<DiscordRPCHandler::RPC>::Instance();
if (rpc->getRPCEnabled() == false) {
rpc->init();
@@ -509,7 +511,7 @@ void Emulator::LoadSystemModules(const std::string& game_serial) {
{"libSceFreeTypeOt.sprx", nullptr}});
std::vector<std::filesystem::path> found_modules;
const auto& sys_module_path = Config::getSysModulesPath();
const auto& sys_module_path = EmulatorSettings::GetInstance()->GetSysModulesDir();
for (const auto& entry : std::filesystem::directory_iterator(sys_module_path)) {
found_modules.push_back(entry.path());
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -43,7 +43,7 @@ private:
void LoadSystemModules(const std::string& game_serial);
Core::MemoryManager* memory;
Input::GameController* controller;
Input::GameControllers* controllers;
Core::Linker* linker;
std::unique_ptr<Frontend::WindowSDL> window;
std::chrono::steady_clock::time_point start_time;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on imgui_impl_sdl3.cpp from Dear ImGui repository
@@ -737,9 +737,8 @@ static void UpdateGamepads() {
ImGuiIO& io = ImGui::GetIO();
SdlData* bd = GetBackendData();
auto controller = Common::Singleton<Input::GameController>::Instance();
auto engine = controller->GetEngine();
SDL_Gamepad* SDLGamepad = engine->m_gamepad;
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
SDL_Gamepad* SDLGamepad = controllers[0]->m_sdl_gamepad;
// Update list of gamepads to use
if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
if (SDLGamepad) {

View File

@@ -1,66 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_set>
#include <SDL3/SDL.h>
#include <common/singleton.h>
#include "common/config.h"
#include "common/logging/log.h"
#include "controller.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "input/controller.h"
static std::string SelectedGamepad = "";
namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
if (isPressed) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
}
void State::OnAxis(Axis axis, int value) {
const auto toggle = [&](const auto button) {
if (value > 0) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
};
switch (axis) {
case Axis::TriggerLeft:
toggle(OrbisPadButtonDataOffset::L2);
break;
case Axis::TriggerRight:
toggle(OrbisPadButtonDataOffset::R2);
break;
default:
break;
}
axes[static_cast<int>(axis)] = value;
}
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
touchpad[touchIndex].state = isDown;
touchpad[touchIndex].x = static_cast<u16>(x * 1920);
touchpad[touchIndex].y = static_cast<u16>(y * 941);
}
void State::OnGyro(const float gyro[3]) {
angularVelocity.x = gyro[0];
angularVelocity.y = gyro[1];
angularVelocity.z = gyro[2];
}
void State::OnAccel(const float accel[3]) {
acceleration.x = accel[0];
acceleration.y = accel[1];
acceleration.z = accel[2];
}
GameController::GameController() {
m_states_num = 0;
m_last_state = State();
@@ -110,7 +65,8 @@ State GameController::GetLastState() const {
return m_last_state;
}
const u32 last = (m_first_state + m_states_num - 1) % MAX_STATES;
return m_states[last];
auto copy = m_states[last];
return copy;
}
void GameController::AddState(const State& state) {
@@ -126,22 +82,50 @@ void GameController::AddState(const State& state) {
m_states_num++;
}
void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
void GameController::CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button,
bool is_pressed) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnButton(button, is_pressed);
if (is_pressed) {
state.buttonsState |= button;
} else {
state.buttonsState &= ~button;
}
AddState(state);
}
void GameController::Axis(int id, Input::Axis axis, int value) {
using Libraries::Pad::OrbisPadButtonDataOffset;
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnAxis(axis, value);
int axis_id = static_cast<int>(axis);
if (std::abs(state.axes[axis_id] - value) > 120) {
LOG_DEBUG(Input, "Keyboard axis change detected");
axis_smoothing_ticks[axis_id] = GameController::max_smoothing_ticks;
axis_smoothing_values[axis_id] = state.axes[axis_id];
}
state.axes[axis_id] = value;
if (axis == Input::Axis::TriggerLeft) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::L2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::L2;
}
}
if (axis == Input::Axis::TriggerRight) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::R2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::R2;
}
}
AddState(state);
}
@@ -152,7 +136,9 @@ void GameController::Gyro(int id, const float gyro[3]) {
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the angular velocity (gyro data)
state.OnGyro(gyro);
state.angularVelocity.x = gyro[0]; // X-axis
state.angularVelocity.y = gyro[1]; // Y-axis
state.angularVelocity.z = gyro[2]; // Z-axis
AddState(state);
}
@@ -162,7 +148,9 @@ void GameController::Acceleration(int id, const float acceleration[3]) {
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the acceleration values
state.OnAccel(acceleration);
state.acceleration.x = acceleration[0]; // X-axis
state.acceleration.y = acceleration[1]; // Y-axis
state.acceleration.z = acceleration[2]; // Z-axis
AddState(state);
}
@@ -203,33 +191,116 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
}
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
if (!m_engine) {
return;
if (m_sdl_gamepad != nullptr) {
SDL_SetGamepadLED(m_sdl_gamepad, r, g, b);
}
std::scoped_lock _{m_mutex};
m_engine->SetLightBarRGB(r, g, b);
}
void GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (!m_engine) {
return;
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_sdl_gamepad != nullptr) {
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
(largeMotor / 255.0f) * 0xFFFF, -1);
}
std::scoped_lock _{m_mutex};
m_engine->SetVibration(smallMotor, largeMotor);
return true;
}
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnTouchpad(touchIndex, touchDown, x, y);
state.touchpad[touchIndex].state = touchDown;
state.touchpad[touchIndex].x = static_cast<u16>(x * 1920);
state.touchpad[touchIndex].y = static_cast<u16>(y * 941);
AddState(state);
}
}
bool is_first_check = true;
void GameControllers::TryOpenSDLControllers(GameControllers& controllers) {
using namespace Libraries::UserService;
int controller_count;
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
std::unordered_set<SDL_JoystickID> assigned_ids;
std::array<bool, 4> slot_taken{false, false, false, false};
for (int i = 0; i < 4; i++) {
SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad;
if (pad) {
SDL_JoystickID id = SDL_GetGamepadID(pad);
bool still_connected = false;
for (int j = 0; j < controller_count; j++) {
if (new_joysticks[j] == id) {
still_connected = true;
assigned_ids.insert(id);
slot_taken[i] = true;
break;
}
}
if (!still_connected) {
AddUserServiceEvent({OrbisUserServiceEventType::Logout, i + 1});
SDL_CloseGamepad(pad);
controllers[i]->m_sdl_gamepad = nullptr;
controllers[i]->user_id = -1;
slot_taken[i] = false;
} else {
controllers[i]->player_index = i;
}
}
}
for (int j = 0; j < controller_count; j++) {
SDL_JoystickID id = new_joysticks[j];
if (assigned_ids.contains(id))
continue;
SDL_Gamepad* pad = SDL_OpenGamepad(id);
if (!pad)
continue;
for (int i = 0; i < 4; i++) {
if (!slot_taken[i]) {
auto* c = controllers[i];
c->m_sdl_gamepad = pad;
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
SDL_GetGamepadID(pad));
c->user_id = i + 1;
slot_taken[i] = true;
c->player_index = i;
AddUserServiceEvent({OrbisUserServiceEventType::Login, i + 1});
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
c->gyro_poll_rate =
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO);
LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}",
c->user_id);
}
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
c->accel_poll_rate =
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL);
LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}",
c->user_id);
}
break;
}
}
}
if (is_first_check) [[unlikely]] {
is_first_check = false;
if (controller_count == 0) {
controllers[0]->user_id = 1;
AddUserServiceEvent({OrbisUserServiceEventType::Login, 1});
}
}
}
u8 GameController::GetTouchCount() {
std::scoped_lock lock{m_mutex};
return m_touch_count;
@@ -293,21 +364,9 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
m_last_update = lastUpdate;
}
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
std::scoped_lock _{m_mutex};
m_engine = std::move(engine);
if (m_engine) {
m_engine->Init();
}
}
Engine* GameController::GetEngine() {
return m_engine.get();
}
u32 GameController::Poll() {
std::scoped_lock lock{m_mutex};
if (m_connected) {
std::scoped_lock lock{m_mutex};
auto time = Libraries::Kernel::sceKernelGetProcessTime();
if (m_states_num == 0) {
auto diff = (time - m_last_state.time) / 1000;
@@ -325,6 +384,12 @@ u32 GameController::Poll() {
return 100;
}
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
s32 index = SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(id));
LOG_TRACE(Input, "Gamepad index: {}", index);
return index;
}
} // namespace Input
namespace GamepadSelect {
@@ -343,6 +408,12 @@ int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) {
return -1;
}
std::optional<u8> GetControllerIndexFromUserID(s32 user_id) {
if (user_id < 1 || user_id > 4)
return std::nullopt;
return static_cast<u8>(user_id - 1);
}
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) {
char GUIDbuf[33];
for (int i = 0; i < gamepadCount; i++) {

View File

@@ -1,15 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <memory>
#include <mutex>
#include <SDL3/SDL_gamepad.h>
#include "SDL3/SDL_joystick.h"
#include "common/assert.h"
#include "common/types.h"
#include "core/libraries/pad/pad.h"
struct SDL_Gamepad;
namespace Input {
enum class Axis {
@@ -30,14 +32,7 @@ struct TouchpadEntry {
u16 y{};
};
class State {
public:
void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool);
void OnAxis(Axis, int);
void OnTouchpad(int touchIndex, bool isDown, float x, float y);
void OnGyro(const float[3]);
void OnAccel(const float[3]);
struct State {
Libraries::Pad::OrbisPadButtonDataOffset buttonsState{};
u64 time = 0;
int axes[static_cast<int>(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0};
@@ -47,25 +42,16 @@ public:
Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f};
};
class Engine {
public:
virtual ~Engine() = default;
virtual void Init() = 0;
virtual void SetLightBarRGB(u8 r, u8 g, u8 b) = 0;
virtual void SetVibration(u8 smallMotor, u8 largeMotor) = 0;
virtual State ReadState() = 0;
virtual float GetAccelPollRate() const = 0;
virtual float GetGyroPollRate() const = 0;
SDL_Gamepad* m_gamepad;
};
inline int GetAxis(int min, int max, int value) {
return std::clamp((255 * (value - min)) / (max - min), 0, 255);
int v = (255 * (value - min)) / (max - min);
return (v < 0 ? 0 : (v > 255 ? 255 : v));
}
constexpr u32 MAX_STATES = 32;
class GameController {
friend class GameControllers;
public:
GameController();
virtual ~GameController() = default;
@@ -79,10 +65,8 @@ public:
void Gyro(int id, const float gyro[3]);
void Acceleration(int id, const float acceleration[3]);
void SetLightBarRGB(u8 r, u8 g, u8 b);
void SetVibration(u8 smallMotor, u8 largeMotor);
bool SetVibration(u8 smallMotor, u8 largeMotor);
void SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
void SetEngine(std::unique_ptr<Engine>);
Engine* GetEngine();
u32 Poll();
u8 GetTouchCount();
@@ -104,6 +88,14 @@ public:
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation);
float gyro_poll_rate;
float accel_poll_rate;
u32 user_id = -1; // ORBIS_USER_SERVICE_USER_ID_INVALID
SDL_Gamepad* m_sdl_gamepad = nullptr;
static constexpr int max_smoothing_ticks = 2;
int axis_smoothing_ticks[static_cast<int>(Input::Axis::AxisMax)]{0};
int axis_smoothing_values[static_cast<int>(Input::Axis::AxisMax)]{0};
private:
struct StateInternal {
bool obtained = false;
@@ -125,13 +117,32 @@ private:
std::chrono::steady_clock::time_point m_last_update = {};
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
std::unique_ptr<Engine> m_engine = nullptr;
u8 player_index = -1;
};
class GameControllers {
std::array<GameController*, 4> controllers;
public:
GameControllers()
: controllers({new GameController(), new GameController(), new GameController(),
new GameController()}) {};
virtual ~GameControllers() = default;
GameController* operator[](const size_t& i) const {
if (i > 3) {
UNREACHABLE_MSG("Index {} is out of bounds for GameControllers!", i);
}
return controllers[i];
}
static void TryOpenSDLControllers(GameControllers& controllers);
static u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id);
};
} // namespace Input
namespace GamepadSelect {
std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID);
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index);
std::string GetSelectedGamepad();

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "input_handler.h"
@@ -22,6 +22,7 @@
#include "common/elf_info.h"
#include "common/io_file.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "input/controller.h"
#include "input/input_mouse.h"
@@ -61,55 +62,11 @@ std::list<std::pair<InputEvent, bool>> pressed_keys;
std::list<InputID> toggled_keys;
static std::vector<BindingConnection> connections;
auto output_array = std::array{
// Important: these have to be the first, or else they will update in the wrong order
ControllerOutput(LEFTJOYSTICK_HALFMODE),
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
ControllerOutput(KEY_TOGGLE),
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
// Button mappings
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
// Axis mappings
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
ControllerOutput(HOTKEY_FULLSCREEN),
ControllerOutput(HOTKEY_PAUSE),
ControllerOutput(HOTKEY_SIMPLE_FPS),
ControllerOutput(HOTKEY_QUIT),
ControllerOutput(HOTKEY_RELOAD_INPUTS),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
ControllerOutput(HOTKEY_RENDERDOC),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
std::array<ControllerAllOutputs, 4> output_arrays = {
ControllerAllOutputs(0),
ControllerAllOutputs(1),
ControllerAllOutputs(2),
ControllerAllOutputs(3),
};
void ControllerOutput::LinkJoystickAxes() {
@@ -216,6 +173,14 @@ InputBinding GetBindingFromString(std::string& line) {
return InputBinding(keys[0], keys[1], keys[2]);
}
std::optional<int> parseInt(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
};
void ParseInputConfig(const std::string game_id = "") {
std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default);
@@ -271,21 +236,39 @@ void ParseInputConfig(const std::string game_id = "") {
std::string output_string = line.substr(0, equal_pos);
std::string input_string = line.substr(equal_pos + 1);
// Remove trailing semicolon from input_string
if (!input_string.empty() && input_string[input_string.length() - 1] == ';' &&
input_string != ";") {
line = line.substr(0, line.length() - 1);
s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified
// todo: here the inputs and outputs are formatted and split, we need to extract the
// controller ID now
// input gamepad id is only for controllers, it's discarded otherwise
std::size_t input_colon_pos = input_string.find(':');
if (input_colon_pos != std::string::npos) {
auto temp = parseInt(input_string.substr(input_colon_pos + 1));
if (!temp) {
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
} else {
input_gamepad_id = *temp;
}
input_string = input_string.substr(0, input_colon_pos);
}
// if not provided, assume it's for all gamepads, if the input is a controller and that also
// doesn't have an ID, and for the first otherwise
std::size_t output_colon_pos = output_string.find(':');
if (output_colon_pos != std::string::npos) {
auto temp = parseInt(output_string.substr(output_colon_pos + 1));
if (!temp) {
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
} else {
output_gamepad_id = *temp;
}
output_string = output_string.substr(0, output_colon_pos);
}
std::size_t comma_pos = input_string.find(',');
auto parseInt = [](const std::string& s) -> std::optional<int> {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
};
// todo: make remapping work for special bindings for gamepads that are not the first
if (output_string == "mouse_to_joystick") {
if (input_string == "left") {
SetMouseToJoystick(1);
@@ -308,7 +291,7 @@ void ParseInputConfig(const std::string game_id = "") {
return;
}
ControllerOutput* toggle_out =
&*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE));
&*std::ranges::find(output_arrays[0].data, ControllerOutput(KEY_TOGGLE));
BindingConnection toggle_connection = BindingConnection(
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
connections.insert(connections.end(), toggle_connection);
@@ -401,40 +384,67 @@ void ParseInputConfig(const std::string game_id = "") {
return;
}
if (button_it != string_to_cbutton_map.end()) {
// todo add new shit here
connection = BindingConnection(
binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second)));
connections.insert(connections.end(), connection);
binding,
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
ControllerOutput(button_it->second)));
} else if (axis_it != string_to_axis_map.end()) {
// todo add new shit here
int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value;
connection = BindingConnection(
binding,
&*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
axis_it->second.axis,
axis_it->second.value >= 0)),
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
axis_it->second.axis,
axis_it->second.value >= 0)),
value_to_set);
connections.insert(connections.end(), connection);
} else {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line);
return;
}
// todo make the following: if the input binding contains a controller input, and gamepad ID
// isn't specified for either inputs or output (both are -1), then multiply the binding and
// add it to all 4 controllers
if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) {
for (int i = 0; i < 4; i++) {
BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1);
copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output);
connections.push_back(copy);
}
} else {
connections.push_back(connection);
}
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
};
while (std::getline(global_config_stream, line)) {
ProcessLine();
}
lineCount = 0;
while (std::getline(config_stream, line)) {
ProcessLine();
}
config_stream.close();
std::sort(connections.begin(), connections.end());
for (auto& c : connections) {
// todo add new shit here
LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString());
}
LOG_DEBUG(Input, "Done parsing the input config!");
}
BindingConnection BindingConnection::CopyWithChangedGamepadId(u8 gamepad) {
BindingConnection copy = *this;
for (auto& key : copy.binding.keys) {
if (key.type == InputType::Controller || key.type == InputType::Axis) {
key.gamepad_id = gamepad;
}
}
return copy;
}
u32 GetMouseWheelEvent(const SDL_Event& event) {
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
LOG_WARNING(Input, "Something went wrong with wheel input parsing!");
@@ -453,6 +463,8 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
}
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
// todo add new shit here
u8 gamepad = 1;
switch (e.type) {
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
@@ -467,20 +479,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
e.type == SDL_EVENT_MOUSE_WHEEL, 0);
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
return InputEvent(InputType::Controller, static_cast<u32>(e.gbutton.button), e.gbutton.down,
0); // clang made me do it
gamepad = GameControllers::GetGamepadIndexFromJoystickId(e.gbutton.which) + 1;
return InputEvent({InputType::Controller, (u32)e.gbutton.button, gamepad}, e.gbutton.down,
0);
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
return InputEvent(InputType::Axis, static_cast<u32>(e.gaxis.axis), true,
e.gaxis.value / 256); // this too
gamepad = GameControllers::GetGamepadIndexFromJoystickId(e.gaxis.which) + 1;
return InputEvent({InputType::Axis, (u32)e.gaxis.axis, gamepad}, true, e.gaxis.value / 256);
default:
return InputEvent();
}
}
GameController* ControllerOutput::controller = nullptr;
void ControllerOutput::SetControllerOutputController(GameController* c) {
ControllerOutput::controller = c;
}
GameControllers ControllerOutput::controllers =
*Common::Singleton<Input::GameControllers>::Instance();
void ToggleKeyInList(InputID input) {
if (input.type == InputType::Axis) {
@@ -527,7 +538,7 @@ void ControllerOutput::AddUpdate(InputEvent event) {
*new_param = (event.active ? event.axis_value : 0) + *new_param;
}
}
void ControllerOutput::FinalizeUpdate() {
void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
auto PushSDLEvent = [&](u32 event_type) {
if (new_button_state) {
SDL_Event e;
@@ -543,6 +554,7 @@ void ControllerOutput::FinalizeUpdate() {
old_button_state = new_button_state;
old_param = *new_param;
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
auto controller = controllers[gamepad_index];
switch (button) {
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
@@ -586,6 +598,12 @@ void ControllerOutput::FinalizeUpdate() {
case HOTKEY_RENDERDOC:
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
break;
case HOTKEY_ADD_VIRTUAL_USER:
PushSDLEvent(SDL_EVENT_ADD_VIRTUAL_USER);
break;
case HOTKEY_REMOVE_VIRTUAL_USER:
PushSDLEvent(SDL_EVENT_REMOVE_VIRTUAL_USER);
break;
case HOTKEY_QUIT:
PushSDLEvent(SDL_EVENT_QUIT_DIALOG);
break;
@@ -626,18 +644,20 @@ void ControllerOutput::FinalizeUpdate() {
break;
case Axis::TriggerLeft:
ApplyDeadzone(new_param, lefttrigger_deadzone);
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20);
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::L2,
*new_param > 0x20);
return;
case Axis::TriggerRight:
ApplyDeadzone(new_param, righttrigger_deadzone);
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::R2,
*new_param > 0x20);
return;
default:
break;
}
controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
}
}
@@ -769,25 +789,33 @@ InputEvent BindingConnection::ProcessBinding() {
}
void ActivateOutputsFromInputs() {
// Reset values and flags
for (auto& it : pressed_keys) {
it.second = false;
}
for (auto& it : output_array) {
it.ResetUpdate();
}
// Check for input blockers
ApplyMouseInputBlockers();
// todo find a better solution
for (int i = 0; i < 4; i++) {
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
it.output->AddUpdate(it.ProcessBinding());
}
// Reset values and flags
for (auto& it : pressed_keys) {
it.second = false;
}
for (auto& it : output_arrays[i].data) {
it.ResetUpdate();
}
// Update all outputs
for (auto& it : output_array) {
it.FinalizeUpdate();
// Check for input blockers
ApplyMouseInputBlockers();
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
// only update this when it's the correct pass
if (it.output->gamepad_id == i) {
it.output->AddUpdate(it.ProcessBinding());
}
}
// Update all outputs
for (auto& it : output_arrays[i].data) {
it.FinalizeUpdate(i);
}
}
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -7,6 +7,7 @@
#include <map>
#include <string>
#include <unordered_set>
#include <vector>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_timer.h"
@@ -35,9 +36,11 @@
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 9
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 10
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 11
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
#define SDL_EVENT_ADD_VIRTUAL_USER SDL_EVENT_USER + 11
#define SDL_EVENT_REMOVE_VIRTUAL_USER SDL_EVENT_USER + 12
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 13
#define LEFTJOYSTICK_HALFMODE 0x00010000
#define RIGHTJOYSTICK_HALFMODE 0x00020000
@@ -53,8 +56,10 @@
#define HOTKEY_RELOAD_INPUTS 0xf0000005
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008
#define HOTKEY_RENDERDOC 0xf0000009
#define HOTKEY_RENDERDOC 0xf0000008
#define HOTKEY_ADD_VIRTUAL_USER 0xf0000009
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000a
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf000000b
#define SDL_UNMAPPED UINT32_MAX - 1
@@ -75,21 +80,24 @@ class InputID {
public:
InputType type;
u32 sdl_id;
InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {}
u8 gamepad_id;
InputID(InputType d = InputType::Count, u32 i = (u32)-1, u8 g = 1)
: type(d), sdl_id(i), gamepad_id(g) {}
bool operator==(const InputID& o) const {
return type == o.type && sdl_id == o.sdl_id;
return type == o.type && sdl_id == o.sdl_id && gamepad_id == o.gamepad_id;
}
bool operator!=(const InputID& o) const {
return type != o.type || sdl_id != o.sdl_id;
return type != o.type || sdl_id != o.sdl_id || gamepad_id != o.gamepad_id;
}
bool operator<=(const InputID& o) const {
return type <= o.type && sdl_id <= o.sdl_id;
return std::tie(gamepad_id, type, sdl_id) <= std::tie(o.gamepad_id, o.type, o.sdl_id);
}
bool IsValid() const {
return *this != InputID();
}
std::string ToString() {
return fmt::format("({}: {:x})", input_type_names[static_cast<u8>(type)], sdl_id);
return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id);
}
};
@@ -145,6 +153,8 @@ const std::map<std::string, u32> string_to_cbutton_map = {
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
{"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER},
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
};
const std::map<std::string, AxisMapping> string_to_axis_map = {
@@ -395,7 +405,7 @@ public:
inline bool IsEmpty() {
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
}
std::string ToString() { // todo add device type
std::string ToString() {
switch (KeyCount()) {
case 1:
return fmt::format("({})", keys[0].ToString());
@@ -414,14 +424,15 @@ public:
};
class ControllerOutput {
static GameController* controller;
static GameControllers controllers;
public:
static void SetControllerOutputController(GameController* c);
static void GetGetGamepadIndexFromSDLJoystickID(const SDL_JoystickID id) {}
static void LinkJoystickAxes();
u32 button;
u32 axis;
u8 gamepad_id;
// these are only used as s8,
// but I added some padding to avoid overflow if it's activated by multiple inputs
// axis_plus and axis_minus pairs share a common new_param, the other outputs have their own
@@ -435,6 +446,7 @@ public:
new_param = new s16(0);
old_param = 0;
positive_axis = p;
gamepad_id = 0;
}
ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) {
new_param = new s16(*o.new_param);
@@ -460,7 +472,7 @@ public:
void ResetUpdate();
void AddUpdate(InputEvent event);
void FinalizeUpdate();
void FinalizeUpdate(u8 gamepad_index);
};
class BindingConnection {
public:
@@ -475,6 +487,13 @@ public:
output = out;
toggle = t;
}
BindingConnection& operator=(const BindingConnection& o) {
binding = o.binding;
output = o.output;
axis_param = o.axis_param;
toggle = o.toggle;
return *this;
}
bool operator<(const BindingConnection& other) const {
// a button is a higher priority than an axis, as buttons can influence axes
// (e.g. joystick_halfmode)
@@ -488,9 +507,80 @@ public:
}
return false;
}
bool HasGamepadInput() {
for (auto& key : binding.keys) {
if (key.type == InputType::Controller || key.type == InputType::Axis) {
return true;
}
}
return false;
}
BindingConnection CopyWithChangedGamepadId(u8 gamepad);
InputEvent ProcessBinding();
};
class ControllerAllOutputs {
public:
static constexpr u64 output_count = 38;
std::array<ControllerOutput, output_count> data = {
// Important: these have to be the first, or else they will update in the wrong order
ControllerOutput(LEFTJOYSTICK_HALFMODE),
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
ControllerOutput(KEY_TOGGLE),
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
// Button mappings
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
// Axis mappings
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
ControllerOutput(HOTKEY_FULLSCREEN),
ControllerOutput(HOTKEY_PAUSE),
ControllerOutput(HOTKEY_SIMPLE_FPS),
ControllerOutput(HOTKEY_QUIT),
ControllerOutput(HOTKEY_RELOAD_INPUTS),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
ControllerOutput(HOTKEY_RENDERDOC),
ControllerOutput(HOTKEY_ADD_VIRTUAL_USER),
ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
};
ControllerAllOutputs(u8 g) {
for (int i = 0; i < output_count; i++) {
data[i].gamepad_id = g;
}
}
};
// Updates the list of pressed keys with the given input.
// Returns whether the list was updated or not.
bool UpdatePressedKeys(InputEvent event);

View File

@@ -21,6 +21,7 @@
#ifdef _WIN32
#include <windows.h>
#endif
#include <core/emulator_settings.h>
int main(int argc, char* argv[]) {
#ifdef _WIN32
@@ -29,6 +30,9 @@ int main(int argc, char* argv[]) {
IPC::Instance().Init();
// Load configurations
std::shared_ptr<EmulatorSettings> emu_settings = std::make_shared<EmulatorSettings>();
EmulatorSettings::SetInstance(emu_settings);
emu_settings->Load();
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(user_dir / "config.toml");
@@ -116,7 +120,7 @@ int main(int argc, char* argv[]) {
exit(1);
}
// Set fullscreen mode without saving it to config file
Config::setIsFullscreen(is_fullscreen);
EmulatorSettings::GetInstance()->SetFullScreen(is_fullscreen);
}},
{"--fullscreen", [&](int& i) { arg_map["-f"](i); }},
{"--add-game-folder",
@@ -133,8 +137,8 @@ int main(int argc, char* argv[]) {
exit(1);
}
Config::addGameInstallDir(config_path);
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
EmulatorSettings::GetInstance()->AddGameInstallDir(config_path);
EmulatorSettings::GetInstance()->Save();
std::cout << "Game folder successfully saved.\n";
exit(0);
}},
@@ -152,8 +156,8 @@ int main(int argc, char* argv[]) {
exit(1);
}
Config::setAddonInstallDir(config_path);
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
EmulatorSettings::GetInstance()->SetAddonInstallDir(config_path);
EmulatorSettings::GetInstance()->Save();
std::cout << "Addon folder successfully saved.\n";
exit(0);
}},
@@ -225,7 +229,7 @@ int main(int argc, char* argv[]) {
}
// If no game directory is set and no command line argument, prompt for it
if (Config::getGameInstallDirs().empty()) {
if (EmulatorSettings::GetInstance()->GetGameInstallDirs().empty()) {
std::cerr << "Warning: No game folder set, please set it by calling shadps4"
" with the --add-game-folder <folder_name> argument\n";
}
@@ -243,7 +247,7 @@ int main(int argc, char* argv[]) {
// If not a file, treat it as a game ID and search in install directories recursively
bool game_found = false;
const int max_depth = 5;
for (const auto& install_dir : Config::getGameInstallDirs()) {
for (const auto& install_dir : EmulatorSettings::GetInstance()->GetGameInstallDirs()) {
if (auto found_path = Common::FS::FindGameByID(install_dir, game_path, max_depth)) {
eboot_path = *found_path;
game_found = true;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "SDL3/SDL_events.h"
@@ -12,8 +12,10 @@
#include "common/elf_info.h"
#include "core/debug_state.h"
#include "core/devtools/layer.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "imgui/renderer/imgui_core.h"
#include "input/controller.h"
#include "input/input_handler.h"
@@ -25,258 +27,55 @@
#include "SDL3/SDL_metal.h"
#endif
namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
using OPBDO = OrbisPadButtonDataOffset;
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return OPBDO::Down;
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return OPBDO::Up;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return OPBDO::Left;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return OPBDO::Right;
case SDL_GAMEPAD_BUTTON_SOUTH:
return OPBDO::Cross;
case SDL_GAMEPAD_BUTTON_NORTH:
return OPBDO::Triangle;
case SDL_GAMEPAD_BUTTON_WEST:
return OPBDO::Square;
case SDL_GAMEPAD_BUTTON_EAST:
return OPBDO::Circle;
case SDL_GAMEPAD_BUTTON_START:
return OPBDO::Options;
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_BACK:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return OPBDO::L1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return OPBDO::R1;
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
return OPBDO::L3;
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
return OPBDO::R3;
default:
return OPBDO::None;
}
}
static SDL_GamepadAxis InputAxisToSDL(Axis axis) {
switch (axis) {
case Axis::LeftX:
return SDL_GAMEPAD_AXIS_LEFTX;
case Axis::LeftY:
return SDL_GAMEPAD_AXIS_LEFTY;
case Axis::RightX:
return SDL_GAMEPAD_AXIS_RIGHTX;
case Axis::RightY:
return SDL_GAMEPAD_AXIS_RIGHTY;
case Axis::TriggerLeft:
return SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
case Axis::TriggerRight:
return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
default:
UNREACHABLE();
}
}
SDLInputEngine::~SDLInputEngine() {
if (m_gamepad) {
SDL_CloseGamepad(m_gamepad);
}
}
void SDLInputEngine::Init() {
if (m_gamepad) {
SDL_CloseGamepad(m_gamepad);
m_gamepad = nullptr;
}
int gamepad_count;
SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count);
if (!gamepads) {
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
return;
}
if (gamepad_count == 0) {
LOG_INFO(Input, "No gamepad found!");
SDL_free(gamepads);
return;
}
int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count,
GamepadSelect::GetSelectedGamepad());
int defaultIndex =
GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID());
// If user selects a gamepad in the GUI, use that, otherwise try the default
if (!m_gamepad) {
if (selectedIndex != -1) {
m_gamepad = SDL_OpenGamepad(gamepads[selectedIndex]);
LOG_INFO(Input, "Opening gamepad selected in GUI.");
} else if (defaultIndex != -1) {
m_gamepad = SDL_OpenGamepad(gamepads[defaultIndex]);
LOG_INFO(Input, "Opening default gamepad.");
} else {
m_gamepad = SDL_OpenGamepad(gamepads[0]);
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
}
}
if (!m_gamepad) {
if (!m_gamepad) {
LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError());
SDL_free(gamepads);
return;
}
}
SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad);
Uint16 vendor = SDL_GetJoystickVendor(joystick);
Uint16 product = SDL_GetJoystickProduct(joystick);
bool isDualSense = (vendor == 0x054C && product == 0x0CE6);
LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product);
if (isDualSense) {
LOG_INFO(Input, "Detected DualSense Controller");
}
if (Config::getIsMotionControlsEnabled()) {
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) {
m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO);
LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}",
SDL_GetError());
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false);
}
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) {
m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL);
LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}",
SDL_GetError());
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false);
}
}
SDL_free(gamepads);
int* rgb = Config::GetControllerCustomColor();
if (isDualSense) {
if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) {
LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]);
} else {
LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError());
}
} else {
SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
}
}
void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) {
if (m_gamepad) {
SDL_SetGamepadLED(m_gamepad, r, g, b);
}
}
void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_gamepad) {
const auto low_freq = (smallMotor / 255.0f) * 0xFFFF;
const auto high_freq = (largeMotor / 255.0f) * 0xFFFF;
SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1);
}
}
State SDLInputEngine::ReadState() {
State state{};
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Buttons
for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
auto orbisButton = SDLGamepadToOrbisButton(i);
if (orbisButton == OrbisPadButtonDataOffset::None) {
continue;
}
state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i));
}
// Axes
for (int i = 0; i < static_cast<int>(Axis::AxisMax); ++i) {
const auto axis = static_cast<Axis>(i);
const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis));
switch (axis) {
case Axis::TriggerLeft:
case Axis::TriggerRight:
state.OnAxis(axis, GetAxis(0, 0x8000, value));
break;
default:
state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value));
break;
}
}
// Touchpad
if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) {
for (int finger = 0; finger < 2; ++finger) {
bool down;
float x, y;
if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) {
state.OnTouchpad(finger, down, x, y);
}
}
}
// Gyro
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) {
float gyro[3];
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) {
state.OnGyro(gyro);
}
}
// Accel
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) {
float accel[3];
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) {
state.OnAccel(accel);
}
}
return state;
}
float SDLInputEngine::GetGyroPollRate() const {
return m_gyro_poll_rate;
}
float SDLInputEngine::GetAccelPollRate() const {
return m_accel_poll_rate;
}
} // namespace Input
namespace Frontend {
using namespace Libraries::Pad;
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return OrbisPadButtonDataOffset::Down;
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return OrbisPadButtonDataOffset::Up;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return OrbisPadButtonDataOffset::Left;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return OrbisPadButtonDataOffset::Right;
case SDL_GAMEPAD_BUTTON_SOUTH:
return OrbisPadButtonDataOffset::Cross;
case SDL_GAMEPAD_BUTTON_NORTH:
return OrbisPadButtonDataOffset::Triangle;
case SDL_GAMEPAD_BUTTON_WEST:
return OrbisPadButtonDataOffset::Square;
case SDL_GAMEPAD_BUTTON_EAST:
return OrbisPadButtonDataOffset::Circle;
case SDL_GAMEPAD_BUTTON_START:
return OrbisPadButtonDataOffset::Options;
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
return OrbisPadButtonDataOffset::TouchPad;
case SDL_GAMEPAD_BUTTON_BACK:
return OrbisPadButtonDataOffset::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return OrbisPadButtonDataOffset::L1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return OrbisPadButtonDataOffset::R1;
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
return OrbisPadButtonDataOffset::L3;
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
return OrbisPadButtonDataOffset::R3;
default:
return OrbisPadButtonDataOffset::None;
}
}
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
return controller->Poll();
}
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_,
std::string_view window_title)
: width{width_}, height{height_}, controller{controller_} {
: width{width_}, height{height_}, controllers{*controllers_} {
if (!SDL_SetHint(SDL_HINT_APP_NAME, "shadPS4")) {
UNREACHABLE_MSG("Failed to set SDL window hint: {}", SDL_GetError());
}
@@ -314,13 +113,14 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
error = true;
}
if (!error) {
SDL_SetWindowFullscreenMode(
window, Config::getFullscreenMode() == "Fullscreen" ? displayMode : NULL);
SDL_SetWindowFullscreenMode(window, EmulatorSettings::GetInstance()->GetFullScreenMode() ==
"Fullscreen"
? displayMode
: NULL);
}
SDL_SetWindowFullscreen(window, Config::getIsFullscreen());
SDL_SetWindowFullscreen(window, EmulatorSettings::GetInstance()->IsFullScreen());
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
#if defined(SDL_PLATFORM_WIN32)
window_info.type = WindowSystemType::Windows;
@@ -345,9 +145,9 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
#endif
// input handler init-s
Input::ControllerOutput::SetControllerOutputController(controller);
Input::ControllerOutput::LinkJoystickAxes();
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
Input::GameControllers::TryOpenSDLControllers(controllers);
if (Config::getBackgroundControllerInput()) {
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
@@ -389,34 +189,32 @@ void WindowSDL::WaitEvent() {
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
break;
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
controller->SetTouchpadState(event.gtouchpad.finger,
event.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event.gtouchpad.x,
event.gtouchpad.y);
// todo handle userserviceevents here
Input::GameControllers::TryOpenSDLControllers(controllers);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
OnGamepadEvent(&event);
break;
// i really would have appreciated ANY KIND OF DOCUMENTATION ON THIS
// AND IT DOESN'T EVEN USE PROPER ENUMS
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
int controller_id =
Input::GameControllers::GetGamepadIndexFromJoystickId(event.gsensor.which);
switch ((SDL_SensorType)event.gsensor.sensor) {
case SDL_SENSOR_GYRO:
controller->Gyro(0, event.gsensor.data);
controllers[controller_id]->Gyro(0, event.gsensor.data);
break;
case SDL_SENSOR_ACCEL:
controller->Acceleration(0, event.gsensor.data);
controllers[controller_id]->Acceleration(0, event.gsensor.data);
break;
default:
break;
}
break;
}
case SDL_EVENT_QUIT:
is_open = false;
break;
@@ -441,7 +239,7 @@ void WindowSDL::WaitEvent() {
}
break;
case SDL_EVENT_CHANGE_CONTROLLER:
controller->GetEngine()->Init();
UNREACHABLE_MSG("todo");
break;
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
Overlay::ToggleSimpleFps();
@@ -462,6 +260,28 @@ void WindowSDL::WaitEvent() {
Input::ToggleMouseModeTo(Input::MouseMode::Touchpad));
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), false);
break;
case SDL_EVENT_ADD_VIRTUAL_USER:
for (int i = 0; i < 4; i++) {
if (controllers[i]->user_id == -1) {
controllers[i]->user_id = i + 1;
Libraries::UserService::AddUserServiceEvent(
{Libraries::UserService::OrbisUserServiceEventType::Login,
(s32)controllers[i]->user_id});
break;
}
}
break;
case SDL_EVENT_REMOVE_VIRTUAL_USER:
LOG_INFO(Input, "Remove user");
for (int i = 3; i >= 0; i--) {
if (controllers[i]->user_id != -1) {
Libraries::UserService::AddUserServiceEvent(
{Libraries::UserService::OrbisUserServiceEventType::Logout,
(s32)controllers[i]->user_id});
controllers[i]->user_id = -1;
break;
}
}
case SDL_EVENT_RDOC_CAPTURE:
VideoCore::TriggerCapture();
break;
@@ -471,8 +291,10 @@ void WindowSDL::WaitEvent() {
}
void WindowSDL::InitTimers() {
SDL_AddTimer(100, &PollController, controller);
SDL_AddTimer(33, Input::MousePolling, (void*)controller);
for (int i = 0; i < 4; ++i) {
SDL_AddTimer(250, &PollController, controllers[i]);
}
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
}
void WindowSDL::RequestKeyboard() {
@@ -540,10 +362,38 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
// as it would break the entire touchpad handling
// You can still bind other things to it though
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) {
controller->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gbutton.which)]
->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
return;
}
switch (event->type) {
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
switch ((SDL_SensorType)event->gsensor.sensor) {
case SDL_SENSOR_GYRO:
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
->Gyro(0, event->gsensor.data);
break;
case SDL_SENSOR_ACCEL:
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
->Acceleration(0, event->gsensor.data);
break;
default:
break;
}
return;
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gtouchpad.which)]
->SetTouchpadState(event->gtouchpad.finger,
event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event->gtouchpad.x,
event->gtouchpad.y);
return;
default:
break;
}
// add/remove it from the list
bool inputs_changed = Input::UpdatePressedKeys(input_event);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -14,23 +14,8 @@ struct SDL_Gamepad;
union SDL_Event;
namespace Input {
class SDLInputEngine : public Engine {
public:
~SDLInputEngine() override;
void Init() override;
void SetLightBarRGB(u8 r, u8 g, u8 b) override;
void SetVibration(u8 smallMotor, u8 largeMotor) override;
float GetGyroPollRate() const override;
float GetAccelPollRate() const override;
State ReadState() override;
private:
float m_gyro_poll_rate = 0.0f;
float m_accel_poll_rate = 0.0f;
};
} // namespace Input
class GameController;
}
namespace Frontend {
@@ -62,7 +47,7 @@ class WindowSDL {
int keyboard_grab = 0;
public:
explicit WindowSDL(s32 width, s32 height, Input::GameController* controller,
explicit WindowSDL(s32 width, s32 height, Input::GameControllers* controllers,
std::string_view window_title);
~WindowSDL();
@@ -100,7 +85,7 @@ private:
private:
s32 width;
s32 height;
Input::GameController* controller;
Input::GameControllers controllers{};
WindowSystemInfo window_info{};
SDL_Window* window{};
bool is_shown{};

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <ranges>
@@ -8,6 +8,7 @@
#include "common/io_file.h"
#include "common/path_util.h"
#include "core/debug_state.h"
#include "core/emulator_settings.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/info.h"
#include "shader_recompiler/recompiler.h"
@@ -295,7 +296,7 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() {
RegisterPipelineData(graphics_key, pipeline_hash, sdata);
++num_new_pipelines;
if (Config::collectShadersForDebug()) {
if (EmulatorSettings::GetInstance()->IsShaderDump()) {
for (auto stage = 0; stage < MaxShaderStages; ++stage) {
if (infos[stage]) {
auto& m = modules[stage];
@@ -324,7 +325,7 @@ const ComputePipeline* PipelineCache::GetComputePipeline() {
RegisterPipelineData(compute_key, sdata);
++num_new_pipelines;
if (Config::collectShadersForDebug()) {
if (EmulatorSettings::GetInstance()->IsShaderDump()) {
auto& m = modules[0];
module_related_pipelines[m].emplace_back(compute_key);
}
@@ -555,7 +556,7 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim
const auto name = GetShaderName(info.stage, info.pgm_hash, perm_idx);
Vulkan::SetObjectName(instance.GetDevice(), module, name);
if (Config::collectShadersForDebug()) {
if (EmulatorSettings::GetInstance()->IsShaderDump()) {
DebugState.CollectShader(name, info.l_stage, module, spv, code,
patch ? *patch : std::span<const u32>{}, is_patched);
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
@@ -7,6 +7,7 @@
#include "common/singleton.h"
#include "core/debug_state.h"
#include "core/devtools/layer.h"
#include "core/emulator_settings.h"
#include "core/libraries/system/systemservice.h"
#include "imgui/renderer/imgui_core.h"
#include "imgui/renderer/imgui_impl_vulkan.h"
@@ -104,7 +105,7 @@ static vk::Rect2D FitImage(s32 frame_width, s32 frame_height, s32 swapchain_widt
Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_)
: window{window_}, liverpool{liverpool_},
instance{window, Config::getGpuId(), Config::vkValidationEnabled(),
instance{window, EmulatorSettings::GetInstance()->GetGpuId(), Config::vkValidationEnabled(),
Config::getVkCrashDiagnosticEnabled()},
draw_scheduler{instance}, present_scheduler{instance}, flip_scheduler{instance},
swapchain{instance, window},
@@ -577,7 +578,7 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) {
ImGui::SetCursorPos(ImGui::GetCursorStartPos() + offset);
ImGui::Image(game_texture, size);
if (Config::nullGpu()) {
if (EmulatorSettings::GetInstance()->IsNullGPU()) {
Core::Devtools::Layer::DrawNullGpuNotice();
}
}

View File

@@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
#include "common/debug.h"
#include "core/emulator_settings.h"
#include "core/memory.h"
#include "shader_recompiler/runtime_info.h"
#include "video_core/amdgpu/liverpool.h"
@@ -38,7 +39,7 @@ Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_,
texture_cache{instance, scheduler, liverpool_, buffer_cache, page_manager},
liverpool{liverpool_}, memory{Core::Memory::Instance()},
pipeline_cache{instance, scheduler, liverpool} {
if (!Config::nullGpu()) {
if (!EmulatorSettings::GetInstance()->IsNullGPU()) {
liverpool->BindRasterizer(this);
}
memory->SetRasterizer(this);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@@ -6,6 +6,7 @@
#include "common/assert.h"
#include "common/config.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "imgui/renderer/imgui_core.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/vk_instance.h"
@@ -199,7 +200,7 @@ void Swapchain::FindPresentMode() {
return;
}
const auto requested_mode = Config::getPresentMode();
const auto requested_mode = EmulatorSettings::GetInstance()->GetPresentMode();
if (requested_mode == "Mailbox") {
present_mode = vk::PresentModeKHR::eMailbox;
} else if (requested_mode == "Fifo") {
@@ -208,7 +209,7 @@ void Swapchain::FindPresentMode() {
present_mode = vk::PresentModeKHR::eImmediate;
} else {
LOG_ERROR(Render_Vulkan, "Unknown present mode {}, defaulting to Mailbox.",
Config::getPresentMode());
EmulatorSettings::GetInstance()->GetPresentMode());
present_mode = vk::PresentModeKHR::eMailbox;
}