diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 43a766053..017b43871 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -22,6 +22,7 @@ set(SRCS
configure_debug.cpp
configure_dialog.cpp
configure_general.cpp
+ configure_system.cpp
game_list.cpp
hotkeys.cpp
main.cpp
@@ -52,6 +53,7 @@ set(HEADERS
configure_debug.h
configure_dialog.h
configure_general.h
+ configure_system.h
game_list.h
game_list_p.h
hotkeys.h
@@ -69,6 +71,7 @@ set(UIS
configure_audio.ui
configure_debug.ui
configure_general.ui
+ configure_system.ui
hotkeys.ui
main.ui
)
diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configure.ui
index e1624bbef..4a9c52650 100644
--- a/src/citra_qt/configure.ui
+++ b/src/citra_qt/configure.ui
@@ -24,6 +24,11 @@
General
+
+
+ System
+
+
Input
@@ -57,6 +62,12 @@
1
+
+ ConfigureSystem
+ QWidget
+
+ 1
+
ConfigureAudio
QWidget
diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configure_dialog.cpp
index 2f0317fe0..77c266d01 100644
--- a/src/citra_qt/configure_dialog.cpp
+++ b/src/citra_qt/configure_dialog.cpp
@@ -9,9 +9,10 @@
#include "core/settings.h"
-ConfigureDialog::ConfigureDialog(QWidget *parent) :
+ConfigureDialog::ConfigureDialog(QWidget *parent, bool running) :
QDialog(parent),
- ui(new Ui::ConfigureDialog)
+ ui(new Ui::ConfigureDialog),
+ emulation_running(running)
{
ui->setupUi(this);
this->setConfiguration();
@@ -21,10 +22,14 @@ ConfigureDialog::~ConfigureDialog() {
}
void ConfigureDialog::setConfiguration() {
+ // System tab needs set manually
+ // depending on whether emulation is running
+ ui->systemTab->setConfiguration(emulation_running);
}
void ConfigureDialog::applyConfiguration() {
ui->generalTab->applyConfiguration();
+ ui->systemTab->applyConfiguration();
ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration();
}
diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configure_dialog.h
index 89020eeb4..305b33bdf 100644
--- a/src/citra_qt/configure_dialog.h
+++ b/src/citra_qt/configure_dialog.h
@@ -16,7 +16,7 @@ class ConfigureDialog : public QDialog
Q_OBJECT
public:
- explicit ConfigureDialog(QWidget *parent = nullptr);
+ explicit ConfigureDialog(QWidget *parent, bool emulation_running);
~ConfigureDialog();
void applyConfiguration();
@@ -26,4 +26,5 @@ private:
private:
std::unique_ptr ui;
+ bool emulation_running;
};
diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configure_system.cpp
new file mode 100644
index 000000000..4f0d4dbfe
--- /dev/null
+++ b/src/citra_qt/configure_system.cpp
@@ -0,0 +1,136 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "citra_qt/configure_system.h"
+#include "citra_qt/ui_settings.h"
+#include "ui_configure_system.h"
+
+#include "core/hle/service/fs/archive.h"
+#include "core/hle/service/cfg/cfg.h"
+
+static const std::array days_in_month = {{
+ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+}};
+
+ConfigureSystem::ConfigureSystem(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::ConfigureSystem) {
+ ui->setupUi(this);
+
+ connect(ui->combo_birthmonth, SIGNAL(currentIndexChanged(int)), SLOT(updateBirthdayComboBox(int)));
+}
+
+ConfigureSystem::~ConfigureSystem() {
+}
+
+void ConfigureSystem::setConfiguration(bool emulation_running) {
+ enabled = !emulation_running;
+
+ if (!enabled) {
+ ReadSystemSettings();
+ ui->group_system_settings->setEnabled(false);
+ } else {
+ // This tab is enabled only when game is not running (i.e. all service are not initialized).
+ // Temporarily register archive types and load the config savegame file to memory.
+ Service::FS::RegisterArchiveTypes();
+ ResultCode result = Service::CFG::LoadConfigNANDSaveFile();
+ Service::FS::UnregisterArchiveTypes();
+
+ if (result.IsError()) {
+ ui->label_disable_info->setText(tr("Failed to load system settings data."));
+ ui->group_system_settings->setEnabled(false);
+ enabled = false;
+ return;
+ }
+
+ ReadSystemSettings();
+ ui->label_disable_info->hide();
+ }
+}
+
+void ConfigureSystem::ReadSystemSettings() {
+ // set username
+ username = Service::CFG::GetUsername();
+ // ui->edit_username->setText(QString::fromStdU16String(username)); // TODO(wwylele): Use this when we move to Qt 5.5
+ ui->edit_username->setText(QString::fromUtf16(reinterpret_cast(username.data())));
+
+ // set birthday
+ std::tie(birthmonth, birthday) = Service::CFG::GetBirthday();
+ ui->combo_birthmonth->setCurrentIndex(birthmonth - 1);
+ ui->combo_birthday->setCurrentIndex(birthday - 1);
+
+ // set system language
+ language_index = Service::CFG::GetSystemLanguage();
+ ui->combo_language->setCurrentIndex(language_index);
+
+ // set sound output mode
+ sound_index = Service::CFG::GetSoundOutputMode();
+ ui->combo_sound->setCurrentIndex(sound_index);
+}
+
+void ConfigureSystem::applyConfiguration() {
+ if (!enabled)
+ return;
+
+ bool modified = false;
+
+ // apply username
+ // std::u16string new_username = ui->edit_username->text().toStdU16String(); // TODO(wwylele): Use this when we move to Qt 5.5
+ std::u16string new_username(reinterpret_cast(ui->edit_username->text().utf16()));
+ if (new_username != username) {
+ Service::CFG::SetUsername(new_username);
+ modified = true;
+ }
+
+ // apply birthday
+ int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
+ int new_birthday = ui->combo_birthday->currentIndex() + 1;
+ if (birthmonth != new_birthmonth || birthday != new_birthday) {
+ Service::CFG::SetBirthday(new_birthmonth, new_birthday);
+ modified = true;
+ }
+
+ // apply language
+ int new_language = ui->combo_language->currentIndex();
+ if (language_index != new_language) {
+ Service::CFG::SetSystemLanguage(static_cast(new_language));
+ modified = true;
+ }
+
+ // apply sound
+ int new_sound = ui->combo_sound->currentIndex();
+ if (sound_index != new_sound) {
+ Service::CFG::SetSoundOutputMode(static_cast(new_sound));
+ modified = true;
+ }
+
+ // update the config savegame if any item is modified.
+ if (modified)
+ Service::CFG::UpdateConfigNANDSavegame();
+}
+
+void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) {
+ if (birthmonth_index < 0 || birthmonth_index >= 12)
+ return;
+
+ // store current day selection
+ int birthday_index = ui->combo_birthday->currentIndex();
+
+ // get number of days in the new selected month
+ int days = days_in_month[birthmonth_index];
+
+ // if the selected day is out of range,
+ // reset it to 1st
+ if (birthday_index < 0 || birthday_index >= days)
+ birthday_index = 0;
+
+ // update the day combo box
+ ui->combo_birthday->clear();
+ for (int i = 1; i <= days; ++i) {
+ ui->combo_birthday->addItem(QString::number(i));
+ }
+
+ // restore the day selection
+ ui->combo_birthday->setCurrentIndex(birthday_index);
+}
diff --git a/src/citra_qt/configure_system.h b/src/citra_qt/configure_system.h
new file mode 100644
index 000000000..1f5577070
--- /dev/null
+++ b/src/citra_qt/configure_system.h
@@ -0,0 +1,38 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+
+namespace Ui {
+class ConfigureSystem;
+}
+
+class ConfigureSystem : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit ConfigureSystem(QWidget *parent = nullptr);
+ ~ConfigureSystem();
+
+ void applyConfiguration();
+ void setConfiguration(bool emulation_running);
+
+public slots:
+ void updateBirthdayComboBox(int birthmonth_index);
+
+private:
+ void ReadSystemSettings();
+
+ std::unique_ptr ui;
+ bool enabled;
+
+ std::u16string username;
+ int birthmonth, birthday;
+ int language_index;
+ int sound_index;
+};
diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configure_system.ui
new file mode 100644
index 000000000..6a906b61b
--- /dev/null
+++ b/src/citra_qt/configure_system.ui
@@ -0,0 +1,252 @@
+
+
+ ConfigureSystem
+
+
+
+ 0
+ 0
+ 360
+ 377
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
+ System Settings
+
+
+
-
+
+
+ Username
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 10
+
+
+
+ -
+
+
+ Birthday
+
+
+
+ -
+
+
-
+
+
-
+
+ January
+
+
+ -
+
+ February
+
+
+ -
+
+ March
+
+
+ -
+
+ April
+
+
+ -
+
+ May
+
+
+ -
+
+ June
+
+
+ -
+
+ July
+
+
+ -
+
+ August
+
+
+ -
+
+ September
+
+
+ -
+
+ October
+
+
+ -
+
+ November
+
+
+ -
+
+ December
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Language
+
+
+
+ -
+
+
-
+
+ Japanese (日本語)
+
+
+ -
+
+ English
+
+
+ -
+
+ French (français)
+
+
+ -
+
+ German (Deutsch)
+
+
+ -
+
+ Italian (italiano)
+
+
+ -
+
+ Spanish (español)
+
+
+ -
+
+ Simplified Chinese (简体中文)
+
+
+ -
+
+ Korean (한국어)
+
+
+ -
+
+ Dutch (Nederlands)
+
+
+ -
+
+ Portuguese (português)
+
+
+ -
+
+ Russian (Русский)
+
+
+ -
+
+ Traditional Chinese (正體中文)
+
+
+
+
+ -
+
+
+ Sound output mode
+
+
+
+ -
+
+
-
+
+ Mono
+
+
+ -
+
+ Stereo
+
+
+ -
+
+ Surround
+
+
+
+
+
+
+
+ -
+
+
+ System settings are available only when game is not running.
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 0ed1ffa5a..6fe5d7a3f 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -508,7 +508,7 @@ void GMainWindow::ToggleWindowMode() {
}
void GMainWindow::OnConfigure() {
- ConfigureDialog configureDialog(this);
+ ConfigureDialog configureDialog(this, emulation_running);
auto result = configureDialog.exec();
if (result == QDialog::Accepted)
{
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index e067db645..a5dc47322 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -40,6 +40,20 @@ struct SaveFileConfig {
};
static_assert(sizeof(SaveFileConfig) == 0x455C, "SaveFileConfig header must be exactly 0x455C bytes");
+enum ConfigBlockID {
+ StereoCameraSettingsBlockID = 0x00050005,
+ SoundOutputModeBlockID = 0x00070001,
+ ConsoleUniqueIDBlockID = 0x00090001,
+ UsernameBlockID = 0x000A0000,
+ BirthdayBlockID = 0x000A0001,
+ LanguageBlockID = 0x000A0002,
+ CountryInfoBlockID = 0x000B0000,
+ CountryNameBlockID = 0x000B0001,
+ StateNameBlockID = 0x000B0002,
+ EULAVersionBlockID = 0x000D0000,
+ ConsoleModelBlockID = 0x000F0004,
+};
+
struct UsernameBlock {
char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary
u32 zero;
@@ -73,8 +87,7 @@ static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } };
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014
-/// TODO(Subv): Find out what this actually is
-static const u8 SOUND_OUTPUT_MODE = 2;
+static const u8 SOUND_OUTPUT_MODE = SOUND_SURROUND;
static const u8 UNITED_STATES_COUNTRY_ID = 49;
/// TODO(Subv): Find what the other bytes are
static const ConsoleCountryInfo COUNTRY_INFO = { { 0, 0, 0 }, UNITED_STATES_COUNTRY_ID };
@@ -224,6 +237,22 @@ void GetConfigInfoBlk8(Service::Interface* self) {
Memory::WriteBlock(data_pointer, data.data(), data.size());
}
+void SetConfigInfoBlk4(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ u32 block_id = cmd_buff[1];
+ u32 size = cmd_buff[2];
+ VAddr data_pointer = cmd_buff[4];
+
+ if (!Memory::IsValidVirtualAddress(data_pointer)) {
+ cmd_buff[1] = -1; // TODO(Subv): Find the right error code
+ return;
+ }
+
+ std::vector data(size);
+ Memory::ReadBlock(data_pointer, data.data(), data.size());
+ cmd_buff[1] = Service::CFG::SetConfigInfoBlock(block_id, size, 0x4, data.data()).raw;
+}
+
void UpdateConfigNANDSavegame(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = Service::CFG::UpdateConfigNANDSavegame().raw;
@@ -234,13 +263,13 @@ void FormatConfig(Service::Interface* self) {
cmd_buff[1] = Service::CFG::FormatConfig().raw;
}
-ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) {
+static ResultVal GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 flag) {
// Read the header
SaveFileConfig* config = reinterpret_cast(cfg_config_file_buffer.data());
auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries),
[&](const SaveConfigBlockEntry& entry) {
- return entry.block_id == block_id && (entry.flags & flag);
+ return entry.block_id == block_id;
});
if (itr == std::end(config->block_entries)) {
@@ -248,17 +277,38 @@ ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) {
return ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
}
+ if ((itr->flags & flag) == 0) {
+ LOG_ERROR(Service_CFG, "Invalid flag %u for config block 0x%X with size %u", flag, block_id, size);
+ return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
+ }
+
if (itr->size != size) {
LOG_ERROR(Service_CFG, "Invalid size %u for config block 0x%X with flags %u", size, block_id, flag);
return ResultCode(ErrorDescription::InvalidSize, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
}
+ void* pointer;
+
// The data is located in the block header itself if the size is less than 4 bytes
if (itr->size <= 4)
- memcpy(output, &itr->offset_or_data, itr->size);
+ pointer = &itr->offset_or_data;
else
- memcpy(output, &cfg_config_file_buffer[itr->offset_or_data], itr->size);
+ pointer = &cfg_config_file_buffer[itr->offset_or_data];
+ return MakeResult(pointer);
+}
+
+ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) {
+ void* pointer;
+ CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
+ memcpy(output, pointer, size);
+ return RESULT_SUCCESS;
+}
+
+ResultCode SetConfigInfoBlock(u32 block_id, u32 size, u32 flag, const void* input) {
+ void* pointer;
+ CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
+ memcpy(pointer, input, size);
return RESULT_SUCCESS;
}
@@ -336,25 +386,25 @@ ResultCode FormatConfig() {
res = CreateConfigInfoBlk(0x00030001, 0x8, 0xE, zero_buffer);
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data());
+ res = CreateConfigInfoBlk(StereoCameraSettingsBlockID, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data());
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE);
+ res = CreateConfigInfoBlk(SoundOutputModeBlockID, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE);
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID);
+ res = CreateConfigInfoBlk(ConsoleUniqueIDBlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID);
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK);
+ res = CreateConfigInfoBlk(UsernameBlockID, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK);
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x000A0001, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
+ res = CreateConfigInfoBlk(BirthdayBlockID, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE);
+ res = CreateConfigInfoBlk(LanguageBlockID, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE);
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
+ res = CreateConfigInfoBlk(CountryInfoBlockID, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
if (!res.IsSuccess()) return res;
u16_le country_name_buffer[16][0x40] = {};
@@ -363,10 +413,10 @@ ResultCode FormatConfig() {
std::copy(region_name.cbegin(), region_name.cend(), country_name_buffer[i]);
}
// 0x000B0001 - Localized names for the profile Country
- res = CreateConfigInfoBlk(0x000B0001, sizeof(country_name_buffer), 0xE, country_name_buffer);
+ res = CreateConfigInfoBlk(CountryNameBlockID, sizeof(country_name_buffer), 0xE, country_name_buffer);
if (!res.IsSuccess()) return res;
// 0x000B0002 - Localized names for the profile State/Province
- res = CreateConfigInfoBlk(0x000B0002, sizeof(country_name_buffer), 0xE, country_name_buffer);
+ res = CreateConfigInfoBlk(StateNameBlockID, sizeof(country_name_buffer), 0xE, country_name_buffer);
if (!res.IsSuccess()) return res;
// 0x000B0003 - Unknown, related to country/address (zip code?)
@@ -382,10 +432,10 @@ ResultCode FormatConfig() {
if (!res.IsSuccess()) return res;
// 0x000D0000 - Accepted EULA version
- res = CreateConfigInfoBlk(0x000D0000, 0x4, 0xE, zero_buffer);
+ res = CreateConfigInfoBlk(EULAVersionBlockID, 0x4, 0xE, zero_buffer);
if (!res.IsSuccess()) return res;
- res = CreateConfigInfoBlk(0x000F0004, sizeof(CONSOLE_MODEL), 0xC, &CONSOLE_MODEL);
+ res = CreateConfigInfoBlk(ConsoleModelBlockID, sizeof(CONSOLE_MODEL), 0xC, &CONSOLE_MODEL);
if (!res.IsSuccess()) return res;
// 0x00170000 - Unknown
@@ -399,11 +449,7 @@ ResultCode FormatConfig() {
return RESULT_SUCCESS;
}
-void Init() {
- AddService(new CFG_I_Interface);
- AddService(new CFG_S_Interface);
- AddService(new CFG_U_Interface);
-
+ResultCode LoadConfigNANDSaveFile() {
// Open the SystemSaveData archive 0x00010017
FileSys::Path archive_path(cfg_system_savedata_id);
auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path);
@@ -431,14 +477,75 @@ void Init() {
if (config_result.Succeeded()) {
auto config = config_result.MoveFrom();
config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data());
- return;
+ return RESULT_SUCCESS;
}
- FormatConfig();
+ return FormatConfig();
+}
+
+void Init() {
+ AddService(new CFG_I_Interface);
+ AddService(new CFG_S_Interface);
+ AddService(new CFG_U_Interface);
+
+ LoadConfigNANDSaveFile();
}
void Shutdown() {
}
+void SetUsername(const std::u16string& name) {
+ ASSERT(name.size() <= 10);
+ UsernameBlock block{};
+ name.copy(block.username, name.size());
+ SetConfigInfoBlock(UsernameBlockID, sizeof(block), 4, &block);
+}
+
+std::u16string GetUsername() {
+ UsernameBlock block;
+ GetConfigInfoBlock(UsernameBlockID, sizeof(block), 8, &block);
+
+ // the username string in the block isn't null-terminated,
+ // so we need to find the end manually.
+ std::u16string username(block.username, ARRAY_SIZE(block.username));
+ const size_t pos = username.find(u'\0');
+ if (pos != std::u16string::npos)
+ username.erase(pos);
+ return username;
+}
+
+void SetBirthday(u8 month, u8 day) {
+ BirthdayBlock block = { month, day };
+ SetConfigInfoBlock(BirthdayBlockID, sizeof(block), 4, &block);
+}
+
+std::tuple GetBirthday() {
+ BirthdayBlock block;
+ GetConfigInfoBlock(BirthdayBlockID, sizeof(block), 8, &block);
+ return std::make_tuple(block.month, block.day);
+}
+
+void SetSystemLanguage(SystemLanguage language) {
+ u8 block = language;
+ SetConfigInfoBlock(LanguageBlockID, sizeof(block), 4, &block);
+}
+
+SystemLanguage GetSystemLanguage() {
+ u8 block;
+ GetConfigInfoBlock(LanguageBlockID, sizeof(block), 8, &block);
+ return static_cast(block);
+}
+
+void SetSoundOutputMode(SoundOutputMode mode) {
+ u8 block = mode;
+ SetConfigInfoBlock(SoundOutputModeBlockID, sizeof(block), 4, &block);
+}
+
+SoundOutputMode GetSoundOutputMode() {
+ u8 block;
+ GetConfigInfoBlock(SoundOutputModeBlockID, sizeof(block), 8, &block);
+ return static_cast(block);
+}
+
} // namespace CFG
} // namespace Service
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h
index c01806836..18f60f4ca 100644
--- a/src/core/hle/service/cfg/cfg.h
+++ b/src/core/hle/service/cfg/cfg.h
@@ -5,6 +5,7 @@
#pragma once
#include
+#include
#include "common/common_types.h"
@@ -35,7 +36,14 @@ enum SystemLanguage {
LANGUAGE_KO = 7,
LANGUAGE_NL = 8,
LANGUAGE_PT = 9,
- LANGUAGE_RU = 10
+ LANGUAGE_RU = 10,
+ LANGUAGE_TW = 11
+};
+
+enum SoundOutputMode {
+ SOUND_MONO = 0,
+ SOUND_STEREO = 1,
+ SOUND_SURROUND = 2
};
/// Block header in the config savedata file
@@ -177,6 +185,22 @@ void GetConfigInfoBlk2(Service::Interface* self);
*/
void GetConfigInfoBlk8(Service::Interface* self);
+/**
+ * CFG::SetConfigInfoBlk4 service function
+ * Inputs:
+ * 0 : 0x04020082 / 0x08020082
+ * 1 : Block ID
+ * 2 : Size
+ * 3 : Descriptor for the output buffer
+ * 4 : Output buffer pointer
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * Note:
+ * The parameters order is different from GetConfigInfoBlk2/8's,
+ * where Block ID and Size are switched.
+ */
+void SetConfigInfoBlk4(Service::Interface* self);
+
/**
* CFG::UpdateConfigNANDSavegame service function
* Inputs:
@@ -205,7 +229,19 @@ void FormatConfig(Service::Interface* self);
* @param output A pointer where we will write the read data
* @returns ResultCode indicating the result of the operation, 0 on success
*/
-ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output);
+ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output);
+
+/**
+ * Reads data from input and writes to a block with the specified id and flag
+ * in the Config savegame buffer.
+ * The input size must match exactly the size of the target block
+ * @param block_id The id of the block we want to write
+ * @param size The size of the block we want to write
+ * @param flag The target block must have this flag set
+ * @param input A pointer where we will read data and write to Config savegame buffer
+ * @returns ResultCode indicating the result of the operation, 0 on success
+ */
+ResultCode SetConfigInfoBlock(u32 block_id, u32 size, u32 flag, const void* input);
/**
* Creates a block with the specified id and writes the input data to the cfg savegame buffer in memory.
@@ -236,11 +272,70 @@ ResultCode UpdateConfigNANDSavegame();
*/
ResultCode FormatConfig();
+/**
+ * Open the config savegame file and load it to the memory buffer
+ * @returns ResultCode indicating the result of the operation, 0 on success
+ */
+ResultCode LoadConfigNANDSaveFile();
+
/// Initialize the config service
void Init();
/// Shutdown the config service
void Shutdown();
+// Utilities for frontend to set config data.
+// Note: before calling these functions, LoadConfigNANDSaveFile should be called,
+// and UpdateConfigNANDSavegame should be called after making changes to config data.
+
+/**
+ * Sets the username in config savegame.
+ * @param name the username to set. The maximum size is 10 in char16_t.
+ */
+void SetUsername(const std::u16string& name);
+
+/**
+ * Gets the username from config savegame.
+ * @returns the username
+ */
+std::u16string GetUsername();
+
+/**
+ * Sets the profile birthday in config savegame.
+ * @param month the month of birthday.
+ * @param day the day of the birthday.
+ */
+void SetBirthday(u8 month, u8 day);
+
+/**
+ * Gets the profile birthday from the config savegame.
+ * @returns a tuple of (month, day) of birthday
+ */
+std::tuple GetBirthday();
+
+/**
+ * Sets the system language in config savegame.
+ * @param language the system language to set.
+ */
+void SetSystemLanguage(SystemLanguage language);
+
+/**
+ * Gets the system language from config savegame.
+ * @returns the system language
+ */
+SystemLanguage GetSystemLanguage();
+
+/**
+ * Sets the sound output mode in config savegame.
+ * @param mode the sound output mode to set
+ */
+void SetSoundOutputMode(SoundOutputMode mode);
+
+/**
+ * Gets the sound output mode from config savegame.
+ * @returns the sound output mode
+ */
+SoundOutputMode GetSoundOutputMode();
+
} // namespace CFG
} // namespace Service
diff --git a/src/core/hle/service/cfg/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp
index b18060f6d..8b0db785f 100644
--- a/src/core/hle/service/cfg/cfg_i.cpp
+++ b/src/core/hle/service/cfg/cfg_i.cpp
@@ -22,7 +22,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x000A0040, GetCountryCodeID, "GetCountryCodeID"},
// cfg:i
{0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"},
- {0x04020082, nullptr, "SetConfigInfoBlk4"},
+ {0x04020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"},
{0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"},
{0x04040042, nullptr, "GetLocalFriendCodeSeedData"},
{0x04050000, nullptr, "GetLocalFriendCodeSeed"},
@@ -31,7 +31,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x04080042, nullptr, "SecureInfoGetSerialNo"},
{0x04090000, nullptr, "UpdateConfigBlk00040003"},
{0x08010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"},
- {0x08020082, nullptr, "SetConfigInfoBlk4"},
+ {0x08020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"},
{0x08030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"},
{0x080400C2, nullptr, "CreateConfigInfoBlk"},
{0x08050000, nullptr, "DeleteConfigNANDSavefile"},
diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp
index e001f7687..12b458783 100644
--- a/src/core/hle/service/cfg/cfg_s.cpp
+++ b/src/core/hle/service/cfg/cfg_s.cpp
@@ -22,7 +22,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x000A0040, GetCountryCodeID, "GetCountryCodeID"},
// cfg:s
{0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"},
- {0x04020082, nullptr, "SetConfigInfoBlk4"},
+ {0x04020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"},
{0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"},
{0x04040042, nullptr, "GetLocalFriendCodeSeedData"},
{0x04050000, nullptr, "GetLocalFriendCodeSeed"},
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index f4acc4895..4c7aaa7f2 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -259,7 +259,7 @@ using FileSys::ArchiveFactory;
/**
* Map of registered archives, identified by id code. Once an archive is registered here, it is
- * never removed until the FS service is shut down.
+ * never removed until UnregisterArchiveTypes is called.
*/
static boost::container::flat_map> id_code_map;
@@ -520,12 +520,7 @@ ResultCode CreateSystemSaveData(u32 high, u32 low) {
return RESULT_SUCCESS;
}
-/// Initialize archives
-void ArchiveInit() {
- next_handle = 1;
-
- AddService(new FS::Interface);
-
+void RegisterArchiveTypes() {
// TODO(Subv): Add the other archive types (see here for the known types:
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
@@ -562,10 +557,23 @@ void ArchiveInit() {
RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData);
}
+void UnregisterArchiveTypes() {
+ id_code_map.clear();
+}
+
+/// Initialize archives
+void ArchiveInit() {
+ next_handle = 1;
+
+ AddService(new FS::Interface);
+
+ RegisterArchiveTypes();
+}
+
/// Shutdown archives
void ArchiveShutdown() {
handle_map.clear();
- id_code_map.clear();
+ UnregisterArchiveTypes();
}
} // namespace FS
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 006606740..f7a50a3a7 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -235,5 +235,11 @@ void ArchiveInit();
/// Shutdown archives
void ArchiveShutdown();
+/// Register all archive types
+void RegisterArchiveTypes();
+
+/// Unregister all archive types
+void UnregisterArchiveTypes();
+
} // namespace FS
} // namespace Service