Преглед изворни кода

Merge pull request #1894 from wwylele/set-config-block

Implement config savegame editing & clean up
Yuri Kunde Schlesner пре 10 година
родитељ
комит
ffda82eea5

+ 3 - 0
src/citra_qt/CMakeLists.txt

@@ -22,6 +22,7 @@ set(SRCS
             configure_debug.cpp
             configure_debug.cpp
             configure_dialog.cpp
             configure_dialog.cpp
             configure_general.cpp
             configure_general.cpp
+            configure_system.cpp
             game_list.cpp
             game_list.cpp
             hotkeys.cpp
             hotkeys.cpp
             main.cpp
             main.cpp
@@ -52,6 +53,7 @@ set(HEADERS
             configure_debug.h
             configure_debug.h
             configure_dialog.h
             configure_dialog.h
             configure_general.h
             configure_general.h
+            configure_system.h
             game_list.h
             game_list.h
             game_list_p.h
             game_list_p.h
             hotkeys.h
             hotkeys.h
@@ -69,6 +71,7 @@ set(UIS
             configure_audio.ui
             configure_audio.ui
             configure_debug.ui
             configure_debug.ui
             configure_general.ui
             configure_general.ui
+            configure_system.ui
             hotkeys.ui
             hotkeys.ui
             main.ui
             main.ui
             )
             )

+ 11 - 0
src/citra_qt/configure.ui

@@ -24,6 +24,11 @@
        <string>General</string>
        <string>General</string>
       </attribute>
       </attribute>
      </widget>
      </widget>
+     <widget class="ConfigureSystem" name="systemTab">
+      <attribute name="title">
+       <string>System</string>
+      </attribute>
+     </widget>
      <widget class="QWidget" name="inputTab">
      <widget class="QWidget" name="inputTab">
       <attribute name="title">
       <attribute name="title">
        <string>Input</string>
        <string>Input</string>
@@ -57,6 +62,12 @@
    <header>configure_general.h</header>
    <header>configure_general.h</header>
    <container>1</container>
    <container>1</container>
   </customwidget>
   </customwidget>
+  <customwidget>
+   <class>ConfigureSystem</class>
+   <extends>QWidget</extends>
+   <header>configure_system.h</header>
+   <container>1</container>
+  </customwidget>
   <customwidget>
   <customwidget>
    <class>ConfigureAudio</class>
    <class>ConfigureAudio</class>
    <extends>QWidget</extends>
    <extends>QWidget</extends>

+ 7 - 2
src/citra_qt/configure_dialog.cpp

@@ -9,9 +9,10 @@
 
 
 #include "core/settings.h"
 #include "core/settings.h"
 
 
-ConfigureDialog::ConfigureDialog(QWidget *parent) :
+ConfigureDialog::ConfigureDialog(QWidget *parent, bool running) :
     QDialog(parent),
     QDialog(parent),
-    ui(new Ui::ConfigureDialog)
+    ui(new Ui::ConfigureDialog),
+    emulation_running(running)
 {
 {
     ui->setupUi(this);
     ui->setupUi(this);
     this->setConfiguration();
     this->setConfiguration();
@@ -21,10 +22,14 @@ ConfigureDialog::~ConfigureDialog() {
 }
 }
 
 
 void ConfigureDialog::setConfiguration() {
 void ConfigureDialog::setConfiguration() {
+    // System tab needs set manually
+    // depending on whether emulation is running
+    ui->systemTab->setConfiguration(emulation_running);
 }
 }
 
 
 void ConfigureDialog::applyConfiguration() {
 void ConfigureDialog::applyConfiguration() {
     ui->generalTab->applyConfiguration();
     ui->generalTab->applyConfiguration();
+    ui->systemTab->applyConfiguration();
     ui->audioTab->applyConfiguration();
     ui->audioTab->applyConfiguration();
     ui->debugTab->applyConfiguration();
     ui->debugTab->applyConfiguration();
 }
 }

+ 2 - 1
src/citra_qt/configure_dialog.h

@@ -16,7 +16,7 @@ class ConfigureDialog : public QDialog
     Q_OBJECT
     Q_OBJECT
 
 
 public:
 public:
-    explicit ConfigureDialog(QWidget *parent = nullptr);
+    explicit ConfigureDialog(QWidget *parent, bool emulation_running);
     ~ConfigureDialog();
     ~ConfigureDialog();
 
 
     void applyConfiguration();
     void applyConfiguration();
@@ -26,4 +26,5 @@ private:
 
 
 private:
 private:
     std::unique_ptr<Ui::ConfigureDialog> ui;
     std::unique_ptr<Ui::ConfigureDialog> ui;
+    bool emulation_running;
 };
 };

