diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/citra_qt/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/citra_qt/configure.ui | 11 | ||||
-rw-r--r-- | src/citra_qt/configure_dialog.cpp | 9 | ||||
-rw-r--r-- | src/citra_qt/configure_dialog.h | 3 | ||||
-rw-r--r-- | src/citra_qt/configure_system.cpp | 136 | ||||
-rw-r--r-- | src/citra_qt/configure_system.h | 38 | ||||
-rw-r--r-- | src/citra_qt/configure_system.ui | 252 | ||||
-rw-r--r-- | src/citra_qt/main.cpp | 2 | ||||
-rw-r--r-- | src/core/hle/service/cfg/cfg.cpp | 155 | ||||
-rw-r--r-- | src/core/hle/service/cfg/cfg.h | 99 | ||||
-rw-r--r-- | src/core/hle/service/cfg/cfg_i.cpp | 4 | ||||
-rw-r--r-- | src/core/hle/service/cfg/cfg_s.cpp | 2 | ||||
-rw-r--r-- | src/core/hle/service/fs/archive.cpp | 24 | ||||
-rw-r--r-- | src/core/hle/service/fs/archive.h | 6 |
14 files changed, 703 insertions, 41 deletions
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 @@ <string>General</string> </attribute> </widget> + <widget class="ConfigureSystem" name="systemTab"> + <attribute name="title"> + <string>System</string> + </attribute> + </widget> <widget class="QWidget" name="inputTab"> <attribute name="title"> <string>Input</string> @@ -58,6 +63,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureSystem</class> + <extends>QWidget</extends> + <header>configure_system.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> <header>configure_audio.h</header> 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::ConfigureDialog> 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<int, 12> 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<const ushort*>(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<const char16_t*>(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<Service::CFG::SystemLanguage>(new_language)); + modified = true; + } + + // apply sound + int new_sound = ui->combo_sound->currentIndex(); + if (sound_index != new_sound) { + Service::CFG::SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(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 <memory> +#include <QWidget> + +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::ConfigureSystem> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureSystem</class> + <widget class="QWidget" name="ConfigureSystem"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>360</width> + <height>377</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="group_system_settings"> + <property name="title"> + <string>System Settings</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_username"> + <property name="text"> + <string>Username</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="edit_username"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maxLength"> + <number>10</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_birthday"> + <property name="text"> + <string>Birthday</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> + <item> + <widget class="QComboBox" name="combo_birthmonth"> + <item> + <property name="text"> + <string>January</string> + </property> + </item> + <item> + <property name="text"> + <string>February</string> + </property> + </item> + <item> + <property name="text"> + <string>March</string> + </property> + </item> + <item> + <property name="text"> + <string>April</string> + </property> + </item> + <item> + <property name="text"> + <string>May</string> + </property> + </item> + <item> + <property name="text"> + <string>June</string> + </property> + </item> + <item> + <property name="text"> + <string>July</string> + </property> + </item> + <item> + <property name="text"> + <string>August</string> + </property> + </item> + <item> + <property name="text"> + <string>September</string> + </property> + </item> + <item> + <property name="text"> + <string>October</string> + </property> + </item> + <item> + <property name="text"> + <string>November</string> + </property> + </item> + <item> + <property name="text"> + <string>December</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="combo_birthday"/> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_language"> + <property name="text"> + <string>Language</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="combo_language"> + <item> + <property name="text"> + <string>Japanese (日本語)</string> + </property> + </item> + <item> + <property name="text"> + <string>English</string> + </property> + </item> + <item> + <property name="text"> + <string>French (français)</string> + </property> + </item> + <item> + <property name="text"> + <string>German (Deutsch)</string> + </property> + </item> + <item> + <property name="text"> + <string>Italian (italiano)</string> + </property> + </item> + <item> + <property name="text"> + <string>Spanish (español)</string> + </property> + </item> + <item> + <property name="text"> + <string>Simplified Chinese (简体中文)</string> + </property> + </item> + <item> + <property name="text"> + <string>Korean (한국어)</string> + </property> + </item> + <item> + <property name="text"> + <string>Dutch (Nederlands)</string> + </property> + </item> + <item> + <property name="text"> + <string>Portuguese (português)</string> + </property> + </item> + <item> + <property name="text"> + <string>Russian (Русский)</string> + </property> + </item> + <item> + <property name="text"> + <string>Traditional Chinese (正體中文)</string> + </property> + </item> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_sound"> + <property name="text"> + <string>Sound output mode</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="combo_sound"> + <item> + <property name="text"> + <string>Mono</string> + </property> + </item> + <item> + <property name="text"> + <string>Stereo</string> + </property> + </item> + <item> + <property name="text"> + <string>Surround</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_disable_info"> + <property name="text"> + <string>System settings are available only when game is not running.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> 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<u8> 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<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 flag) { // Read the header SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(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<void*>(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<u8, u8> 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<SystemLanguage>(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<SoundOutputMode>(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 <array> +#include <string> #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 @@ -178,6 +186,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: * 0 : 0x04030000 / 0x08030000 @@ -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<u8, u8> 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<ArchiveIdCode, std::unique_ptr<ArchiveFactory>> 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 |