Merge pull request #1894 from wwylele/set-config-block
Implement config savegame editing & clean up
This commit is contained in:
		| @@ -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 | ||||
|             ) | ||||
|   | ||||
| @@ -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> | ||||
| @@ -57,6 +62,12 @@ | ||||
|    <header>configure_general.h</header> | ||||
|    <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> | ||||
|   | ||||
| @@ -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(); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										136
									
								
								src/citra_qt/configure_system.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/citra_qt/configure_system.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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
									
								
								src/citra_qt/configure_system.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/citra_qt/configure_system.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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
									
								
								src/citra_qt/configure_system.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								src/citra_qt/configure_system.ui
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
| @@ -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) | ||||
|     { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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<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 | ||||
|   | ||||
| @@ -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"}, | ||||
|   | ||||
| @@ -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"}, | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user