+ 136 - 0
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);
+}

+ 38 - 0
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;
+};

+ 252 - 0
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>

+ 1 - 1
src/citra_qt/main.cpp

@@ -508,7 +508,7 @@ void GMainWindow::ToggleWindowMode() {
 }
 }
 
 
 void GMainWindow::OnConfigure() {
 void GMainWindow::OnConfigure() {
-    ConfigureDialog configureDialog(this);
+    ConfigureDialog configureDialog(this, emulation_running);
     auto result = configureDialog.exec();
     auto result = configureDialog.exec();
     if (result == QDialog::Accepted)
     if (result == QDialog::Accepted)
     {
     {

+ 131 - 24
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");
 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 {
 struct UsernameBlock {
     char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary
     char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary
     u32 zero;
     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 u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
 static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
 static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
 static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014
 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;
 static const u8 UNITED_STATES_COUNTRY_ID = 49;
 /// TODO(Subv): Find what the other bytes are
 /// TODO(Subv): Find what the other bytes are
 static const ConsoleCountryInfo COUNTRY_INFO = { { 0, 0, 0 }, UNITED_STATES_COUNTRY_ID };
 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());
     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) {
 void UpdateConfigNANDSavegame(Service::Interface* self) {
     u32* cmd_buff = Kernel::GetCommandBuffer();
     u32* cmd_buff = Kernel::GetCommandBuffer();
     cmd_buff[1] = Service::CFG::UpdateConfigNANDSavegame().raw;
     cmd_buff[1] = Service::CFG::UpdateConfigNANDSavegame().raw;
@@ -234,13 +263,13 @@ void FormatConfig(Service::Interface* self) {
     cmd_buff[1] = Service::CFG::FormatConfig().raw;
     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
     // Read the header
     SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data());
     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),
     auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries),
         [&](const SaveConfigBlockEntry& entry) {
         [&](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)) {
     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);
         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) {
     if (itr->size != size) {
         LOG_ERROR(Service_CFG, "Invalid size %u for config block 0x%X with flags %u", size, block_id, flag);
         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);
         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
     // The data is located in the block header itself if the size is less than 4 bytes
     if (itr->size <= 4)
     if (itr->size <= 4)
-        memcpy(output, &itr->offset_or_data, itr->size);
+        pointer = &itr->offset_or_data;
     else
     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;
     return RESULT_SUCCESS;
 }
 }
 
 
@@ -336,25 +386,25 @@ ResultCode FormatConfig() {
     res = CreateConfigInfoBlk(0x00030001, 0x8, 0xE, zero_buffer);
     res = CreateConfigInfoBlk(0x00030001, 0x8, 0xE, zero_buffer);
     if (!res.IsSuccess()) return res;
     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;
     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;
     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;
     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;
     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;
     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;
     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;
     if (!res.IsSuccess()) return res;
 
 
     u16_le country_name_buffer[16][0x40] = {};
     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]);
         std::copy(region_name.cbegin(), region_name.cend(), country_name_buffer[i]);
     }
     }
     // 0x000B0001 - Localized names for the profile Country
     // 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;
     if (!res.IsSuccess()) return res;
     // 0x000B0002 - Localized names for the profile State/Province
     // 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;
     if (!res.IsSuccess()) return res;
 
 
     // 0x000B0003 - Unknown, related to country/address (zip code?)
     // 0x000B0003 - Unknown, related to country/address (zip code?)
