diff --git a/include/config.hpp b/include/config.hpp index 6041286dc..5301ae94e 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -5,6 +5,7 @@ #include "audio/dsp_core.hpp" #include "frontend_settings.hpp" #include "renderer.hpp" +#include "services/region_codes.hpp" struct AudioDeviceConfig { // Audio curve to use for volumes between 0-100 @@ -77,6 +78,8 @@ struct EmulatorConfig { // Default to 3% battery to make users suffer int batteryPercentage = 3; + LanguageCodes systemLanguage = LanguageCodes::English; + // Default ROM path to open in Qt and misc frontends std::filesystem::path defaultRomPath = ""; std::filesystem::path filePath; @@ -104,4 +107,7 @@ struct EmulatorConfig { EmulatorConfig(const std::filesystem::path& path); void load(); void save(); + + static LanguageCodes languageCodeFromString(std::string inString); + static const char* languageCodeToString(LanguageCodes code); }; \ No newline at end of file diff --git a/include/services/cfg.hpp b/include/services/cfg.hpp index 82c8153a0..6e6f697a4 100644 --- a/include/services/cfg.hpp +++ b/include/services/cfg.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "config.hpp" #include "helpers.hpp" #include "logger.hpp" #include "memory.hpp" @@ -11,6 +12,8 @@ class CFGService { using Handle = HorizonHandle; Memory& mem; + const EmulatorConfig& settings; + CountryCodes country = CountryCodes::US; // Default to USA MAKE_LOG_FUNCTION(log, cfgLogger) @@ -45,7 +48,7 @@ class CFGService { NOR, // cfg:nor }; - CFGService(Memory& mem) : mem(mem) {} + CFGService(Memory& mem, const EmulatorConfig& settings) : mem(mem), settings(settings) {} void reset(); void handleSyncRequest(u32 messagePointer, Type type); }; \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index 73b5d6644..5d9791c60 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "helpers.hpp" #include "toml.hpp" @@ -45,6 +46,7 @@ void EmulatorConfig::load() { defaultRomPath = toml::find_or(general, "DefaultRomPath", ""); printAppVersion = toml::find_or(general, "PrintAppVersion", true); + systemLanguage = languageCodeFromString(toml::find_or(general, "SystemLanguage", "en")); } } @@ -169,6 +171,7 @@ void EmulatorConfig::save() { data["General"]["UsePortableBuild"] = usePortableBuild; data["General"]["DefaultRomPath"] = defaultRomPath.string(); data["General"]["PrintAppVersion"] = printAppVersion; + data["General"]["SystemLanguage"] = languageCodeToString(systemLanguage); data["Window"]["AppVersionOnWindow"] = windowSettings.showAppVersion; data["Window"]["RememberWindowPosition"] = windowSettings.rememberPosition; @@ -231,4 +234,34 @@ const char* AudioDeviceConfig::volumeCurveToString(AudioDeviceConfig::VolumeCurv case VolumeCurve::Cubic: default: return "cubic"; } +} + +LanguageCodes EmulatorConfig::languageCodeFromString(std::string inString) { // Transform to lower-case to make the setting case-insensitive + std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); }); + + static const std::unordered_map map = { + {"ja", LanguageCodes::Japanese}, {"en", LanguageCodes::English}, {"fr", LanguageCodes::French}, {"de", LanguageCodes::German}, + {"it", LanguageCodes::Italian}, {"es", LanguageCodes::Spanish}, {"zh", LanguageCodes::Chinese}, {"ko", LanguageCodes::Korean}, + {"nl", LanguageCodes::Dutch}, {"pt", LanguageCodes::Portuguese}, {"ru", LanguageCodes::Russian}, {"tw", LanguageCodes::Taiwanese}, + }; + + if (auto search = map.find(inString); search != map.end()) { + return search->second; + } + + // Default to English if no language code in our map matches + return LanguageCodes::English; +} + +const char* EmulatorConfig::languageCodeToString(LanguageCodes code) { + static constexpr std::array codes = { + "ja", "en", "fr", "de", "it", "es", "zh", "ko", "nl", "pt", "ru", "tw", + }; + + // Invalid country code, return english + if (static_cast(code) > static_cast(LanguageCodes::Taiwanese)) { + return "en"; + } else { + return codes[static_cast(code)]; + } } \ No newline at end of file diff --git a/src/core/services/cfg.cpp b/src/core/services/cfg.cpp index 68d76f7a5..a9b804727 100644 --- a/src/core/services/cfg.cpp +++ b/src/core/services/cfg.cpp @@ -140,7 +140,7 @@ void CFGService::getConfigInfo(u32 output, u32 blockID, u32 size, u32 permission if (size == 1 && blockID == 0x70001) { // Sound output mode mem.write8(output, static_cast(DSPService::SoundOutputMode::Stereo)); } else if (size == 1 && blockID == 0xA0002) { // System language - mem.write8(output, static_cast(LanguageCodes::English)); + mem.write8(output, static_cast(settings.systemLanguage)); } else if (size == 4 && blockID == 0xB0000) { // Country info mem.write8(output, 0); // Unknown mem.write8(output + 1, 0); // Unknown diff --git a/src/core/services/service_manager.cpp b/src/core/services/service_manager.cpp index b8d68789c..ccbbdee8c 100644 --- a/src/core/services/service_manager.cpp +++ b/src/core/services/service_manager.cpp @@ -6,8 +6,8 @@ #include "kernel.hpp" ServiceManager::ServiceManager(std::span regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config) - : regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem), - csnd(mem, kernel), dlp_srvr(mem), dsp(mem, kernel, config), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), + : regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), + cfg(mem, config), csnd(mem, kernel), dlp_srvr(mem), dsp(mem, kernel, config), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel, config), gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem, kernel), mcu_hwc(mem, config), mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem), news_u(mem), ns(mem), nwm_uds(mem, kernel), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {} diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp index 519a4a227..5065ffa13 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -145,6 +145,27 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win romLayout->addWidget(browseRomPath); genLayout->addRow(tr("Default ROMs path"), romLayout); + QComboBox* systemLanguage = new QComboBox(); + systemLanguage->addItem(tr("Japanese")); + systemLanguage->addItem(tr("English")); + systemLanguage->addItem(tr("French")); + systemLanguage->addItem(tr("German")); + systemLanguage->addItem(tr("Italian")); + systemLanguage->addItem(tr("Spanish")); + systemLanguage->addItem(tr("Chinese")); + systemLanguage->addItem(tr("Korean")); + systemLanguage->addItem(tr("Dutch")); + systemLanguage->addItem(tr("Portuguese")); + systemLanguage->addItem(tr("Russian")); + systemLanguage->addItem(tr("Taiwanese")); + + systemLanguage->setCurrentIndex(static_cast(config.systemLanguage)); + connect(systemLanguage, &QComboBox::currentIndexChanged, this, [&](int index) { + config.systemLanguage = static_cast(index); + updateConfig(); + }); + genLayout->addRow(tr("System language"), systemLanguage); + QCheckBox* discordRpcEnabled = new QCheckBox(tr("Enable Discord RPC")); connectCheckbox(discordRpcEnabled, config.discordRpcEnabled); genLayout->addRow(discordRpcEnabled); @@ -163,7 +184,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win gpuLayout->setHorizontalSpacing(20); gpuLayout->setVerticalSpacing(10); - QComboBox* rendererType = new QComboBox; + QComboBox* rendererType = new QComboBox(); rendererType->addItem(tr("Null")); rendererType->addItem(tr("OpenGL")); rendererType->addItem(tr("Vulkan")); @@ -217,7 +238,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win audioLayout->setHorizontalSpacing(20); audioLayout->setVerticalSpacing(10); - QComboBox* dspType = new QComboBox; + QComboBox* dspType = new QComboBox(); dspType->addItem(tr("Null")); dspType->addItem(tr("LLE")); dspType->addItem(tr("HLE")); @@ -244,7 +265,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win connectCheckbox(muteAudio, config.audioDeviceConfig.muteAudio); audioLayout->addRow(muteAudio); - QComboBox* volumeCurveType = new QComboBox; + QComboBox* volumeCurveType = new QComboBox(); volumeCurveType->addItem(tr("Cubic")); volumeCurveType->addItem(tr("Linear")); volumeCurveType->setCurrentIndex(static_cast(config.audioDeviceConfig.volumeCurve));