@@ -382,10 +432,10 @@ ResultCode FormatConfig() {
     if (!res.IsSuccess()) return res;
     if (!res.IsSuccess()) return res;
 
 
     // 0x000D0000 - Accepted EULA version
     // 0x000D0000 - Accepted EULA version
-    res = CreateConfigInfoBlk(0x000D0000, 0x4, 0xE, zero_buffer);
+    res = CreateConfigInfoBlk(EULAVersionBlockID, 0x4, 0xE, zero_buffer);
     if (!res.IsSuccess()) return res;
     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;
     if (!res.IsSuccess()) return res;
 
 
     // 0x00170000 - Unknown
     // 0x00170000 - Unknown
@@ -399,11 +449,7 @@ ResultCode FormatConfig() {
     return RESULT_SUCCESS;
     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
     // Open the SystemSaveData archive 0x00010017
     FileSys::Path archive_path(cfg_system_savedata_id);
     FileSys::Path archive_path(cfg_system_savedata_id);
     auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path);
     auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path);
@@ -431,14 +477,75 @@ void Init() {
     if (config_result.Succeeded()) {
     if (config_result.Succeeded()) {
         auto config = config_result.MoveFrom();
         auto config = config_result.MoveFrom();
         config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data());
         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 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 CFG
 } // namespace Service
 } // namespace Service

+ 97 - 2
src/core/hle/service/cfg/cfg.h

@@ -5,6 +5,7 @@
 #pragma once
 #pragma once
 
 
 #include <array>
 #include <array>
+#include <string>
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
 
 
@@ -35,7 +36,14 @@ enum SystemLanguage {
     LANGUAGE_KO = 7,
     LANGUAGE_KO = 7,
     LANGUAGE_NL = 8,
     LANGUAGE_NL = 8,
     LANGUAGE_PT = 9,
     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
 /// Block header in the config savedata file
@@ -177,6 +185,22 @@ void GetConfigInfoBlk2(Service::Interface* self);
  */
  */
 void GetConfigInfoBlk8(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
  * CFG::UpdateConfigNANDSavegame service function
  *  Inputs:
  *  Inputs:
@@ -205,7 +229,19 @@ void FormatConfig(Service::Interface* self);
  * @param output A pointer where we will write the read data
  * @param output A pointer where we will write the read data
  * @returns ResultCode indicating the result of the operation, 0 on success
  * @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.
  * 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();
 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
 /// Initialize the config service
 void Init();
 void Init();
 
 
 /// Shutdown the config service
 /// Shutdown the config service
 void Shutdown();
 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 CFG
 } // namespace Service
 } // namespace Service

+ 2 - 2
src/core/hle/service/cfg/cfg_i.cpp

@@ -22,7 +22,7 @@ const Interface::FunctionInfo FunctionTable[] = {
     {0x000A0040, GetCountryCodeID,                     "GetCountryCodeID"},
     {0x000A0040, GetCountryCodeID,                     "GetCountryCodeID"},
     // cfg:i
     // cfg:i
     {0x04010082, GetConfigInfoBlk8,                    "GetConfigInfoBlk8"},
     {0x04010082, GetConfigInfoBlk8,                    "GetConfigInfoBlk8"},
-    {0x04020082, nullptr,                              "SetConfigInfoBlk4"},
+    {0x04020082, SetConfigInfoBlk4,                    "SetConfigInfoBlk4"},
     {0x04030000, UpdateConfigNANDSavegame,             "UpdateConfigNANDSavegame"},
     {0x04030000, UpdateConfigNANDSavegame,             "UpdateConfigNANDSavegame"},
     {0x04040042, nullptr,                              "GetLocalFriendCodeSeedData"},
     {0x04040042, nullptr,                              "GetLocalFriendCodeSeedData"},
     {0x04050000, nullptr,                              "GetLocalFriendCodeSeed"},
     {0x04050000, nullptr,                              "GetLocalFriendCodeSeed"},
@@ -31,7 +31,7 @@ const Interface::FunctionInfo FunctionTable[] = {
     {0x04080042, nullptr,                              "SecureInfoGetSerialNo"},
     {0x04080042, nullptr,                              "SecureInfoGetSerialNo"},
     {0x04090000, nullptr,                              "UpdateConfigBlk00040003"},
     {0x04090000, nullptr,                              "UpdateConfigBlk00040003"},
     {0x08010082, GetConfigInfoBlk8,                    "GetConfigInfoBlk8"},
     {0x08010082, GetConfigInfoBlk8,                    "GetConfigInfoBlk8"},
-    {0x08020082, nullptr,                              "SetConfigInfoBlk4"},
+    {0x08020082, SetConfigInfoBlk4,                    "SetConfigInfoBlk4"},
     {0x08030000, UpdateConfigNANDSavegame,             "UpdateConfigNANDSavegame"},
     {0x08030000, UpdateConfigNANDSavegame,             "UpdateConfigNANDSavegame"},
     {0x080400C2, nullptr,                              "CreateConfigInfoBlk"},
     {0x080400C2, nullptr,                              "CreateConfigInfoBlk"},
     {0x08050000, nullptr,                              "DeleteConfigNANDSavefile"},
     {0x08050000, nullptr,                              "DeleteConfigNANDSavefile"},

+ 1 - 1
src/core/hle/service/cfg/cfg_s.cpp

@@ -22,7 +22,7 @@ const Interface::FunctionInfo FunctionTable[] = {
     {0x000A0040, GetCountryCodeID,                     "GetCountryCodeID"},
     {0x000A0040, GetCountryCodeID,                     "GetCountryCodeID"},
     // cfg:s
     // cfg:s
     {0x04010082, GetConfigInfoBlk8,                    "GetConfigInfoBlk8"},
     {0x04010082, GetConfigInfoBlk8,                    "GetConfigInfoBlk8"},
-    {0x04020082, nullptr,                              "SetConfigInfoBlk4"},
+    {0x04020082, SetConfigInfoBlk4,                    "SetConfigInfoBlk4"},
     {0x04030000, UpdateConfigNANDSavegame,             "UpdateConfigNANDSavegame"},
     {0x04030000, UpdateConfigNANDSavegame,             "UpdateConfigNANDSavegame"},
     {0x04040042, nullptr,                              "GetLocalFriendCodeSeedData"},
     {0x04040042, nullptr,                              "GetLocalFriendCodeSeedData"},
     {0x04050000, nullptr,                              "GetLocalFriendCodeSeed"},
     {0x04050000, nullptr,                              "GetLocalFriendCodeSeed"},

+ 16 - 8
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
  * 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;
 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;
     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:
     // TODO(Subv): Add the other archive types (see here for the known types:
     // http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
     // http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
 
 
@@ -562,10 +557,23 @@ void ArchiveInit() {
     RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData);
     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
 /// Shutdown archives
 void ArchiveShutdown() {
 void ArchiveShutdown() {
     handle_map.clear();
     handle_map.clear();
-    id_code_map.clear();
+    UnregisterArchiveTypes();
 }
 }
 
 
 } // namespace FS
 } // namespace FS

+ 6 - 0
src/core/hle/service/fs/archive.h

@@ -235,5 +235,11 @@ void ArchiveInit();
 /// Shutdown archives
 /// Shutdown archives
 void ArchiveShutdown();
 void ArchiveShutdown();
 
 
+/// Register all archive types
+void RegisterArchiveTypes();
+
+/// Unregister all archive types
+void UnregisterArchiveTypes();
+
 } // namespace FS
 } // namespace FS
 } // namespace Service
 } // namespace Service