From 572d3ab71c6a1b0850bb07a25039a496b4408173 Mon Sep 17 00:00:00 2001 From: Reg Tiangha Date: Wed, 8 May 2024 03:30:57 -0600 Subject: [PATCH 01/30] citra_qt: Restore Web Tab (#103) * citra_qt: Restore Web tab * configure_web: Remove telemetry and Citra Web Services --- dist/languages/da_DK.ts | 20 +++++- dist/languages/de.ts | 20 +++++- dist/languages/el.ts | 20 +++++- dist/languages/es_ES.ts | 20 +++++- dist/languages/fi.ts | 20 +++++- dist/languages/fr.ts | 20 +++++- dist/languages/hu_HU.ts | 68 ++++++++++++++++++- dist/languages/id.ts | 20 +++++- dist/languages/it.ts | 20 +++++- dist/languages/ja_JP.ts | 20 +++++- dist/languages/ko_KR.ts | 20 +++++- dist/languages/lt_LT.ts | 20 +++++- dist/languages/nb.ts | 20 +++++- dist/languages/nl.ts | 20 +++++- dist/languages/pl_PL.ts | 20 +++++- dist/languages/pt_BR.ts | 20 +++++- dist/languages/ro_RO.ts | 20 +++++- dist/languages/ru_RU.ts | 20 +++++- dist/languages/tr_TR.ts | 20 +++++- dist/languages/vi_VN.ts | 20 +++++- dist/languages/zh_CN.ts | 20 +++++- dist/languages/zh_TW.ts | 20 +++++- src/citra_qt/CMakeLists.txt | 3 + src/citra_qt/configuration/configure.ui | 6 ++ .../configuration/configure_dialog.cpp | 10 ++- src/citra_qt/configuration/configure_dialog.h | 2 + src/citra_qt/configuration/configure_web.cpp | 36 ++++++++++ src/citra_qt/configuration/configure_web.h | 28 ++++++++ src/citra_qt/configuration/configure_web.ui | 53 +++++++++++++++ 29 files changed, 602 insertions(+), 24 deletions(-) create mode 100644 src/citra_qt/configuration/configure_web.cpp create mode 100644 src/citra_qt/configuration/configure_web.h create mode 100644 src/citra_qt/configuration/configure_web.ui diff --git a/dist/languages/da_DK.ts b/dist/languages/da_DK.ts index 861b59046..3c3125c1d 100644 --- a/dist/languages/da_DK.ts +++ b/dist/languages/da_DK.ts @@ -3583,6 +3583,24 @@ Drag points to change position, or double-click table cells to edit values.Engelsk + + ConfigureWeb + + + Form + Form + + + + Discord Presence + Discord-presence + + + + Show Current Game in your Discord Status + Vis kørende spil som din Discord-status + + DirectConnect @@ -6861,4 +6879,4 @@ They may have left the room. Afventningstræ - + \ No newline at end of file diff --git a/dist/languages/de.ts b/dist/languages/de.ts index 64f526171..da1f1053d 100644 --- a/dist/languages/de.ts +++ b/dist/languages/de.ts @@ -3585,6 +3585,24 @@ Ziehe Punkte, um ihre Position zu verändern, oder doppelklicke auf Zellen in de Englisch + + ConfigureWeb + + + Form + Form + + + + Discord Presence + Discord Presence + + + + Show Current Game in your Discord Status + Aktuelles Spiel in Ihrem Discordstatus anzeigen + + DirectConnect @@ -6868,4 +6886,4 @@ They may have left the room. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/el.ts b/dist/languages/el.ts index 2731abe21..5aa88a814 100644 --- a/dist/languages/el.ts +++ b/dist/languages/el.ts @@ -3584,6 +3584,24 @@ Drag points to change position, or double-click table cells to edit values.Αγγλικά + + ConfigureWeb + + + Form + Φόρμα + + + + Discord Presence + Παρουσία Discord + + + + Show Current Game in your Discord Status + Εμφάνιση τρέχοντος παιχνιδιού στην κατάσταση Discord σας + + DirectConnect @@ -6865,4 +6883,4 @@ They may have left the room. Δένδρο αναμονής - + \ No newline at end of file diff --git a/dist/languages/es_ES.ts b/dist/languages/es_ES.ts index ce5e326d4..090e7a4cc 100644 --- a/dist/languages/es_ES.ts +++ b/dist/languages/es_ES.ts @@ -3585,6 +3585,24 @@ Mueve los puntos para cambiar la posición, o haz doble click en las celdas de l Inglés (English) + + ConfigureWeb + + + Form + Formulario + + + + Discord Presence + Presencia en Discord + + + + Show Current Game in your Discord Status + Mostrar Juego Actual en el Estado de Discord + + DirectConnect @@ -6878,4 +6896,4 @@ Puede que haya dejado la sala. Árbol de Espera - + \ No newline at end of file diff --git a/dist/languages/fi.ts b/dist/languages/fi.ts index b01398ff4..52f9b2b6e 100644 --- a/dist/languages/fi.ts +++ b/dist/languages/fi.ts @@ -3583,6 +3583,24 @@ Drag points to change position, or double-click table cells to edit values.Englanti + + ConfigureWeb + + + Form + Muot + + + + Discord Presence + Discord läsnäolo + + + + Show Current Game in your Discord Status + Näytä nykyinen peli Discord tilassa + + DirectConnect @@ -6845,4 +6863,4 @@ They may have left the room. - + \ No newline at end of file diff --git a/dist/languages/fr.ts b/dist/languages/fr.ts index 656611455..8dfbf7261 100644 --- a/dist/languages/fr.ts +++ b/dist/languages/fr.ts @@ -3585,6 +3585,24 @@ Glissez les points pour modifier la position, ou double-cliquez les cellules pou Anglais + + ConfigureWeb + + + Form + Forme + + + + Discord Presence + Présence sur Discord + + + + Show Current Game in your Discord Status + Afficher votre jeu en cours dans votre statut Discord + + DirectConnect @@ -6873,4 +6891,4 @@ Il a peut-être quitté la salon. Arbre d'instructions - + \ No newline at end of file diff --git a/dist/languages/hu_HU.ts b/dist/languages/hu_HU.ts index 6abf3b4df..2d5545538 100644 --- a/dist/languages/hu_HU.ts +++ b/dist/languages/hu_HU.ts @@ -3582,6 +3582,72 @@ Drag points to change position, or double-click table cells to edit values.Angol + + ConfigureWeb + + + Form + Forma + + + + Discord Presence + Discord jelenlét + + + + Show Current Game in your Discord Status + Jelenlegi játék megjelenítése a Discord állapotodban + + + + DirectConnect + + + Direct Connect + Közvetlen Kapcsolódás + + + + Server Address + + + + + <html><head/><body><p>Server address of the host</p></body></html> + + + + + Port + Port + + + + <html><head/><body><p>Port number the host is listening on</p></body></html> + <html><head/><body><p>Annak a portnak a száma, amire a gazda figyel</p></body></html> + + + + 24872 + 24872 + + + + Nickname + Becenév + + + + Password + Jelszó + + + + Connect + Kapcsolás + + DirectConnectWindow @@ -6807,4 +6873,4 @@ They may have left the room. Várakozási Fa - + \ No newline at end of file diff --git a/dist/languages/id.ts b/dist/languages/id.ts index 4bb737e4f..a6c6cff68 100644 --- a/dist/languages/id.ts +++ b/dist/languages/id.ts @@ -3584,6 +3584,24 @@ Drag points to change position, or double-click table cells to edit values.Inggris + + ConfigureWeb + + + Form + Formulir + + + + Discord Presence + Status Discord + + + + Show Current Game in your Discord Status + Tampilkan Game Saat Ini ke Status Discord Anda + + DirectConnect @@ -6861,4 +6879,4 @@ They may have left the room. Tunggu Tree - + \ No newline at end of file diff --git a/dist/languages/it.ts b/dist/languages/it.ts index d4bd4ccb8..bde755430 100644 --- a/dist/languages/it.ts +++ b/dist/languages/it.ts @@ -3585,6 +3585,24 @@ Trascina i punti per cambiarne la posizione, o fai doppio clic sulla tabella per Inglese + + ConfigureWeb + + + Form + Modulo + + + + Discord Presence + Discord Presence + + + + Show Current Game in your Discord Status + Mostra il gioco attuale nel tuo stato di Discord + + DirectConnect @@ -6874,4 +6892,4 @@ Potrebbe aver lasciato la stanza. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/ja_JP.ts b/dist/languages/ja_JP.ts index 95ec541b4..3cc021d2a 100644 --- a/dist/languages/ja_JP.ts +++ b/dist/languages/ja_JP.ts @@ -3587,6 +3587,24 @@ Drag points to change position, or double-click table cells to edit values.英語 + + ConfigureWeb + + + Form + フォーム + + + + Discord Presence + Discord Presence + + + + Show Current Game in your Discord Status + プレイ中のゲームをDiscordに表示 + + DirectConnect @@ -6866,4 +6884,4 @@ They may have left the room. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/ko_KR.ts b/dist/languages/ko_KR.ts index fba684f70..351783d05 100644 --- a/dist/languages/ko_KR.ts +++ b/dist/languages/ko_KR.ts @@ -3585,6 +3585,24 @@ Drag points to change position, or double-click table cells to edit values.English + + ConfigureWeb + + + Form + 종류 + + + + Discord Presence + 디스코드 있음 + + + + Show Current Game in your Discord Status + 사용자의 디스코드 상태에 현재 게임 표시하기 + + DirectConnect @@ -6868,4 +6886,4 @@ They may have left the room. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/lt_LT.ts b/dist/languages/lt_LT.ts index 176e6cc01..1c7b45213 100644 --- a/dist/languages/lt_LT.ts +++ b/dist/languages/lt_LT.ts @@ -3581,6 +3581,24 @@ Drag points to change position, or double-click table cells to edit values.Anglų k. + + ConfigureWeb + + + Form + Forma + + + + Discord Presence + Discord nustatymai + + + + Show Current Game in your Discord Status + Rodyti jūsų žaidžiamą žaidimą Discord'e + + DirectConnect @@ -6853,4 +6871,4 @@ They may have left the room. Laukimo gijų medis - + \ No newline at end of file diff --git a/dist/languages/nb.ts b/dist/languages/nb.ts index a3c44b557..30ee9deac 100644 --- a/dist/languages/nb.ts +++ b/dist/languages/nb.ts @@ -3584,6 +3584,24 @@ Dra punkter for å endre posisjon, eller dobbeltklikk på tabellceller for å re Engelsk + + ConfigureWeb + + + Form + Form + + + + Discord Presence + Discord tilstedeværelse + + + + Show Current Game in your Discord Status + Vis Gjeldende Spill i Discord Statusen din. + + DirectConnect @@ -6865,4 +6883,4 @@ They may have left the room. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/nl.ts b/dist/languages/nl.ts index a5b218d06..9bedcf20a 100644 --- a/dist/languages/nl.ts +++ b/dist/languages/nl.ts @@ -3585,6 +3585,24 @@ Sleep punten om de positie te wijzigen of dubbelklik op tabelcellen om waarden t Engels + + ConfigureWeb + + + Form + Formulier + + + + Discord Presence + Discord Presence + + + + Show Current Game in your Discord Status + Toon Huidige Spel in je Discord Status + + DirectConnect @@ -6876,4 +6894,4 @@ Misschien hebben ze de kamer verlaten. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/pl_PL.ts b/dist/languages/pl_PL.ts index 64be5be5d..ad25cfd1f 100644 --- a/dist/languages/pl_PL.ts +++ b/dist/languages/pl_PL.ts @@ -3583,6 +3583,24 @@ Drag points to change position, or double-click table cells to edit values.Angielski (English) + + ConfigureWeb + + + Form + Formularz + + + + Discord Presence + Widoczność na Discordzie + + + + Show Current Game in your Discord Status + Pokaż obecnie włączoną grę w statusie na Discrodzie + + DirectConnect @@ -6856,4 +6874,4 @@ They may have left the room. Kolejka Oczekiwania - + \ No newline at end of file diff --git a/dist/languages/pt_BR.ts b/dist/languages/pt_BR.ts index 8c7da57f5..de10a88a8 100644 --- a/dist/languages/pt_BR.ts +++ b/dist/languages/pt_BR.ts @@ -3585,6 +3585,24 @@ Arraste os pontos para alterar a posição ou clique duas vezes nas células da Inglês (English) + + ConfigureWeb + + + Form + Formulário + + + + Discord Presence + Presença no Discord + + + + Show Current Game in your Discord Status + Mostrar o jogo atual no seu perfil do Discord + + DirectConnect @@ -6869,4 +6887,4 @@ They may have left the room. Árvore de espera - + \ No newline at end of file diff --git a/dist/languages/ro_RO.ts b/dist/languages/ro_RO.ts index 141b171a6..ca8f8091e 100644 --- a/dist/languages/ro_RO.ts +++ b/dist/languages/ro_RO.ts @@ -3584,6 +3584,24 @@ Drag points to change position, or double-click table cells to edit values.Engleză + + ConfigureWeb + + + Form + Model + + + + Discord Presence + Prezență pe Discord + + + + Show Current Game in your Discord Status + Afișează Jocul Prezent pe Statusul Discord + + DirectConnect @@ -6863,4 +6881,4 @@ They may have left the room. Copac de Așteptare - + \ No newline at end of file diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts index 992939f49..1e7304d26 100644 --- a/dist/languages/ru_RU.ts +++ b/dist/languages/ru_RU.ts @@ -3587,6 +3587,24 @@ Drag points to change position, or double-click table cells to edit values.Английский + + ConfigureWeb + + + Form + Форма + + + + Discord Presence + Интеграция с Discord + + + + Show Current Game in your Discord Status + Показывать текущую игру в статусе Discord + + DirectConnect @@ -6868,4 +6886,4 @@ They may have left the room. Дерево цепочки ожидания - + \ No newline at end of file diff --git a/dist/languages/tr_TR.ts b/dist/languages/tr_TR.ts index 025759bdf..1a7a2e17c 100644 --- a/dist/languages/tr_TR.ts +++ b/dist/languages/tr_TR.ts @@ -3583,6 +3583,24 @@ Drag points to change position, or double-click table cells to edit values.İngilizce + + ConfigureWeb + + + Form + Form + + + + Discord Presence + Discord Görünümü + + + + Show Current Game in your Discord Status + Şu Anki Oyunu Discord Durumunda Göster + + DirectConnect @@ -6865,4 +6883,4 @@ They may have left the room. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/vi_VN.ts b/dist/languages/vi_VN.ts index 617efbbe2..0b8866d55 100644 --- a/dist/languages/vi_VN.ts +++ b/dist/languages/vi_VN.ts @@ -3583,6 +3583,24 @@ Drag points to change position, or double-click table cells to edit values.Tiếng Anh + + ConfigureWeb + + + Form + Định dạng + + + + Discord Presence + + + + + Show Current Game in your Discord Status + Hiển thị game đang chơi trên trạng thái Discord + + DirectConnect @@ -6861,4 +6879,4 @@ They may have left the room. Wait Tree - + \ No newline at end of file diff --git a/dist/languages/zh_CN.ts b/dist/languages/zh_CN.ts index 402eafcb4..fbc6b80f8 100644 --- a/dist/languages/zh_CN.ts +++ b/dist/languages/zh_CN.ts @@ -3585,6 +3585,24 @@ Drag points to change position, or double-click table cells to edit values.英语 + + ConfigureWeb + + + Form + 格式 + + + + Discord Presence + Discord 状态 + + + + Show Current Game in your Discord Status + 在您的 Discord 状态中显示当前游戏 + + DirectConnect @@ -6873,4 +6891,4 @@ They may have left the room. 等待树 - + \ No newline at end of file diff --git a/dist/languages/zh_TW.ts b/dist/languages/zh_TW.ts index 55eb19ad9..220cdf95c 100644 --- a/dist/languages/zh_TW.ts +++ b/dist/languages/zh_TW.ts @@ -3584,6 +3584,24 @@ Drag points to change position, or double-click table cells to edit values.English + + ConfigureWeb + + + Form + Form + + + + Discord Presence + Discord 狀態 + + + + Show Current Game in your Discord Status + 在 Discord 狀態中顯示正在玩的遊戲 + + DirectConnect @@ -6865,4 +6883,4 @@ They may have left the room. 樹狀等待 - + \ No newline at end of file diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d8fce8c41..b17a143d3 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -81,6 +81,9 @@ add_executable(citra-qt configuration/configure_ui.cpp configuration/configure_ui.h configuration/configure_ui.ui + configuration/configure_web.cpp + configuration/configure_web.h + configuration/configure_web.ui configuration/configure_cheats.cpp configuration/configure_cheats.h configuration/configure_cheats.ui diff --git a/src/citra_qt/configuration/configure.ui b/src/citra_qt/configuration/configure.ui index e94cbbc9c..f6f5a517a 100644 --- a/src/citra_qt/configuration/configure.ui +++ b/src/citra_qt/configuration/configure.ui @@ -97,6 +97,12 @@
configuration/configure_enhancements.h
1 + + ConfigureWeb + QWidget +
configuration/configure_web.h
+ 1 +
ConfigureUi QWidget diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 4dd5635c5..896009352 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -16,6 +16,7 @@ #include "citra_qt/configuration/configure_storage.h" #include "citra_qt/configuration/configure_system.h" #include "citra_qt/configuration/configure_ui.h" +#include "citra_qt/configuration/configure_web.h" #include "citra_qt/hotkeys.h" #include "common/settings.h" #include "core/core.h" @@ -37,7 +38,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor camera_tab{std::make_unique(this)}, debug_tab{std::make_unique(is_powered_on, this)}, storage_tab{std::make_unique(is_powered_on, this)}, - ui_tab{std::make_unique(this)} { + web_tab{std::make_unique(this)}, ui_tab{std::make_unique(this)} { Settings::SetConfiguringGlobal(true); ui->setupUi(this); @@ -52,6 +53,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor ui->tabWidget->addTab(camera_tab.get(), tr("Camera")); ui->tabWidget->addTab(debug_tab.get(), tr("Debug")); ui->tabWidget->addTab(storage_tab.get(), tr("Storage")); + ui->tabWidget->addTab(web_tab.get(), tr("Web")); ui->tabWidget->addTab(ui_tab.get(), tr("UI")); hotkeys_tab->Populate(registry); @@ -87,6 +89,7 @@ void ConfigureDialog::SetConfiguration() { audio_tab->SetConfiguration(); camera_tab->SetConfiguration(); debug_tab->SetConfiguration(); + web_tab->SetConfiguration(); ui_tab->SetConfiguration(); storage_tab->SetConfiguration(); } @@ -102,6 +105,7 @@ void ConfigureDialog::ApplyConfiguration() { audio_tab->ApplyConfiguration(); camera_tab->ApplyConfiguration(); debug_tab->ApplyConfiguration(); + web_tab->ApplyConfiguration(); ui_tab->ApplyConfiguration(); storage_tab->ApplyConfiguration(); system.ApplySettings(); @@ -114,7 +118,7 @@ void ConfigureDialog::PopulateSelectionList() { ui->selectorList->clear(); const std::array>, 5> items{ - {{tr("General"), {general_tab.get(), debug_tab.get(), ui_tab.get()}}, + {{tr("General"), {general_tab.get(), web_tab.get(), debug_tab.get(), ui_tab.get()}}, {tr("System"), {system_tab.get(), camera_tab.get(), storage_tab.get()}}, {tr("Graphics"), {enhancements_tab.get(), graphics_tab.get()}}, {tr("Audio"), {audio_tab.get()}}, @@ -154,6 +158,7 @@ void ConfigureDialog::RetranslateUI() { audio_tab->RetranslateUI(); camera_tab->RetranslateUI(); debug_tab->RetranslateUI(); + web_tab->RetranslateUI(); ui_tab->RetranslateUI(); storage_tab->RetranslateUI(); } @@ -173,6 +178,7 @@ void ConfigureDialog::UpdateVisibleTabs() { {camera_tab.get(), tr("Camera")}, {debug_tab.get(), tr("Debug")}, {storage_tab.get(), tr("Storage")}, + {web_tab.get(), tr("Web")}, {ui_tab.get(), tr("UI")}}; ui->tabWidget->clear(); diff --git a/src/citra_qt/configuration/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h index ac500224e..db2549fcb 100644 --- a/src/citra_qt/configuration/configure_dialog.h +++ b/src/citra_qt/configuration/configure_dialog.h @@ -29,6 +29,7 @@ class ConfigureAudio; class ConfigureCamera; class ConfigureDebug; class ConfigureStorage; +class ConfigureWeb; class ConfigureUi; class ConfigureDialog : public QDialog { @@ -69,5 +70,6 @@ private: std::unique_ptr camera_tab; std::unique_ptr debug_tab; std::unique_ptr storage_tab; + std::unique_ptr web_tab; std::unique_ptr ui_tab; }; diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp new file mode 100644 index 000000000..2c61b02dc --- /dev/null +++ b/src/citra_qt/configuration/configure_web.cpp @@ -0,0 +1,36 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "citra_qt/configuration/configure_web.h" +#include "citra_qt/uisettings.h" +#include "network/network_settings.h" +#include "ui_configure_web.h" + +ConfigureWeb::ConfigureWeb(QWidget* parent) + : QWidget(parent), ui(std::make_unique()) { + ui->setupUi(this); + +#ifndef USE_DISCORD_PRESENCE + ui->discord_group->setVisible(false); +#endif + SetConfiguration(); +} + +ConfigureWeb::~ConfigureWeb() = default; + +void ConfigureWeb::SetConfiguration() { + + ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue()); +} + +void ConfigureWeb::ApplyConfiguration() { + UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); +} + +void ConfigureWeb::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_web.h b/src/citra_qt/configuration/configure_web.h new file mode 100644 index 000000000..53f7f2b18 --- /dev/null +++ b/src/citra_qt/configuration/configure_web.h @@ -0,0 +1,28 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +namespace Ui { +class ConfigureWeb; +} + +class ConfigureWeb : public QWidget { + Q_OBJECT + +public: + explicit ConfigureWeb(QWidget* parent = nullptr); + ~ConfigureWeb() override; + + void ApplyConfiguration(); + void RetranslateUI(); + void SetConfiguration(); + +private: + std::unique_ptr ui; +}; diff --git a/src/citra_qt/configuration/configure_web.ui b/src/citra_qt/configuration/configure_web.ui new file mode 100644 index 000000000..a68936c59 --- /dev/null +++ b/src/citra_qt/configuration/configure_web.ui @@ -0,0 +1,53 @@ + + + ConfigureWeb + + + + 0 + 0 + 996 + 561 + + + + Form + + + + + + Discord Presence + + + + + + Show Current Game in your Discord Status + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + toggle_discordrpc + + + + From 24c6ec5e6a37e1d7d637c44b6dcffdfd5af7126d Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sun, 12 May 2024 20:17:06 +0200 Subject: [PATCH 02/30] Add Artic Base support (#105) * Add Artic Base support * Add Android support --- .../java/org/citra/citra_emu/NativeLibrary.kt | 66 +- .../fragments/HomeSettingsFragment.kt | 41 + src/android/app/src/main/jni/native.cpp | 10 +- .../app/src/main/res/drawable/ic_network.xml | 9 + .../app/src/main/res/values-es/strings.xml | 7 + .../app/src/main/res/values/strings.xml | 7 + src/citra_qt/configuration/config.cpp | 4 + src/citra_qt/main.cpp | 125 ++- src/citra_qt/main.h | 3 + src/citra_qt/main.ui | 6 + src/citra_qt/uisettings.h | 1 + src/common/static_lru_cache.h | 1 + src/core/CMakeLists.txt | 12 +- src/core/core.cpp | 14 +- src/core/core.h | 18 + src/core/file_sys/archive_artic.cpp | 535 ++++++++++ src/core/file_sys/archive_artic.h | 268 +++++ src/core/file_sys/archive_backend.cpp | 3 +- src/core/file_sys/archive_backend.h | 56 +- src/core/file_sys/archive_extsavedata.cpp | 229 ++++- src/core/file_sys/archive_extsavedata.h | 39 +- src/core/file_sys/archive_ncch.cpp | 23 +- src/core/file_sys/archive_ncch.h | 37 +- src/core/file_sys/archive_other_savedata.cpp | 22 +- src/core/file_sys/archive_other_savedata.h | 12 +- src/core/file_sys/archive_savedata.cpp | 10 +- src/core/file_sys/archive_savedata.h | 8 +- src/core/file_sys/archive_sdmc.cpp | 12 +- src/core/file_sys/archive_sdmc.h | 14 +- src/core/file_sys/archive_sdmcwriteonly.cpp | 9 +- src/core/file_sys/archive_sdmcwriteonly.h | 10 +- src/core/file_sys/archive_selfncch.cpp | 15 +- src/core/file_sys/archive_selfncch.h | 4 +- .../file_sys/archive_source_sd_savedata.cpp | 115 ++- .../file_sys/archive_source_sd_savedata.h | 29 +- src/core/file_sys/archive_systemsavedata.cpp | 3 +- src/core/file_sys/archive_systemsavedata.h | 4 +- src/core/file_sys/artic_cache.cpp | 235 +++++ src/core/file_sys/artic_cache.h | 154 +++ src/core/file_sys/directory_backend.h | 6 +- src/core/file_sys/disk_archive.cpp | 4 +- src/core/file_sys/disk_archive.h | 6 +- src/core/file_sys/file_backend.h | 4 +- src/core/file_sys/ivfc_archive.cpp | 15 +- src/core/file_sys/ivfc_archive.h | 20 +- src/core/file_sys/romfs_reader.cpp | 102 ++ src/core/file_sys/romfs_reader.h | 53 + src/core/file_sys/savedata_archive.cpp | 10 +- src/core/file_sys/savedata_archive.h | 10 +- src/core/file_sys/secure_value_backend.cpp | 74 ++ src/core/file_sys/secure_value_backend.h | 65 ++ .../file_sys/secure_value_backend_artic.cpp | 119 +++ .../file_sys/secure_value_backend_artic.h | 53 + src/core/hle/ipc_helpers.h | 5 + src/core/hle/kernel/hle_ipc.h | 5 + src/core/hle/service/am/am.cpp | 19 +- src/core/hle/service/am/am.h | 8 +- src/core/hle/service/apt/apt.cpp | 2 +- src/core/hle/service/boss/online_service.cpp | 2 +- src/core/hle/service/cecd/cecd.cpp | 20 +- src/core/hle/service/cfg/cfg.cpp | 4 +- src/core/hle/service/fs/archive.cpp | 139 ++- src/core/hle/service/fs/archive.h | 59 +- src/core/hle/service/fs/file.cpp | 149 ++- src/core/hle/service/fs/fs_user.cpp | 964 +++++++++++++++--- src/core/hle/service/fs/fs_user.h | 27 +- src/core/hle/service/http/http_c.cpp | 2 +- src/core/hle/service/news/news.cpp | 6 +- src/core/hle/service/ptm/ptm.cpp | 5 +- src/core/hle/service/soc/soc_u.cpp | 10 +- src/core/loader/artic.cpp | 564 ++++++++++ src/core/loader/artic.h | 135 +++ src/core/loader/loader.cpp | 30 + src/core/loader/loader.h | 10 + src/core/perf_stats.cpp | 4 + src/core/perf_stats.h | 46 + src/core/savestate.cpp | 12 + src/network/CMakeLists.txt | 5 + src/network/artic_base/artic_base_client.cpp | 735 +++++++++++++ src/network/artic_base/artic_base_client.h | 288 ++++++ src/network/artic_base/artic_base_common.h | 92 ++ src/network/socket_manager.cpp | 31 + src/network/socket_manager.h | 19 + 83 files changed, 5592 insertions(+), 516 deletions(-) create mode 100644 src/android/app/src/main/res/drawable/ic_network.xml create mode 100644 src/core/file_sys/archive_artic.cpp create mode 100644 src/core/file_sys/archive_artic.h create mode 100644 src/core/file_sys/artic_cache.cpp create mode 100644 src/core/file_sys/artic_cache.h create mode 100644 src/core/file_sys/secure_value_backend.cpp create mode 100644 src/core/file_sys/secure_value_backend.h create mode 100644 src/core/file_sys/secure_value_backend_artic.cpp create mode 100644 src/core/file_sys/secure_value_backend_artic.h create mode 100644 src/core/loader/artic.cpp create mode 100644 src/core/loader/artic.h create mode 100644 src/network/artic_base/artic_base_client.cpp create mode 100644 src/network/artic_base/artic_base_client.h create mode 100644 src/network/artic_base/artic_base_common.h create mode 100644 src/network/socket_manager.cpp create mode 100644 src/network/socket_manager.h diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index bfbe658f8..84a62d898 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -183,13 +183,13 @@ object NativeLibrary { private var coreErrorAlertResult = false private val coreErrorAlertLock = Object() - private fun onCoreErrorImpl(title: String, message: String) { + private fun onCoreErrorImpl(title: String, message: String, canContinue: Boolean) { val emulationActivity = sEmulationActivity.get() if (emulationActivity == null) { Log.error("[NativeLibrary] EmulationActivity not present") return } - val fragment = CoreErrorDialogFragment.newInstance(title, message) + val fragment = CoreErrorDialogFragment.newInstance(title, message, canContinue) fragment.show(emulationActivity.supportFragmentManager, CoreErrorDialogFragment.TAG) } @@ -207,6 +207,7 @@ object NativeLibrary { } val title: String val message: String + val canContinue: Boolean when (error) { CoreError.ErrorSystemFiles -> { title = emulationActivity.getString(R.string.system_archive_not_found) @@ -214,16 +215,25 @@ object NativeLibrary { R.string.system_archive_not_found_message, details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } ) + canContinue = true } CoreError.ErrorSavestate -> { title = emulationActivity.getString(R.string.save_load_error) message = details + canContinue = true + } + + CoreError.ErrorArticDisconnected -> { + title = emulationActivity.getString(R.string.artic_base) + message = emulationActivity.getString(R.string.artic_server_comm_error) + canContinue = false } CoreError.ErrorUnknown -> { title = emulationActivity.getString(R.string.fatal_error) message = emulationActivity.getString(R.string.fatal_error_message) + canContinue = true } else -> { @@ -232,7 +242,7 @@ object NativeLibrary { } // Show the AlertDialog on the main thread. - emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) }) + emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message, canContinue) }) // Wait for the lock to notify that it is complete. synchronized(coreErrorAlertLock) { @@ -346,6 +356,11 @@ object NativeLibrary { return } + if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) { + emulationActivity.finish() + return + } + emulationActivity.runOnUiThread { EmulationErrorDialogFragment.newInstance(resultCode).showNow( emulationActivity.supportFragmentManager, @@ -361,16 +376,23 @@ object NativeLibrary { emulationActivity = requireActivity() as EmulationActivity var captionId = R.string.loader_error_invalid_format - if (requireArguments().getInt(RESULT_CODE) == ErrorLoader_ErrorEncrypted) { + val result = requireArguments().getInt(RESULT_CODE) + if (result == ErrorLoader_ErrorEncrypted) { captionId = R.string.loader_error_encrypted } + if (result == ErrorArticDisconnected) { + captionId = R.string.artic_base + } val alert = MaterialAlertDialogBuilder(requireContext()) .setTitle(captionId) .setMessage( Html.fromHtml( - CitraApplication.appContext.resources.getString(R.string.redump_games), - Html.FROM_HTML_MODE_LEGACY + if (result == ErrorArticDisconnected) + CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error) + else + CitraApplication.appContext.resources.getString(R.string.redump_games), + Html.FROM_HTML_MODE_LEGACY ) ) .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> @@ -398,7 +420,10 @@ object NativeLibrary { const val ErrorLoader = 4 const val ErrorLoader_ErrorEncrypted = 5 const val ErrorLoader_ErrorInvalidFormat = 6 - const val ErrorSystemFiles = 7 + const val ErrorLoader_ErrorGBATitle = 7 + const val ErrorSystemFiles = 8 + const val ErrorSavestate = 9 + const val ErrorArticDisconnected = 10 const val ShutdownRequested = 11 const val ErrorUnknown = 12 @@ -619,6 +644,7 @@ object NativeLibrary { enum class CoreError { ErrorSystemFiles, ErrorSavestate, + ErrorArticDisconnected, ErrorUnknown } @@ -633,23 +659,33 @@ object NativeLibrary { } class CoreErrorDialogFragment : DialogFragment() { + private var userChosen = false override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val title = requireArguments().getString(TITLE) val message = requireArguments().getString(MESSAGE) - return MaterialAlertDialogBuilder(requireContext()) + val canContinue = requireArguments().getBoolean(CAN_CONTINUE) + val dialog = MaterialAlertDialogBuilder(requireContext()) .setTitle(title) .setMessage(message) - .setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int -> + if (canContinue) { + dialog.setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int -> coreErrorAlertResult = true + userChosen = true } - .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> - coreErrorAlertResult = false - }.show() + } + dialog.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> + coreErrorAlertResult = false + userChosen = true + } + return dialog.show() } override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) - coreErrorAlertResult = true + val canContinue = requireArguments().getBoolean(CAN_CONTINUE) + if (!userChosen) { + coreErrorAlertResult = canContinue + } synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() } } @@ -658,12 +694,14 @@ object NativeLibrary { const val TITLE = "title" const val MESSAGE = "message" + const val CAN_CONTINUE = "canContinue" - fun newInstance(title: String, message: String): CoreErrorDialogFragment { + fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment { val frag = CoreErrorDialogFragment() val args = Bundle() args.putString(TITLE, title) args.putString(MESSAGE, message) + args.putBoolean(CAN_CONTINUE, canContinue) frag.arguments = args return frag } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt index b19fdd7b9..091616054 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt @@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import androidx.core.widget.doOnTextChanged import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -23,14 +24,19 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.transition.MaterialSharedAxis import org.citra.citra_emu.CitraApplication +import org.citra.citra_emu.HomeNavigationDirections import org.citra.citra_emu.R import org.citra.citra_emu.adapters.HomeSettingAdapter +import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding import org.citra.citra_emu.features.settings.model.Settings +import org.citra.citra_emu.features.settings.model.StringSetting import org.citra.citra_emu.features.settings.ui.SettingsActivity import org.citra.citra_emu.features.settings.utils.SettingsFile +import org.citra.citra_emu.model.Game import org.citra.citra_emu.model.HomeSetting import org.citra.citra_emu.ui.main.MainActivity import org.citra.citra_emu.utils.GameHelper @@ -76,6 +82,41 @@ class HomeSettingsFragment : Fragment() { R.drawable.ic_settings, { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } ), + HomeSetting( + R.string.artic_base_connect, + R.string.artic_base_connect_description, + R.drawable.ic_network, + { + val inflater = LayoutInflater.from(context) + val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater) + var textInputValue: String = "" + + inputBinding.editTextInput.setText(textInputValue) + inputBinding.editTextInput.doOnTextChanged { text, _, _, _ -> + textInputValue = text.toString() + } + + val dialog = context?.let { + MaterialAlertDialogBuilder(it) + .setView(inputBinding.root) + .setTitle(getString(R.string.artic_base_enter_address)) + .setPositiveButton(android.R.string.ok) { _, _ -> + if (textInputValue.isNotEmpty()) { + val menu = Game( + title = getString(R.string.artic_base), + path = "articbase://$textInputValue", + filename = "" + ) + val action = + HomeNavigationDirections.actionGlobalEmulationActivity(menu) + binding.root.findNavController().navigate(action) + } + } + .setNegativeButton(android.R.string.cancel) {_, _ -> } + .show() + } + } + ), HomeSetting( R.string.system_files, R.string.system_files_description, diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 13dbc0f2c..d84562e1a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -82,6 +82,7 @@ static jobject ToJavaCoreError(Core::System::ResultStatus result) { static const std::map CoreErrorNameMap{ {Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"}, {Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"}, + {Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"}, {Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"}, }; @@ -178,6 +179,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { auto app_loader = Loader::GetLoader(filepath); if (app_loader) { app_loader->ReadProgramId(program_id); + system.RegisterAppLoaderEarly(app_loader); GameSettings::LoadOverrides(program_id); } system.ApplySettings(); @@ -231,6 +233,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { InputManager::NDKMotionHandler()->DisableSensors(); if (!HandleCoreError(result, system.GetStatusDetails())) { // Frontend requests us to abort + // If the error was an Artic disconnect, return shutdown request. + if (result == Core::System::ResultStatus::ErrorArticDisconnected) { + return Core::System::ResultStatus::ShutdownRequested; + } return result; } InputManager::NDKMotionHandler()->EnableSensors(); @@ -314,7 +320,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en if (stop_run || pause_emulation) { return; } - window->TryPresenting(); + if (window) { + window->TryPresenting(); + } } void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver( diff --git a/src/android/app/src/main/res/drawable/ic_network.xml b/src/android/app/src/main/res/drawable/ic_network.xml new file mode 100644 index 000000000..91559b988 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_network.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 7755c81cc..7c7ba9e53 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -657,4 +657,11 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Noviembre Diciembre + + Fallo de comunicación con el servidor Artic Base. La emulación se detendrá. + Artic Base + Conectar con Artic Base + Conectar con una consola real que esté ejecutando un servidor Artic Base + Introduce la dirección del servidor Artic Base + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 781e78da1..6614e97f1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -683,4 +683,11 @@ November December + + Failed to communicate with the Artic Base server. Emulation will stop. + Artic Base + Connect to a real console that is running an Artic Base server + Connect to Artic Base + Enter Artic Base server address + diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 7ddbc7eb9..e54fcee87 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -636,6 +636,8 @@ void Config::ReadPathValues() { UISettings::values.game_dirs.append(game_dir); } } + UISettings::values.last_artic_base_addr = + ReadSetting(QStringLiteral("last_artic_base_addr"), QString{}).toString(); UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString(); } @@ -1135,6 +1137,8 @@ void Config::SavePathValues() { WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true); } qt_config->endArray(); + WriteSetting(QStringLiteral("last_artic_base_addr"), + UISettings::values.last_artic_base_addr, QString{}); WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{}); } diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index e724ea37b..efe2bc05b 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -381,6 +381,10 @@ void GMainWindow::InitializeWidgets() { progress_bar->hide(); statusBar()->addPermanentWidget(progress_bar); + artic_traffic_label = new QLabel(); + artic_traffic_label->setToolTip( + tr("Current Artic Base traffic speed. Higher values indicate bigger transfer loads.")); + emu_speed_label = new QLabel(); emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " "indicate emulation is running faster or slower than a 3DS.")); @@ -392,7 +396,8 @@ void GMainWindow::InitializeWidgets() { tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " "full-speed emulation this should be at most 16.67 ms.")); - for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) { + for (auto& label : + {artic_traffic_label, emu_speed_label, game_fps_label, emu_frametime_label}) { label->setVisible(false); label->setFrameStyle(QFrame::NoFrame); label->setContentsMargins(4, 0, 4, 0); @@ -866,6 +871,7 @@ void GMainWindow::ConnectMenuEvents() { // File connect_menu(ui->action_Load_File, &GMainWindow::OnMenuLoadFile); connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA); + connect_menu(ui->action_Connect_Artic, &GMainWindow::OnMenuConnectArticBase); for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) { connect_menu(ui->menu_Boot_Home_Menu->actions().at(region), [this, region] { OnMenuBootHomeMenu(region); }); @@ -1203,6 +1209,11 @@ bool GMainWindow::LoadROM(const QString& filename) { tr("GBA Virtual Console ROMs are not supported by Citra.")); break; + case Core::System::ResultStatus::ErrorArticDisconnected: + QMessageBox::critical( + this, tr("Artic Base Server"), + tr("An error has occurred whilst communicating with the Artic Base Server.")); + break; default: QMessageBox::critical( this, tr("Error while loading ROM!"), @@ -1223,7 +1234,9 @@ bool GMainWindow::LoadROM(const QString& filename) { } void GMainWindow::BootGame(const QString& filename) { - if (filename.endsWith(QStringLiteral(".cia"))) { + const bool is_artic = filename.startsWith(QString::fromStdString("articbase://")); + + if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) { const auto answer = QMessageBox::question( this, tr("CIA must be installed before usage"), tr("Before using this CIA, you must install it. Do you want to install it now?"), @@ -1235,8 +1248,12 @@ void GMainWindow::BootGame(const QString& filename) { return; } + show_artic_label = is_artic; + LOG_INFO(Frontend, "Citra starting..."); - StoreRecentFile(filename); // Put the filename on top of the list + if (!is_artic) { + StoreRecentFile(filename); // Put the filename on top of the list + } if (movie_record_on_start) { movie.PrepareForRecording(); @@ -1246,16 +1263,26 @@ void GMainWindow::BootGame(const QString& filename) { } const std::string path = filename.toStdString(); - const auto loader = Loader::GetLoader(path); + auto loader = Loader::GetLoader(path); u64 title_id{0}; - loader->ReadProgramId(title_id); + Loader::ResultStatus res = loader->ReadProgramId(title_id); + + if (Loader::ResultStatus::Success == res) { + // Load per game settings + const std::string name{is_artic ? "" : FileUtil::GetFilename(filename.toStdString())}; + const std::string config_file_name = + title_id == 0 ? name : fmt::format("{:016X}", title_id); + LOG_INFO(Frontend, "Loading per game config file for title {}", config_file_name); + Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); + } + + // Artic Base Server cannot accept a client multiple times, so multiple loaders are not + // possible. Instead register the app loader early and do not create it again on system load. + if (!loader->SupportsMultipleInstancesForSameFile()) { + system.RegisterAppLoaderEarly(loader); + } - // Load per game settings - const std::string name{FileUtil::GetFilename(filename.toStdString())}; - const std::string config_file_name = title_id == 0 ? name : fmt::format("{:016X}", title_id); - LOG_INFO(Frontend, "Loading per game config file for title {}", config_file_name); - Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); system.ApplySettings(); Settings::LogSettings(); @@ -1265,8 +1292,11 @@ void GMainWindow::BootGame(const QString& filename) { game_list->SaveInterfaceLayout(); config->Save(); - if (!LoadROM(filename)) + if (!LoadROM(filename)) { + render_window->ReleaseRenderTarget(); + secondary_window->ReleaseRenderTarget(); return; + } // Set everything up if (movie_record_on_start) { @@ -1420,6 +1450,8 @@ void GMainWindow::ShutdownGame() { // Disable status bar updates status_bar_update_timer.stop(); message_label_used_for_movie = false; + show_artic_label = false; + artic_traffic_label->setVisible(false); emu_speed_label->setVisible(false); game_fps_label->setVisible(false); emu_frametime_label->setVisible(false); @@ -1759,6 +1791,17 @@ void GMainWindow::OnMenuInstallCIA() { InstallCIA(filepaths); } +void GMainWindow::OnMenuConnectArticBase() { + bool ok = false; + auto res = QInputDialog::getText(this, tr("Connect to Artic Base"), + tr("Enter Artic Base server address:"), QLineEdit::Normal, + UISettings::values.last_artic_base_addr, &ok); + if (ok) { + UISettings::values.last_artic_base_addr = res; + BootGame(QString::fromStdString("articbase://").append(res)); + } +} + void GMainWindow::OnMenuBootHomeMenu(u32 region) { BootGame(QString::fromStdString(Core::GetHomeMenuNcchPath(region))); } @@ -2575,6 +2618,51 @@ void GMainWindow::UpdateStatusBar() { auto results = system.GetAndResetPerfStats(); + if (show_artic_label) { + const bool do_mb = results.artic_transmitted >= (1000.0 * 1000.0); + const double value = do_mb ? (results.artic_transmitted / (1000.0 * 1000.0)) + : (results.artic_transmitted / 1000.0); + static const std::array, 4> + perf_events = { + std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA, + tr("(Accessing SharedExtData)")), + std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA, + tr("(Accessing BossExtData)")), + std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA, + tr("(Accessing ExtData)")), + std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SAVE_DATA, + tr("(Accessing SaveData)")), + }; + + const QString unit = do_mb ? tr("MB/s") : tr("KB/s"); + QString event{}; + for (auto p : perf_events) { + if (results.artic_events.Get(p.first)) { + event = QString::fromStdString(" ") + p.second; + break; + } + } + + static const std::array label_color = {QStringLiteral("#ffffff"), QStringLiteral("#eed202"), + QStringLiteral("#ff3333")}; + + int style_index; + + if (value > 200.0) { + style_index = 2; + } else if (value > 125.0) { + style_index = 1; + } else { + style_index = 0; + } + const QString style_sheet = + QStringLiteral("QLabel { color: %0; }").arg(label_color[style_index]); + + artic_traffic_label->setText( + tr("Artic Base Traffic: %1 %2%3").arg(value, 0, 'f', 0).arg(unit).arg(event)); + artic_traffic_label->setStyleSheet(style_sheet); + } + if (Settings::values.frame_limit.GetValue() == 0) { emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); } else { @@ -2585,6 +2673,9 @@ void GMainWindow::UpdateStatusBar() { game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); + if (show_artic_label) { + artic_traffic_label->setVisible(true); + } emu_speed_label->setVisible(true); game_fps_label->setVisible(true); emu_frametime_label->setVisible(true); @@ -2736,6 +2827,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det QString title, message; QMessageBox::Icon error_severity_icon; + bool can_continue = true; if (result == Core::System::ResultStatus::ErrorSystemFiles) { const QString common_message = tr("%1 is missing. Please + @@ -222,6 +223,11 @@ Install CIA... + + + Connect to Artic Base... + + JPN diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h index 6dedff0f0..5ac61e204 100644 --- a/src/citra_qt/uisettings.h +++ b/src/citra_qt/uisettings.h @@ -116,6 +116,7 @@ struct Values { bool game_dir_deprecated_deepscan; QVector game_dirs; QStringList recent_files; + QString last_artic_base_addr; QString language; QString theme; diff --git a/src/common/static_lru_cache.h b/src/common/static_lru_cache.h index b91f046a0..bd692e94e 100644 --- a/src/common/static_lru_cache.h +++ b/src/common/static_lru_cache.h @@ -14,6 +14,7 @@ //---------------------------------------------------------------------------// #pragma once +#include #include #include #include diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 62a9cde64..de4439ece 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,6 +40,8 @@ add_library(citra_core STATIC dumping/backend.h dumping/ffmpeg_backend.cpp dumping/ffmpeg_backend.h + file_sys/archive_artic.cpp + file_sys/archive_artic.h file_sys/archive_backend.cpp file_sys/archive_backend.h file_sys/archive_extsavedata.cpp @@ -60,6 +62,8 @@ add_library(citra_core STATIC file_sys/archive_source_sd_savedata.h file_sys/archive_systemsavedata.cpp file_sys/archive_systemsavedata.h + file_sys/artic_cache.cpp + file_sys/artic_cache.h file_sys/cia_common.h file_sys/cia_container.cpp file_sys/cia_container.h @@ -87,6 +91,10 @@ add_library(citra_core STATIC file_sys/romfs_reader.h file_sys/savedata_archive.cpp file_sys/savedata_archive.h + file_sys/secure_value_backend_artic.cpp + file_sys/secure_value_backend_artic.h + file_sys/secure_value_backend.cpp + file_sys/secure_value_backend.h file_sys/seed_db.cpp file_sys/seed_db.h file_sys/ticket.cpp @@ -445,6 +453,8 @@ add_library(citra_core STATIC hw/y2r.h loader/3dsx.cpp loader/3dsx.h + loader/artic.cpp + loader/artic.h loader/elf.cpp loader/elf.h loader/loader.cpp @@ -470,7 +480,7 @@ add_library(citra_core STATIC tracer/citrace.h tracer/recorder.cpp tracer/recorder.h -) + ) create_target_directory_groups(citra_core) diff --git a/src/core/core.cpp b/src/core/core.cpp index 3db61dfcd..9f4fcf406 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -256,7 +256,11 @@ System::ResultStatus System::SingleStep() { System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath, Frontend::EmuWindow* secondary_window) { FileUtil::SetCurrentRomPath(filepath); - app_loader = Loader::GetLoader(filepath); + if (early_app_loader) { + app_loader = std::move(early_app_loader); + } else { + app_loader = Loader::GetLoader(filepath); + } if (!app_loader) { LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); return ResultStatus::ErrorGetLoader; @@ -286,6 +290,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st return ResultStatus::ErrorLoader_ErrorInvalidFormat; case Loader::ResultStatus::ErrorGbaTitle: return ResultStatus::ErrorLoader_ErrorGbaTitle; + case Loader::ResultStatus::ErrorArtic: + return ResultStatus::ErrorArticDisconnected; default: return ResultStatus::ErrorSystemMode; } @@ -334,6 +340,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st return ResultStatus::ErrorLoader_ErrorInvalidFormat; case Loader::ResultStatus::ErrorGbaTitle: return ResultStatus::ErrorLoader_ErrorGbaTitle; + case Loader::ResultStatus::ErrorArtic: + return ResultStatus::ErrorArticDisconnected; default: return ResultStatus::ErrorLoader; } @@ -691,6 +699,10 @@ void System::ApplySettings() { } } +void System::RegisterAppLoaderEarly(std::unique_ptr& loader) { + early_app_loader = std::move(loader); +} + template void System::serialize(Archive& ar, const unsigned int file_version) { diff --git a/src/core/core.h b/src/core/core.h index 50d31ff74..a395360ac 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -99,6 +99,7 @@ public: ///< Console ErrorSystemFiles, ///< Error in finding system files ErrorSavestate, ///< Error saving or loading + ErrorArticDisconnected, ///< Error when artic base disconnects ShutdownRequested, ///< Emulated program requested a system shutdown ErrorUnknown ///< Any other error }; @@ -169,6 +170,18 @@ public: [[nodiscard]] PerfStats::Results GetAndResetPerfStats(); + void ReportArticTraffic(u32 bytes) { + if (perf_stats) { + perf_stats->AddArticBaseTraffic(bytes); + } + } + + void ReportPerfArticEvent(PerfStats::PerfArticEventBits event, bool set) { + if (perf_stats) { + perf_stats->ReportPerfArticEvent(event, set); + } + } + [[nodiscard]] PerfStats::Results GetLastPerfStats(); /** @@ -346,6 +359,8 @@ public: /// Applies any changes to settings to this core instance. void ApplySettings(); + void RegisterAppLoaderEarly(std::unique_ptr& loader); + private: /** * Initialize the emulated system. @@ -366,6 +381,9 @@ private: /// AppLoader used to load the current executing application std::unique_ptr app_loader; + // Temporary app loader passed from frontend + std::unique_ptr early_app_loader; + /// ARM11 CPU core std::vector> cpu_cores; ARM_Interface* running_core = nullptr; diff --git a/src/core/file_sys/archive_artic.cpp b/src/core/file_sys/archive_artic.cpp new file mode 100644 index 000000000..36f395a23 --- /dev/null +++ b/src/core/file_sys/archive_artic.cpp @@ -0,0 +1,535 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "archive_artic.h" + +namespace FileSys { + +std::vector ArticArchive::BuildFSPath(const Path& path) { + std::vector ret(sizeof(u32) * 2); + u32* raw_data = reinterpret_cast(ret.data()); + auto path_type = path.GetType(); + auto binary = path.AsBinary(); + raw_data[0] = static_cast(path_type); + raw_data[1] = static_cast(binary.size()); + if (!binary.empty()) { + ret.insert(ret.end(), binary.begin(), binary.end()); + } + + // The insert may have invalidated the pointer + raw_data = reinterpret_cast(ret.data()); + if (path_type != LowPathType::Binary && path_type != LowPathType::Invalid) { + if (path_type == LowPathType::Wchar) { + raw_data[1] += 2; + ret.push_back(0); + ret.push_back(0); + } else { + raw_data[1] += 1; + ret.push_back(0); + } + } + + return ret; +} + +Result ArticArchive::RespResult(const std::optional& resp) { + if (!resp.has_value() || !resp->Succeeded()) { + return ResultUnknown; + } + return Result(static_cast(resp->GetMethodResult())); +} + +ArticArchive::~ArticArchive() { + if (clear_cache_on_close) { + cache_provider->ClearAllCache(); + } + if (archive_handle != -1) { + auto req = client->NewRequest("FSUSER_CloseArchive"); + req.AddParameterS64(archive_handle); + client->Send(req); + if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { + client->ReportArticEvent(static_cast(report_artic_event)); + } + } +} + +ResultVal> ArticArchive::Open( + std::shared_ptr& client, Service::FS::ArchiveIdCode archive_id, + const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event, + ArticCacheProvider& cache_provider, bool clear_cache_on_close) { + + auto req = client->NewRequest("FSUSER_OpenArchive"); + + req.AddParameterS32(static_cast(archive_id)); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded()) { + return ResultUnknown; + } + Result res(static_cast(resp->GetMethodResult())); + if (res.IsError()) + return res; + + auto handle_opt = resp->GetResponseS64(0); + if (!handle_opt.has_value()) { + return ResultUnknown; + } + + return std::make_unique(client, *handle_opt, report_artic_event, cache_provider, + path, clear_cache_on_close); +} + +void ArticArchive::Close() { + if (clear_cache_on_close) { + cache_provider->ClearAllCache(); + } + + auto req = client->NewRequest("FSUSER_CloseArchive"); + req.AddParameterS64(archive_handle); + if (RespResult(client->Send(req)).IsSuccess()) { + archive_handle = -1; + if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { + client->ReportArticEvent(static_cast(report_artic_event)); + } + } +} + +std::string ArticArchive::GetName() const { + return "ArticArchive"; +} + +ResultVal> ArticArchive::OpenFile(const Path& path, const Mode& mode, + u32 attributes) { + if (mode.create_flag) { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, path), false); + if (cache != nullptr) { + cache->Clear(); + } + } + auto req = client->NewRequest("FSUSER_OpenFile"); + + req.AddParameterS64(archive_handle); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + req.AddParameterU32(mode.hex); + req.AddParameterU32(attributes); + + auto resp = client->Send(req); + auto res = RespResult(resp); + if (res.IsError()) + return res; + + auto handle_opt = resp->GetResponseS32(0); + if (!handle_opt.has_value()) + return ResultUnknown; + + auto size_opt = resp->GetResponseU64(1); + if (size_opt.has_value()) { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, path), true); + if (cache != nullptr) { + cache->ForceSetSize(static_cast(*size_opt)); + } + } + + if (open_reporter->open_files++ == 0 && + report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { + client->ReportArticEvent(static_cast(report_artic_event) | (1ULL << 32)); + } + + return std::make_unique(client, *handle_opt, open_reporter, archive_path, + *cache_provider, path); +} + +Result ArticArchive::DeleteFile(const Path& path) const { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, path), false); + if (cache != nullptr) { + cache->Clear(); + } + + auto req = client->NewRequest("FSUSER_DeleteFile"); + + req.AddParameterS64(archive_handle); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + + return RespResult(client->Send(req)); +} + +Result ArticArchive::RenameFile(const Path& src_path, const Path& dest_path) const { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, src_path), false); + if (cache != nullptr) { + cache->Clear(); + } + cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, dest_path), false); + if (cache != nullptr) { + cache->Clear(); + } + + auto req = client->NewRequest("FSUSER_RenameFile"); + + req.AddParameterS64(archive_handle); + auto src_path_buf = BuildFSPath(src_path); + req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size()); + req.AddParameterS64(archive_handle); + auto dest_path_buf = BuildFSPath(dest_path); + req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size()); + + return RespResult(client->Send(req)); +} + +Result ArticArchive::DeleteDirectory(const Path& path) const { + cache_provider->ClearAllCache(); + + auto req = client->NewRequest("FSUSER_DeleteDirectory"); + + req.AddParameterS64(archive_handle); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + + return RespResult(client->Send(req)); +} + +Result ArticArchive::DeleteDirectoryRecursively(const Path& path) const { + cache_provider->ClearAllCache(); + + auto req = client->NewRequest("FSUSER_DeleteDirectoryRec"); + + req.AddParameterS64(archive_handle); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + + return RespResult(client->Send(req)); +} + +Result ArticArchive::CreateFile(const Path& path, u64 size, u32 attributes) const { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, path), false); + if (cache != nullptr) { + cache->Clear(); + } + + auto req = client->NewRequest("FSUSER_CreateFile"); + + req.AddParameterS64(archive_handle); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + req.AddParameterU32(attributes); + req.AddParameterU64(size); + + return RespResult(client->Send(req)); +} + +Result ArticArchive::CreateDirectory(const Path& path, u32 attributes) const { + auto req = client->NewRequest("FSUSER_CreateDirectory"); + + req.AddParameterS64(archive_handle); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + req.AddParameterU32(attributes); + + return RespResult(client->Send(req)); +} + +Result ArticArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { + cache_provider->ClearAllCache(); + + auto req = client->NewRequest("FSUSER_RenameDirectory"); + + req.AddParameterS64(archive_handle); + auto src_path_buf = BuildFSPath(src_path); + req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size()); + req.AddParameterS64(archive_handle); + auto dest_path_buf = BuildFSPath(dest_path); + req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size()); + + return RespResult(client->Send(req)); +} + +ResultVal> ArticArchive::OpenDirectory(const Path& path) { + auto req = client->NewRequest("FSUSER_OpenDirectory"); + + req.AddParameterS64(archive_handle); + auto path_buf = BuildFSPath(path); + req.AddParameterBuffer(path_buf.data(), path_buf.size()); + + auto resp = client->Send(req); + auto res = RespResult(resp); + if (res.IsError()) + return res; + + auto handle_opt = resp->GetResponseS32(0); + if (!handle_opt.has_value()) + return ResultUnknown; + + if (open_reporter->open_files++ == 0 && + report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { + client->ReportArticEvent(static_cast(report_artic_event) | (1ULL << 32)); + } + + return std::make_unique(client, *handle_opt, archive_path, + open_reporter); +} + +u64 ArticArchive::GetFreeBytes() const { + auto req = client->NewRequest("FSUSER_GetFreeBytes"); + + req.AddParameterS64(archive_handle); + + auto resp = client->Send(req); + auto res = RespResult(resp); + if (res.IsError()) // TODO(PabloMK7): Return error code and not u64 + return 0; + + auto free_bytes_opt = resp->GetResponseS64(0); + return free_bytes_opt.has_value() ? static_cast(*free_bytes_opt) : 0; +} + +Result ArticArchive::Control(u32 action, u8* input, size_t input_size, u8* output, + size_t output_size) { + auto req = client->NewRequest("FSUSER_ControlArchive"); + + req.AddParameterS64(archive_handle); + req.AddParameterU32(action); + req.AddParameterBuffer(input, input_size); + req.AddParameterU32(static_cast(output_size)); + + auto resp = client->Send(req); + auto res = RespResult(resp); + if (res.IsError()) + return res; + + auto output_buf = resp->GetResponseBuffer(0); + if (!output_buf.has_value()) + return res; + + if (output_buf->second != output_size) + return ResultUnknown; + + memcpy(output, output_buf->first, output_buf->second); + return res; +} + +Result ArticArchive::SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) { + auto req = client->NewRequest("FSUSER_SetSaveDataSecureValue"); + + req.AddParameterS64(archive_handle); + req.AddParameterU32(secure_value_slot); + req.AddParameterU64(secure_value); + req.AddParameterS8(flush != 0); + + return RespResult(client->Send(req)); +} + +ResultVal> ArticArchive::GetSaveDataSecureValue(u32 secure_value_slot) { + auto req = client->NewRequest("FSUSER_GetSaveDataSecureValue"); + + req.AddParameterS64(archive_handle); + req.AddParameterU32(secure_value_slot); + + auto resp = client->Send(req); + auto res = RespResult(resp); + if (res.IsError()) + return res; + + struct { + bool exists; + bool isGamecard; + u64 secure_value; + } secure_value_result; + static_assert(sizeof(secure_value_result) == 0x10); + + auto output_buf = resp->GetResponseBuffer(0); + if (!output_buf.has_value()) + return res; + + if (output_buf->second != sizeof(secure_value_result)) + return ResultUnknown; + + memcpy(&secure_value_result, output_buf->first, output_buf->second); + return std::make_tuple(secure_value_result.exists, secure_value_result.isGamecard, + secure_value_result.secure_value); +} + +void ArticArchive::OpenFileReporter::OnFileClosed() { + if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { + client->ReportArticEvent(static_cast(report_artic_event)); + } +} + +void ArticArchive::OpenFileReporter::OnDirectoryClosed() { + if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) { + client->ReportArticEvent(static_cast(report_artic_event)); + } +} + +ArticFileBackend::~ArticFileBackend() { + if (file_handle != -1) { + auto req = client->NewRequest("FSFILE_Close"); + req.AddParameterS32(file_handle); + client->Send(req); + open_reporter->OnFileClosed(); + } +} + +ResultVal ArticFileBackend::Read(u64 offset, std::size_t length, u8* buffer) const { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, file_path), true); + + if (cache != nullptr) { + return cache->Read(file_handle, offset, length, buffer); + } + + auto req = client->NewRequest("FSFILE_Read"); + + req.AddParameterS32(file_handle); + req.AddParameterU64(offset); + req.AddParameterU32(static_cast(length)); + + auto resp = client->Send(req); + auto res = ArticArchive::RespResult(resp); + if (res.IsError()) + return res; + + auto read_buf = resp->GetResponseBuffer(0); + if (!read_buf || read_buf->second > length) { + return std::size_t(0); + } + + memcpy(buffer, read_buf->first, read_buf->second); + return read_buf->second; +} + +ResultVal ArticFileBackend::Write(u64 offset, std::size_t length, bool flush, + bool update_timestamp, const u8* buffer) { + u32 flags = (flush ? 1 : 0) | (update_timestamp ? (1 << 8) : 0); + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, file_path), true); + if (cache != nullptr) { + return cache->Write(file_handle, offset, length, buffer, flags); + } else { + auto req = client->NewRequest("FSFILE_Write"); + + req.AddParameterS32(file_handle); + req.AddParameterU64(offset); + req.AddParameterU32(static_cast(length)); + req.AddParameterU32(flags); + req.AddParameterBuffer(buffer, length); + + auto resp = client->Send(req); + auto res = ArticArchive::RespResult(resp); + if (res.IsError()) + return res; + + auto writen_buf = resp->GetResponseS32(0); + if (!writen_buf) { + return std::size_t(0); + } + + return std::size_t(*writen_buf); + } +} + +u64 ArticFileBackend::GetSize() const { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, file_path), true); + if (cache != nullptr) { + auto res = cache->GetSize(file_handle); + if (res.Failed()) + return 0; + return res.Unwrap(); + } else { + + auto req = client->NewRequest("FSFILE_GetSize"); + + req.AddParameterS32(file_handle); + + auto resp = client->Send(req); + auto res = ArticArchive::RespResult(resp); + if (res.IsError()) + return 0; + + auto size_buf = resp->GetResponseS64(0); + if (!size_buf) { + return 0; + } + return *size_buf; + } +} + +bool ArticFileBackend::SetSize(u64 size) const { + auto req = client->NewRequest("FSFILE_SetSize"); + + req.AddParameterS32(file_handle); + req.AddParameterU64(size); + + return ArticArchive::RespResult(client->Send(req)).IsSuccess(); +} + +bool ArticFileBackend::Close() { + auto req = client->NewRequest("FSFILE_Close"); + req.AddParameterS32(file_handle); + bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess(); + if (ret) { + file_handle = -1; + open_reporter->OnFileClosed(); + } + return ret; +} + +void ArticFileBackend::Flush() const { + auto req = client->NewRequest("FSFILE_Flush"); + + req.AddParameterS32(file_handle); + + client->Send(req); +} + +ArticDirectoryBackend::~ArticDirectoryBackend() { + if (dir_handle != -1) { + auto req = client->NewRequest("FSDIR_Close"); + req.AddParameterS32(dir_handle); + client->Send(req); + open_reporter->OnDirectoryClosed(); + } +} + +u32 ArticDirectoryBackend::Read(const u32 count, Entry* entries) { + auto req = client->NewRequest("FSDIR_Read"); + + req.AddParameterS32(dir_handle); + req.AddParameterU32(count); + + auto resp = client->Send(req); + auto res = ArticArchive::RespResult(resp); + if (res.IsError()) + return 0; + + auto entry_buf = resp->GetResponseBuffer(0); + if (!entry_buf) { + return 0; + } + u32 ret_count = static_cast(entry_buf->second / sizeof(Entry)); + + memcpy(entries, entry_buf->first, ret_count * sizeof(Entry)); + return ret_count; +} + +bool ArticDirectoryBackend::Close() { + auto req = client->NewRequest("FSDIR_Close"); + req.AddParameterS32(dir_handle); + bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess(); + if (ret) { + dir_handle = -1; + open_reporter->OnDirectoryClosed(); + } + return ret; +} +} // namespace FileSys diff --git a/src/core/file_sys/archive_artic.h b/src/core/file_sys/archive_artic.h new file mode 100644 index 000000000..beb0f63b2 --- /dev/null +++ b/src/core/file_sys/archive_artic.h @@ -0,0 +1,268 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "atomic" + +#include +#include "common/common_types.h" +#include "core/file_sys/archive_backend.h" +#include "core/file_sys/artic_cache.h" +#include "core/file_sys/directory_backend.h" +#include "core/file_sys/file_backend.h" +#include "core/hle/service/fs/archive.h" +#include "core/perf_stats.h" +#include "network/artic_base/artic_base_client.h" + +namespace FileSys { + +class ArticArchive : public ArchiveBackend { +public: + static std::vector BuildFSPath(const Path& path); + static Result RespResult(const std::optional& resp); + + explicit ArticArchive(std::shared_ptr& _client, s64 _archive_handle, + Core::PerfStats::PerfArticEventBits _report_artic_event, + ArticCacheProvider& _cache_provider, const Path& _archive_path, + bool _clear_cache_on_close) + : client(_client), archive_handle(_archive_handle), report_artic_event(_report_artic_event), + cache_provider(&_cache_provider), archive_path(_archive_path), + clear_cache_on_close(_clear_cache_on_close) { + open_reporter = std::make_shared(_client, _report_artic_event); + } + ~ArticArchive() override; + + static ResultVal> Open( + std::shared_ptr& client, Service::FS::ArchiveIdCode archive_id, + const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event, + ArticCacheProvider& cache_provider, bool clear_cache_on_close); + + void Close() override; + + /** + * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) + */ + std::string GetName() const override; + + /** + * Open a file specified by its path, using the specified mode + * @param path Path relative to the archive + * @param mode Mode to open the file with + * @return Opened file, or error code + */ + ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes) override; + + /** + * Delete a file specified by its path + * @param path Path relative to the archive + * @return Result of the operation + */ + Result DeleteFile(const Path& path) const override; + + /** + * Rename a File specified by its path + * @param src_path Source path relative to the archive + * @param dest_path Destination path relative to the archive + * @return Result of the operation + */ + Result RenameFile(const Path& src_path, const Path& dest_path) const override; + + /** + * Delete a directory specified by its path + * @param path Path relative to the archive + * @return Result of the operation + */ + Result DeleteDirectory(const Path& path) const override; + + /** + * Delete a directory specified by its path and anything under it + * @param path Path relative to the archive + * @return Result of the operation + */ + Result DeleteDirectoryRecursively(const Path& path) const override; + + /** + * Create a file specified by its path + * @param path Path relative to the Archive + * @param size The size of the new file, filled with zeroes + * @return Result of the operation + */ + Result CreateFile(const Path& path, u64 size, u32 attributes) const override; + + /** + * Create a directory specified by its path + * @param path Path relative to the archive + * @return Result of the operation + */ + Result CreateDirectory(const Path& path, u32 attributes) const override; + + /** + * Rename a Directory specified by its path + * @param src_path Source path relative to the archive + * @param dest_path Destination path relative to the archive + * @return Result of the operation + */ + Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; + + /** + * Open a directory specified by its path + * @param path Path relative to the archive + * @return Opened directory, or error code + */ + ResultVal> OpenDirectory(const Path& path) override; + + /** + * Get the free space + * @return The number of free bytes in the archive + */ + u64 GetFreeBytes() const override; + + Result Control(u32 action, u8* input, size_t input_size, u8* output, + size_t output_size) override; + + Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) override; + + ResultVal> GetSaveDataSecureValue(u32 secure_value_slot) override; + + bool IsSlow() override { + return true; + } + + const Path& GetArchivePath() { + return archive_path; + } + +protected: + ArticArchive() = default; + +private: + friend class ArticFileBackend; + friend class ArticDirectoryBackend; + class OpenFileReporter { + public: + OpenFileReporter(const std::shared_ptr& cli, + Core::PerfStats::PerfArticEventBits _report_artic_event) + : client(cli), report_artic_event(_report_artic_event) {} + + void OnFileClosed(); + + void OnDirectoryClosed(); + + std::shared_ptr client; + Core::PerfStats::PerfArticEventBits report_artic_event = + Core::PerfStats::PerfArticEventBits::NONE; + std::atomic open_files = 0; + }; + + std::shared_ptr client; + s64 archive_handle; + std::shared_ptr open_reporter; + Core::PerfStats::PerfArticEventBits report_artic_event = + Core::PerfStats::PerfArticEventBits::NONE; + ArticCacheProvider* cache_provider = nullptr; + Path archive_path; + bool clear_cache_on_close; + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar& archive_handle; + } + friend class boost::serialization::access; +}; + +class ArticFileBackend : public FileBackend { +public: + explicit ArticFileBackend(std::shared_ptr& _client, + s32 _file_handle, + const std::shared_ptr& _open_reporter, + const Path& _archive_path, ArticCacheProvider& _cache_provider, + const Path& _file_path) + : client(_client), file_handle(_file_handle), open_reporter(_open_reporter), + archive_path(_archive_path), cache_provider(&_cache_provider), file_path(_file_path) {} + ~ArticFileBackend() override; + + ResultVal Read(u64 offset, std::size_t length, u8* buffer) const override; + + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, + const u8* buffer) override; + + u64 GetSize() const override; + + bool SetSize(u64 size) const override; + + bool Close() override; + + void Flush() const override; + + bool AllowsCachedReads() const override { + return true; + } + + bool CacheReady(std::size_t file_offset, std::size_t length) override { + auto cache = cache_provider->ProvideCache( + client, cache_provider->PathsToVector(archive_path, file_path), true); + if (cache == nullptr) { + return false; + } + return cache->CacheReady(file_offset, length); + } + +protected: + ArticFileBackend() = default; + +private: + std::shared_ptr client; + s32 file_handle; + std::shared_ptr open_reporter; + Path archive_path; + ArticCacheProvider* cache_provider = nullptr; + Path file_path; + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar& file_handle; + } + friend class boost::serialization::access; +}; + +class ArticDirectoryBackend : public DirectoryBackend { +public: + explicit ArticDirectoryBackend( + std::shared_ptr& _client, s32 _dir_handle, + const Path& _archive_path, + const std::shared_ptr& _open_reporter) + : client(_client), dir_handle(_dir_handle), archive_path(_archive_path), + open_reporter(_open_reporter) {} + ~ArticDirectoryBackend() override; + + u32 Read(const u32 count, Entry* entries) override; + bool Close() override; + + bool IsSlow() override { + return true; + } + +protected: + ArticDirectoryBackend() = default; + +private: + std::shared_ptr client; + s32 dir_handle; + Path archive_path; + std::shared_ptr open_reporter; + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar& dir_handle; + } + friend class boost::serialization::access; +}; +} // namespace FileSys + +BOOST_CLASS_EXPORT_KEY(FileSys::ArticArchive) +BOOST_CLASS_EXPORT_KEY(FileSys::ArticFileBackend) +BOOST_CLASS_EXPORT_KEY(FileSys::ArticDirectoryBackend) \ No newline at end of file diff --git a/src/core/file_sys/archive_backend.cpp b/src/core/file_sys/archive_backend.cpp index bc4df30d0..1600171fa 100644 --- a/src/core/file_sys/archive_backend.cpp +++ b/src/core/file_sys/archive_backend.cpp @@ -105,8 +105,7 @@ std::vector Path::AsBinary() const { std::vector to_return(u16str.size() * 2); for (std::size_t i = 0; i < u16str.size(); ++i) { u16 tmp_char = u16str.at(i); - to_return[i * 2] = (tmp_char & 0xFF00) >> 8; - to_return[i * 2 + 1] = (tmp_char & 0x00FF); + *reinterpret_cast(to_return.data() + i * 2) = tmp_char; } return to_return; } diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h index 7eb3893ba..7997c72ab 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/archive_backend.h @@ -103,6 +103,7 @@ struct ArchiveFormatInfo { u8 duplicate_data; ///< Whether the archive should duplicate the data. }; static_assert(std::is_trivial_v, "ArchiveFormatInfo is not POD"); +static_assert(sizeof(ArchiveFormatInfo) == 16, "Invalid ArchiveFormatInfo size"); class ArchiveBackend : NonCopyable { public: @@ -119,8 +120,8 @@ public: * @param mode Mode to open the file with * @return Opened file, or error code */ - virtual ResultVal> OpenFile(const Path& path, - const Mode& mode) const = 0; + virtual ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes = 0) = 0; /** * Delete a file specified by its path @@ -157,14 +158,14 @@ public: * @param size The size of the new file, filled with zeroes * @return Result of the operation */ - virtual Result CreateFile(const Path& path, u64 size) const = 0; + virtual Result CreateFile(const Path& path, u64 size, u32 attributes = 0) const = 0; /** * Create a directory specified by its path * @param path Path relative to the archive * @return Result of the operation */ - virtual Result CreateDirectory(const Path& path) const = 0; + virtual Result CreateDirectory(const Path& path, u32 attributes = 0) const = 0; /** * Rename a Directory specified by its path @@ -179,7 +180,7 @@ public: * @param path Path relative to the archive * @return Opened directory, or error code */ - virtual ResultVal> OpenDirectory(const Path& path) const = 0; + virtual ResultVal> OpenDirectory(const Path& path) = 0; /** * Get the free space @@ -187,6 +188,20 @@ public: */ virtual u64 GetFreeBytes() const = 0; + /** + * Close the archive + */ + virtual void Close() {} + + virtual Result Control(u32 action, u8* input, size_t input_size, u8* output, + size_t output_size) { + LOG_WARNING(Service_FS, + "(STUBBED) called, archive={}, action={:08X}, input_size={:08X}, " + "output_size={:08X}", + GetName(), action, input_size, output_size); + return ResultSuccess; + } + u64 GetOpenDelayNs() { if (delay_generator != nullptr) { return delay_generator->GetOpenDelayNs(); @@ -196,6 +211,31 @@ public: return delay_generator->GetOpenDelayNs(); } + virtual Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) { + + // TODO: Generate and Save the Secure Value + + LOG_WARNING(Service_FS, + "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:04X} " + "flush={}", + secure_value, secure_value_slot, flush); + + return ResultSuccess; + } + + virtual ResultVal> GetSaveDataSecureValue(u32 secure_value_slot) { + + // TODO: Implement Secure Value Lookup & Generation + + LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot); + + return std::make_tuple(false, true, 0); + } + + virtual bool IsSlow() { + return false; + } + protected: std::unique_ptr delay_generator; @@ -232,7 +272,7 @@ public: * @return Result of the operation, 0 on success */ virtual Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) = 0; + u64 program_id, u32 directory_buckets, u32 file_buckets) = 0; /** * Retrieves the format info about the archive with the specified path @@ -242,6 +282,10 @@ public: */ virtual ResultVal GetFormatInfo(const Path& path, u64 program_id) const = 0; + virtual bool IsSlow() { + return false; + } + template void serialize(Archive& ar, const unsigned int) {} friend class boost::serialization::access; diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index cfa97fe7f..54fc25ee5 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -10,6 +10,7 @@ #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/file_sys/archive_artic.h" #include "core/file_sys/archive_extsavedata.h" #include "core/file_sys/disk_archive.h" #include "core/file_sys/errors.h" @@ -37,7 +38,7 @@ public: return false; } - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override { if (offset > size) { return ResultWriteBeyondEnd; @@ -49,7 +50,7 @@ public: length = size - offset; } - return DiskFile::Write(offset, length, flush, buffer); + return DiskFile::Write(offset, length, flush, update_timestamp, buffer); } private: @@ -100,8 +101,8 @@ public: return "ExtSaveDataArchive: " + mount_point; } - ResultVal> OpenFile(const Path& path, - const Mode& mode) const override { + ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes) override { LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex); const PathParser path_parser(path); @@ -234,69 +235,187 @@ Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) { return {binary_data}; } -ResultVal> ArchiveFactory_ExtSaveData::Open(const Path& path, - u64 program_id) { - const auto directory = type == ExtSaveDataType::Boss ? "boss/" : "user/"; - const auto fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + directory; - if (!FileUtil::Exists(fullpath)) { - // TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData. - // ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist. - if (type != ExtSaveDataType::Shared) { - return ResultNotFoundInvalidState; - } else { - return ResultNotFormatted; - } +static Service::FS::ArchiveIdCode ExtSaveDataTypeToArchiveID(ExtSaveDataType type) { + switch (type) { + case FileSys::ExtSaveDataType::Normal: + return Service::FS::ArchiveIdCode::ExtSaveData; + case FileSys::ExtSaveDataType::Shared: + return Service::FS::ArchiveIdCode::SharedExtSaveData; + case FileSys::ExtSaveDataType::Boss: + return Service::FS::ArchiveIdCode::BossExtSaveData; + default: + return Service::FS::ArchiveIdCode::ExtSaveData; } - std::unique_ptr delay_generator = std::make_unique(); - return std::make_unique(fullpath, std::move(delay_generator)); } -Result ArchiveFactory_ExtSaveData::Format(const Path& path, - const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { - auto corrected_path = GetCorrectedPath(path); - - // These folders are always created with the ExtSaveData - std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/"; - std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/"; - FileUtil::CreateFullPath(user_path); - FileUtil::CreateFullPath(boss_path); - - // Write the format metadata - std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata"; - FileUtil::IOFile file(metadata_path, "wb"); - - if (!file.IsOpen()) { - // TODO(Subv): Find the correct error code - return ResultUnknown; +static Core::PerfStats::PerfArticEventBits ExtSaveDataTypeToPerfArtic(ExtSaveDataType type) { + switch (type) { + case FileSys::ExtSaveDataType::Normal: + return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA; + case FileSys::ExtSaveDataType::Shared: + return Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA; + case FileSys::ExtSaveDataType::Boss: + return Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA; + default: + return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA; } +} - file.WriteBytes(&format_info, sizeof(format_info)); - return ResultSuccess; +ResultVal> ArchiveFactory_ExtSaveData::Open(const Path& path, + u64 program_id) { + if (IsUsingArtic()) { + EnsureCacheCreated(); + return ArticArchive::Open(artic_client, ExtSaveDataTypeToArchiveID(type), path, + ExtSaveDataTypeToPerfArtic(type), *this, + type != FileSys::ExtSaveDataType::Normal); + } else { + const auto directory = type == ExtSaveDataType::Boss ? "boss/" : "user/"; + const auto fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + directory; + if (!FileUtil::Exists(fullpath)) { + // TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData. + // ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist. + if (type != ExtSaveDataType::Shared) { + return ResultNotFoundInvalidState; + } else { + return ResultNotFormatted; + } + } + std::unique_ptr delay_generator = + std::make_unique(); + return std::make_unique(fullpath, std::move(delay_generator)); + } +} + +Result ArchiveFactory_ExtSaveData::FormatAsExtData(const Path& path, + const FileSys::ArchiveFormatInfo& format_info, + u8 unknown, u64 program_id, u64 total_size, + std::span icon) { + if (IsUsingArtic()) { + ExtSaveDataArchivePath path_data; + std::memcpy(&path_data, path.AsBinary().data(), sizeof(path_data)); + + Service::FS::ExtSaveDataInfo artic_extdata_path; + + artic_extdata_path.media_type = static_cast(path_data.media_type); + artic_extdata_path.unknown = unknown; + artic_extdata_path.save_id_low = path_data.save_low; + artic_extdata_path.save_id_high = path_data.save_high; + + auto req = artic_client->NewRequest("FSUSER_CreateExtSaveData"); + + req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path)); + req.AddParameterU32(format_info.number_directories); + req.AddParameterU32(format_info.number_files); + req.AddParameterU64(total_size); + req.AddParameterBuffer(icon.data(), icon.size()); + + return ArticArchive::RespResult(artic_client->Send(req)); + } else { + auto corrected_path = GetCorrectedPath(path); + + // These folders are always created with the ExtSaveData + std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/"; + std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/"; + FileUtil::CreateFullPath(user_path); + FileUtil::CreateFullPath(boss_path); + + // Write the format metadata + std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata"; + FileUtil::IOFile file(metadata_path, "wb"); + + if (!file.IsOpen()) { + // TODO(Subv): Find the correct error code + return ResultUnknown; + } + + file.WriteBytes(&format_info, sizeof(format_info)); + + FileUtil::IOFile icon_file(FileSys::GetExtSaveDataPath(GetMountPoint(), path) + "icon", + "wb"); + icon_file.WriteBytes(icon.data(), icon.size()); + + return ResultSuccess; + } +} + +Result ArchiveFactory_ExtSaveData::DeleteExtData(Service::FS::MediaType media_type, u8 unknown, + u32 high, u32 low) { + if (IsUsingArtic()) { + Service::FS::ExtSaveDataInfo artic_extdata_path; + + artic_extdata_path.media_type = static_cast(media_type); + artic_extdata_path.unknown = unknown; + artic_extdata_path.save_id_low = low; + artic_extdata_path.save_id_high = high; + + auto req = artic_client->NewRequest("FSUSER_DeleteExtSaveData"); + + req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path)); + + return ArticArchive::RespResult(artic_client->Send(req)); + } else { + // Construct the binary path to the archive first + FileSys::Path path = + FileSys::ConstructExtDataBinaryPath(static_cast(media_type), high, low); + + std::string media_type_directory; + if (media_type == Service::FS::MediaType::NAND) { + media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); + } else if (media_type == Service::FS::MediaType::SDMC) { + media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); + } else { + LOG_ERROR(Service_FS, "Unsupported media type {}", media_type); + return ResultUnknown; // TODO(Subv): Find the right error code + } + + // Delete all directories (/user, /boss) and the icon file. + std::string base_path = FileSys::GetExtDataContainerPath( + media_type_directory, media_type == Service::FS::MediaType::NAND); + std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path); + if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path)) + return ResultUnknown; // TODO(Subv): Find the right error code + return ResultSuccess; + } } ResultVal ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path, u64 program_id) const { - std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata"; - FileUtil::IOFile file(metadata_path, "rb"); + if (IsUsingArtic()) { + auto req = artic_client->NewRequest("FSUSER_GetFormatInfo"); - if (!file.IsOpen()) { - LOG_ERROR(Service_FS, "Could not open metadata information for archive"); - // TODO(Subv): Verify error code - return ResultNotFormatted; + req.AddParameterS32(static_cast(ExtSaveDataTypeToArchiveID(type))); + auto path_artic = ArticArchive::BuildFSPath(path); + req.AddParameterBuffer(path_artic.data(), path_artic.size()); + + auto resp = artic_client->Send(req); + Result res = ArticArchive::RespResult(resp); + if (R_FAILED(res)) { + return res; + } + + auto info_buf = resp->GetResponseBuffer(0); + if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) { + return ResultUnknown; + } + + ArchiveFormatInfo info; + memcpy(&info, info_buf->first, sizeof(info)); + return info; + } else { + std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata"; + FileUtil::IOFile file(metadata_path, "rb"); + + if (!file.IsOpen()) { + LOG_ERROR(Service_FS, "Could not open metadata information for archive"); + // TODO(Subv): Verify error code + return ResultNotFormatted; + } + + ArchiveFormatInfo info = {}; + file.ReadBytes(&info, sizeof(info)); + return info; } - - ArchiveFormatInfo info = {}; - file.ReadBytes(&info, sizeof(info)); - return info; } - -void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, std::span icon) { - std::string game_path = FileSys::GetExtSaveDataPath(GetMountPoint(), path); - FileUtil::IOFile icon_file(game_path + "icon", "wb"); - icon_file.WriteBytes(icon.data(), icon.size()); -} - } // namespace FileSys SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataDelayGenerator) diff --git a/src/core/file_sys/archive_extsavedata.h b/src/core/file_sys/archive_extsavedata.h index 5093ecdc1..e89909ec3 100644 --- a/src/core/file_sys/archive_extsavedata.h +++ b/src/core/file_sys/archive_extsavedata.h @@ -11,7 +11,10 @@ #include #include "common/common_types.h" #include "core/file_sys/archive_backend.h" +#include "core/file_sys/artic_cache.h" #include "core/hle/result.h" +#include "core/hle/service/fs/archive.h" +#include "network/artic_base/artic_base_client.h" namespace FileSys { @@ -22,7 +25,7 @@ enum class ExtSaveDataType { }; /// File system interface to the ExtSaveData archive -class ArchiveFactory_ExtSaveData final : public ArchiveFactory { +class ArchiveFactory_ExtSaveData final : public ArchiveFactory, public ArticCacheProvider { public: ArchiveFactory_ExtSaveData(const std::string& mount_point, ExtSaveDataType type_); @@ -31,21 +34,34 @@ public: } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; + bool IsSlow() override { + return IsUsingArtic(); + } + const std::string& GetMountPoint() const { return mount_point; } - /** - * Writes the SMDH icon of the ExtSaveData to file - * @param path Path of this ExtSaveData - * @param icon_data Binary data of the icon - * @param icon_size Size of the icon data - */ - void WriteIcon(const Path& path, std::span icon); + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override { + return UnimplementedFunction(ErrorModule::FS); + }; + + Result FormatAsExtData(const Path& path, const FileSys::ArchiveFormatInfo& format_info, + u8 unknown, u64 program_id, u64 total_size, std::span icon); + + Result DeleteExtData(Service::FS::MediaType media_type, u8 unknown, u32 high, u32 low); + + void RegisterArtic(std::shared_ptr& client) { + artic_client = client; + } + + bool IsUsingArtic() const { + return artic_client.get() != nullptr; + } private: /// Type of ext save data archive being accessed. @@ -61,10 +77,13 @@ private: /// Returns a path with the correct SaveIdHigh value for Shared extdata paths. Path GetCorrectedPath(const Path& path); + std::shared_ptr artic_client = nullptr; + ArchiveFactory_ExtSaveData() = default; template void serialize(Archive& ar, const unsigned int) { ar& boost::serialization::base_object(*this); + ar& boost::serialization::base_object(*this); ar& type; ar& mount_point; } diff --git a/src/core/file_sys/archive_ncch.cpp b/src/core/file_sys/archive_ncch.cpp index 4b3a478e2..54d4639c5 100644 --- a/src/core/file_sys/archive_ncch.cpp +++ b/src/core/file_sys/archive_ncch.cpp @@ -15,6 +15,7 @@ #include "common/string_util.h" #include "common/swap.h" #include "core/core.h" +#include "core/file_sys/archive_artic.h" #include "core/file_sys/archive_ncch.h" #include "core/file_sys/errors.h" #include "core/file_sys/ivfc_archive.h" @@ -69,8 +70,9 @@ Path MakeNCCHFilePath(NCCHFileOpenType open_type, u32 content_index, NCCHFilePat return FileSys::Path(std::move(file)); } -ResultVal> NCCHArchive::OpenFile(const Path& path, - const Mode& mode) const { +ResultVal> NCCHArchive::OpenFile(const Path& path, const Mode& mode, + u32 attributes) { + if (path.GetType() != LowPathType::Binary) { LOG_ERROR(Service_FS, "Path need to be Binary"); return ResultInvalidPath; @@ -207,14 +209,14 @@ Result NCCHArchive::DeleteDirectoryRecursively(const Path& path) const { return ResultUnknown; } -Result NCCHArchive::CreateFile(const Path& path, u64 size) const { +Result NCCHArchive::CreateFile(const Path& path, u64 size, u32 attributes) const { LOG_CRITICAL(Service_FS, "Attempted to create a file in an NCCH archive ({}).", GetName()); // TODO: Verify error code return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, ErrorLevel::Permanent); } -Result NCCHArchive::CreateDirectory(const Path& path) const { +Result NCCHArchive::CreateDirectory(const Path& path, u32 attributes) const { LOG_CRITICAL(Service_FS, "Attempted to create a directory in an NCCH archive ({}).", GetName()); // TODO(wwylele): Use correct error code return ResultUnknown; @@ -226,7 +228,7 @@ Result NCCHArchive::RenameDirectory(const Path& src_path, const Path& dest_path) return ResultUnknown; } -ResultVal> NCCHArchive::OpenDirectory(const Path& path) const { +ResultVal> NCCHArchive::OpenDirectory(const Path& path) { LOG_CRITICAL(Service_FS, "Attempted to open a directory within an NCCH archive ({}).", GetName().c_str()); // TODO(shinyquagsire23): Use correct error code @@ -255,7 +257,7 @@ ResultVal NCCHFile::Read(const u64 offset, const std::size_t length } ResultVal NCCHFile::Write(const u64 offset, const std::size_t length, const bool flush, - const u8* buffer) { + const bool update_timestamp, const u8* buffer) { LOG_ERROR(Service_FS, "Attempted to write to NCCH file"); // TODO(shinyquagsire23): Find error code return 0ULL; @@ -274,6 +276,13 @@ ArchiveFactory_NCCH::ArchiveFactory_NCCH() {} ResultVal> ArchiveFactory_NCCH::Open(const Path& path, u64 program_id) { + + if (IsUsingArtic()) { + EnsureCacheCreated(); + return ArticArchive::Open(artic_client, Service::FS::ArchiveIdCode::NCCH, path, + Core::PerfStats::PerfArticEventBits::NONE, *this, false); + } + if (path.GetType() != LowPathType::Binary) { LOG_ERROR(Service_FS, "Path need to be Binary"); return ResultInvalidPath; @@ -293,7 +302,7 @@ ResultVal> ArchiveFactory_NCCH::Open(const Path& } Result ArchiveFactory_NCCH::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { + u64 program_id, u32 directory_buckets, u32 file_buckets) { LOG_ERROR(Service_FS, "Attempted to format a NCCH archive."); // TODO: Verify error code return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, diff --git a/src/core/file_sys/archive_ncch.h b/src/core/file_sys/archive_ncch.h index 3e22ef02a..725752f19 100644 --- a/src/core/file_sys/archive_ncch.h +++ b/src/core/file_sys/archive_ncch.h @@ -11,8 +11,10 @@ #include #include #include "core/file_sys/archive_backend.h" +#include "core/file_sys/artic_cache.h" #include "core/file_sys/file_backend.h" #include "core/hle/result.h" +#include "network/artic_base/artic_base_client.h" namespace Service::FS { enum class MediaType : u32; @@ -48,16 +50,16 @@ public: return "NCCHArchive"; } - ResultVal> OpenFile(const Path& path, - const Mode& mode) const override; + ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes) override; Result DeleteFile(const Path& path) const override; Result RenameFile(const Path& src_path, const Path& dest_path) const override; Result DeleteDirectory(const Path& path) const override; Result DeleteDirectoryRecursively(const Path& path) const override; - Result CreateFile(const Path& path, u64 size) const override; - Result CreateDirectory(const Path& path) const override; + Result CreateFile(const Path& path, u64 size, u32 attributes) const override; + Result CreateDirectory(const Path& path, u32 attributes) const override; Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal> OpenDirectory(const Path& path) const override; + ResultVal> OpenDirectory(const Path& path) override; u64 GetFreeBytes() const override; protected: @@ -82,11 +84,11 @@ public: explicit NCCHFile(std::vector buffer, std::unique_ptr delay_generator_); ResultVal Read(u64 offset, std::size_t length, u8* buffer) const override; - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override; u64 GetSize() const override; bool SetSize(u64 size) const override; - bool Close() const override { + bool Close() override { return false; } void Flush() const override {} @@ -105,7 +107,7 @@ private: }; /// File system interface to the NCCH archive -class ArchiveFactory_NCCH final : public ArchiveFactory { +class ArchiveFactory_NCCH final : public ArchiveFactory, public ArticCacheProvider { public: explicit ArchiveFactory_NCCH(); @@ -114,14 +116,29 @@ public: } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; + bool IsSlow() override { + return IsUsingArtic(); + } + + void RegisterArtic(std::shared_ptr& client) { + artic_client = client; + } + + bool IsUsingArtic() const { + return artic_client.get() != nullptr; + } + private: + std::shared_ptr artic_client = nullptr; + template void serialize(Archive& ar, const unsigned int) { ar& boost::serialization::base_object(*this); + ar& boost::serialization::base_object(*this); } friend class boost::serialization::access; }; diff --git a/src/core/file_sys/archive_other_savedata.cpp b/src/core/file_sys/archive_other_savedata.cpp index 3944fce6f..c6aaaff0c 100644 --- a/src/core/file_sys/archive_other_savedata.cpp +++ b/src/core/file_sys/archive_other_savedata.cpp @@ -75,12 +75,14 @@ ResultVal> ArchiveFactory_OtherSaveDataPermitted return ResultGamecardNotInserted; } - return sd_savedata_source->Open(program_id); + return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path, + program_id); } Result ArchiveFactory_OtherSaveDataPermitted::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { + u64 program_id, u32 directory_buckets, + u32 file_buckets) { LOG_ERROR(Service_FS, "Attempted to format a OtherSaveDataPermitted archive."); return ResultInvalidPath; } @@ -96,7 +98,8 @@ ResultVal ArchiveFactory_OtherSaveDataPermitted::GetFormatInf return ResultGamecardNotInserted; } - return sd_savedata_source->GetFormatInfo(program_id); + return sd_savedata_source->GetFormatInfo( + program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path); } ArchiveFactory_OtherSaveDataGeneral::ArchiveFactory_OtherSaveDataGeneral( @@ -114,12 +117,14 @@ ResultVal> ArchiveFactory_OtherSaveDataGeneral:: return ResultGamecardNotInserted; } - return sd_savedata_source->Open(program_id); + return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataGeneral, path, + program_id); } Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 /*client_program_id*/) { + u64 /*client_program_id*/, u32 directory_buckets, + u32 file_buckets) { MediaType media_type; u64 program_id; CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path)); @@ -129,7 +134,9 @@ Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path, return ResultGamecardNotInserted; } - return sd_savedata_source->Format(program_id, format_info); + return sd_savedata_source->Format(program_id, format_info, + Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path, + directory_buckets, file_buckets); } ResultVal ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo( @@ -143,7 +150,8 @@ ResultVal ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo( return ResultGamecardNotInserted; } - return sd_savedata_source->GetFormatInfo(program_id); + return sd_savedata_source->GetFormatInfo( + program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path); } } // namespace FileSys diff --git a/src/core/file_sys/archive_other_savedata.h b/src/core/file_sys/archive_other_savedata.h index c9e720a8d..fffcb5481 100644 --- a/src/core/file_sys/archive_other_savedata.h +++ b/src/core/file_sys/archive_other_savedata.h @@ -22,10 +22,14 @@ public: } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; + bool IsSlow() override { + return sd_savedata_source->IsUsingArtic(); + } + private: std::shared_ptr sd_savedata_source; @@ -49,8 +53,8 @@ public: } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; private: diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp index a5e767b14..aa06f9544 100644 --- a/src/core/file_sys/archive_savedata.cpp +++ b/src/core/file_sys/archive_savedata.cpp @@ -18,18 +18,20 @@ ArchiveFactory_SaveData::ArchiveFactory_SaveData( ResultVal> ArchiveFactory_SaveData::Open(const Path& path, u64 program_id) { - return sd_savedata_source->Open(program_id); + return sd_savedata_source->Open(Service::FS::ArchiveIdCode::SaveData, path, program_id); } Result ArchiveFactory_SaveData::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { - return sd_savedata_source->Format(program_id, format_info); + u64 program_id, u32 directory_buckets, u32 file_buckets) { + return sd_savedata_source->Format(program_id, format_info, Service::FS::ArchiveIdCode::SaveData, + path, directory_buckets, file_buckets); } ResultVal ArchiveFactory_SaveData::GetFormatInfo(const Path& path, u64 program_id) const { - return sd_savedata_source->GetFormatInfo(program_id); + return sd_savedata_source->GetFormatInfo(program_id, Service::FS::ArchiveIdCode::SaveData, + path); } } // namespace FileSys diff --git a/src/core/file_sys/archive_savedata.h b/src/core/file_sys/archive_savedata.h index dba6d5cdb..4b018f214 100644 --- a/src/core/file_sys/archive_savedata.h +++ b/src/core/file_sys/archive_savedata.h @@ -20,11 +20,15 @@ public: } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; + bool IsSlow() override { + return sd_savedata_source->IsUsingArtic(); + } + private: std::shared_ptr sd_savedata_source; diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 8ba01e7b8..91b1fa680 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -43,8 +43,8 @@ public: SERIALIZE_DELAY_GENERATOR }; -ResultVal> SDMCArchive::OpenFile(const Path& path, - const Mode& mode) const { +ResultVal> SDMCArchive::OpenFile(const Path& path, const Mode& mode, + u32 attributes) { Mode modified_mode; modified_mode.hex = mode.hex; @@ -222,7 +222,7 @@ Result SDMCArchive::DeleteDirectoryRecursively(const Path& path) const { path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); } -Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { +Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const { const PathParser path_parser(path); if (!path_parser.IsValid()) { @@ -267,7 +267,7 @@ Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { ErrorLevel::Info); } -Result SDMCArchive::CreateDirectory(const Path& path) const { +Result SDMCArchive::CreateDirectory(const Path& path, u32 attributes) const { const PathParser path_parser(path); if (!path_parser.IsValid()) { @@ -331,7 +331,7 @@ Result SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) ErrorSummary::NothingHappened, ErrorLevel::Status); } -ResultVal> SDMCArchive::OpenDirectory(const Path& path) const { +ResultVal> SDMCArchive::OpenDirectory(const Path& path) { const PathParser path_parser(path); if (!path_parser.IsValid()) { @@ -392,7 +392,7 @@ ResultVal> ArchiveFactory_SDMC::Open(const Path& } Result ArchiveFactory_SDMC::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { + u64 program_id, u32 directory_buckets, u32 file_buckets) { // This is kind of an undesirable operation, so let's just ignore it. :) return ResultSuccess; } diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h index 267b1dfdf..2da56cd2e 100644 --- a/src/core/file_sys/archive_sdmc.h +++ b/src/core/file_sys/archive_sdmc.h @@ -27,16 +27,16 @@ public: return "SDMCArchive: " + mount_point; } - ResultVal> OpenFile(const Path& path, - const Mode& mode) const override; + ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes) override; Result DeleteFile(const Path& path) const override; Result RenameFile(const Path& src_path, const Path& dest_path) const override; Result DeleteDirectory(const Path& path) const override; Result DeleteDirectoryRecursively(const Path& path) const override; - Result CreateFile(const Path& path, u64 size) const override; - Result CreateDirectory(const Path& path) const override; + Result CreateFile(const Path& path, u64 size, u32 attributes) const override; + Result CreateDirectory(const Path& path, u32 attributes) const override; Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal> OpenDirectory(const Path& path) const override; + ResultVal> OpenDirectory(const Path& path) override; u64 GetFreeBytes() const override; protected: @@ -68,8 +68,8 @@ public: } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; private: diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp index 31c27c2d2..fc8191964 100644 --- a/src/core/file_sys/archive_sdmcwriteonly.cpp +++ b/src/core/file_sys/archive_sdmcwriteonly.cpp @@ -41,7 +41,8 @@ public: }; ResultVal> SDMCWriteOnlyArchive::OpenFile(const Path& path, - const Mode& mode) const { + const Mode& mode, + u32 attributes) { if (mode.read_flag) { LOG_ERROR(Service_FS, "Read flag is not supported"); return ResultInvalidReadFlag; @@ -49,8 +50,7 @@ ResultVal> SDMCWriteOnlyArchive::OpenFile(const Pat return SDMCArchive::OpenFileBase(path, mode); } -ResultVal> SDMCWriteOnlyArchive::OpenDirectory( - const Path& path) const { +ResultVal> SDMCWriteOnlyArchive::OpenDirectory(const Path& path) { LOG_ERROR(Service_FS, "Not supported"); return ResultUnsupportedOpenFlags; } @@ -83,7 +83,8 @@ ResultVal> ArchiveFactory_SDMCWriteOnly::Open(co Result ArchiveFactory_SDMCWriteOnly::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { + u64 program_id, u32 directory_buckets, + u32 file_buckets) { // TODO(wwylele): hwtest this LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive."); return ResultUnknown; diff --git a/src/core/file_sys/archive_sdmcwriteonly.h b/src/core/file_sys/archive_sdmcwriteonly.h index c05f408d9..c60723a04 100644 --- a/src/core/file_sys/archive_sdmcwriteonly.h +++ b/src/core/file_sys/archive_sdmcwriteonly.h @@ -24,10 +24,10 @@ public: return "SDMCWriteOnlyArchive: " + mount_point; } - ResultVal> OpenFile(const Path& path, - const Mode& mode) const override; + ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes) override; - ResultVal> OpenDirectory(const Path& path) const override; + ResultVal> OpenDirectory(const Path& path) override; private: SDMCWriteOnlyArchive() = default; @@ -54,8 +54,8 @@ public: } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; private: diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp index 60454d674..5472eda01 100644 --- a/src/core/file_sys/archive_selfncch.cpp +++ b/src/core/file_sys/archive_selfncch.cpp @@ -51,7 +51,7 @@ public: return data->size(); } - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override { LOG_ERROR(Service_FS, "The file is read-only!"); return ResultUnsupportedOpenFlags; @@ -65,7 +65,7 @@ public: return false; } - bool Close() const override { + bool Close() override { return true; } @@ -94,7 +94,8 @@ public: return "SelfNCCHArchive"; } - ResultVal> OpenFile(const Path& path, const Mode&) const override { + ResultVal> OpenFile(const Path& path, const Mode&, + u32 attributes) override { // Note: SelfNCCHArchive doesn't check the open mode. if (path.GetType() != LowPathType::Binary) { @@ -154,12 +155,12 @@ public: return ResultUnsupportedOpenFlags; } - Result CreateFile(const Path& path, u64 size) const override { + Result CreateFile(const Path& path, u64 size, u32 attributes) const override { LOG_ERROR(Service_FS, "Unsupported"); return ResultUnsupportedOpenFlags; } - Result CreateDirectory(const Path& path) const override { + Result CreateDirectory(const Path& path, u32 attributes) const override { LOG_ERROR(Service_FS, "Unsupported"); return ResultUnsupportedOpenFlags; } @@ -169,7 +170,7 @@ public: return ResultUnsupportedOpenFlags; } - ResultVal> OpenDirectory(const Path& path) const override { + ResultVal> OpenDirectory(const Path& path) override { LOG_ERROR(Service_FS, "Unsupported"); return ResultUnsupportedOpenFlags; } @@ -297,7 +298,7 @@ ResultVal> ArchiveFactory_SelfNCCH::Open(const P } Result ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&, - u64 program_id) { + u64 program_id, u32 directory_buckets, u32 file_buckets) { LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive."); return ResultInvalidPath; } diff --git a/src/core/file_sys/archive_selfncch.h b/src/core/file_sys/archive_selfncch.h index 0643faf63..e25526105 100644 --- a/src/core/file_sys/archive_selfncch.h +++ b/src/core/file_sys/archive_selfncch.h @@ -50,8 +50,8 @@ public: return "SelfNCCH"; } ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; private: diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp index 2f4cdcb54..9ab16be0a 100644 --- a/src/core/file_sys/archive_source_sd_savedata.cpp +++ b/src/core/file_sys/archive_source_sd_savedata.cpp @@ -6,6 +6,7 @@ #include "common/archives.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/file_sys/archive_artic.h" #include "core/file_sys/archive_source_sd_savedata.h" #include "core/file_sys/errors.h" #include "core/file_sys/savedata_archive.h" @@ -40,49 +41,101 @@ ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_direc LOG_DEBUG(Service_FS, "Directory {} set as SaveData.", mount_point); } -ResultVal> ArchiveSource_SDSaveData::Open(u64 program_id) { - std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); - if (!FileUtil::Exists(concrete_mount_point)) { - // When a SaveData archive is created for the first time, it is not yet formatted and the - // save file/directory structure expected by the game has not yet been initialized. - // Returning the NotFormatted error code will signal the game to provision the SaveData - // archive with the files and folders that it expects. - return ResultNotFormatted; - } +ResultVal> ArchiveSource_SDSaveData::Open( + Service::FS::ArchiveIdCode archive_id, const Path& path, u64 program_id) { + if (IsUsingArtic()) { + EnsureCacheCreated(); + return ArticArchive::Open(artic_client, archive_id, path, + Core::PerfStats::PerfArticEventBits::ARTIC_SAVE_DATA, *this, + archive_id != Service::FS::ArchiveIdCode::SaveData); + } else { + std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); + if (!FileUtil::Exists(concrete_mount_point)) { + // When a SaveData archive is created for the first time, it is not yet formatted and + // the save file/directory structure expected by the game has not yet been initialized. + // Returning the NotFormatted error code will signal the game to provision the SaveData + // archive with the files and folders that it expects. + return ResultNotFormatted; + } - return std::make_unique(std::move(concrete_mount_point)); + return std::make_unique(std::move(concrete_mount_point)); + } } Result ArchiveSource_SDSaveData::Format(u64 program_id, - const FileSys::ArchiveFormatInfo& format_info) { - std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); - FileUtil::DeleteDirRecursively(concrete_mount_point); - FileUtil::CreateFullPath(concrete_mount_point); + const FileSys::ArchiveFormatInfo& format_info, + Service::FS::ArchiveIdCode archive_id, const Path& path, + u32 directory_buckets, u32 file_buckets) { + if (IsUsingArtic()) { + ClearAllCache(); + auto req = artic_client->NewRequest("FSUSER_FormatSaveData"); - // Write the format metadata - std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); - FileUtil::IOFile file(metadata_path, "wb"); + req.AddParameterS32(static_cast(archive_id)); + auto artic_path = ArticArchive::BuildFSPath(path); + req.AddParameterBuffer(artic_path.data(), artic_path.size()); + req.AddParameterU32(format_info.total_size / 512); + req.AddParameterU32(format_info.number_directories); + req.AddParameterU32(format_info.number_files); + req.AddParameterU32(directory_buckets); + req.AddParameterU32(file_buckets); + req.AddParameterU8(format_info.duplicate_data); - if (file.IsOpen()) { - file.WriteBytes(&format_info, sizeof(format_info)); + auto resp = artic_client->Send(req); + return ArticArchive::RespResult(resp); + } else { + std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); + FileUtil::DeleteDirRecursively(concrete_mount_point); + FileUtil::CreateFullPath(concrete_mount_point); + + // Write the format metadata + std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); + FileUtil::IOFile file(metadata_path, "wb"); + + if (file.IsOpen()) { + file.WriteBytes(&format_info, sizeof(format_info)); + return ResultSuccess; + } return ResultSuccess; } - return ResultSuccess; } -ResultVal ArchiveSource_SDSaveData::GetFormatInfo(u64 program_id) const { - std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); - FileUtil::IOFile file(metadata_path, "rb"); +ResultVal ArchiveSource_SDSaveData::GetFormatInfo( + u64 program_id, Service::FS::ArchiveIdCode archive_id, const Path& path) const { + if (IsUsingArtic()) { + auto req = artic_client->NewRequest("FSUSER_GetFormatInfo"); - if (!file.IsOpen()) { - LOG_ERROR(Service_FS, "Could not open metadata information for archive"); - // TODO(Subv): Verify error code - return ResultNotFormatted; + req.AddParameterS32(static_cast(archive_id)); + auto path_artic = ArticArchive::BuildFSPath(path); + req.AddParameterBuffer(path_artic.data(), path_artic.size()); + + auto resp = artic_client->Send(req); + Result res = ArticArchive::RespResult(resp); + if (R_FAILED(res)) { + return res; + } + + auto info_buf = resp->GetResponseBuffer(0); + if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) { + return ResultUnknown; + } + + ArchiveFormatInfo info; + memcpy(&info, info_buf->first, sizeof(info)); + return info; + } else { + std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); + FileUtil::IOFile file(metadata_path, "rb"); + + if (!file.IsOpen()) { + LOG_ERROR(Service_FS, "Could not open metadata information for archive"); + // TODO(Subv): Verify error code + return ResultNotFormatted; + } + + ArchiveFormatInfo info = {}; + file.ReadBytes(&info, sizeof(info)); + return info; } - - ArchiveFormatInfo info = {}; - file.ReadBytes(&info, sizeof(info)); - return info; } std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point, diff --git a/src/core/file_sys/archive_source_sd_savedata.h b/src/core/file_sys/archive_source_sd_savedata.h index 07832c3ae..56fdb2c5b 100644 --- a/src/core/file_sys/archive_source_sd_savedata.h +++ b/src/core/file_sys/archive_source_sd_savedata.h @@ -9,27 +9,48 @@ #include #include #include "core/file_sys/archive_backend.h" +#include "core/file_sys/artic_cache.h" #include "core/hle/result.h" +#include "network/artic_base/artic_base_client.h" + +namespace Service::FS { +enum class ArchiveIdCode : u32; +} // namespace Service::FS namespace FileSys { /// A common source of SD save data archive -class ArchiveSource_SDSaveData { +class ArchiveSource_SDSaveData : public ArticCacheProvider { public: explicit ArchiveSource_SDSaveData(const std::string& mount_point); - ResultVal> Open(u64 program_id); - Result Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info); - ResultVal GetFormatInfo(u64 program_id) const; + ResultVal> Open(Service::FS::ArchiveIdCode archive_id, + const Path& path, u64 program_id); + Result Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info, + Service::FS::ArchiveIdCode archive_id, const Path& path, u32 directory_buckets, + u32 file_buckets); + ResultVal GetFormatInfo(u64 program_id, + Service::FS::ArchiveIdCode archive_id, + const Path& path) const; static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id); + void RegisterArtic(std::shared_ptr& client) { + artic_client = client; + } + + bool IsUsingArtic() const { + return artic_client.get() != nullptr; + } + private: std::string mount_point; + std::shared_ptr artic_client = nullptr; ArchiveSource_SDSaveData() = default; template void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); ar& mount_point; } friend class boost::serialization::access; diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index 62734fca6..79ae33cd7 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp @@ -64,7 +64,8 @@ ResultVal> ArchiveFactory_SystemSaveData::Open(c Result ArchiveFactory_SystemSaveData::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { + u64 program_id, u32 directory_buckets, + u32 file_buckets) { std::string fullpath = GetSystemSaveDataPath(base_path, path); FileUtil::DeleteDirRecursively(fullpath); FileUtil::CreateFullPath(fullpath); diff --git a/src/core/file_sys/archive_systemsavedata.h b/src/core/file_sys/archive_systemsavedata.h index af7c341e2..5cb109122 100644 --- a/src/core/file_sys/archive_systemsavedata.h +++ b/src/core/file_sys/archive_systemsavedata.h @@ -20,8 +20,8 @@ public: explicit ArchiveFactory_SystemSaveData(const std::string& mount_point); ResultVal> Open(const Path& path, u64 program_id) override; - Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) override; + Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; std::string GetName() const override { diff --git a/src/core/file_sys/artic_cache.cpp b/src/core/file_sys/artic_cache.cpp new file mode 100644 index 000000000..b5c963495 --- /dev/null +++ b/src/core/file_sys/artic_cache.cpp @@ -0,0 +1,235 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "artic_cache.h" + +namespace FileSys { +ResultVal ArticCache::Read(s32 file_handle, std::size_t offset, std::size_t length, + u8* buffer) { + if (length == 0) + return size_t(); + + const auto segments = BreakupRead(offset, length); + std::size_t read_progress = 0; + + // Skip cache if the read is too big + if (segments.size() == 1 && segments[0].second > cache_line_size) { + if (segments[0].second < big_cache_skip) { + std::unique_lock big_read_guard(big_cache_mutex); + auto big_cache_entry = big_cache.request(std::make_pair(offset, length)); + if (!big_cache_entry.first) { + LOG_TRACE(Service_FS, "ArticCache BMISS: offset={}, length={}", offset, length); + big_cache_entry.second.clear(); + big_cache_entry.second.resize(length); + auto res = + ReadFromArtic(file_handle, reinterpret_cast(big_cache_entry.second.data()), + length, offset); + if (res.Failed()) + return res; + length = res.Unwrap(); + } else { + LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, length); + } + memcpy(buffer, big_cache_entry.second.data(), length); + } else { + if (segments[0].second < very_big_cache_skip) { + std::unique_lock very_big_read_guard(very_big_cache_mutex); + auto very_big_cache_entry = very_big_cache.request(std::make_pair(offset, length)); + if (!very_big_cache_entry.first) { + LOG_TRACE(Service_FS, "ArticCache VBMISS: offset={}, length={}", offset, + length); + very_big_cache_entry.second.clear(); + very_big_cache_entry.second.resize(length); + auto res = ReadFromArtic( + file_handle, reinterpret_cast(very_big_cache_entry.second.data()), + length, offset); + if (res.Failed()) + return res; + length = res.Unwrap(); + } else { + LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, length); + } + memcpy(buffer, very_big_cache_entry.second.data(), length); + } else { + LOG_TRACE(Service_FS, "ArticCache SKIP: offset={}, length={}", offset, length); + + auto res = ReadFromArtic(file_handle, buffer, length, offset); + if (res.Failed()) + return res; + length = res.Unwrap(); + } + } + return length; + } + + // TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function. + std::unique_lock read_guard(cache_mutex); + for (const auto& seg : segments) { + std::size_t read_size = cache_line_size; + std::size_t page = OffsetToPage(seg.first); + // Check if segment is in cache + auto cache_entry = cache.request(page); + if (!cache_entry.first) { + // If not found, read from artic and cache the data + auto res = ReadFromArtic(file_handle, cache_entry.second.data(), read_size, page); + if (res.Failed()) + return res; + read_size = res.Unwrap(); + LOG_TRACE(Service_FS, "ArticCache MISS: page={}, length={}, into={}", page, seg.second, + (seg.first - page)); + } else { + LOG_TRACE(Service_FS, "ArticCache HIT: page={}, length={}, into={}", page, seg.second, + (seg.first - page)); + } + std::size_t copy_amount = + (read_size > (seg.first - page)) + ? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page) + : 0; + std::memcpy(buffer + read_progress, cache_entry.second.data() + (seg.first - page), + copy_amount); + read_progress += copy_amount; + } + return read_progress; +} + +bool ArticCache::CacheReady(std::size_t file_offset, std::size_t length) { + auto segments = BreakupRead(file_offset, length); + if (segments.size() == 1 && segments[0].second > cache_line_size) { + return false; + } else { + std::shared_lock read_guard(cache_mutex); + for (auto it = segments.begin(); it != segments.end(); it++) { + if (!cache.contains(OffsetToPage(it->first))) + return false; + } + return true; + } +} + +void ArticCache::Clear() { + std::unique_lock l1(cache_mutex), l2(big_cache_mutex), l3(very_big_cache_mutex); + cache.clear(); + big_cache.clear(); + very_big_cache.clear(); + data_size = std::nullopt; +} + +ResultVal ArticCache::Write(s32 file_handle, std::size_t offset, std::size_t length, + const u8* buffer, u32 flags) { + // Can probably do better, but write operations are usually done at the end, so it doesn't + // matter much + Clear(); + + size_t written_amount = 0; + while (written_amount != length) { + size_t to_write = + std::min(client->GetServerRequestMaxSize() - 0x100, length - written_amount); + + auto req = client->NewRequest("FSFILE_Write"); + req.AddParameterS32(file_handle); + req.AddParameterS64(static_cast(offset + written_amount)); + req.AddParameterS32(static_cast(to_write)); + req.AddParameterS32(static_cast(flags)); + req.AddParameterBuffer(buffer + written_amount, to_write); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded()) + return Result(-1); + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) + return res; + + auto actually_written_opt = resp->GetResponseS32(0); + if (!actually_written_opt.has_value()) + return Result(-1); + + size_t actually_written = static_cast(actually_written_opt.value()); + + written_amount += actually_written; + if (actually_written != to_write) + break; + } + return written_amount; +} + +ResultVal ArticCache::GetSize(s32 file_handle) { + std::unique_lock l1(cache_mutex); + + if (data_size.has_value()) + return data_size.value(); + + auto req = client->NewRequest("FSFILE_GetSize"); + + req.AddParameterS32(file_handle); + + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded()) + return Result(-1); + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) + return res; + + auto size_buf = resp->GetResponseS64(0); + if (!size_buf) { + return Result(-1); + } + + data_size = static_cast(*size_buf); + return data_size.value(); +} + +ResultVal ArticCache::ReadFromArtic(s32 file_handle, u8* buffer, size_t len, + size_t offset) { + size_t read_amount = 0; + while (read_amount != len) { + size_t to_read = + std::min(client->GetServerRequestMaxSize() - 0x100, len - read_amount); + + auto req = client->NewRequest("FSFILE_Read"); + req.AddParameterS32(file_handle); + req.AddParameterS64(static_cast(offset + read_amount)); + req.AddParameterS32(static_cast(to_read)); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded()) + return Result(-1); + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) + return res; + + auto read_buff = resp->GetResponseBuffer(0); + if (!read_buff.has_value()) + return Result(-1); + size_t actually_read = read_buff->second; + + memcpy(buffer + read_amount, read_buff->first, actually_read); + read_amount += actually_read; + if (actually_read != to_read) + break; + } + return read_amount; +} + +std::vector> ArticCache::BreakupRead(std::size_t offset, + std::size_t length) { + std::vector> ret; + + // Reads bigger than the cache line size will probably never hit again + if (length > max_breakup_size) { + ret.push_back(std::make_pair(offset, length)); + return ret; + } + + std::size_t curr_offset = offset; + while (length) { + std::size_t next_page = OffsetToPage(curr_offset + cache_line_size); + std::size_t curr_page_len = std::min(length, next_page - curr_offset); + ret.push_back(std::make_pair(curr_offset, curr_page_len)); + curr_offset = next_page; + length -= curr_page_len; + } + return ret; +} +} // namespace FileSys diff --git a/src/core/file_sys/artic_cache.h b/src/core/file_sys/artic_cache.h new file mode 100644 index 000000000..7e8ed591c --- /dev/null +++ b/src/core/file_sys/artic_cache.h @@ -0,0 +1,154 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "vector" + +#include +#include +#include +#include "common/alignment.h" +#include "common/common_types.h" +#include "common/static_lru_cache.h" +#include "core/file_sys/archive_backend.h" +#include "core/hle/result.h" +#include "network/artic_base/artic_base_client.h" + +namespace FileSys { +class ArticCache { +public: + ArticCache() = default; + + ArticCache(const std::shared_ptr& cli) : client(cli) {} + + ResultVal Read(s32 file_handle, std::size_t offset, std::size_t length, + u8* buffer); + + bool CacheReady(std::size_t file_offset, std::size_t length); + + void Clear(); + + ResultVal Write(s32 file_handle, std::size_t offset, std::size_t length, + const u8* buffer, u32 flags); + + ResultVal GetSize(s32 file_handle); + + void ForceSetSize(const std::optional& size) { + data_size = size; + } + +private: + std::shared_ptr client; + std::optional data_size; + + // Total cache size: 32MB small, 512MB big (worst case), 160MB very big (worst case). + // The worst case values are unrealistic, they will never happen in any real game. + static constexpr std::size_t cache_line_size = 4 * 1024; + static constexpr std::size_t cache_line_count = 256; + static constexpr std::size_t max_breakup_size = 8 * 1024; + + static constexpr std::size_t big_cache_skip = 1 * 1024 * 1024; + static constexpr std::size_t big_cache_lines = 1024; + + static constexpr std::size_t very_big_cache_skip = 10 * 1024 * 1024; + static constexpr std::size_t very_big_cache_lines = 24; + + Common::StaticLRUCache, cache_line_count> cache; + std::shared_mutex cache_mutex; + + struct NoInitChar { + u8 value; + NoInitChar() noexcept { + // do nothing + static_assert(sizeof *this == sizeof value, "invalid size"); + } + }; + Common::StaticLRUCache, std::vector, + big_cache_lines> + big_cache; + std::shared_mutex big_cache_mutex; + Common::StaticLRUCache, std::vector, + very_big_cache_lines> + very_big_cache; + std::shared_mutex very_big_cache_mutex; + + ResultVal ReadFromArtic(s32 file_handle, u8* buffer, size_t len, size_t offset); + + std::size_t OffsetToPage(std::size_t offset) { + return Common::AlignDown(offset, cache_line_size); + } + + std::vector> BreakupRead(std::size_t offset, + std::size_t length); + +protected: + template + void serialize(Archive& ar, const unsigned int) {} + friend class boost::serialization::access; +}; + +class ArticCacheProvider { +public: + virtual ~ArticCacheProvider() {} + + std::vector PathsToVector(const Path& archive_path, const Path& file_path) { + auto archive_path_binary = archive_path.AsBinary(); + auto file_path_binary = file_path.AsBinary(); + + std::vector ret; + ret.push_back(static_cast(file_path.GetType())); + ret.insert(ret.end(), archive_path_binary.begin(), archive_path_binary.end()); + ret.push_back(static_cast(archive_path.GetType())); + ret.insert(ret.end(), file_path_binary.begin(), file_path_binary.end()); + return ret; + } + + virtual std::shared_ptr ProvideCache( + const std::shared_ptr& cli, const std::vector& path, + bool create) { + if (file_caches == nullptr) + return nullptr; + + auto it = file_caches->find(path); + if (it == file_caches->end()) { + if (!create) { + return nullptr; + } + auto res = std::make_shared(cli); + file_caches->insert({path, res}); + return res; + } + return it->second; + } + + virtual void ClearAllCache() { + if (file_caches != nullptr) { + file_caches->clear(); + } + } + + virtual void EnsureCacheCreated() { + if (file_caches == nullptr) { + file_caches = + std::make_unique, std::shared_ptr>>(); + } + } + +protected: + template + void serialize(Archive& ar, const unsigned int) {} + friend class boost::serialization::access; + +private: + std::unique_ptr, std::shared_ptr>> file_caches = nullptr; + std::shared_ptr client; +}; + +} // namespace FileSys + +BOOST_CLASS_EXPORT_KEY(FileSys::ArticCache) +BOOST_CLASS_EXPORT_KEY(FileSys::ArticCacheProvider) \ No newline at end of file diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h index b5a2617bb..e88363ae6 100644 --- a/src/core/file_sys/directory_backend.h +++ b/src/core/file_sys/directory_backend.h @@ -49,7 +49,11 @@ public: * Close the directory * @return true if the directory closed correctly */ - virtual bool Close() const = 0; + virtual bool Close() = 0; + + virtual bool IsSlow() { + return false; + } private: template diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index 12ea8932a..a7ae5e92e 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -26,7 +26,7 @@ ResultVal DiskFile::Read(const u64 offset, const std::size_t length } ResultVal DiskFile::Write(const u64 offset, const std::size_t length, const bool flush, - const u8* buffer) { + const bool update_timestamp, const u8* buffer) { if (!mode.write_flag) return ResultInvalidOpenFlags; @@ -47,7 +47,7 @@ bool DiskFile::SetSize(const u64 size) const { return true; } -bool DiskFile::Close() const { +bool DiskFile::Close() { return file->Close(); } diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index 5843a37d5..0af741be7 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h @@ -30,11 +30,11 @@ public: } ResultVal Read(u64 offset, std::size_t length, u8* buffer) const override; - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override; u64 GetSize() const override; bool SetSize(u64 size) const override; - bool Close() const override; + bool Close() override; void Flush() const override { file->Flush(); @@ -66,7 +66,7 @@ public: u32 Read(u32 count, Entry* entries) override; - bool Close() const override { + bool Close() override { return true; } diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h index bf0a1b493..e491ae0e8 100644 --- a/src/core/file_sys/file_backend.h +++ b/src/core/file_sys/file_backend.h @@ -37,7 +37,7 @@ public: * @return Number of bytes written, or error code */ virtual ResultVal Write(u64 offset, std::size_t length, bool flush, - const u8* buffer) = 0; + bool update_timestamp, const u8* buffer) = 0; /** * Get the amount of time a 3ds needs to read those data @@ -79,7 +79,7 @@ public: * Close the file * @return true if the file closed correctly */ - virtual bool Close() const = 0; + virtual bool Close() = 0; /** * Flushes the file diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp index de71883a7..e58c78d22 100644 --- a/src/core/file_sys/ivfc_archive.cpp +++ b/src/core/file_sys/ivfc_archive.cpp @@ -28,8 +28,8 @@ std::string IVFCArchive::GetName() const { return "IVFC"; } -ResultVal> IVFCArchive::OpenFile(const Path& path, - const Mode& mode) const { +ResultVal> IVFCArchive::OpenFile(const Path& path, const Mode& mode, + u32 attributes) { std::unique_ptr delay_generator = std::make_unique(); return std::make_unique(romfs_file, std::move(delay_generator)); } @@ -61,14 +61,14 @@ Result IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { return ResultUnknown; } -Result IVFCArchive::CreateFile(const Path& path, u64 size) const { +Result IVFCArchive::CreateFile(const Path& path, u64 size, u32 attributes) const { LOG_CRITICAL(Service_FS, "Attempted to create a file in an IVFC archive ({}).", GetName()); // TODO: Verify error code return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, ErrorLevel::Permanent); } -Result IVFCArchive::CreateDirectory(const Path& path) const { +Result IVFCArchive::CreateDirectory(const Path& path, u32 attributes) const { LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive ({}).", GetName()); // TODO(wwylele): Use correct error code return ResultUnknown; @@ -80,7 +80,7 @@ Result IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) return ResultUnknown; } -ResultVal> IVFCArchive::OpenDirectory(const Path& path) const { +ResultVal> IVFCArchive::OpenDirectory(const Path& path) { return std::make_unique(); } @@ -102,7 +102,7 @@ ResultVal IVFCFile::Read(const u64 offset, const std::size_t length } ResultVal IVFCFile::Write(const u64 offset, const std::size_t length, const bool flush, - const u8* buffer) { + const bool update_timestamp, const u8* buffer) { LOG_ERROR(Service_FS, "Attempted to write to IVFC file"); // TODO(Subv): Find error code return 0ULL; @@ -133,7 +133,8 @@ ResultVal IVFCFileInMemory::Read(const u64 offset, const std::size_ } ResultVal IVFCFileInMemory::Write(const u64 offset, const std::size_t length, - const bool flush, const u8* buffer) { + const bool flush, const bool update_timestamp, + const u8* buffer) { LOG_ERROR(Service_FS, "Attempted to write to IVFC file"); // TODO(Subv): Find error code return 0ULL; diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h index 765e41444..632d643ec 100644 --- a/src/core/file_sys/ivfc_archive.h +++ b/src/core/file_sys/ivfc_archive.h @@ -101,16 +101,16 @@ public: std::string GetName() const override; - ResultVal> OpenFile(const Path& path, - const Mode& mode) const override; + ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes) override; Result DeleteFile(const Path& path) const override; Result RenameFile(const Path& src_path, const Path& dest_path) const override; Result DeleteDirectory(const Path& path) const override; Result DeleteDirectoryRecursively(const Path& path) const override; - Result CreateFile(const Path& path, u64 size) const override; - Result CreateDirectory(const Path& path) const override; + Result CreateFile(const Path& path, u64 size, u32 attributes) const override; + Result CreateDirectory(const Path& path, u32 attributes) const override; Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal> OpenDirectory(const Path& path) const override; + ResultVal> OpenDirectory(const Path& path) override; u64 GetFreeBytes() const override; protected: @@ -122,11 +122,11 @@ public: IVFCFile(std::shared_ptr file, std::unique_ptr delay_generator_); ResultVal Read(u64 offset, std::size_t length, u8* buffer) const override; - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override; u64 GetSize() const override; bool SetSize(u64 size) const override; - bool Close() const override { + bool Close() override { return false; } void Flush() const override {} @@ -157,7 +157,7 @@ public: u32 Read(const u32 count, Entry* entries) override { return 0; } - bool Close() const override { + bool Close() override { return false; } }; @@ -168,11 +168,11 @@ public: std::unique_ptr delay_generator_); ResultVal Read(u64 offset, std::size_t length, u8* buffer) const override; - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override; u64 GetSize() const override; bool SetSize(u64 size) const override; - bool Close() const override { + bool Close() override { return false; } void Flush() const override {} diff --git a/src/core/file_sys/romfs_reader.cpp b/src/core/file_sys/romfs_reader.cpp index 40823d833..2cff2825b 100644 --- a/src/core/file_sys/romfs_reader.cpp +++ b/src/core/file_sys/romfs_reader.cpp @@ -4,7 +4,11 @@ #include #include "common/archives.h" #include "common/logging/log.h" +#include "core/file_sys/archive_artic.h" +#include "core/file_sys/archive_backend.h" #include "core/file_sys/romfs_reader.h" +#include "core/hle/service/fs/fs_user.h" +#include "core/loader/loader.h" SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader) @@ -109,4 +113,102 @@ std::vector> DirectRomFSReader::BreakupRead( return ret; } +ArticRomFSReader::ArticRomFSReader(std::shared_ptr& cli, + bool is_update_romfs) + : client(cli), cache(cli) { + auto req = client->NewRequest("FSUSER_OpenFileDirectly"); + + FileSys::Path archive(FileSys::LowPathType::Empty, {}); + std::vector fileVec(0xC); + fileVec[0] = static_cast(is_update_romfs ? 5 : 0); + FileSys::Path file(FileSys::LowPathType::Binary, fileVec); + + req.AddParameterS32(static_cast(Service::FS::ArchiveIdCode::SelfNCCH)); + + auto archive_buf = ArticArchive::BuildFSPath(archive); + req.AddParameterBuffer(archive_buf.data(), archive_buf.size()); + auto file_buf = ArticArchive::BuildFSPath(file); + req.AddParameterBuffer(file_buf.data(), file_buf.size()); + + req.AddParameterS32(1); + req.AddParameterS32(0); + + auto resp = client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + load_status = Loader::ResultStatus::Error; + return; + } + if (resp->GetMethodResult() != 0) { + load_status = Loader::ResultStatus::ErrorNotUsed; + return; + } + + auto handle_buf = resp->GetResponseBuffer(0); + if (!handle_buf.has_value() || handle_buf->second != sizeof(s32)) { + load_status = Loader::ResultStatus::Error; + return; + } + + romfs_handle = *reinterpret_cast(handle_buf->first); + + req = client->NewRequest("FSFILE_GetSize"); + + req.AddParameterS32(romfs_handle); + + resp = client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + load_status = Loader::ResultStatus::Error; + return; + } + if (resp->GetMethodResult() != 0) { + load_status = Loader::ResultStatus::ErrorNotUsed; + return; + } + + auto size_buf = resp->GetResponseBuffer(0); + if (!size_buf.has_value() || size_buf->second != sizeof(u64)) { + load_status = Loader::ResultStatus::Error; + return; + } + + data_size = static_cast(*reinterpret_cast(size_buf->first)); + load_status = Loader::ResultStatus::Success; +} + +ArticRomFSReader::~ArticRomFSReader() { + if (romfs_handle != -1) { + auto req = client->NewRequest("FSFILE_Close"); + req.AddParameterS32(romfs_handle); + client->Send(req); + romfs_handle = -1; + } +} + +std::size_t ArticRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) { + length = std::min(length, static_cast(data_size) - offset); + auto res = cache.Read(romfs_handle, offset, length, buffer); + if (res.Failed()) + return 0; + return res.Unwrap(); +} + +bool ArticRomFSReader::AllowsCachedReads() const { + return true; +} + +bool ArticRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) { + return cache.CacheReady(file_offset, length); +} + +void ArticRomFSReader::CloseFile() { + if (romfs_handle != -1) { + auto req = client->NewRequest("FSFILE_Close"); + req.AddParameterS32(romfs_handle); + client->Send(req); + romfs_handle = -1; + } +} + } // namespace FileSys diff --git a/src/core/file_sys/romfs_reader.h b/src/core/file_sys/romfs_reader.h index 128d10dbd..63b88e262 100644 --- a/src/core/file_sys/romfs_reader.h +++ b/src/core/file_sys/romfs_reader.h @@ -9,6 +9,12 @@ #include "common/common_types.h" #include "common/file_util.h" #include "common/static_lru_cache.h" +#include "core/file_sys/artic_cache.h" +#include "network/artic_base/artic_base_client.h" + +namespace Loader { +enum class ResultStatus; +} namespace FileSys { @@ -97,6 +103,53 @@ private: friend class boost::serialization::access; }; +/** + * A RomFS reader that reads from an artic base server. + */ +class ArticRomFSReader : public RomFSReader { +public: + ArticRomFSReader() = default; + ArticRomFSReader(std::shared_ptr& cli, bool is_update_romfs); + + ~ArticRomFSReader() override; + + std::size_t GetSize() const override { + return data_size; + } + + std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override; + + bool AllowsCachedReads() const override; + + bool CacheReady(std::size_t file_offset, std::size_t length) override; + + Loader::ResultStatus OpenStatus() { + return load_status; + } + + void ClearCache() { + cache.Clear(); + } + + void CloseFile(); + +private: + std::shared_ptr client; + size_t data_size = 0; + s32 romfs_handle = -1; + Loader::ResultStatus load_status; + + ArticCache cache; + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar& data_size; + } + friend class boost::serialization::access; +}; + } // namespace FileSys BOOST_CLASS_EXPORT_KEY(FileSys::DirectRomFSReader) +BOOST_CLASS_EXPORT_KEY(FileSys::ArticRomFSReader) diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp index 17380e6ac..00ee84d8f 100644 --- a/src/core/file_sys/savedata_archive.cpp +++ b/src/core/file_sys/savedata_archive.cpp @@ -36,7 +36,8 @@ public: }; ResultVal> SaveDataArchive::OpenFile(const Path& path, - const Mode& mode) const { + const Mode& mode, + u32 attributes) { LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex); const PathParser path_parser(path); @@ -203,7 +204,7 @@ Result SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const { path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); } -Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { +Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const { const PathParser path_parser(path); if (!path_parser.IsValid()) { @@ -253,7 +254,7 @@ Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { ErrorLevel::Info); } -Result SaveDataArchive::CreateDirectory(const Path& path) const { +Result SaveDataArchive::CreateDirectory(const Path& path, u32 attributes) const { const PathParser path_parser(path); if (!path_parser.IsValid()) { @@ -319,8 +320,7 @@ Result SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_p ErrorSummary::NothingHappened, ErrorLevel::Status); } -ResultVal> SaveDataArchive::OpenDirectory( - const Path& path) const { +ResultVal> SaveDataArchive::OpenDirectory(const Path& path) { const PathParser path_parser(path); if (!path_parser.IsValid()) { diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h index f72f924f1..0169d918e 100644 --- a/src/core/file_sys/savedata_archive.h +++ b/src/core/file_sys/savedata_archive.h @@ -22,16 +22,16 @@ public: return "SaveDataArchive: " + mount_point; } - ResultVal> OpenFile(const Path& path, - const Mode& mode) const override; + ResultVal> OpenFile(const Path& path, const Mode& mode, + u32 attributes) override; Result DeleteFile(const Path& path) const override; Result RenameFile(const Path& src_path, const Path& dest_path) const override; Result DeleteDirectory(const Path& path) const override; Result DeleteDirectoryRecursively(const Path& path) const override; - Result CreateFile(const Path& path, u64 size) const override; - Result CreateDirectory(const Path& path) const override; + Result CreateFile(const Path& path, u64 size, u32 attributes) const override; + Result CreateDirectory(const Path& path, u32 attributes) const override; Result RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal> OpenDirectory(const Path& path) const override; + ResultVal> OpenDirectory(const Path& path) override; u64 GetFreeBytes() const override; protected: diff --git a/src/core/file_sys/secure_value_backend.cpp b/src/core/file_sys/secure_value_backend.cpp new file mode 100644 index 000000000..c0b0bcae6 --- /dev/null +++ b/src/core/file_sys/secure_value_backend.cpp @@ -0,0 +1,74 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/archives.h" +#include "secure_value_backend.h" + +SERIALIZE_EXPORT_IMPL(FileSys::DefaultSecureValueBackend) + +namespace FileSys { + +Result DefaultSecureValueBackend::ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, + u32 secure_value_slot, + u64 secure_value) { + + // TODO: Generate and Save the Secure Value + + LOG_WARNING(Service_FS, + "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:08X} " + "unqiue_id=0x{:08X} title_variation=0x{:02X}", + secure_value, secure_value_slot, unique_id, title_variation); + + return ResultSuccess; +} + +ResultVal> DefaultSecureValueBackend::ObsoletedGetSaveDataSecureValue( + u32 unique_id, u8 title_variation, u32 secure_value_slot) { + + // TODO: Implement Secure Value Lookup & Generation + + LOG_WARNING(Service_FS, + "(STUBBED) called, secure_value_slot=0x{:08X} " + "unqiue_id=0x{:08X} title_variation=0x{:02X}", + secure_value_slot, unique_id, title_variation); + + return std::make_tuple(false, 0); +} + +Result DefaultSecureValueBackend::ControlSecureSave(u32 action, u8* input, size_t input_size, + u8* output, size_t output_size) { + + LOG_WARNING(Service_FS, + "(STUBBED) called, action=0x{:08X} " + "input_size=0x{:016X} output_size=0x{:016X}", + action, input_size, output_size); + + return ResultSuccess; +} + +Result DefaultSecureValueBackend::SetThisSaveDataSecureValue(u32 secure_value_slot, + u64 secure_value) { + // TODO: Generate and Save the Secure Value + + LOG_WARNING(Service_FS, "(STUBBED) called, secure_value=0x{:016x} secure_value_slot=0x{:08X}", + secure_value, secure_value_slot); + + return ResultSuccess; +} + +ResultVal> DefaultSecureValueBackend::GetThisSaveDataSecureValue( + u32 secure_value_slot) { + + // TODO: Implement Secure Value Lookup & Generation + + LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot); + + return std::make_tuple(false, true, 0); +} + +template +void FileSys::DefaultSecureValueBackend::serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); +} +} // namespace FileSys \ No newline at end of file diff --git a/src/core/file_sys/secure_value_backend.h b/src/core/file_sys/secure_value_backend.h new file mode 100644 index 000000000..e9d3d3da2 --- /dev/null +++ b/src/core/file_sys/secure_value_backend.h @@ -0,0 +1,65 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "tuple" + +#include "common/common_types.h" +#include "core/hle/result.h" +#include "core/hle/service/fs/archive.h" + +namespace FileSys { +class SecureValueBackend : NonCopyable { +public: + virtual ~SecureValueBackend(){}; + + virtual Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, + u32 secure_value_slot, u64 secure_value) = 0; + + virtual ResultVal> ObsoletedGetSaveDataSecureValue( + u32 unique_id, u8 title_variation, u32 secure_value_slot) = 0; + + virtual Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output, + size_t output_size) = 0; + + virtual Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) = 0; + + virtual ResultVal> GetThisSaveDataSecureValue( + u32 secure_value_slot) = 0; + + virtual bool BackendIsSlow() { + return false; + } + +protected: + template + void serialize(Archive& ar, const unsigned int) {} + friend class boost::serialization::access; +}; + +class DefaultSecureValueBackend : public SecureValueBackend { +public: + Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, u32 secure_value_slot, + u64 secure_value) override; + + ResultVal> ObsoletedGetSaveDataSecureValue( + u32 unique_id, u8 title_variation, u32 secure_value_slot) override; + + Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output, + size_t output_size) override; + + Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) override; + + ResultVal> GetThisSaveDataSecureValue( + u32 secure_value_slot) override; + +protected: + template + void serialize(Archive& ar, const unsigned int); + friend class boost::serialization::access; +}; +} // namespace FileSys + +BOOST_CLASS_EXPORT_KEY(FileSys::DefaultSecureValueBackend) \ No newline at end of file diff --git a/src/core/file_sys/secure_value_backend_artic.cpp b/src/core/file_sys/secure_value_backend_artic.cpp new file mode 100644 index 000000000..11f0449ab --- /dev/null +++ b/src/core/file_sys/secure_value_backend_artic.cpp @@ -0,0 +1,119 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/archives.h" +#include "core/file_sys/archive_artic.h" +#include "core/file_sys/secure_value_backend_artic.h" + +SERIALIZE_EXPORT_IMPL(FileSys::ArticSecureValueBackend) + +namespace FileSys { +Result ArticSecureValueBackend::ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, + u32 secure_value_slot, + u64 secure_value) { + auto req = client->NewRequest("FSUSER_ObsSetSaveDataSecureVal"); + + req.AddParameterU64(secure_value); + req.AddParameterU32(secure_value_slot); + req.AddParameterU32(unique_id); + req.AddParameterU8(title_variation); + + return ArticArchive::RespResult(client->Send(req)); +} + +ResultVal> ArticSecureValueBackend::ObsoletedGetSaveDataSecureValue( + u32 unique_id, u8 title_variation, u32 secure_value_slot) { + + auto req = client->NewRequest("FSUSER_ObsGetSaveDataSecureVal"); + + req.AddParameterU32(secure_value_slot); + req.AddParameterU32(unique_id); + req.AddParameterU8(title_variation); + + auto resp = client->Send(req); + auto res = ArticArchive::RespResult(resp); + if (res.IsError()) + return res; + + struct { + bool exists; + u64 secure_value; + } secure_value_result; + static_assert(sizeof(secure_value_result) == 0x10); + + auto output_buf = resp->GetResponseBuffer(0); + if (!output_buf.has_value()) + return res; + + if (output_buf->second != sizeof(secure_value_result)) + return ResultUnknown; + + memcpy(&secure_value_result, output_buf->first, output_buf->second); + return std::make_tuple(secure_value_result.exists, secure_value_result.secure_value); +} + +Result ArticSecureValueBackend::ControlSecureSave(u32 action, u8* input, size_t input_size, + u8* output, size_t output_size) { + auto req = client->NewRequest("FSUSER_ControlSecureSave"); + + req.AddParameterU32(action); + req.AddParameterBuffer(input, input_size); + req.AddParameterU32(static_cast(output_size)); + + auto resp = client->Send(req); + auto res = ArticArchive::RespResult(resp); + if (res.IsError()) + return res; + + auto output_buf = resp->GetResponseBuffer(0); + if (!output_buf.has_value()) + return res; + + if (output_buf->second != output_size) + return ResultUnknown; + + memcpy(output, output_buf->first, output_buf->second); + return res; +} + +Result ArticSecureValueBackend::SetThisSaveDataSecureValue(u32 secure_value_slot, + u64 secure_value) { + auto req = client->NewRequest("FSUSER_SetThisSaveDataSecVal"); + + req.AddParameterU32(secure_value_slot); + req.AddParameterU64(secure_value); + + return ArticArchive::RespResult(client->Send(req)); +} + +ResultVal> ArticSecureValueBackend::GetThisSaveDataSecureValue( + u32 secure_value_slot) { + auto req = client->NewRequest("FSUSER_GetThisSaveDataSecVal"); + + req.AddParameterU32(secure_value_slot); + + auto resp = client->Send(req); + auto res = ArticArchive::RespResult(resp); + if (res.IsError()) + return res; + + struct { + bool exists; + bool isGamecard; + u64 secure_value; + } secure_value_result; + static_assert(sizeof(secure_value_result) == 0x10); + + auto output_buf = resp->GetResponseBuffer(0); + if (!output_buf.has_value()) + return res; + + if (output_buf->second != sizeof(secure_value_result)) + return ResultUnknown; + + memcpy(&secure_value_result, output_buf->first, output_buf->second); + return std::make_tuple(secure_value_result.exists, secure_value_result.isGamecard, + secure_value_result.secure_value); +} +} // namespace FileSys \ No newline at end of file diff --git a/src/core/file_sys/secure_value_backend_artic.h b/src/core/file_sys/secure_value_backend_artic.h new file mode 100644 index 000000000..23d6fae1b --- /dev/null +++ b/src/core/file_sys/secure_value_backend_artic.h @@ -0,0 +1,53 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "tuple" + +#include "common/common_types.h" +#include "core/file_sys/secure_value_backend.h" +#include "core/hle/result.h" +#include "core/hle/service/fs/archive.h" +#include "network/artic_base/artic_base_client.h" + +namespace FileSys { +class ArticSecureValueBackend : public SecureValueBackend { +public: + ArticSecureValueBackend(const std::shared_ptr& _client) + : client(_client) {} + + Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, u32 secure_value_slot, + u64 secure_value) override; + + ResultVal> ObsoletedGetSaveDataSecureValue( + u32 unique_id, u8 title_variation, u32 secure_value_slot) override; + + Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output, + size_t output_size) override; + + Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) override; + + ResultVal> GetThisSaveDataSecureValue( + u32 secure_value_slot) override; + + bool BackendIsSlow() override { + return true; + } + +protected: + ArticSecureValueBackend() = default; + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + } + friend class boost::serialization::access; + +private: + std::shared_ptr client; +}; +} // namespace FileSys + +BOOST_CLASS_EXPORT_KEY(FileSys::ArticSecureValueBackend) \ No newline at end of file diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 9b7dbe73a..46c87514e 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -58,6 +58,11 @@ public: : RequestBuilder( context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {} + RequestBuilder(Kernel::HLERequestContext& context, unsigned normal_params_size, + unsigned translate_params_size) + : RequestBuilder(context, Header{MakeHeader(context.CommandID(), normal_params_size, + translate_params_size)}) {} + // Validate on destruction, as there shouldn't be any case where we don't want it ~RequestBuilder() { ValidateHeader(); diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 5b6ab88b5..64a6ed949 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -206,6 +206,11 @@ public: return {cmd_buf[0]}; } + /// Returns the Command ID from the IPC command buffer. + u16 CommandID() const { + return static_cast(CommandHeader().command_id.Value()); + } + /** * Returns the session through which this request was made. This can be used as a map key to * access per-client data on services. diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 1807fc185..cd3f49651 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -266,7 +266,7 @@ ResultVal CIAFile::WriteContentData(u64 offset, std::size_t length, } ResultVal CIAFile::Write(u64 offset, std::size_t length, bool flush, - const u8* buffer) { + bool update_timestamp, const u8* buffer) { written += length; // TODO(shinyquagsire23): Can we assume that things will only be written in sequence? @@ -347,7 +347,7 @@ bool CIAFile::SetSize(u64 size) const { return false; } -bool CIAFile::Close() const { +bool CIAFile::Close() { bool complete = install_state >= CIAInstallState::TMDLoaded && content_written.size() == container.GetTitleMetadata().GetContentCount() && @@ -419,7 +419,7 @@ ResultVal TicketFile::Read(u64 offset, std::size_t length, u8* buff } ResultVal TicketFile::Write(u64 offset, std::size_t length, bool flush, - const u8* buffer) { + bool update_timestamp, const u8* buffer) { written += length; data.resize(written); std::memcpy(data.data() + offset, buffer, length); @@ -434,7 +434,7 @@ bool TicketFile::SetSize(u64 size) const { return false; } -bool TicketFile::Close() const { +bool TicketFile::Close() { FileSys::Ticket ticket; if (ticket.Load(data, 0) == Loader::ResultStatus::Success) { LOG_WARNING(Service_AM, "Discarding ticket for {:#016X}.", ticket.GetTitleID()); @@ -480,7 +480,7 @@ InstallStatus InstallCIA(const std::string& path, while (total_bytes_read != file_size) { std::size_t bytes_read = file.ReadBytes(buffer.data(), buffer.size()); auto result = installFile.Write(static_cast(total_bytes_read), bytes_read, true, - static_cast(buffer.data())); + false, static_cast(buffer.data())); if (update_callback) { update_callback(total_bytes_read, file_size); @@ -590,7 +590,8 @@ InstallStatus InstallFromNus(u64 title_id, int version) { const u64 offset = Common::AlignUp(current_offset + data.size(), FileSys::CIA_SECTION_ALIGNMENT); data.resize(offset - current_offset, 0); - const auto result = install_file.Write(current_offset, data.size(), true, data.data()); + const auto result = + install_file.Write(current_offset, data.size(), true, false, data.data()); if (result.Failed()) { LOG_ERROR(Service_AM, "CIA file installation aborted with error code {:08x}", result.Code().raw); @@ -1464,9 +1465,9 @@ public: return file->backend->Read(offset + file_offset, length, buffer); } - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override { - return file->backend->Write(offset + file_offset, length, flush, buffer); + return file->backend->Write(offset + file_offset, length, flush, update_timestamp, buffer); } u64 GetSize() const override { @@ -1475,7 +1476,7 @@ public: bool SetSize(u64 size) const override { return false; } - bool Close() const override { + bool Close() override { return false; } void Flush() const override {} diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 26b9b1056..7ef96f985 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -111,11 +111,11 @@ public: Result WriteTicket(); Result WriteTitleMetadata(); ResultVal WriteContentData(u64 offset, std::size_t length, const u8* buffer); - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override; u64 GetSize() const override; bool SetSize(u64 size) const override; - bool Close() const override; + bool Close() override; void Flush() const override; private: @@ -146,11 +146,11 @@ public: ~TicketFile(); ResultVal Read(u64 offset, std::size_t length, u8* buffer) const override; - ResultVal Write(u64 offset, std::size_t length, bool flush, + ResultVal Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, const u8* buffer) override; u64 GetSize() const override; bool SetSize(u64 size) const override; - bool Close() const override; + bool Close() override; void Flush() const override; private: diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 3e552dfc4..58cac93e3 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -217,7 +217,7 @@ bool Module::LoadSharedFont() { const FileSys::Path file_path(std::vector(20, 0)); FileSys::Mode open_mode = {}; open_mode.read_flag.Assign(1); - auto file_result = archive.OpenFile(file_path, open_mode); + auto file_result = archive.OpenFile(file_path, open_mode, 0); if (file_result.Failed()) return false; diff --git a/src/core/hle/service/boss/online_service.cpp b/src/core/hle/service/boss/online_service.cpp index d289c910b..cd587aa51 100644 --- a/src/core/hle/service/boss/online_service.cpp +++ b/src/core/hle/service/boss/online_service.cpp @@ -75,7 +75,7 @@ Result OnlineService::InitializeSession(u64 init_program_id) { boss_system_save_data_archive = std::move(archive_result).Unwrap(); } else if (archive_result.Code() == FileSys::ResultNotFound) { // If the archive didn't exist, create the files inside - systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); + systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); // Open it again to get a valid archive now that the folder exists auto create_archive_result = systemsavedata_factory.Open(archive_path, 0); diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp index bbd4cc0a2..112e766af 100644 --- a/src/core/hle/service/cecd/cecd.cpp +++ b/src/core/hle/service/cecd/cecd.cpp @@ -116,7 +116,7 @@ void Module::Interface::Open(Kernel::HLERequestContext& ctx) { std::vector program_id(8); u64_le le_program_id = cecd->system.Kernel().GetCurrentProcess()->codeset->program_id; std::memcpy(program_id.data(), &le_program_id, sizeof(u64)); - session_data->file->Write(0, sizeof(u64), true, program_id.data()); + session_data->file->Write(0, sizeof(u64), true, false, program_id.data()); session_data->file->Close(); } } @@ -373,7 +373,7 @@ void Module::Interface::Write(Kernel::HLERequestContext& ctx) { } [[maybe_unused]] const u32 bytes_written = static_cast( - session_data->file->Write(0, buffer.size(), true, buffer.data()).Unwrap()); + session_data->file->Write(0, buffer.size(), true, false, buffer.data()).Unwrap()); session_data->file->Close(); rb.Push(ResultSuccess); @@ -435,7 +435,7 @@ void Module::Interface::WriteMessage(Kernel::HLERequestContext& ctx) { msg_header.forward_count, msg_header.user_data); [[maybe_unused]] const u32 bytes_written = - static_cast(message->Write(0, buffer_size, true, buffer.data()).Unwrap()); + static_cast(message->Write(0, buffer_size, true, false, buffer.data()).Unwrap()); message->Close(); rb.Push(ResultSuccess); @@ -522,7 +522,7 @@ void Module::Interface::WriteMessageWithHMAC(Kernel::HLERequestContext& ctx) { std::memcpy(buffer.data() + hmac_offset, hmac_digest.data(), hmac_size); [[maybe_unused]] const u32 bytes_written = - static_cast(message->Write(0, buffer_size, true, buffer.data()).Unwrap()); + static_cast(message->Write(0, buffer_size, true, false, buffer.data()).Unwrap()); message->Close(); rb.Push(ResultSuccess); @@ -607,7 +607,7 @@ void Module::Interface::SetData(Kernel::HLERequestContext& ctx) { cecd->CheckAndUpdateFile(CecDataPathType::OutboxIndex, ncch_program_id, buffer); - file->Write(0, buffer.size(), true, buffer.data()); + file->Write(0, buffer.size(), true, false, buffer.data()); file->Close(); } } @@ -764,8 +764,8 @@ void Module::Interface::OpenAndWrite(Kernel::HLERequestContext& ctx) { cecd->CheckAndUpdateFile(path_type, ncch_program_id, buffer); } - [[maybe_unused]] const u32 bytes_written = - static_cast(file->Write(0, buffer.size(), true, buffer.data()).Unwrap()); + [[maybe_unused]] const u32 bytes_written = static_cast( + file->Write(0, buffer.size(), true, false, buffer.data()).Unwrap()); file->Close(); rb.Push(ResultSuccess); @@ -1409,7 +1409,7 @@ Module::Module(Core::System& system) : system(system) { cecd_system_save_data_archive = std::move(archive_result).Unwrap(); } else { // Format the archive to create the directories - systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); + systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); // Open it again to get a valid archive now that the folder exists cecd_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); @@ -1442,7 +1442,7 @@ Module::Module(Core::System& system) : system(system) { eventlog_buffer[1] = 0x41; eventlog_buffer[2] = 0x12; - eventlog->Write(0, eventlog_size, true, eventlog_buffer.data()); + eventlog->Write(0, eventlog_size, true, false, eventlog_buffer.data()); eventlog->Close(); /// MBoxList____ resides within the root CEC/ directory. @@ -1464,7 +1464,7 @@ Module::Module(Core::System& system) : system(system) { // mboxlist_buffer[2-3] are already zeroed mboxlist_buffer[4] = 0x01; - mboxlist->Write(0, mboxlist_size, true, mboxlist_buffer.data()); + mboxlist->Write(0, mboxlist_size, true, false, mboxlist_buffer.data()); mboxlist->Close(); } } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index d118250ba..864369215 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -565,7 +565,7 @@ Result Module::UpdateConfigNANDSavegame() { ASSERT_MSG(config_result.Succeeded(), "could not open file"); auto config = std::move(config_result).Unwrap(); - config->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); + config->Write(0, CONFIG_SAVEFILE_SIZE, true, false, cfg_config_file_buffer.data()); return ResultSuccess; } @@ -625,7 +625,7 @@ Result Module::LoadConfigNANDSaveFile() { // If the archive didn't exist, create the files inside if (archive_result.Code() == FileSys::ResultNotFound) { // Format the archive to create the directories - systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); + systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); // Open it again to get a valid archive now that the folder exists cfg_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 44e303129..f70f03c7e 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -67,10 +67,24 @@ ResultVal ArchiveManager::OpenArchive(ArchiveIdCode id_code, } Result ArchiveManager::CloseArchive(ArchiveHandle handle) { - if (handle_map.erase(handle) == 0) + auto itr = handle_map.find(handle); + if (itr != handle_map.end()) { + itr->second->Close(); + } else { return FileSys::ResultInvalidArchiveHandle; - else - return ResultSuccess; + } + handle_map.erase(itr); + return ResultSuccess; +} + +Result ArchiveManager::ControlArchive(ArchiveHandle handle, u32 action, u8* input, + size_t input_size, u8* output, size_t output_size) { + auto itr = handle_map.find(handle); + if (itr != handle_map.end()) { + return itr->second->Control(action, input, input_size, output, output_size); + } else { + return FileSys::ResultInvalidArchiveHandle; + } } // TODO(yuriks): This might be what the fs:REG service is for. See the Register/Unregister calls in @@ -90,14 +104,14 @@ Result ArchiveManager::RegisterArchiveType(std::unique_ptr>, std::chrono::nanoseconds> ArchiveManager::OpenFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path, - const FileSys::Mode mode) { + const FileSys::Mode mode, u32 attributes) { ArchiveBackend* archive = GetArchive(archive_handle); if (archive == nullptr) { return std::make_pair(FileSys::ResultInvalidArchiveHandle, std::chrono::nanoseconds{0}); } const std::chrono::nanoseconds open_timeout_ns{archive->GetOpenDelayNs()}; - auto backend = archive->OpenFile(path, mode); + auto backend = archive->OpenFile(path, mode, attributes); if (backend.Failed()) { return std::make_pair(backend.Code(), open_timeout_ns); } @@ -151,21 +165,21 @@ Result ArchiveManager::DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archi } Result ArchiveManager::CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, - u64 file_size) { + u64 file_size, u32 attributes) { ArchiveBackend* archive = GetArchive(archive_handle); if (archive == nullptr) return FileSys::ResultInvalidArchiveHandle; - return archive->CreateFile(path, file_size); + return archive->CreateFile(path, file_size, attributes); } Result ArchiveManager::CreateDirectoryFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path) { + const FileSys::Path& path, u32 attributes) { ArchiveBackend* archive = GetArchive(archive_handle); if (archive == nullptr) return FileSys::ResultInvalidArchiveHandle; - return archive->CreateDirectory(path); + return archive->CreateDirectory(path, attributes); } Result ArchiveManager::RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, @@ -210,13 +224,15 @@ ResultVal ArchiveManager::GetFreeBytesInArchive(ArchiveHandle archive_handl Result ArchiveManager::FormatArchive(ArchiveIdCode id_code, const FileSys::ArchiveFormatInfo& format_info, - const FileSys::Path& path, u64 program_id) { + const FileSys::Path& path, u64 program_id, + u32 directory_buckets, u32 file_buckets) { auto archive_itr = id_code_map.find(id_code); if (archive_itr == id_code_map.end()) { return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error } - return archive_itr->second->Format(path, format_info, program_id); + return archive_itr->second->Format(path, format_info, program_id, directory_buckets, + file_buckets); } ResultVal ArchiveManager::GetArchiveFormatInfo( @@ -229,10 +245,10 @@ ResultVal ArchiveManager::GetArchiveFormatInfo( return archive->second->GetFormatInfo(archive_path, program_id); } -Result ArchiveManager::CreateExtSaveData(MediaType media_type, u32 high, u32 low, +Result ArchiveManager::CreateExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low, std::span smdh_icon, const FileSys::ArchiveFormatInfo& format_info, - u64 program_id) { + u64 program_id, u64 total_size) { // Construct the binary path to the archive first FileSys::Path path = FileSys::ConstructExtDataBinaryPath(static_cast(media_type), high, low); @@ -246,37 +262,26 @@ Result ArchiveManager::CreateExtSaveData(MediaType media_type, u32 high, u32 low auto ext_savedata = static_cast(archive->second.get()); - Result result = ext_savedata->Format(path, format_info, program_id); + Result result = ext_savedata->FormatAsExtData(path, format_info, unknown, program_id, + total_size, smdh_icon); if (result.IsError()) { return result; } - ext_savedata->WriteIcon(path, smdh_icon); return ResultSuccess; } -Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u32 high, u32 low) { - // Construct the binary path to the archive first - FileSys::Path path = - FileSys::ConstructExtDataBinaryPath(static_cast(media_type), high, low); +Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low) { + auto archive = id_code_map.find(media_type == MediaType::NAND ? ArchiveIdCode::SharedExtSaveData + : ArchiveIdCode::ExtSaveData); - std::string media_type_directory; - if (media_type == MediaType::NAND) { - media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); - } else if (media_type == MediaType::SDMC) { - media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); - } else { - LOG_ERROR(Service_FS, "Unsupported media type {}", media_type); - return ResultUnknown; // TODO(Subv): Find the right error code + if (archive == id_code_map.end()) { + return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error } - // Delete all directories (/user, /boss) and the icon file. - std::string base_path = - FileSys::GetExtDataContainerPath(media_type_directory, media_type == MediaType::NAND); - std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path); - if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path)) - return ResultUnknown; // TODO(Subv): Find the right error code - return ResultSuccess; + auto ext_savedata = static_cast(archive->second.get()); + + return ext_savedata->DeleteExtData(media_type, unknown, high, low); } Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) { @@ -317,6 +322,24 @@ ResultVal ArchiveManager::GetArchiveResource(MediaType media_ty return resource; } +Result ArchiveManager::SetSaveDataSecureValue(ArchiveHandle archive_handle, u32 secure_value_slot, + u64 secure_value, bool flush) { + ArchiveBackend* archive = GetArchive(archive_handle); + if (archive == nullptr) { + return FileSys::ResultInvalidArchiveHandle; + } + return archive->SetSaveDataSecureValue(secure_value_slot, secure_value, flush); +} + +ResultVal> ArchiveManager::GetSaveDataSecureValue( + ArchiveHandle archive_handle, u32 secure_value_slot) { + ArchiveBackend* archive = GetArchive(archive_handle); + if (archive == nullptr) { + return FileSys::ResultInvalidArchiveHandle; + } + return archive->GetSaveDataSecureValue(secure_value_slot); +} + void ArchiveManager::RegisterArchiveTypes() { // TODO(Subv): Add the other archive types (see here for the known types: // http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes). @@ -337,7 +360,7 @@ void ArchiveManager::RegisterArchiveTypes() { sdmc_directory); // Create the SaveData archive - auto sd_savedata_source = std::make_shared(sdmc_directory); + sd_savedata_source = std::make_shared(sdmc_directory); auto savedata_factory = std::make_unique(sd_savedata_source); RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); auto other_savedata_permitted_factory = @@ -373,6 +396,23 @@ void ArchiveManager::RegisterArchiveTypes() { RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH); } +bool ArchiveManager::ArchiveIsSlow(ArchiveIdCode archive_id) { + auto itr = id_code_map.find(archive_id); + if (itr == id_code_map.end() || itr->second.get() == nullptr) { + return false; + } + + return itr->second->IsSlow(); +} + +bool ArchiveManager::ArchiveIsSlow(ArchiveHandle archive_handle) { + ArchiveBackend* archive = GetArchive(archive_handle); + if (archive == nullptr) { + return false; + } + return archive->IsSlow(); +} + void ArchiveManager::RegisterSelfNCCH(Loader::AppLoader& app_loader) { auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH); if (itr == id_code_map.end()) { @@ -385,6 +425,35 @@ void ArchiveManager::RegisterSelfNCCH(Loader::AppLoader& app_loader) { factory->Register(app_loader); } +void ArchiveManager::RegisterArticSaveDataSource( + std::shared_ptr& client) { + if (!sd_savedata_source.get()) { + LOG_ERROR(Service_FS, "Could not register artic save data source."); + return; + } + sd_savedata_source->RegisterArtic(client); +} + +void ArchiveManager::RegisterArticExtData(std::shared_ptr& client) { + for (auto it : {ArchiveIdCode::ExtSaveData, ArchiveIdCode::SharedExtSaveData, + ArchiveIdCode::BossExtSaveData}) { + auto itr = id_code_map.find(it); + if (itr == id_code_map.end() || itr->second.get() == nullptr) { + continue; + } + reinterpret_cast(itr->second.get()) + ->RegisterArtic(client); + } +} + +void ArchiveManager::RegisterArticNCCH(std::shared_ptr& client) { + auto itr = id_code_map.find(ArchiveIdCode::NCCH); + if (itr == id_code_map.end() || itr->second.get() == nullptr) { + return; + } + reinterpret_cast(itr->second.get())->RegisterArtic(client); +} + ArchiveManager::ArchiveManager(Core::System& system) : system(system) { RegisterArchiveTypes(); } diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 245e929f9..a20713066 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -12,9 +12,11 @@ #include #include "common/common_types.h" #include "core/file_sys/archive_backend.h" +#include "core/file_sys/archive_source_sd_savedata.h" #include "core/hle/result.h" #include "core/hle/service/fs/directory.h" #include "core/hle/service/fs/file.h" +#include "network/artic_base/artic_base_client.h" /// The unique system identifier hash, also known as ID0 static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"}; @@ -67,6 +69,19 @@ struct ArchiveResource { }; static_assert(sizeof(ArchiveResource) == 0x10, "ArchiveResource has incorrect size"); +struct ExtSaveDataInfo { + u8 media_type; + u8 unknown; + u16 reserved1; + u32 save_id_low; + u32 save_id_high; + u32 reserved2; +}; +static_assert(sizeof(ExtSaveDataInfo) == 0x10, "ExtSaveDataInfo struct has incorrect size"); +static_assert(std::is_trivial(), "ExtSaveDataInfo should be trivial"); +static_assert(std::is_trivially_copyable(), + "ExtSaveDataInfo should be trivially copyable"); + using FileSys::ArchiveBackend; using FileSys::ArchiveFactory; @@ -90,6 +105,9 @@ public: */ Result CloseArchive(ArchiveHandle handle); + Result ControlArchive(ArchiveHandle handle, u32 action, u8* input, size_t input_size, + u8* output, size_t output_size); + /** * Open a File from an Archive * @param archive_handle Handle to an open Archive object @@ -98,7 +116,8 @@ public: * @return Pair containing the opened File object and the open delay */ std::pair>, std::chrono::nanoseconds> OpenFileFromArchive( - ArchiveHandle archive_handle, const FileSys::Path& path, FileSys::Mode mode); + ArchiveHandle archive_handle, const FileSys::Path& path, FileSys::Mode mode, + u32 attributes); /** * Delete a File from an Archive @@ -146,7 +165,7 @@ public: * @return File creation result code */ Result CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, - u64 file_size); + u64 file_size, u32 attributes); /** * Create a Directory from an Archive @@ -154,7 +173,8 @@ public: * @param path Path to the Directory inside of the Archive * @return Whether creation of directory succeeded */ - Result CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path); + Result CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path, + u32 attributes); /** * Rename a Directory between two Archives @@ -195,7 +215,8 @@ public: * @return Result 0 on success or the corresponding code on error */ Result FormatArchive(ArchiveIdCode id_code, const FileSys::ArchiveFormatInfo& format_info, - const FileSys::Path& path, u64 program_id); + const FileSys::Path& path, u64 program_id, u32 directory_buckets, + u32 file_buckets); /** * Retrieves the format info about the archive of the specified type and path. @@ -219,8 +240,10 @@ public: * @param program_id the program ID of the client that requests the operation * @return Result 0 on success or the corresponding code on error */ - Result CreateExtSaveData(MediaType media_type, u32 high, u32 low, std::span smdh_icon, - const FileSys::ArchiveFormatInfo& format_info, u64 program_id); + Result CreateExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low, + std::span smdh_icon, + const FileSys::ArchiveFormatInfo& format_info, u64 program_id, + u64 total_size); /** * Deletes the SharedExtSaveData archive for the specified extdata ID @@ -229,7 +252,7 @@ public: * @param low The low word of the extdata id to delete * @return Result 0 on success or the corresponding code on error */ - Result DeleteExtSaveData(MediaType media_type, u32 high, u32 low); + Result DeleteExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low); /** * Deletes the SystemSaveData archive folder for the specified save data id @@ -254,9 +277,25 @@ public: */ ResultVal GetArchiveResource(MediaType media_type) const; + Result SetSaveDataSecureValue(ArchiveHandle archive_handle, u32 secure_value_slot, + u64 secure_value, bool flush); + + ResultVal> GetSaveDataSecureValue(ArchiveHandle archive_handle, + u32 secure_value_slot); + + bool ArchiveIsSlow(ArchiveIdCode archive_id); + + bool ArchiveIsSlow(ArchiveHandle archive_handle); + /// Registers a new NCCH file with the SelfNCCH archive factory void RegisterSelfNCCH(Loader::AppLoader& app_loader); + void RegisterArticSaveDataSource(std::shared_ptr& client); + + void RegisterArticExtData(std::shared_ptr& client); + + void RegisterArticNCCH(std::shared_ptr& client); + private: Core::System& system; @@ -285,11 +324,17 @@ private: std::unordered_map> handle_map; ArchiveHandle next_handle = 1; + /** + * Savedata source + */ + std::shared_ptr sd_savedata_source; + template void serialize(Archive& ar, const unsigned int) { ar& id_code_map; ar& handle_map; ar& next_handle; + ar& sd_savedata_source; } friend class boost::serialization::access; }; diff --git a/src/core/hle/service/fs/file.cpp b/src/core/hle/service/fs/file.cpp index f9134a848..347aac3ae 100644 --- a/src/core/hle/service/fs/file.cpp +++ b/src/core/hle/service/fs/file.cpp @@ -169,10 +169,9 @@ void File::Write(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u64 offset = rp.Pop(); u32 length = rp.Pop(); - u32 flush = rp.Pop(); - auto& buffer = rp.PopMappedBuffer(); - LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flush=0x{:x}", GetName(), offset, - length, flush); + u32 flags = rp.Pop(); + LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flags=0x{:x}", GetName(), offset, + length, flags); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); @@ -182,25 +181,75 @@ void File::Write(Kernel::HLERequestContext& ctx) { if (file->subfile) { rb.Push(FileSys::ResultUnsupportedOpenFlags); rb.Push(0); + rb.PushMappedBuffer(rp.PopMappedBuffer()); + return; + } + bool flush = (flags & 0xFF) != 0, update_timestamp = (flags & 0xFF00) != 0; + + if (!backend->AllowsCachedReads()) { + std::vector data(length); + auto& buffer = rp.PopMappedBuffer(); + buffer.Read(data.data(), 0, data.size()); + ResultVal written = + backend->Write(offset, data.size(), flush, update_timestamp, data.data()); + + // Update file size + file->size = backend->GetSize(); + + if (written.Failed()) { + rb.Push(written.Code()); + rb.Push(0); + } else { + rb.Push(ResultSuccess); + rb.Push(static_cast(*written)); + } rb.PushMappedBuffer(buffer); return; } - std::vector data(length); - buffer.Read(data.data(), 0, data.size()); - ResultVal written = backend->Write(offset, data.size(), flush != 0, data.data()); + struct AsyncData { + // Input + u32 length; + u64 offset; + bool flush; + bool update_timestamp; + Kernel::MappedBuffer* buffer; + FileSessionSlot* file; - // Update file size - file->size = backend->GetSize(); + // Output + ResultVal written; + }; + auto async_data = std::make_shared(); + async_data->length = length; + async_data->offset = offset; + async_data->flush = flush; + async_data->update_timestamp = update_timestamp; + async_data->buffer = &rp.PopMappedBuffer(); + async_data->file = file; - if (written.Failed()) { - rb.Push(written.Code()); - rb.Push(0); - } else { - rb.Push(ResultSuccess); - rb.Push(static_cast(*written)); - } - rb.PushMappedBuffer(buffer); + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + std::vector data(async_data->length); + async_data->buffer->Read(data.data(), 0, data.size()); + async_data->written = backend->Write(async_data->offset, data.size(), async_data->flush, + async_data->update_timestamp, data.data()); + + // Update file size + async_data->file->size = backend->GetSize(); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 2, 2); + if (async_data->written.Failed()) { + rb.Push(async_data->written.Code()); + rb.Push(0); + } else { + rb.Push(ResultSuccess); + rb.Push(static_cast(*async_data->written)); + } + rb.PushMappedBuffer(*async_data->buffer); + }, + true); } void File::GetSize(Kernel::HLERequestContext& ctx) { @@ -219,17 +268,32 @@ void File::SetSize(Kernel::HLERequestContext& ctx) { FileSessionSlot* file = GetSessionData(ctx.Session()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - // SetSize can not be called on subfiles. if (file->subfile) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(FileSys::ResultUnsupportedOpenFlags); return; } - file->size = size; - backend->SetSize(size); - rb.Push(ResultSuccess); + if (!backend->AllowsCachedReads()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + file->size = size; + backend->SetSize(size); + rb.Push(ResultSuccess); + return; + } + + ctx.RunAsync( + [file, size, this](Kernel::HLERequestContext& ctx) { + file->size = size; + backend->SetSize(size); + return 0; + }, + [](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(ResultSuccess); + }, + true); } void File::Close(Kernel::HLERequestContext& ctx) { @@ -240,26 +304,53 @@ void File::Close(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected", connected_sessions.size()); - backend->Close(); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); + if (!backend->AllowsCachedReads()) { + backend->Close(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); + return; + } + + ctx.RunAsync( + [this](Kernel::HLERequestContext& ctx) { + backend->Close(); + return 0; + }, + [](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(ResultSuccess); + }, + true); } void File::Flush(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - const FileSessionSlot* file = GetSessionData(ctx.Session()); // Subfiles can not be flushed. if (file->subfile) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(FileSys::ResultUnsupportedOpenFlags); return; } - backend->Flush(); - rb.Push(ResultSuccess); + if (!backend->AllowsCachedReads()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + backend->Flush(); + rb.Push(ResultSuccess); + } + + ctx.RunAsync( + [this](Kernel::HLERequestContext& ctx) { + backend->Flush(); + return 0; + }, + [](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(ResultSuccess); + }, + true); } void File::SetPriority(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index f893c8df3..e5b4cb240 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -54,26 +54,72 @@ void FS_USER::OpenFile(Kernel::HLERequestContext& ctx) { const auto filename_type = rp.PopEnum(); const auto filename_size = rp.Pop(); const FileSys::Mode mode{rp.Pop()}; - const auto attributes = rp.Pop(); // TODO(Link Mauve): do something with those attributes. + const auto attributes = rp.Pop(); std::vector filename = rp.PopStaticBuffer(); ASSERT(filename.size() == filename_size); const FileSys::Path file_path(filename_type, std::move(filename)); LOG_DEBUG(Service_FS, "path={}, mode={} attrs={}", file_path.DebugStr(), mode.hex, attributes); - const auto [file_res, open_timeout_ns] = - archives.OpenFileFromArchive(archive_handle, file_path, mode); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(file_res.Code()); - if (file_res.Succeeded()) { - std::shared_ptr file = *file_res; - rb.PushMoveObjects(file->Connect()); - } else { - rb.PushMoveObjects(nullptr); - LOG_DEBUG(Service_FS, "failed to get a handle for file {}", file_path.DebugStr()); + if (!archives.ArchiveIsSlow(archive_handle)) { + const auto [file_res, open_timeout_ns] = + archives.OpenFileFromArchive(archive_handle, file_path, mode, attributes); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(file_res.Code()); + if (file_res.Succeeded()) { + std::shared_ptr file = *file_res; + rb.PushMoveObjects(file->Connect()); + } else { + rb.PushMoveObjects(nullptr); + LOG_DEBUG(Service_FS, "failed to get a handle for file {}", file_path.DebugStr()); + } + + ctx.SleepClientThread("fs_user::open", open_timeout_ns, nullptr); + return; } - ctx.SleepClientThread("fs_user::open", open_timeout_ns, nullptr); + struct AsyncData { + ArchiveHandle archive_handle; + FileSys::Path file_path; + FileSys::Mode mode; + u32 attributes; + std::chrono::steady_clock::time_point pre_timer; + + std::pair>, std::chrono::nanoseconds> file; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->file_path = file_path; + async_data->mode = mode; + async_data->attributes = attributes; + async_data->pre_timer = std::chrono::steady_clock::now(); + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->file = + archives.OpenFileFromArchive(async_data->archive_handle, async_data->file_path, + async_data->mode, async_data->attributes); + const auto time_took = std::chrono::duration_cast( + std::chrono::steady_clock::now() - async_data->pre_timer); + return static_cast(((async_data->file.second > time_took) + ? (async_data->file.second - time_took) + : std::chrono::nanoseconds()) + .count()); + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 2); + + rb.Push(async_data->file.first.Code()); + if (async_data->file.first.Succeeded()) { + std::shared_ptr file = *async_data->file.first; + rb.PushMoveObjects(file->Connect()); + } else { + rb.PushMoveObjects(nullptr); + LOG_DEBUG(Service_FS, "failed to get a handle for file {}", + async_data->file_path.DebugStr()); + } + }, + true); } void FS_USER::OpenFileDirectly(Kernel::HLERequestContext& ctx) { @@ -97,35 +143,100 @@ void FS_USER::OpenFileDirectly(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "archive_id=0x{:08X} archive_path={} file_path={}, mode={} attributes={}", archive_id, archive_path.DebugStr(), file_path.DebugStr(), mode.hex, attributes); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + u64 program_id = GetSessionData(ctx.Session())->program_id; - ClientSlot* slot = GetSessionData(ctx.Session()); + if (!archives.ArchiveIsSlow(archive_id)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - ResultVal archive_handle = - archives.OpenArchive(archive_id, archive_path, slot->program_id); - if (archive_handle.Failed()) { - LOG_ERROR(Service_FS, - "Failed to get a handle for archive archive_id=0x{:08X} archive_path={}", - archive_id, archive_path.DebugStr()); - rb.Push(archive_handle.Code()); - rb.PushMoveObjects(nullptr); + ResultVal archive_handle = + archives.OpenArchive(archive_id, archive_path, program_id); + if (archive_handle.Failed()) { + LOG_ERROR(Service_FS, + "Failed to get a handle for archive archive_id=0x{:08X} archive_path={}", + archive_id, archive_path.DebugStr()); + rb.Push(archive_handle.Code()); + rb.PushMoveObjects(nullptr); + return; + } + SCOPE_EXIT({ archives.CloseArchive(*archive_handle); }); + + const auto [file_res, open_timeout_ns] = + archives.OpenFileFromArchive(*archive_handle, file_path, mode, attributes); + rb.Push(file_res.Code()); + if (file_res.Succeeded()) { + std::shared_ptr file = *file_res; + rb.PushMoveObjects(file->Connect()); + } else { + rb.PushMoveObjects(nullptr); + LOG_DEBUG(Service_FS, "failed to get a handle for file {} mode={} attributes={}", + file_path.DebugStr(), mode.hex, attributes); + } + + ctx.SleepClientThread("fs_user::open_directly", open_timeout_ns, nullptr); return; } - SCOPE_EXIT({ archives.CloseArchive(*archive_handle); }); - const auto [file_res, open_timeout_ns] = - archives.OpenFileFromArchive(*archive_handle, file_path, mode); - rb.Push(file_res.Code()); - if (file_res.Succeeded()) { - std::shared_ptr file = *file_res; - rb.PushMoveObjects(file->Connect()); - } else { - rb.PushMoveObjects(nullptr); - LOG_DEBUG(Service_FS, "failed to get a handle for file {} mode={} attributes={}", - file_path.DebugStr(), mode.hex, attributes); - } + struct AsyncData { + ArchiveIdCode archive_id; + FileSys::Path archive_path; + FileSys::Path file_path; + u64 program_id; + FileSys::Mode mode; + u32 attributes; + std::chrono::steady_clock::time_point pre_timer; - ctx.SleepClientThread("fs_user::open_directly", open_timeout_ns, nullptr); + ResultVal archive_handle; + std::pair>, std::chrono::nanoseconds> file; + }; + auto async_data = std::make_shared(); + async_data->archive_id = archive_id; + async_data->archive_path = archive_path; + async_data->file_path = file_path; + async_data->program_id = program_id; + async_data->mode = mode; + async_data->attributes = attributes; + async_data->pre_timer = std::chrono::steady_clock::now(); + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->archive_handle = archives.OpenArchive( + async_data->archive_id, async_data->archive_path, async_data->program_id); + if (async_data->archive_handle.Failed()) { + LOG_ERROR(Service_FS, + "Failed to get a handle for archive archive_id=0x{:08X} archive_path={}", + async_data->archive_id, async_data->archive_path.DebugStr()); + return s64(); + } + async_data->file = + archives.OpenFileFromArchive(*async_data->archive_handle, async_data->file_path, + async_data->mode, async_data->attributes); + archives.CloseArchive(*async_data->archive_handle); + const auto time_took = std::chrono::duration_cast( + std::chrono::steady_clock::now() - async_data->pre_timer); + return static_cast(((async_data->file.second > time_took) + ? (async_data->file.second - time_took) + : std::chrono::nanoseconds()) + .count()); + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 2); + + if (async_data->archive_handle.Failed()) { + rb.Push(async_data->archive_handle.Code()); + rb.PushMoveObjects(nullptr); + } + + rb.Push(async_data->file.first.Code()); + if (async_data->file.first.Succeeded()) { + std::shared_ptr file = *async_data->file.first; + rb.PushMoveObjects(file->Connect()); + } else { + rb.PushMoveObjects(nullptr); + LOG_DEBUG(Service_FS, "failed to get a handle for file {}", + async_data->file_path.DebugStr()); + } + }, + true); } void FS_USER::DeleteFile(Kernel::HLERequestContext& ctx) { @@ -142,8 +253,33 @@ void FS_USER::DeleteFile(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "type={} size={} data={}", filename_type, filename_size, file_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.DeleteFileFromArchive(archive_handle, file_path)); + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.DeleteFileFromArchive(archive_handle, file_path)); + return; + } + + struct AsyncData { + ArchiveHandle archive_handle; + FileSys::Path file_path; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->file_path = file_path; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = + archives.DeleteFileFromArchive(async_data->archive_handle, async_data->file_path); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::RenameFile(Kernel::HLERequestContext& ctx) { @@ -169,9 +305,40 @@ void FS_USER::RenameFile(Kernel::HLERequestContext& ctx) { src_filename_type, src_filename_size, src_file_path.DebugStr(), dest_filename_type, dest_filename_size, dest_file_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.RenameFileBetweenArchives(src_archive_handle, src_file_path, - dest_archive_handle, dest_file_path)); + if (!archives.ArchiveIsSlow(src_archive_handle) && + !archives.ArchiveIsSlow(dest_archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.RenameFileBetweenArchives(src_archive_handle, src_file_path, + dest_archive_handle, dest_file_path)); + return; + } + + struct AsyncData { + ArchiveHandle src_archive_handle; + FileSys::Path src_file_path; + ArchiveHandle dest_archive_handle; + FileSys::Path dest_file_path; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->src_archive_handle = src_archive_handle; + async_data->src_file_path = src_file_path; + async_data->dest_archive_handle = dest_archive_handle; + async_data->dest_file_path = dest_file_path; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.RenameFileBetweenArchives( + async_data->src_archive_handle, async_data->src_file_path, + async_data->dest_archive_handle, async_data->dest_file_path); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::DeleteDirectory(Kernel::HLERequestContext& ctx) { @@ -189,8 +356,33 @@ void FS_USER::DeleteDirectory(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size, dir_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.DeleteDirectoryFromArchive(archive_handle, dir_path)); + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.DeleteDirectoryFromArchive(archive_handle, dir_path)); + return; + } + + struct AsyncData { + ArchiveHandle archive_handle; + FileSys::Path dir_path; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->dir_path = dir_path; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.DeleteDirectoryFromArchive(async_data->archive_handle, + async_data->dir_path); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) { @@ -208,8 +400,33 @@ void FS_USER::DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size, dir_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.DeleteDirectoryRecursivelyFromArchive(archive_handle, dir_path)); + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.DeleteDirectoryRecursivelyFromArchive(archive_handle, dir_path)); + return; + } + + struct AsyncData { + ArchiveHandle archive_handle; + FileSys::Path dir_path; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->dir_path = dir_path; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.DeleteDirectoryRecursivelyFromArchive( + async_data->archive_handle, async_data->dir_path); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::CreateFile(Kernel::HLERequestContext& ctx) { @@ -229,8 +446,38 @@ void FS_USER::CreateFile(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "type={} attributes={} size={:x} data={}", filename_type, attributes, file_size, file_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.CreateFileInArchive(archive_handle, file_path, file_size)); + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.CreateFileInArchive(archive_handle, file_path, file_size, attributes)); + return; + } + + struct AsyncData { + ArchiveHandle archive_handle; + FileSys::Path file_path; + u64 file_size; + u32 attributes; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->file_path = file_path; + async_data->file_size = file_size; + async_data->attributes = attributes; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = + archives.CreateFileInArchive(async_data->archive_handle, async_data->file_path, + async_data->file_size, async_data->attributes); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::CreateDirectory(Kernel::HLERequestContext& ctx) { @@ -239,7 +486,7 @@ void FS_USER::CreateDirectory(Kernel::HLERequestContext& ctx) { const auto archive_handle = rp.PopRaw(); const auto dirname_type = rp.PopEnum(); const auto dirname_size = rp.Pop(); - [[maybe_unused]] const auto attributes = rp.Pop(); + const auto attributes = rp.Pop(); std::vector dirname = rp.PopStaticBuffer(); ASSERT(dirname.size() == dirname_size); const FileSys::Path dir_path(dirname_type, std::move(dirname)); @@ -247,8 +494,35 @@ void FS_USER::CreateDirectory(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size, dir_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.CreateDirectoryFromArchive(archive_handle, dir_path)); + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.CreateDirectoryFromArchive(archive_handle, dir_path, attributes)); + return; + } + + struct AsyncData { + ArchiveHandle archive_handle; + FileSys::Path dir_path; + u32 attributes; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->dir_path = dir_path; + async_data->attributes = attributes; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.CreateDirectoryFromArchive( + async_data->archive_handle, async_data->dir_path, async_data->attributes); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::RenameDirectory(Kernel::HLERequestContext& ctx) { @@ -273,9 +547,40 @@ void FS_USER::RenameDirectory(Kernel::HLERequestContext& ctx) { src_dirname_type, src_dirname_size, src_dir_path.DebugStr(), dest_dirname_type, dest_dirname_size, dest_dir_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.RenameDirectoryBetweenArchives(src_archive_handle, src_dir_path, - dest_archive_handle, dest_dir_path)); + if (!archives.ArchiveIsSlow(src_archive_handle) && + !archives.ArchiveIsSlow(dest_archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.RenameDirectoryBetweenArchives(src_archive_handle, src_dir_path, + dest_archive_handle, dest_dir_path)); + return; + } + + struct AsyncData { + ArchiveHandle src_archive_handle; + FileSys::Path src_dir_path; + ArchiveHandle dest_archive_handle; + FileSys::Path dest_dir_path; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->src_archive_handle = src_archive_handle; + async_data->src_dir_path = src_dir_path; + async_data->dest_archive_handle = dest_archive_handle; + async_data->dest_dir_path = dest_dir_path; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.RenameDirectoryBetweenArchives( + async_data->src_archive_handle, async_data->src_dir_path, + async_data->dest_archive_handle, async_data->dest_dir_path); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::OpenDirectory(Kernel::HLERequestContext& ctx) { @@ -291,20 +596,56 @@ void FS_USER::OpenDirectory(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "type={} size={} data={}", dirname_type, dirname_size, dir_path.DebugStr()); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - ResultVal> dir_res = - archives.OpenDirectoryFromArchive(archive_handle, dir_path); - rb.Push(dir_res.Code()); - if (dir_res.Succeeded()) { - std::shared_ptr directory = *dir_res; - auto [server, client] = system.Kernel().CreateSessionPair(directory->GetName()); - directory->ClientConnected(server); - rb.PushMoveObjects(client); - } else { - LOG_DEBUG(Service_FS, "failed to get a handle for directory type={} size={} data={}", - dirname_type, dirname_size, dir_path.DebugStr()); - rb.PushMoveObjects(nullptr); + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + ResultVal> dir_res = + archives.OpenDirectoryFromArchive(archive_handle, dir_path); + rb.Push(dir_res.Code()); + if (dir_res.Succeeded()) { + std::shared_ptr directory = *dir_res; + auto [server, client] = system.Kernel().CreateSessionPair(directory->GetName()); + directory->ClientConnected(server); + rb.PushMoveObjects(client); + } else { + LOG_DEBUG(Service_FS, "failed to get a handle for directory type={} size={} data={}", + dirname_type, dirname_size, dir_path.DebugStr()); + rb.PushMoveObjects(nullptr); + } + return; } + + struct AsyncData { + ArchiveHandle archive_handle; + FileSys::Path dir_path; + + ResultVal> dir_res; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->dir_path = dir_path; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->dir_res = + archives.OpenDirectoryFromArchive(async_data->archive_handle, async_data->dir_path); + return 0; + }, + [this, async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 2); + + rb.Push(async_data->dir_res.Code()); + if (async_data->dir_res.Succeeded()) { + std::shared_ptr directory = *async_data->dir_res; + auto [server, client] = system.Kernel().CreateSessionPair(directory->GetName()); + directory->ClientConnected(server); + rb.PushMoveObjects(client); + } else { + LOG_DEBUG(Service_FS, "failed to get a handle for directory path={}", + async_data->dir_path.DebugStr()); + rb.PushMoveObjects(nullptr); + } + }, + true); } void FS_USER::OpenArchive(Kernel::HLERequestContext& ctx) { @@ -318,20 +659,59 @@ void FS_USER::OpenArchive(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "archive_id=0x{:08X} archive_path={}", archive_id, archive_path.DebugStr()); - - IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); ClientSlot* slot = GetSessionData(ctx.Session()); - const ResultVal handle = - archives.OpenArchive(archive_id, archive_path, slot->program_id); - rb.Push(handle.Code()); - if (handle.Succeeded()) { - rb.PushRaw(*handle); - } else { - rb.Push(0); - LOG_ERROR(Service_FS, - "failed to get a handle for archive archive_id=0x{:08X} archive_path={}", - archive_id, archive_path.DebugStr()); + u64 program_id = slot->program_id; + + // Conventional opening + if (!archives.ArchiveIsSlow(archive_id)) { + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + const ResultVal handle = + archives.OpenArchive(archive_id, archive_path, program_id); + rb.Push(handle.Code()); + if (handle.Succeeded()) { + rb.PushRaw(*handle); + } else { + rb.Push(0); + LOG_ERROR(Service_FS, + "failed to get a handle for archive archive_id=0x{:08X} archive_path={}", + archive_id, archive_path.DebugStr()); + } + return; } + + struct AsyncData { + // Input + ArchiveIdCode archive_id; + FileSys::Path archive_path; + u64 program_id; + + // Output + ResultVal handle; + }; + auto async_data = std::make_shared(); + async_data->archive_id = archive_id; + async_data->archive_path = archive_path; + async_data->program_id = program_id; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->handle = archives.OpenArchive( + async_data->archive_id, async_data->archive_path, async_data->program_id); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 3, 0); + rb.Push(async_data->handle.Code()); + if (async_data->handle.Succeeded()) { + rb.PushRaw(*async_data->handle); + } else { + rb.Push(0); + LOG_ERROR(Service_FS, + "failed to get a handle for archive archive_id=0x{:08X} archive_path={}", + async_data->archive_id, async_data->archive_path.DebugStr()); + } + }, + true); } void FS_USER::ControlArchive(Kernel::HLERequestContext& ctx) { @@ -340,24 +720,96 @@ void FS_USER::ControlArchive(Kernel::HLERequestContext& ctx) { const auto action = rp.Pop(); const auto input_size = rp.Pop(); const auto output_size = rp.Pop(); - [[maybe_unused]] const auto input = rp.PopMappedBuffer(); - [[maybe_unused]] const auto output = rp.PopMappedBuffer(); - LOG_WARNING(Service_FS, - "(STUBBED) called, archive_handle={:016X}, action={:08X}, input_size={:08X}, " - "output_size={:08X}", - archive_handle, action, input_size, output_size); + if (!archives.ArchiveIsSlow(archive_handle)) { + auto input = rp.PopMappedBuffer(); + auto output = rp.PopMappedBuffer(); + std::vector in_data(input_size); + input.Read(in_data.data(), 0, in_data.size()); + std::vector out_data(output_size); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); + const Result res = + archives.ControlArchive(archive_handle, action, in_data.data(), in_data.size(), + out_data.data(), out_data.size()); + + if (res.IsSuccess() && output_size != 0) { + output.Write(out_data.data(), 0, out_data.size()); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(res); + return; + } + + struct AsyncData { + ArchiveHandle handle; + u32 action; + Kernel::MappedBuffer* in_buffer; + u32 in_size; + u32 out_size; + + Result res{0}; + std::vector out_data; + Kernel::MappedBuffer* out_buffer; + }; + + auto async_data = std::make_shared(); + async_data->handle = archive_handle; + async_data->action = action; + async_data->in_size = input_size; + async_data->out_size = output_size; + async_data->in_buffer = &rp.PopMappedBuffer(); + async_data->out_buffer = &rp.PopMappedBuffer(); + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + std::vector in_data(async_data->in_size); + async_data->in_buffer->Read(in_data.data(), 0, in_data.size()); + async_data->out_data.resize(async_data->out_size); + + async_data->res = archives.ControlArchive( + async_data->handle, async_data->action, in_data.data(), in_data.size(), + async_data->out_data.data(), async_data->out_data.size()); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + if (async_data->res.IsSuccess() && async_data->out_size != 0) { + async_data->out_buffer->Write(async_data->out_data.data(), 0, + async_data->out_data.size()); + } + rb.Push(async_data->res); + }, + true); } void FS_USER::CloseArchive(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto archive_handle = rp.PopRaw(); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.CloseArchive(archive_handle)); + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(archives.CloseArchive(archive_handle)); + return; + } + + struct AsyncData { + ArchiveHandle handle; + + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->handle = archive_handle; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.CloseArchive(async_data->handle); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::IsSdmcDetected(Kernel::HLERequestContext& ctx) { @@ -377,8 +829,6 @@ void FS_USER::IsSdmcWriteable(Kernel::HLERequestContext& ctx) { } void FS_USER::FormatSaveData(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED)"); - IPC::RequestParser rp(ctx); const auto archive_id = rp.PopEnum(); const auto archivename_type = rp.PopEnum(); @@ -386,8 +836,8 @@ void FS_USER::FormatSaveData(Kernel::HLERequestContext& ctx) { const auto block_size = rp.Pop(); const auto number_directories = rp.Pop(); const auto number_files = rp.Pop(); - [[maybe_unused]] const auto directory_buckets = rp.Pop(); - [[maybe_unused]] const auto file_buckets = rp.Pop(); + const auto directory_buckets = rp.Pop(); + const auto file_buckets = rp.Pop(); const bool duplicate_data = rp.Pop(); std::vector archivename = rp.PopStaticBuffer(); ASSERT(archivename.size() == archivename_size); @@ -417,7 +867,7 @@ void FS_USER::FormatSaveData(Kernel::HLERequestContext& ctx) { ClientSlot* slot = GetSessionData(ctx.Session()); rb.Push(archives.FormatArchive(ArchiveIdCode::SaveData, format_info, archive_path, - slot->program_id)); + slot->program_id, directory_buckets, file_buckets)); } void FS_USER::FormatThisUserSaveData(Kernel::HLERequestContext& ctx) { @@ -425,8 +875,8 @@ void FS_USER::FormatThisUserSaveData(Kernel::HLERequestContext& ctx) { const auto block_size = rp.Pop(); const auto number_directories = rp.Pop(); const auto number_files = rp.Pop(); - [[maybe_unused]] const auto directory_buckets = rp.Pop(); - [[maybe_unused]] const auto file_buckets = rp.Pop(); + const auto directory_buckets = rp.Pop(); + const auto file_buckets = rp.Pop(); const auto duplicate_data = rp.Pop(); FileSys::ArchiveFormatInfo format_info; @@ -438,7 +888,7 @@ void FS_USER::FormatThisUserSaveData(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); ClientSlot* slot = GetSessionData(ctx.Session()); rb.Push(archives.FormatArchive(ArchiveIdCode::SaveData, format_info, FileSys::Path(), - slot->program_id)); + slot->program_id, directory_buckets, file_buckets)); LOG_TRACE(Service_FS, "called"); } @@ -495,10 +945,7 @@ void FS_USER::GetNandArchiveResource(Kernel::HLERequestContext& ctx) { void FS_USER::CreateExtSaveData(Kernel::HLERequestContext& ctx) { // TODO(Subv): Figure out the other parameters. IPC::RequestParser rp(ctx); - MediaType media_type = static_cast(rp.Pop()); // the other bytes are unknown - u32 save_low = rp.Pop(); - u32 save_high = rp.Pop(); - u32 unknown = rp.Pop(); + auto ext_data_info = rp.PopRaw(); u32 directories = rp.Pop(); u32 files = rp.Pop(); u64 size_limit = rp.Pop(); @@ -512,33 +959,34 @@ void FS_USER::CreateExtSaveData(Kernel::HLERequestContext& ctx) { format_info.number_directories = directories; format_info.number_files = files; format_info.duplicate_data = false; - format_info.total_size = 0; + format_info.total_size = static_cast(size_limit); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); ClientSlot* slot = GetSessionData(ctx.Session()); - rb.Push(archives.CreateExtSaveData(media_type, save_high, save_low, icon, format_info, - slot->program_id)); + rb.Push(archives.CreateExtSaveData(static_cast(ext_data_info.media_type), + ext_data_info.unknown, ext_data_info.save_id_high, + ext_data_info.save_id_low, icon, format_info, + slot->program_id, size_limit)); rb.PushMappedBuffer(icon_buffer); LOG_DEBUG(Service_FS, "called, savedata_high={:08X} savedata_low={:08X} unknown={:08X} " "files={:08X} directories={:08X} size_limit={:016x} icon_size={:08X}", - save_high, save_low, unknown, directories, files, size_limit, icon_size); + ext_data_info.save_id_high, ext_data_info.save_id_low, ext_data_info.unknown, + directories, files, size_limit, icon_size); } void FS_USER::DeleteExtSaveData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - MediaType media_type = static_cast(rp.Pop()); // the other bytes are unknown - u32 save_low = rp.Pop(); - u32 save_high = rp.Pop(); - u32 unknown = rp.Pop(); // TODO(Subv): Figure out what this is + ExtSaveDataInfo info = rp.PopRaw(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.DeleteExtSaveData(media_type, save_high, save_low)); + rb.Push(archives.DeleteExtSaveData(static_cast(info.media_type), info.unknown, + info.save_id_high, info.save_id_low)); LOG_DEBUG(Service_FS, - "called, save_low={:08X} save_high={:08X} media_type={:08X} unknown={:08X}", save_low, - save_high, media_type, unknown); + "called, save_low={:08X} save_high={:08X} media_type={:08X} unknown={:08X}", + info.save_id_low, info.save_id_high, info.media_type, info.unknown); } void FS_USER::CardSlotIsInserted(Kernel::HLERequestContext& ctx) { @@ -757,13 +1205,12 @@ void FS_USER::ObsoletedCreateExtSaveData(Kernel::HLERequestContext& ctx) { FileSys::ArchiveFormatInfo format_info; format_info.number_directories = directories; format_info.number_files = files; - format_info.duplicate_data = false; - format_info.total_size = 0; + format_info.total_size = -1; IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); ClientSlot* slot = GetSessionData(ctx.Session()); - rb.Push(archives.CreateExtSaveData(media_type, save_high, save_low, icon, format_info, - slot->program_id)); + rb.Push(archives.CreateExtSaveData(media_type, 0, save_high, save_low, icon, format_info, + slot->program_id, -1)); rb.PushMappedBuffer(icon_buffer); LOG_DEBUG(Service_FS, @@ -778,7 +1225,7 @@ void FS_USER::ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx) { u32 save_low = rp.Pop(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.DeleteExtSaveData(media_type, 0, save_low)); + rb.Push(archives.DeleteExtSaveData(media_type, 0, 0, save_low)); LOG_DEBUG(Service_FS, "called, save_low={:08X} media_type={:08X}", save_low, media_type); } @@ -832,16 +1279,39 @@ void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { const u32 unique_id = rp.Pop(); const u8 title_variation = rp.Pop(); - // TODO: Generate and Save the Secure Value + if (!secure_value_backend->BackendIsSlow()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(secure_value_backend->ObsoletedSetSaveDataSecureValue(unique_id, title_variation, + secure_value_slot, value)); + return; + } - LOG_WARNING(Service_FS, - "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:08X} " - "unqiue_id=0x{:08X} title_variation=0x{:02X}", - value, secure_value_slot, unique_id, title_variation); + struct AsyncData { + u64 value; + u32 secure_value_slot; + u32 unique_id; + u8 title_variation; - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->value = value; + async_data->secure_value_slot = secure_value_slot; + async_data->unique_id = unique_id; + async_data->title_variation = title_variation; - rb.Push(ResultSuccess); + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = secure_value_backend->ObsoletedSetSaveDataSecureValue( + async_data->unique_id, async_data->title_variation, async_data->secure_value_slot, + async_data->value); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { @@ -850,19 +1320,74 @@ void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { const u32 unique_id = rp.Pop(); const u8 title_variation = rp.Pop(); - LOG_WARNING( - Service_FS, - "(STUBBED) called secure_value_slot=0x{:08X} unqiue_id=0x{:08X} title_variation=0x{:02X}", - secure_value_slot, unique_id, title_variation); + if (!secure_value_backend->BackendIsSlow()) { + auto res = secure_value_backend->ObsoletedGetSaveDataSecureValue(unique_id, title_variation, + secure_value_slot); + if (res.Failed()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(res.Code()); + } else { + IPC::RequestBuilder rb = rp.MakeBuilder(4, 0); + rb.Push(res.Code()); + rb.Push(std::get<0>(*res)); // indicates if the secure value exists + rb.Push(std::get<1>(*res)); // the secure value + } + return; + } - IPC::RequestBuilder rb = rp.MakeBuilder(4, 0); + struct AsyncData { + u32 secure_value_slot; + u32 unique_id; + u8 title_variation; - rb.Push(ResultSuccess); + ResultVal> res; + }; + auto async_data = std::make_shared(); + async_data->secure_value_slot = secure_value_slot; + async_data->unique_id = unique_id; + async_data->title_variation = title_variation; - // TODO: Implement Secure Value Lookup & Generation + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = secure_value_backend->ObsoletedGetSaveDataSecureValue( + async_data->unique_id, async_data->title_variation, async_data->secure_value_slot); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.Failed()) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res.Code()); + } else { + IPC::RequestBuilder rb(ctx, 4, 0); + rb.Push(async_data->res.Code()); + rb.Push( + std::get<0>(*async_data->res)); // indicates if the secure value exists + rb.Push(std::get<1>(*async_data->res)); // the secure value + } + }, + true); +} - rb.Push(false); // indicates that the secure value doesn't exist - rb.Push(0); // the secure value +void FS_USER::ControlSecureSave(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const auto action = rp.Pop(); + const auto input_size = rp.Pop(); + const auto output_size = rp.Pop(); + auto input = rp.PopMappedBuffer(); + auto output = rp.PopMappedBuffer(); + + std::vector in_data(input_size); + input.Read(in_data.data(), 0, in_data.size()); + std::vector out_data(output_size); + + Result res = secure_value_backend->ControlSecureSave(action, in_data.data(), in_data.size(), + out_data.data(), out_data.size()); + + if (res.IsSuccess() && output_size != 0) { + output.Write(out_data.data(), 0, out_data.size()); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(res); } void FS_USER::SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) { @@ -870,31 +1395,85 @@ void FS_USER::SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) { const u32 secure_value_slot = rp.Pop(); const u64 value = rp.Pop(); - // TODO: Generate and Save the Secure Value + if (!secure_value_backend->BackendIsSlow()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(secure_value_backend->SetThisSaveDataSecureValue(secure_value_slot, value)); + return; + } - LOG_WARNING(Service_FS, "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:08X}", value, - secure_value_slot); + struct AsyncData { + u64 value; + u32 secure_value_slot; - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->value = value; + async_data->secure_value_slot = secure_value_slot; - rb.Push(ResultSuccess); + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = secure_value_backend->SetThisSaveDataSecureValue( + async_data->secure_value_slot, async_data->value); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 secure_value_slot = rp.Pop(); - LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot); + if (!secure_value_backend->BackendIsSlow()) { + auto res = secure_value_backend->GetThisSaveDataSecureValue(secure_value_slot); - IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + if (res.Failed()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(res.Code()); + } else { + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + rb.Push(res.Code()); + rb.Push(std::get<0>(*res)); // indicates if the secure value exists + rb.Push(std::get<1>( + *res)); // indicates if the requesting process is a gamecard, overriding the check + rb.Push(std::get<2>(*res)); // the secure value + } + return; + } - rb.Push(ResultSuccess); + struct AsyncData { + u32 secure_value_slot; - // TODO: Implement Secure Value Lookup & Generation + ResultVal> res; + }; + auto async_data = std::make_shared(); + async_data->secure_value_slot = secure_value_slot; - rb.Push(false); // indicates that the secure value doesn't exist - rb.Push(true); // indicates the requesting process is a gamecard, overriding the check - rb.Push(0); // the secure value + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = + secure_value_backend->GetThisSaveDataSecureValue(async_data->secure_value_slot); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.Failed()) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res.Code()); + } else { + IPC::RequestBuilder rb(ctx, 5, 0); + rb.Push(async_data->res.Code()); + rb.Push( + std::get<0>(*async_data->res)); // indicates if the secure value exists + rb.Push(std::get<1>(*async_data->res)); // indicates if the requesting process + // is a gamecard, overriding the check + rb.Push(std::get<2>(*async_data->res)); // the secure value + } + }, + true); } void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { @@ -904,16 +1483,39 @@ void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { const u64 value = rp.Pop(); const bool flush = rp.Pop(); - // TODO: Generate and Save the Secure Value + if (!archives.ArchiveIsSlow(archive_handle)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - LOG_WARNING(Service_FS, - "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:04X} " - "archive_handle=0x{:08X} flush={}", - value, secure_value_slot, archive_handle, flush); + rb.Push(archives.SetSaveDataSecureValue(archive_handle, secure_value_slot, value, flush)); + return; + } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + struct AsyncData { + ArchiveHandle archive_handle; + u64 value; + u32 secure_value_slot; + bool flush; - rb.Push(ResultSuccess); + Result res{0}; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->value = value; + async_data->secure_value_slot = secure_value_slot; + async_data->flush = flush; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.SetSaveDataSecureValue(async_data->archive_handle, + async_data->secure_value_slot, + async_data->value, async_data->flush); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); } void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { @@ -921,18 +1523,53 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { const auto archive_handle = rp.PopRaw(); const u32 secure_value_slot = rp.Pop(); - LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X} archive_handle=0x{:08X}", - secure_value_slot, archive_handle); + if (!archives.ArchiveIsSlow(archive_handle)) { + auto res = archives.GetSaveDataSecureValue(archive_handle, secure_value_slot); + if (res.Failed()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(res.Code()); + } else { + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + rb.Push(res.Code()); + rb.Push(std::get<0>(*res)); // indicates if the secure value exists + rb.Push(std::get<1>( + *res)); // indicates if the requesting process is a gamecard, overriding the check + rb.Push(std::get<2>(*res)); // the secure value + } + return; + } - IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + struct AsyncData { + ArchiveHandle archive_handle; + u32 secure_value_slot; - rb.Push(ResultSuccess); + ResultVal> res; + }; + auto async_data = std::make_shared(); + async_data->archive_handle = archive_handle; + async_data->secure_value_slot = secure_value_slot; - // TODO: Implement Secure Value Lookup & Generation - - rb.Push(false); // indicates that the secure value doesn't exist - rb.Push(true); // indicates the requesting process is a gamecard, overriding the check - rb.Push(0); // the secure value + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + async_data->res = archives.GetSaveDataSecureValue(async_data->archive_handle, + async_data->secure_value_slot); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.Failed()) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res.Code()); + } else { + IPC::RequestBuilder rb(ctx, 5, 0); + rb.Push(async_data->res.Code()); + rb.Push( + std::get<0>(*async_data->res)); // indicates if the secure value exists + rb.Push(std::get<1>(*async_data->res)); // indicates if the requesting process + // is a gamecard, overriding the check + rb.Push(std::get<2>(*async_data->res)); // the secure value + } + }, + true); } void FS_USER::RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath) { @@ -1118,7 +1755,7 @@ FS_USER::FS_USER(Core::System& system) {0x0864, nullptr, "GetNandInfo"}, {0x0865, &FS_USER::ObsoletedSetSaveDataSecureValue, "SetSaveDataSecureValue"}, {0x0866, &FS_USER::ObsoletedGetSaveDataSecureValue, "GetSaveDataSecureValue"}, - {0x0867, nullptr, "ControlSecureSave"}, + {0x0867, &FS_USER::ControlSecureSave, "ControlSecureSave"}, {0x0868, nullptr, "GetMediaType"}, {0x0869, nullptr, "GetNandEraseCount"}, {0x086A, nullptr, "ReadNandReport"}, @@ -1132,6 +1769,13 @@ FS_USER::FS_USER(Core::System& system) // clang-format on }; RegisterHandlers(functions); + secure_value_backend = std::make_shared(); +} +template +void Service::FS::FS_USER::serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar& priority; + ar& secure_value_backend; } void InstallInterfaces(Core::System& system) { diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index 57285995a..206a9c3b6 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -9,6 +9,7 @@ #include #include "common/common_types.h" #include "core/file_sys/errors.h" +#include "core/file_sys/secure_value_backend.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/service.h" @@ -77,6 +78,10 @@ public: } } + void RegisterSecureValueBackend(const std::shared_ptr& backend) { + secure_value_backend = backend; + } + private: void Initialize(Kernel::HLERequestContext& ctx); @@ -657,6 +662,21 @@ private: */ void ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx); + /** + * FS_User::ControlSecureSave service function + * Inputs: + * 1 : Action + * 2 : Input Size + * 3 : Output Size + * 4 : (Input Size << 4) | 0xA + * 5 : Input Pointer + * 6 : (Output Size << 4) | 0xC + * 7 : Output Pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void ControlSecureSave(Kernel::HLERequestContext& ctx); + /** * FS_User::SetThisSaveDataSecureValue service function. * Inputs: @@ -722,11 +742,10 @@ private: Core::System& system; ArchiveManager& archives; + std::shared_ptr secure_value_backend; + template - void serialize(Archive& ar, const unsigned int) { - ar& boost::serialization::base_object(*this); - ar& priority; - } + void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; }; diff --git a/src/core/hle/service/http/http_c.cpp b/src/core/hle/service/http/http_c.cpp index 3337580a5..039a43044 100644 --- a/src/core/hle/service/http/http_c.cpp +++ b/src/core/hle/service/http/http_c.cpp @@ -1969,7 +1969,7 @@ void HTTP_C::DecryptClCertA() { FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::RomFS, exefs_filepath); FileSys::Mode open_mode = {}; open_mode.read_flag.Assign(1); - auto file_result = archive.OpenFile(file_path, open_mode); + auto file_result = archive.OpenFile(file_path, open_mode, 0); if (file_result.Failed()) { LOG_ERROR(Service_HTTP, "ClCertA file missing"); return; diff --git a/src/core/hle/service/news/news.cpp b/src/core/hle/service/news/news.cpp index f1a32d168..919041f37 100644 --- a/src/core/hle/service/news/news.cpp +++ b/src/core/hle/service/news/news.cpp @@ -151,7 +151,7 @@ void Module::Interface::ResetNotifications(Kernel::HLERequestContext& ctx) { FileSys::Path archive_path(news_system_savedata_id); // Format the SystemSaveData archive 0x00010035 - systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); + systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); news->news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); @@ -655,7 +655,7 @@ Result Module::LoadNewsDBSavedata() { // If the archive didn't exist, create the files inside if (archive_result.Code() == FileSys::ResultNotFound) { // Format the archive to create the directories - systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); + systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); // Open it again to get a valid archive now that the folder exists news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); @@ -722,7 +722,7 @@ Result Module::SaveFileToSavedata(std::string filename, std::span buff ASSERT_MSG(result.Succeeded(), "could not open file"); auto file = std::move(result).Unwrap(); - file->Write(0, buffer.size(), 1, buffer.data()); + file->Write(0, buffer.size(), true, false, buffer.data()); file->Close(); return ResultSuccess; diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 6f609182b..fb3a51b6b 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -158,7 +158,7 @@ static void WriteGameCoinData(GameCoin gamecoin_data) { // If the archive didn't exist, create the files inside if (archive_result.Code() == FileSys::ResultNotFormatted) { // Format the archive to create the directories - extdata_archive_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); + extdata_archive_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); // Open it again to get a valid archive now that the folder exists archive = extdata_archive_factory.Open(archive_path, 0).Unwrap(); // Create the game coin file @@ -174,7 +174,8 @@ static void WriteGameCoinData(GameCoin gamecoin_data) { auto gamecoin_result = archive->OpenFile(gamecoin_path, open_mode); if (gamecoin_result.Succeeded()) { auto gamecoin = std::move(gamecoin_result).Unwrap(); - gamecoin->Write(0, sizeof(GameCoin), true, reinterpret_cast(&gamecoin_data)); + gamecoin->Write(0, sizeof(GameCoin), true, false, + reinterpret_cast(&gamecoin_data)); gamecoin->Close(); } } diff --git a/src/core/hle/service/soc/soc_u.cpp b/src/core/hle/service/soc/soc_u.cpp index ae2ecfcb2..1ef35da26 100644 --- a/src/core/hle/service/soc/soc_u.cpp +++ b/src/core/hle/service/soc/soc_u.cpp @@ -19,6 +19,7 @@ #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/soc/soc_u.h" +#include "network/socket_manager.h" #ifdef _WIN32 #include @@ -2221,17 +2222,12 @@ SOC_U::SOC_U() : ServiceFramework("soc:U", 18) { RegisterHandlers(functions); -#ifdef _WIN32 - WSADATA data; - WSAStartup(MAKEWORD(2, 2), &data); -#endif + Network::SocketManager::EnableSockets(); } SOC_U::~SOC_U() { CloseAndDeleteAllSockets(); -#ifdef _WIN32 - WSACleanup(); -#endif + Network::SocketManager::DisableSockets(); } std::optional SOC_U::GetDefaultInterfaceInfo() { diff --git a/src/core/loader/artic.cpp b/src/core/loader/artic.cpp new file mode 100644 index 000000000..f2e74b9a6 --- /dev/null +++ b/src/core/loader/artic.cpp @@ -0,0 +1,564 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include "common/literals.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/file_sys/ncch_container.h" +#include "core/file_sys/romfs_reader.h" +#include "core/file_sys/secure_value_backend_artic.h" +#include "core/file_sys/title_metadata.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/cfg/cfg.h" +#include "core/hle/service/fs/archive.h" +#include "core/hle/service/fs/fs_user.h" +#include "core/loader/artic.h" +#include "core/loader/smdh.h" +#include "core/memory.h" +#include "core/system_titles.h" +#include "network/network.h" + +namespace Loader { + +using namespace Common::Literals; + +Apploader_Artic::~Apploader_Artic() { + // TODO(PabloMK7) Find memory leak that prevents the romfs readers being destroyed + // when emulation stops. Looks like the mem leak comes from IVFCFile objects + // not being destroyed... + if (main_romfs_reader) { + static_cast(main_romfs_reader.get())->ClearCache(); + static_cast(main_romfs_reader.get())->CloseFile(); + main_romfs_reader.reset(); + } + if (update_romfs_reader) { + static_cast(update_romfs_reader.get())->ClearCache(); + static_cast(update_romfs_reader.get())->CloseFile(); + update_romfs_reader.reset(); + } + client->Stop(); +} + +FileType Apploader_Artic::IdentifyType(FileUtil::IOFile& file) { + return FileType::ARTIC; +} + +std::pair, ResultStatus> Apploader_Artic::LoadCoreVersion() { + if (!is_loaded) { + bool success = LoadExheader(); + if (!success) { + return std::make_pair(std::nullopt, ResultStatus::ErrorArtic); + } + } + + // Provide the core version from the exheader. + auto& ncch_caps = program_exheader.arm11_system_local_caps; + return std::make_pair(ncch_caps.core_version, ResultStatus::Success); +} + +std::pair, ResultStatus> Apploader_Artic::LoadKernelMemoryMode() { + if (!is_loaded) { + bool success = LoadExheader(); + if (!success) { + return std::make_pair(std::nullopt, ResultStatus::ErrorArtic); + } + } + + if (memory_mode_override.has_value()) { + return std::make_pair(memory_mode_override, ResultStatus::Success); + } + + // Provide the memory mode from the exheader. + auto& ncch_caps = program_exheader.arm11_system_local_caps; + auto mode = static_cast(ncch_caps.system_mode.Value()); + return std::make_pair(mode, ResultStatus::Success); +} + +std::pair, ResultStatus> +Apploader_Artic::LoadNew3dsHwCapabilities() { + if (!is_loaded) { + bool success = LoadExheader(); + if (!success) { + return std::make_pair(std::nullopt, ResultStatus::ErrorArtic); + } + } + + // Provide the capabilities from the exheader. + auto& ncch_caps = program_exheader.arm11_system_local_caps; + auto caps = Kernel::New3dsHwCapabilities{ + ncch_caps.enable_l2_cache != 0, + ncch_caps.enable_804MHz_cpu != 0, + static_cast(ncch_caps.n3ds_mode), + }; + return std::make_pair(std::move(caps), ResultStatus::Success); +} + +ResultStatus Apploader_Artic::LoadExec(std::shared_ptr& process) { + using Kernel::CodeSet; + + if (!is_loaded) + return ResultStatus::ErrorNotLoaded; + + std::vector code; + u64_le program_id; + if (ResultStatus::Success == ReadCode(code) && + ResultStatus::Success == ReadProgramId(program_id)) { + + std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( + (const char*)program_exheader.codeset_info.name, 8); + + std::shared_ptr codeset = system.Kernel().CreateCodeSet(process_name, program_id); + + codeset->CodeSegment().offset = 0; + codeset->CodeSegment().addr = program_exheader.codeset_info.text.address; + codeset->CodeSegment().size = + program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE; + + codeset->RODataSegment().offset = + codeset->CodeSegment().offset + codeset->CodeSegment().size; + codeset->RODataSegment().addr = program_exheader.codeset_info.ro.address; + codeset->RODataSegment().size = + program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE; + + // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just + // to the regular size. Playing it safe for now. + u32 bss_page_size = (program_exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF; + code.resize(code.size() + bss_page_size, 0); + + codeset->DataSegment().offset = + codeset->RODataSegment().offset + codeset->RODataSegment().size; + codeset->DataSegment().addr = program_exheader.codeset_info.data.address; + codeset->DataSegment().size = + program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE + + bss_page_size; + + // Apply patches now that the entire codeset (including .bss) has been allocated + // const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code); + // if (patch_result != ResultStatus::Success && patch_result != ResultStatus::ErrorNotUsed) + // return patch_result; + + codeset->entrypoint = codeset->CodeSegment().addr; + codeset->memory = std::move(code); + + process = system.Kernel().CreateProcess(std::move(codeset)); + + // Attach a resource limit to the process based on the resource limit category + const auto category = static_cast( + program_exheader.arm11_system_local_caps.resource_limit_category); + process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category); + + // When running N3DS-unaware titles pm will lie about the amount of memory available. + // This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of + // APPLICATION. See: + // https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237 + auto& ncch_caps = program_exheader.arm11_system_local_caps; + const auto o3ds_mode = *LoadKernelMemoryMode().first; + const auto n3ds_mode = static_cast(ncch_caps.n3ds_mode); + const bool is_new_3ds = Settings::values.is_new_3ds.GetValue(); + if (is_new_3ds && n3ds_mode == Kernel::New3dsMemoryMode::Legacy && + category == Kernel::ResourceLimitCategory::Application) { + u64 new_limit = 0; + switch (o3ds_mode) { + case Kernel::MemoryMode::Prod: + new_limit = 64_MiB; + break; + case Kernel::MemoryMode::Dev1: + new_limit = 96_MiB; + break; + case Kernel::MemoryMode::Dev2: + new_limit = 80_MiB; + break; + default: + break; + } + process->resource_limit->SetLimitValue(Kernel::ResourceLimitType::Commit, + static_cast(new_limit)); + } + + // Set the default CPU core for this process + process->ideal_processor = program_exheader.arm11_system_local_caps.ideal_processor; + + // Copy data while converting endianness + using KernelCaps = std::array; + KernelCaps kernel_caps; + std::copy_n(program_exheader.arm11_kernel_caps.descriptors, kernel_caps.size(), + begin(kernel_caps)); + process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); + + s32 priority = program_exheader.arm11_system_local_caps.priority; + u32 stack_size = program_exheader.codeset_info.stack_size; + + // On real HW this is done with FS:Reg, but we can be lazy + auto fs_user = system.ServiceManager().GetService("fs:USER"); + fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, + "articbase://"); + + Service::FS::FS_USER::ProductInfo product_info{}; + if (LoadProductInfo(product_info) != ResultStatus::Success) { + return ResultStatus::ErrorArtic; + } + fs_user->RegisterProductInfo(process->process_id, product_info); + + process->Run(priority, stack_size); + return ResultStatus::Success; + } + return ResultStatus::ErrorArtic; +} + +void Apploader_Artic::ParseRegionLockoutInfo(u64 program_id) { + if (Settings::values.region_value.GetValue() != Settings::REGION_VALUE_AUTO_SELECT) { + return; + } + + preferred_regions.clear(); + + std::vector smdh_buffer; + if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { + SMDH smdh; + std::memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH)); + u32 region_lockout = smdh.region_lockout; + constexpr u32 REGION_COUNT = 7; + for (u32 region = 0; region < REGION_COUNT; ++region) { + if (region_lockout & 1) { + preferred_regions.push_back(region); + } + region_lockout >>= 1; + } + } else { + const auto region = Core::GetSystemTitleRegion(program_id); + if (region.has_value()) { + preferred_regions.push_back(region.value()); + } + } +} + +bool Apploader_Artic::LoadExheader() { + if (program_exheader_loaded) + return true; + + if (!client_connected) + client_connected = client->Connect(); + if (!client_connected) + return false; + + auto req = client->NewRequest("Process_GetExheader"); + auto resp = client->Send(req); + if (!resp.has_value()) + return false; + + auto exheader_buf = resp->GetResponseBuffer(0); + if (!exheader_buf.has_value()) + return false; + + if (exheader_buf->second != sizeof(ExHeader_Header) - sizeof(ExHeader_Header::access_desc)) + return false; + + u8* prg_exh = reinterpret_cast(&program_exheader); + memcpy(prg_exh, exheader_buf->first, + sizeof(ExHeader_Header) - sizeof(ExHeader_Header::access_desc)); + memcpy(prg_exh + offsetof(ExHeader_Header, access_desc.arm11_system_local_caps), + reinterpret_cast(exheader_buf->first) + + offsetof(ExHeader_Header, arm11_system_local_caps), + offsetof(ExHeader_Header, access_desc) - + offsetof(ExHeader_Header, arm11_system_local_caps)); + program_exheader_loaded = true; + return true; +} + +ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo& out_product_info) { + if (cached_product_info.has_value()) { + out_product_info = *cached_product_info; + return ResultStatus::Success; + } + + if (!client_connected) + client_connected = client->Connect(); + if (!client_connected) + return ResultStatus::ErrorArtic; + + auto req = client->NewRequest("Process_GetProductInfo"); + auto resp = client->Send(req); + if (!resp.has_value()) + return ResultStatus::ErrorArtic; + + auto pinfo_buf = resp->GetResponseBuffer(0); + if (!pinfo_buf.has_value() || pinfo_buf->second != sizeof(Service::FS::FS_USER::ProductInfo)) + return ResultStatus::ErrorArtic; + + out_product_info = *reinterpret_cast(pinfo_buf->first); + cached_product_info = out_product_info; + + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { + u64_le ncch_program_id; + + if (is_loaded) + return ResultStatus::ErrorAlreadyLoaded; + + ResultStatus result = ReadProgramId(ncch_program_id); + if (result != ResultStatus::Success) { + return result; + } + + std::string program_id{fmt::format("{:016X}", ncch_program_id)}; + + LOG_INFO(Loader, "Program ID: {}", program_id); + + if (auto room_member = Network::GetRoomMember().lock()) { + Network::GameInfo game_info; + ReadTitle(game_info.name); + game_info.id = ncch_program_id; + room_member->SendGameInfo(game_info); + } + + is_loaded = true; // Set state to loaded + + result = LoadExec(process); // Load the executable into memory for booting + if (ResultStatus::Success != result) + return result; + + system.ArchiveManager().RegisterSelfNCCH(*this); + system.ArchiveManager().RegisterArticSaveDataSource(client); + system.ArchiveManager().RegisterArticExtData(client); + system.ArchiveManager().RegisterArticNCCH(client); + + auto fs_user = system.ServiceManager().GetService("fs:USER"); + fs_user->RegisterSecureValueBackend(std::make_shared(client)); + + ParseRegionLockoutInfo(ncch_program_id); + + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::IsExecutable(bool& out_executable) { + out_executable = true; + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::ReadCode(std::vector& buffer) { + // Code is only read once, there is no need to cache it. + + if (!client_connected) + client_connected = client->Connect(); + if (!client_connected) + return ResultStatus::ErrorArtic; + + size_t code_size = program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE; + code_size += program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE; + code_size += program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE; + + size_t read_amount = 0; + buffer.clear(); + + while (read_amount != code_size) { + size_t to_read = + std::min(client->GetServerRequestMaxSize() - 0x100, code_size - read_amount); + + auto req = client->NewRequest("Process_ReadCode"); + req.AddParameterS32(static_cast(read_amount)); + req.AddParameterS32(static_cast(to_read)); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) + return ResultStatus::ErrorArtic; + + auto code_buff = resp->GetResponseBuffer(0); + if (!code_buff.has_value() || code_buff->second != to_read) + return ResultStatus::ErrorArtic; + + buffer.resize(read_amount + to_read); + memcpy(buffer.data() + read_amount, code_buff->first, to_read); + read_amount += to_read; + } + + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::ReadIcon(std::vector& buffer) { + if (!cached_icon.empty()) { + buffer = cached_icon; + return ResultStatus::Success; + } + + if (!client_connected) + client_connected = client->Connect(); + if (!client_connected) + return ResultStatus::ErrorArtic; + + auto req = client->NewRequest("Process_ReadIcon"); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) + return ResultStatus::ErrorArtic; + + auto icon_buf = resp->GetResponseBuffer(0); + if (!icon_buf.has_value()) + return ResultStatus::ErrorArtic; + + cached_icon.resize(icon_buf->second); + memcpy(cached_icon.data(), icon_buf->first, icon_buf->second); + buffer = cached_icon; + + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::ReadBanner(std::vector& buffer) { + if (!cached_banner.empty()) { + buffer = cached_banner; + return ResultStatus::Success; + } + + if (!client_connected) + client_connected = client->Connect(); + if (!client_connected) + return ResultStatus::ErrorArtic; + + auto req = client->NewRequest("Process_ReadBanner"); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) + return ResultStatus::ErrorArtic; + + auto banner_buf = resp->GetResponseBuffer(0); + if (!banner_buf.has_value()) + return ResultStatus::ErrorArtic; + + cached_banner.resize(banner_buf->second); + memcpy(cached_banner.data(), banner_buf->first, banner_buf->second); + buffer = cached_banner; + + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::ReadLogo(std::vector& buffer) { + if (!cached_logo.empty()) { + buffer = cached_logo; + return ResultStatus::Success; + } + + if (!client_connected) + client_connected = client->Connect(); + if (!client_connected) + return ResultStatus::ErrorArtic; + + auto req = client->NewRequest("Process_ReadLogo"); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0) + return ResultStatus::ErrorArtic; + + auto logo_buf = resp->GetResponseBuffer(0); + if (!logo_buf.has_value()) + return ResultStatus::ErrorArtic; + + cached_logo.resize(logo_buf->second); + memcpy(cached_logo.data(), logo_buf->first, logo_buf->second); + buffer = cached_logo; + + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::ReadProgramId(u64& out_program_id) { + if (cached_title_id.has_value()) { + out_program_id = *cached_title_id; + return ResultStatus::Success; + } + + if (!client_connected) + client_connected = client->Connect(); + if (!client_connected) + return ResultStatus::ErrorArtic; + + auto req = client->NewRequest("Process_GetTitleID"); + auto resp = client->Send(req); + if (!resp.has_value()) + return ResultStatus::ErrorArtic; + + auto tid_buf = resp->GetResponseBuffer(0); + if (!tid_buf.has_value() || tid_buf->second != sizeof(u64)) + return ResultStatus::ErrorArtic; + + out_program_id = *reinterpret_cast(tid_buf->first); + cached_title_id = out_program_id; + + return ResultStatus::Success; +} + +ResultStatus Apploader_Artic::ReadExtdataId(u64& out_extdata_id) { + if (program_exheader.arm11_system_local_caps.storage_info.other_attributes >> 1) { + // Using extended save data access + // There would be multiple possible extdata IDs in this case. The best we can do for now is + // guessing that the first one would be the main save. + const std::array extdata_ids{{ + program_exheader.arm11_system_local_caps.storage_info.extdata_id0.Value(), + program_exheader.arm11_system_local_caps.storage_info.extdata_id1.Value(), + program_exheader.arm11_system_local_caps.storage_info.extdata_id2.Value(), + program_exheader.arm11_system_local_caps.storage_info.extdata_id3.Value(), + program_exheader.arm11_system_local_caps.storage_info.extdata_id4.Value(), + program_exheader.arm11_system_local_caps.storage_info.extdata_id5.Value(), + }}; + for (u64 id : extdata_ids) { + if (id) { + // Found a non-zero ID, use it + out_extdata_id = id; + return ResultStatus::Success; + } + } + + return ResultStatus::ErrorNotUsed; + } + + out_extdata_id = program_exheader.arm11_system_local_caps.storage_info.ext_save_data_id; + return Loader::ResultStatus::Success; +} + +ResultStatus Apploader_Artic::ReadRomFS(std::shared_ptr& romfs_file) { + main_romfs_reader = romfs_file = std::make_shared(client, false); + return static_cast(romfs_file.get())->OpenStatus(); +} + +ResultStatus Apploader_Artic::ReadUpdateRomFS(std::shared_ptr& romfs_file) { + update_romfs_reader = romfs_file = std::make_shared(client, true); + return static_cast(romfs_file.get())->OpenStatus(); +} + +ResultStatus Apploader_Artic::DumpRomFS(const std::string& target_path) { + return ResultStatus::ErrorNotImplemented; +} + +ResultStatus Apploader_Artic::DumpUpdateRomFS(const std::string& target_path) { + return ResultStatus::ErrorNotImplemented; +} + +ResultStatus Apploader_Artic::ReadTitle(std::string& title) { + std::vector data; + Loader::SMDH smdh; + ResultStatus result = ReadIcon(data); + if (result != ResultStatus::Success) { + return result; + } + + if (!Loader::IsValidSMDH(data)) { + return ResultStatus::ErrorInvalidFormat; + } + + std::memcpy(&smdh, data.data(), sizeof(Loader::SMDH)); + + const auto& short_title = smdh.GetShortTitle(SMDH::TitleLanguage::English); + auto title_end = std::find(short_title.begin(), short_title.end(), u'\0'); + title = Common::UTF16ToUTF8(std::u16string{short_title.begin(), title_end}); + + return ResultStatus::Success; +} + +} // namespace Loader diff --git a/src/core/loader/artic.h b/src/core/loader/artic.h new file mode 100644 index 000000000..d51202b2e --- /dev/null +++ b/src/core/loader/artic.h @@ -0,0 +1,135 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/file_sys/ncch_container.h" +#include "core/hle/service/fs/fs_user.h" +#include "core/loader/loader.h" +#include "network/artic_base/artic_base_client.h" + +namespace Loader { + +/// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) +class Apploader_Artic final : public AppLoader { +public: + Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port) + : AppLoader(system_, FileUtil::IOFile()) { + client = std::make_shared(server_addr, server_port); + client->SetCommunicationErrorCallback([&system_]() { + system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected); + }); + client->SetArticReportTrafficCallback( + [&system_](u32 bytes) { system_.ReportArticTraffic(bytes); }); + client->SetReportArticEventCallback([&system_](u64 event) { + Core::PerfStats::PerfArticEventBits ev = + static_cast(event & 0xFFFFFFFF); + bool set = (event > 32) != 0; + system_.ReportPerfArticEvent(ev, set); + }); + } + + ~Apploader_Artic() override; + + /** + * Returns the type of the file + * @param file FileUtil::IOFile open file + * @return FileType found, or FileType::Error if this loader doesn't know it + */ + static FileType IdentifyType(FileUtil::IOFile& file); + + FileType GetFileType() override { + return IdentifyType(file); + } + + [[nodiscard]] std::span GetPreferredRegions() const override { + return preferred_regions; + } + + ResultStatus Load(std::shared_ptr& process) override; + + std::pair, ResultStatus> LoadCoreVersion() override; + + /** + * Loads the Exheader and returns the system mode for this application. + * @returns A pair with the optional system mode, and and the status. + */ + std::pair, ResultStatus> LoadKernelMemoryMode() override; + + std::pair, ResultStatus> LoadNew3dsHwCapabilities() + override; + + ResultStatus IsExecutable(bool& out_executable) override; + + ResultStatus ReadCode(std::vector& buffer) override; + + ResultStatus ReadIcon(std::vector& buffer) override; + + ResultStatus ReadBanner(std::vector& buffer) override; + + ResultStatus ReadLogo(std::vector& buffer) override; + + ResultStatus ReadProgramId(u64& out_program_id) override; + + ResultStatus ReadExtdataId(u64& out_extdata_id) override; + + ResultStatus ReadRomFS(std::shared_ptr& romfs_file) override; + + ResultStatus ReadUpdateRomFS(std::shared_ptr& romfs_file) override; + + ResultStatus DumpRomFS(const std::string& target_path) override; + + ResultStatus DumpUpdateRomFS(const std::string& target_path) override; + + ResultStatus ReadTitle(std::string& title) override; + + bool SupportsSaveStates() override { + return false; + } + + bool SupportsMultipleInstancesForSameFile() override { + return false; + } + +private: + /** + * Loads .code section into memory for booting + * @param process The newly created process + * @return ResultStatus result of function + */ + ResultStatus LoadExec(std::shared_ptr& process); + + /// Reads the region lockout info in the SMDH and send it to CFG service + /// If an SMDH is not present, the program ID is compared against a list + /// of known system titles to determine the region. + void ParseRegionLockoutInfo(u64 program_id); + + bool LoadExheader(); + + ResultStatus LoadProductInfo(Service::FS::FS_USER::ProductInfo& out); + + ExHeader_Header program_exheader{}; + bool program_exheader_loaded = false; + + std::optional cached_title_id = std::nullopt; + std::optional cached_product_info = std::nullopt; + std::vector cached_icon; + std::vector cached_banner; + std::vector cached_logo; + + std::vector preferred_regions; + + std::string server_address; + std::shared_ptr client; + bool client_connected = false; + + std::shared_ptr main_romfs_reader = nullptr; + std::shared_ptr update_romfs_reader = nullptr; +}; + +} // namespace Loader diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index b282d0a24..f520e4f00 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -9,6 +9,7 @@ #include "core/core.h" #include "core/hle/kernel/process.h" #include "core/loader/3dsx.h" +#include "core/loader/artic.h" #include "core/loader/elf.h" #include "core/loader/ncch.h" @@ -74,6 +75,8 @@ const char* GetFileTypeString(FileType type) { return "ELF"; case FileType::THREEDSX: return "3DSX"; + case FileType::ARTIC: + return "ARTIC"; case FileType::Error: case FileType::Unknown: break; @@ -108,12 +111,39 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileUtil:: case FileType::CCI: return std::make_unique(system, std::move(file), filepath); + case FileType::ARTIC: { + auto strToUInt = [](const std::string& str) -> int { + char* pEnd = NULL; + unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10); + if (*pEnd) + return -1; + return static_cast(ul); + }; + + u16 port = 5543; + std::string server_addr = filename; + auto pos = server_addr.find(":"); + if (pos != server_addr.npos) { + int newVal = strToUInt(server_addr.substr(pos + 1)); + if (newVal >= 0 && newVal <= 0xFFFF) { + port = static_cast(newVal); + server_addr = server_addr.substr(0, pos); + } + } + return std::make_unique(system, server_addr, port); + } + default: return nullptr; } } std::unique_ptr GetLoader(const std::string& filename) { + if (filename.starts_with("articbase://")) { + return GetFileLoader(Core::System::GetInstance(), FileUtil::IOFile(), FileType::ARTIC, + filename.substr(12), ""); + } + FileUtil::IOFile file(filename, "rb"); if (!file.IsOpen()) { LOG_ERROR(Loader, "Failed to load file {}", filename); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 737075249..a47d5ae1d 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -31,6 +31,7 @@ enum class FileType { CIA, ELF, THREEDSX, // 3DSX + ARTIC, }; /** @@ -73,6 +74,7 @@ enum class ResultStatus { ErrorMemoryAllocationFailed, ErrorEncrypted, ErrorGbaTitle, + ErrorArtic, }; constexpr u32 MakeMagic(char a, char b, char c, char d) { @@ -264,6 +266,14 @@ public: return ResultStatus::ErrorNotImplemented; } + virtual bool SupportsSaveStates() { + return true; + } + + virtual bool SupportsMultipleInstancesForSameFile() { + return true; + } + protected: Core::System& system; FileUtil::IOFile file; diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 0d30a9187..c05cf80e5 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -101,6 +101,8 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_ last_stats.frametime = duration_cast(accumulated_frametime).count() / static_cast(system_frames); last_stats.emulation_speed = system_us_per_second.count() / 1'000'000.0; + last_stats.artic_transmitted = static_cast(artic_transmitted) / interval; + last_stats.artic_events.raw = artic_events.raw | prev_artic_event.raw; // Reset counters reset_point = now; @@ -108,6 +110,8 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_ accumulated_frametime = Clock::duration::zero(); system_frames = 0; game_frames = 0; + artic_transmitted = 0; + prev_artic_event.raw &= artic_events.raw; return last_stats; } diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index d2451dbc8..5cd69afb2 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -9,6 +9,7 @@ #include #include #include +#include "common/bit_field.h" #include "common/common_types.h" #include "common/thread.h" @@ -25,6 +26,28 @@ public: using Clock = std::chrono::high_resolution_clock; + enum class PerfArticEventBits { + NONE = 0, + ARTIC_SAVE_DATA = (1 << 0), + ARTIC_EXT_DATA = (1 << 1), + ARTIC_BOSS_EXT_DATA = (1 << 2), + ARTIC_SHARED_EXT_DATA = (1 << 3), + }; + union PerfArticEvents { + u32 raw{}; + BitField<0, 1, u32> artic_save_data; + BitField<1, 1, u32> artic_ext_data; + BitField<2, 1, u32> artic_boss_ext_data; + BitField<3, 1, u32> artic_shared_ext_data; + + void Set(PerfArticEventBits event, bool set) { + raw = (raw & ~static_cast(event)) | (set ? static_cast(event) : 0); + } + bool Get(PerfArticEventBits event) { + return (raw & static_cast(event)) != 0; + } + }; + struct Results { /// System FPS (LCD VBlanks) in Hz double system_fps; @@ -34,6 +57,10 @@ public: double frametime; /// Ratio of walltime / emulated time elapsed double emulation_speed; + /// Artic base bytes per second + double artic_transmitted = 0; + /// Artic base events + PerfArticEvents artic_events{}; }; void BeginSystemFrame(); @@ -55,6 +82,19 @@ public: */ double GetLastFrameTimeScale() const; + void AddArticBaseTraffic(u32 bytes) { + artic_transmitted += bytes; + } + + void ReportPerfArticEvent(PerfArticEventBits event, bool set) { + if (set) { + artic_events.Set(event, set); + prev_artic_event.Set(event, set); + } else { + artic_events.Set(event, set); + } + } + private: mutable std::mutex object_mutex; @@ -77,6 +117,12 @@ private: u32 system_frames = 0; /// Cumulative number of game frames (GSP frame submissions) since last reset u32 game_frames = 0; + /// Cumulative number of transmitted artic base traffic + std::atomic artic_transmitted = 0; + // System events that affect performance + PerfArticEvents artic_events; + + PerfArticEvents prev_artic_event; /// Point when the previous system frame ended Clock::time_point previous_frame_end = reset_point; diff --git a/src/core/savestate.cpp b/src/core/savestate.cpp index bc1f53684..bb750d0c9 100644 --- a/src/core/savestate.cpp +++ b/src/core/savestate.cpp @@ -13,6 +13,7 @@ #include "common/swap.h" #include "common/zstd_compression.h" #include "core/core.h" +#include "core/loader/loader.h" #include "core/movie.h" #include "core/savestate.h" #include "core/savestate_data.h" @@ -122,6 +123,12 @@ std::vector ListSaveStates(u64 program_id, u64 movie_id) { } void System::SaveState(u32 slot) const { + if (app_loader) { + if (!app_loader->SupportsSaveStates()) { + throw std::runtime_error("The current app loader doesn't support save states"); + } + } + std::ostringstream sstream{std::ios_base::binary}; // Serialize oarchive oa{sstream}; @@ -164,6 +171,11 @@ void System::SaveState(u32 slot) const { } void System::LoadState(u32 slot) { + if (app_loader) { + if (!app_loader->SupportsSaveStates()) { + throw std::runtime_error("The current app loader doesn't support save states"); + } + } if (Network::GetRoomMember().lock()->IsConnected()) { throw std::runtime_error("Unable to load while connected to multiplayer"); } diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index d8058dfd2..3feb6f479 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,6 +1,9 @@ add_library(network STATIC announce_multiplayer_session.cpp announce_multiplayer_session.h + artic_base/artic_base_client.cpp + artic_base/artic_base_client.h + artic_base/artic_base_common.h network.cpp network.h network_settings.cpp @@ -12,6 +15,8 @@ add_library(network STATIC room.h room_member.cpp room_member.h + socket_manager.cpp + socket_manager.h verify_user.cpp verify_user.h ) diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp new file mode 100644 index 000000000..5de531662 --- /dev/null +++ b/src/network/artic_base/artic_base_client.cpp @@ -0,0 +1,735 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "artic_base_client.h" +#include "common/assert.h" +#include "common/logging/log.h" + +#include "chrono" +#include "limits.h" +#include "memory" +#include "sstream" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +#define WSAEAGAIN WSAEWOULDBLOCK +#define WSAEMULTIHOP -1 // Invalid dummy value +#define ERRNO(x) WSA##x +#define GET_ERRNO WSAGetLastError() +#define poll(x, y, z) WSAPoll(x, y, z); +#define SHUT_RD SD_RECEIVE +#define SHUT_WR SD_SEND +#define SHUT_RDWR SD_BOTH +#else +#define ERRNO(x) x +#define GET_ERRNO errno +#define closesocket(x) close(x) +#endif + +// #define DISABLE_PING_TIMEOUT + +namespace Network::ArticBase { + +using namespace std::chrono_literals; + +bool Client::Request::AddParameterS8(s8 parameter) { + if (parameters.size() >= max_param_count) { + LOG_ERROR(Network, "Too many parameters added to method: {}", method_name); + return false; + } + auto& param = parameters.emplace_back(); + param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_8; + std::memcpy(param.data, ¶meter, sizeof(s8)); + return true; +} + +bool Client::Request::AddParameterS16(s16 parameter) { + if (parameters.size() >= max_param_count) { + LOG_ERROR(Network, "Too many parameters added to method: {}", method_name); + return false; + } + auto& param = parameters.emplace_back(); + param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_16; + std::memcpy(param.data, ¶meter, sizeof(s16)); + return true; +} + +bool Client::Request::AddParameterS32(s32 parameter) { + if (parameters.size() >= max_param_count) { + LOG_ERROR(Network, "Too many parameters added to method: {}", method_name); + return false; + } + auto& param = parameters.emplace_back(); + param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_32; + std::memcpy(param.data, ¶meter, sizeof(s32)); + return true; +} + +bool Client::Request::AddParameterS64(s64 parameter) { + if (parameters.size() >= max_param_count) { + LOG_ERROR(Network, "Too many parameters added to method: {}", method_name); + return false; + } + auto& param = parameters.emplace_back(); + param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_64; + std::memcpy(param.data, ¶meter, sizeof(s64)); + return true; +} + +bool Client::Request::AddParameterBuffer(const void* buffer, size_t size) { + if (parameters.size() >= max_param_count) { + LOG_ERROR(Network, "Too many parameters added to method: {}", method_name); + return false; + } + auto& param = parameters.emplace_back(); + if (size <= sizeof(param.data)) { + param.type = ArticBaseCommon::RequestParameterType::IN_SMALL_BUFFER; + std::memcpy(param.data, buffer, size); + param.parameterSize = static_cast(size); + } else { + param.type = ArticBaseCommon::RequestParameterType::IN_BIG_BUFFER; + param.bigBufferID = static_cast(pending_big_buffers.size()); + s32 size_32 = static_cast(size); + std::memcpy(param.data, &size_32, sizeof(size_32)); + pending_big_buffers.push_back(std::make_pair(buffer, size)); + } + return true; +} + +Client::Request::Request(u32 request_id, const std::string& method, size_t max_params) { + method_name = method; + max_param_count = max_params; + request_packet.requestID = request_id; + std::memcpy(request_packet.method.data(), method.data(), + std::min(request_packet.method.size(), method.size())); +} + +Client::~Client() { + StopImpl(false); + + for (auto it = handlers.begin(); it != handlers.end(); it++) { + Handler* h = *it; + h->thread->join(); + delete h; + } + + if (ping_thread.joinable()) { + ping_thread.join(); + } + + SocketManager::DisableSockets(); +} + +bool Client::Connect() { + if (connected) + return true; + + auto str_to_int = [](const std::string& str) -> int { + char* pEnd = NULL; + unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10); + if (*pEnd) + return -1; + return static_cast(ul); + }; + + struct addrinfo hints, *addrinfo; + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_INET; + + LOG_INFO(Network, "Starting Artic Base Client"); + + if (getaddrinfo(address.data(), NULL, &hints, &addrinfo) != 0) { + LOG_ERROR(Network, "Failed to get server address"); + SignalCommunicationError(); + return false; + } + + main_socket = ::socket(AF_INET, SOCK_STREAM, 0); + if (main_socket == -1) { + LOG_ERROR(Network, "Failed to create socket"); + SignalCommunicationError(); + return false; + } + + if (!SetNonBlock(main_socket, true)) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Cannot set non-blocking socket mode"); + SignalCommunicationError(); + return false; + } + + struct sockaddr_in servaddr = {0}; + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = ((struct sockaddr_in*)(addrinfo->ai_addr))->sin_addr.s_addr; + servaddr.sin_port = htons(port); + freeaddrinfo(addrinfo); + + if (!ConnectWithTimeout(main_socket, &servaddr, sizeof(servaddr), 10)) { + closesocket(main_socket); + LOG_ERROR(Network, "Failed to connect"); + SignalCommunicationError(); + return false; + } + + auto version = SendSimpleRequest("VERSION"); + if (version.has_value()) { + int version_value = str_to_int(*version); + if (version_value != SERVER_VERSION) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Incompatible server version: {}", version_value); + SignalCommunicationError(); + return false; + } + } else { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Couldn't fetch server version."); + SignalCommunicationError(); + return false; + } + + auto max_work_size = SendSimpleRequest("MAXSIZE"); + int max_work_size_value = -1; + if (max_work_size.has_value()) { + max_work_size_value = str_to_int(*max_work_size); + } + if (max_work_size_value < 0) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Couldn't fetch server work ram size"); + SignalCommunicationError(); + return false; + } + max_server_work_ram = max_work_size_value; + + auto max_params = SendSimpleRequest("MAXPARAM"); + int max_param_value = -1; + if (max_params.has_value()) { + max_param_value = str_to_int(*max_params); + } + if (max_param_value < 0) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Couldn't fetch server max params"); + SignalCommunicationError(); + return false; + } + max_parameter_count = max_param_value; + + auto worker_ports = SendSimpleRequest("PORTS"); + if (!worker_ports.has_value()) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Couldn't fetch server worker ports"); + SignalCommunicationError(); + return false; + } + std::vector ports; + std::string str_port; + std::stringstream ss_port(worker_ports.value()); + while (std::getline(ss_port, str_port, ',')) { + int port = str_to_int(str_port); + if (port < 0 || port > USHRT_MAX) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Couldn't parse server worker ports"); + SignalCommunicationError(); + return false; + } + ports.push_back(static_cast(port)); + } + if (ports.empty()) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Couldn't parse server worker ports"); + SignalCommunicationError(); + return false; + } + + for (int i = 0; i < 101; i++) { + auto ready_server = SendSimpleRequest("READY"); + if (!ready_server.has_value() || i == 100) { + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); + LOG_ERROR(Network, "Couldn't fetch server readiness"); + SignalCommunicationError(); + return false; + } + if (*ready_server == "1") + break; + std::this_thread::sleep_for(100ms); + } + + ping_thread = std::thread(&Client::PingFunction, this); + + int i = 0; + running_handlers = ports.size(); + for (auto it = ports.begin(); it != ports.end(); it++) { + handlers.push_back(new Handler(*this, static_cast(servaddr.sin_addr.s_addr), *it, i)); + i++; + } + + connected = true; + return true; +} + +void Client::StopImpl(bool from_error) { + bool expected = false; + if (!stopped.compare_exchange_strong(expected, true)) + return; + + if (!from_error) { + SendSimpleRequest("STOP"); + } + + if (ping_thread.joinable()) { + std::scoped_lock l2(ping_cv_mutex); + ping_run = false; + ping_cv.notify_one(); + } + + // Stop handlers + for (auto it = handlers.begin(); it != handlers.end(); it++) { + Handler* handler = *it; + handler->should_run = false; + // Shouldn't matter if the socket is shut down twice + shutdown(handler->handler_socket, SHUT_RDWR); + closesocket(handler->handler_socket); + } + + // Close main socket + shutdown(main_socket, SHUT_RDWR); + closesocket(main_socket); +} + +std::optional> Client::Response::GetResponseBuffer(u32 buffer_id) const { + if (!resp_data_buffer) + return std::nullopt; + + char* resp_data_buffer_end = resp_data_buffer + resp_data_size; + char* resp_data_buffer_start = resp_data_buffer; + while (resp_data_buffer_start + sizeof(ArticBaseCommon::Buffer) < resp_data_buffer_end) { + ArticBaseCommon::Buffer* curr_buffer = + reinterpret_cast(resp_data_buffer_start); + resp_data_buffer_start += sizeof(ArticBaseCommon::Buffer); + if (curr_buffer->bufferID == buffer_id) { + if (curr_buffer->data + curr_buffer->bufferSize <= resp_data_buffer_end) { + return std::make_pair(curr_buffer->data, curr_buffer->bufferSize); + } else { + return std::nullopt; + } + } + resp_data_buffer_start += curr_buffer->bufferSize; + } + return std::nullopt; +} + +std::optional Client::Send(Request& request) { + if (stopped) + return std::nullopt; + + request.request_packet.parameterCount = static_cast(request.parameters.size()); + PendingResponse resp(request); + + { + std::scoped_lock l(recv_map_mutex); + pending_responses[request.request_packet.requestID] = &resp; + } + + auto respPacket = SendRequestPacket(request.request_packet, false, request.parameters); + if (stopped || !respPacket.has_value()) { + std::scoped_lock l(recv_map_mutex); + pending_responses.erase(request.request_packet.requestID); + return std::nullopt; + } + + std::unique_lock cv_lk(resp.cv_mutex); + resp.cv.wait(cv_lk, [&resp]() { return resp.is_done; }); + + return std::optional(std::move(resp.response)); +} + +void Client::SignalCommunicationError() { + StopImpl(true); + LOG_CRITICAL(Network, "Communication error"); + if (communication_error_callback) + communication_error_callback(); +} + +void Client::PingFunction() { + // Max silence time => 7 secs interval + 3 secs wait + 10 seconds timeout = 25 seconds + while (ping_run) { + std::chrono::time_point last = last_sent_request; + if (std::chrono::steady_clock::now() - last > std::chrono::seconds(7)) { +#ifdef DISABLE_PING_TIMEOUT + client->last_sent_request = std::chrono::steady_clock::now(); +#else + auto ping_reply = SendSimpleRequest("PING"); + if (!ping_reply.has_value()) { + SignalCommunicationError(); + break; + } +#endif // DISABLE_PING_TIMEOUT + } + + std::unique_lock lk(ping_cv_mutex); + ping_cv.wait_for(lk, std::chrono::seconds(3)); + } +} + +bool Client::ConnectWithTimeout(SocketHolder sockFD, void* server_addr, size_t server_addr_len, + int timeout_seconds) { + + int res = ::connect(sockFD, (struct sockaddr*)server_addr, static_cast(server_addr_len)); + if (res == -1 && ((GET_ERRNO == ERRNO(EINPROGRESS) || GET_ERRNO == ERRNO(EWOULDBLOCK)))) { + struct timeval tv; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(sockFD, &fdset); + + tv.tv_sec = timeout_seconds; + tv.tv_usec = 0; + int select_res = ::select(static_cast(sockFD + 1), NULL, &fdset, NULL, &tv); +#ifdef _WIN32 + if (select_res == 0) { + return false; + } +#else + bool select_good = false; + if (select_res == 1) { + int so_error; + socklen_t len = sizeof so_error; + + getsockopt(sockFD, SOL_SOCKET, SO_ERROR, &so_error, &len); + + if (so_error == 0) { + select_good = true; + } + } + if (!select_good) { + return false; + } +#endif // _WIN32 + + } else if (res == -1) { + return false; + } + return true; +} + +bool Client::SetNonBlock(SocketHolder sockFD, bool nonBlocking) { + bool blocking = !nonBlocking; +#ifdef _WIN32 + unsigned long nonblocking = (blocking) ? 0 : 1; + int ret = ioctlsocket(sockFD, FIONBIO, &nonblocking); + if (ret == -1) { + return false; + } +#else + int flags = ::fcntl(sockFD, F_GETFL, 0); + if (flags == -1) { + return false; + } + + flags &= ~O_NONBLOCK; + if (!blocking) { // O_NONBLOCK + flags |= O_NONBLOCK; + } + + const int ret = ::fcntl(sockFD, F_SETFL, flags); + if (ret == -1) { + return false; + } +#endif + return true; +} + +bool Client::Read(SocketHolder sockFD, void* buffer, size_t size, + const std::chrono::nanoseconds& timeout) { + size_t read_bytes = 0; + auto before = std::chrono::steady_clock::now(); + while (read_bytes != size) { + int new_read = + ::recv(sockFD, (char*)((uintptr_t)buffer + read_bytes), (int)(size - read_bytes), 0); + if (new_read < 0) { + if (GET_ERRNO == ERRNO(EWOULDBLOCK) && + (timeout == std::chrono::nanoseconds(0) || + std::chrono::steady_clock::now() - before < timeout)) { + continue; + } + read_bytes = 0; + break; + } + if (report_traffic_callback && new_read) { + report_traffic_callback(new_read); + } + read_bytes += new_read; + } + return read_bytes == size; +} + +bool Client::Write(SocketHolder sockFD, const void* buffer, size_t size, + const std::chrono::nanoseconds& timeout) { + size_t write_bytes = 0; + auto before = std::chrono::steady_clock::now(); + while (write_bytes != size) { + int new_written = ::send(sockFD, (const char*)((uintptr_t)buffer + write_bytes), + (int)(size - write_bytes), 0); + if (new_written < 0) { + if (GET_ERRNO == ERRNO(EWOULDBLOCK) && + (timeout == std::chrono::nanoseconds(0) || + std::chrono::steady_clock::now() - before < timeout)) { + continue; + } + write_bytes = 0; + break; + } + if (report_traffic_callback && new_written) { + report_traffic_callback(new_written); + } + write_bytes += new_written; + } + return write_bytes == size; +} + +std::optional Client::SendRequestPacket( + const ArticBaseCommon::RequestPacket& req, bool expect_response, + const std::vector& params, + const std::chrono::nanoseconds& read_timeout) { + std::scoped_lock l(send_mutex); + + if (main_socket == -1) { + return std::nullopt; + } + + if (!Write(main_socket, &req, sizeof(req))) { + LOG_WARNING(Network, "Failed to write to socket"); + SignalCommunicationError(); + return std::nullopt; + } + + if (!params.empty()) { + if (!Write(main_socket, params.data(), + params.size() * sizeof(ArticBaseCommon::RequestParameter))) { + LOG_WARNING(Network, "Failed to write to socket"); + SignalCommunicationError(); + return std::nullopt; + } + } + + ArticBaseCommon::DataPacket resp; + if (expect_response) { + if (!Read(main_socket, &resp, sizeof(resp), read_timeout)) { + LOG_WARNING(Network, "Failed to read from socket"); + SignalCommunicationError(); + return std::nullopt; + } + + if (resp.requestID != req.requestID) { + return std::nullopt; + } + } + + last_sent_request = std::chrono::steady_clock::now(); + return resp; +} + +std::optional Client::SendSimpleRequest(const std::string& method) { + ArticBaseCommon::RequestPacket req{}; + req.requestID = GetNextRequestID(); + const std::string final_method = "$" + method; + if (final_method.size() > sizeof(req.method)) { + return std::nullopt; + } + std::memcpy(req.method.data(), final_method.data(), final_method.size()); + auto resp = SendRequestPacket(req, true, {}, std::chrono::seconds(10)); + if (!resp.has_value() || resp->requestID != req.requestID) { + return std::nullopt; + } + char respBody[sizeof(ArticBaseCommon::DataPacket::dataRaw) + 1] = {0}; + std::memcpy(respBody, resp->dataRaw, sizeof(ArticBaseCommon::DataPacket::dataRaw)); + return respBody; +} + +Client::Handler::Handler(Client& _client, u32 _addr, u16 _port, int _id) + : id(_id), client(_client), addr(_addr), port(_port) { + thread = new std::thread( + [](Handler* handler) { + handler->RunLoop(); + handler->should_run = false; + if (--handler->client.running_handlers == 0) { + handler->client.OnAllHandlersFinished(); + } + }, + this); +} + +void Client::Handler::RunLoop() { + handler_socket = ::socket(AF_INET, SOCK_STREAM, 0); + if (handler_socket == -1) { + LOG_ERROR(Network, "Failed to create socket"); + return; + } + + if (!SetNonBlock(handler_socket, true)) { + closesocket(handler_socket); + client.SignalCommunicationError(); + LOG_ERROR(Network, "Cannot set non-blocking socket mode"); + return; + } + + struct sockaddr_in servaddr = {0}; + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = static_cast(addr); + servaddr.sin_port = htons(port); + + if (!ConnectWithTimeout(handler_socket, &servaddr, sizeof(servaddr), 10)) { + closesocket(handler_socket); + LOG_ERROR(Network, "Failed to connect"); + client.SignalCommunicationError(); + return; + } + + const auto signal_error = [&] { + if (should_run) { + client.SignalCommunicationError(); + } + }; + + ArticBaseCommon::DataPacket dataPacket; + u32 retry_count = 0; + while (should_run) { + if (!client.Read(handler_socket, &dataPacket, sizeof(dataPacket))) { + if (should_run) { + LOG_WARNING(Network, "Failed to read from socket"); + std::this_thread::sleep_for(100ms); + if (++retry_count == 300) { + signal_error(); + break; + } + continue; + } else { + break; + } + } + retry_count = 0; + + PendingResponse* pending_response; + { + std::scoped_lock l(client.recv_map_mutex); + auto it = client.pending_responses.find(dataPacket.requestID); + if (it == client.pending_responses.end()) { + continue; + } + pending_response = it->second; + } + + switch (dataPacket.resp.articResult) { + case ArticBaseCommon::ResponseMethod::ArticResult::SUCCESS: { + pending_response->response.articResult = dataPacket.resp.articResult; + pending_response->response.methodResult = dataPacket.resp.methodResult; + if (dataPacket.resp.bufferSize) { + pending_response->response.resp_data_buffer = + reinterpret_cast(operator new(dataPacket.resp.bufferSize)); + ASSERT_MSG(pending_response->response.resp_data_buffer != nullptr, + "ArticBase Handler: Cannot allocate buffer"); + pending_response->response.resp_data_size = + static_cast(dataPacket.resp.bufferSize); + if (!client.Read(handler_socket, pending_response->response.resp_data_buffer, + dataPacket.resp.bufferSize)) { + signal_error(); + } + } + } break; + case ArticBaseCommon::ResponseMethod::ArticResult::METHOD_NOT_FOUND: { + LOG_ERROR(Network, "Method {} not found by server", + pending_response->request.method_name); + pending_response->response.articResult = dataPacket.resp.articResult; + } break; + + case ArticBaseCommon::ResponseMethod::ArticResult::PROVIDE_INPUT: { + size_t bufferID = static_cast(dataPacket.resp.provideInputBufferID); + if (bufferID >= pending_response->request.pending_big_buffers.size() || + pending_response->request.pending_big_buffers[bufferID].second != + static_cast(dataPacket.resp.bufferSize)) { + LOG_ERROR(Network, "Method {} incorrect big buffer state {}", + pending_response->request.method_name, bufferID); + dataPacket.resp.articResult = + ArticBaseCommon::ResponseMethod::ArticResult::METHOD_ERROR; + if (client.Write(handler_socket, &dataPacket, sizeof(dataPacket))) { + continue; + } else { + signal_error(); + } + } else { + auto& buffer = pending_response->request.pending_big_buffers[bufferID]; + if (client.Write(handler_socket, &dataPacket, sizeof(dataPacket))) { + if (client.Write(handler_socket, buffer.first, buffer.second)) { + continue; + } else { + signal_error(); + } + } else { + signal_error(); + } + } + } break; + case ArticBaseCommon::ResponseMethod::ArticResult::METHOD_ERROR: + default: { + LOG_ERROR(Network, "Method {} error {}", pending_response->request.method_name, + dataPacket.resp.methodResult); + pending_response->response.articResult = dataPacket.resp.articResult; + pending_response->response.methodState = + static_cast(dataPacket.resp.methodResult); + } break; + } + + { + std::scoped_lock l(client.recv_map_mutex); + client.pending_responses.erase(dataPacket.requestID); + } + + { + std::scoped_lock lk(pending_response->cv_mutex); + pending_response->is_done = true; + pending_response->cv.notify_one(); + } + } + should_run = false; + shutdown(handler_socket, SHUT_RDWR); + closesocket(handler_socket); +} + +void Client::OnAllHandlersFinished() { + // If no handlers are running, signal all pending requests so that + // they don't become stuck. + std::scoped_lock l(recv_map_mutex); + for (auto& [id, response] : pending_responses) { + std::scoped_lock l2(response->cv_mutex); + response->is_done = true; + response->cv.notify_one(); + } + pending_responses.clear(); +} + +} // namespace Network::ArticBase \ No newline at end of file diff --git a/src/network/artic_base/artic_base_client.h b/src/network/artic_base/artic_base_client.h new file mode 100644 index 000000000..4e9bb4ec8 --- /dev/null +++ b/src/network/artic_base/artic_base_client.h @@ -0,0 +1,288 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "condition_variable" +#include "cstring" +#include "functional" +#include "map" +#include "memory" +#include "mutex" +#include "optional" +#include "string" +#include "thread" +#include "utility" + +#include "artic_base_common.h" +#include "network/socket_manager.h" + +#ifdef _WIN32 +using SocketHolder = unsigned long long; +#else +using SocketHolder = int; +#endif // _WIN32 + +namespace Network::ArticBase { + +class Client { +public: + class Request { + public: + bool AddParameterS8(s8 parameter); + bool AddParameterU8(u8 parameter) { + return AddParameterS8(static_cast(parameter)); + } + bool AddParameterS16(s16 parameter); + bool AddParameterU16(u16 parameter) { + return AddParameterS16(static_cast(parameter)); + } + bool AddParameterS32(s32 parameter); + bool AddParameterU32(u32 parameter) { + return AddParameterS32(static_cast(parameter)); + } + bool AddParameterS64(s64 parameter); + bool AddParameterU64(u64 parameter) { + return AddParameterS64(static_cast(parameter)); + } + + // NOTE: Buffer pointer must remain alive until the response is received + bool AddParameterBuffer(const void* buffer, size_t bufferSize); + + private: + friend class Client; + Request(u32 request_id, const std::string& method, size_t max_params); + + ArticBaseCommon::RequestPacket request_packet{}; + std::vector parameters; + std::string method_name; + size_t max_param_count; + std::vector> pending_big_buffers; + }; + + Client(const std::string& _address, u16 _port) : address(_address), port(_port) { + SocketManager::EnableSockets(); + } + ~Client(); + + bool Connect(); + bool connected = false; + + size_t GetServerRequestMaxSize() { + return max_server_work_ram; + } + + Request NewRequest(const std::string& method) { + return Request(GetNextRequestID(), method, max_parameter_count); + } + + void Stop() { + StopImpl(false); + } + + void SetCommunicationErrorCallback(const std::function& callback) { + communication_error_callback = callback; + } + + void SetArticReportTrafficCallback(const std::function& callback) { + report_traffic_callback = callback; + } + + void ReportArticEvent(u64 event) { + if (report_artic_event_callback) { + report_artic_event_callback(event); + } + } + void SetReportArticEventCallback(const std::function& callback) { + report_artic_event_callback = callback; + } + +private: + static constexpr const int SERVER_VERSION = 0; + + std::string address; + u16 port; + + SocketHolder main_socket = -1; + std::atomic currRequestID; + u32 GetNextRequestID() { + return currRequestID++; + } + + void SignalCommunicationError(); + std::function communication_error_callback; + + std::function report_artic_event_callback; + + size_t max_server_work_ram = 0; + size_t max_parameter_count = 0; + std::mutex send_mutex; + + std::atomic stopped = false; + + std::atomic> last_sent_request; + std::thread ping_thread; + std::condition_variable ping_cv; + std::mutex ping_cv_mutex; + bool ping_run = true; + + void StopImpl(bool from_error); + + void PingFunction(); + + static bool ConnectWithTimeout(SocketHolder sockFD, void* server_addr, size_t server_addr_len, + int timeout_seconds); + static bool SetNonBlock(SocketHolder sockFD, bool blocking); + bool Read(SocketHolder sockFD, void* buffer, size_t size, + const std::chrono::nanoseconds& timeout = std::chrono::nanoseconds(0)); + bool Write(SocketHolder sockFD, const void* buffer, size_t size, + const std::chrono::nanoseconds& timeout = std::chrono::nanoseconds(0)); + std::function report_traffic_callback; + + std::optional SendRequestPacket( + const ArticBaseCommon::RequestPacket& req, bool expect_response, + const std::vector& params, + const std::chrono::nanoseconds& read_timeout = std::chrono::nanoseconds(0)); + std::optional SendSimpleRequest(const std::string& method); + + class Handler { + public: + Handler(Client& _client, u32 _addr, u16 _port, int _id); + ~Handler() { + delete thread; + } + void RunLoop(); + + int id = 0; + bool should_run = true; + SocketHolder handler_socket = -1; + std::thread* thread = nullptr; + + private: + Client& client; + u32 addr; + u16 port; + }; + + class PendingResponse; + +public: + class Response { + public: + Response() {} + Response(Response& other) + : articResult(other.articResult), methodResult(other.methodResult), + resp_data_size(other.resp_data_size) { + if (resp_data_size) { + resp_data_buffer = reinterpret_cast(operator new(resp_data_size)); + std::memcpy(resp_data_buffer, other.resp_data_buffer, resp_data_size); + } + } + Response(Response&& other) noexcept + : articResult(other.articResult), methodResult(other.methodResult), + resp_data_buffer(std::exchange(other.resp_data_buffer, nullptr)), + resp_data_size(other.resp_data_size) {} + + Response& operator=(Response& other) { + articResult = other.articResult; + methodResult = other.methodResult; + resp_data_size = other.resp_data_size; + if (resp_data_size) { + resp_data_buffer = reinterpret_cast(operator new(resp_data_size)); + std::memcpy(resp_data_buffer, other.resp_data_buffer, resp_data_size); + } + return *this; + } + + Response& operator=(Response&& other) noexcept { + articResult = other.articResult; + methodResult = other.methodResult; + resp_data_size = other.resp_data_size; + resp_data_buffer = std::exchange(other.resp_data_buffer, nullptr); + return *this; + } + + ~Response() { + if (resp_data_buffer) { + operator delete(resp_data_buffer); + } + } + + bool Succeeded() const { + return articResult == ArticBaseCommon::ResponseMethod::ArticResult::SUCCESS; + } + + int GetMethodResult() const { + return methodResult; + } + + std::optional> GetResponseBuffer(u32 buffer_id) const; + + std::optional GetResponseS32(u32 buffer_id) const { + auto buf = GetResponseBuffer(buffer_id); + if (!buf.has_value() || buf->second != sizeof(s32)) { + return std::nullopt; + } + return *reinterpret_cast(buf->first); + } + + std::optional GetResponseS64(u32 buffer_id) const { + auto buf = GetResponseBuffer(buffer_id); + if (!buf.has_value() || buf->second != sizeof(s64)) { + return std::nullopt; + } + return *reinterpret_cast(buf->first); + } + + std::optional GetResponseU64(u32 buffer_id) const { + auto buf = GetResponseBuffer(buffer_id); + if (!buf.has_value() || buf->second != sizeof(u64)) { + return std::nullopt; + } + return *reinterpret_cast(buf->first); + } + + private: + friend class Client; + friend class Client::Handler; + friend class PendingResponse; + + // Start in error state in case the request is not fullfilled properly. + ArticBaseCommon::ResponseMethod::ArticResult articResult = + ArticBaseCommon::ResponseMethod::ArticResult::METHOD_ERROR; + union { + ArticBaseCommon::MethodState methodState = + ArticBaseCommon::MethodState::INTERNAL_METHOD_ERROR; + int methodResult; + }; + char* resp_data_buffer{}; + size_t resp_data_size = 0; + }; + + std::optional Send(Request& request); + +private: + class PendingResponse { + public: + bool is_done = false; + + private: + friend class Client; + friend class Client::Handler; + PendingResponse(const Request& req) : request(req) {} + std::condition_variable cv; + std::mutex cv_mutex; + + const Request& request; + + Response response{}; + }; + + std::mutex recv_map_mutex; + std::map pending_responses; + + std::vector handlers; + std::atomic running_handlers; + void OnAllHandlersFinished(); +}; +} // namespace Network::ArticBase diff --git a/src/network/artic_base/artic_base_common.h b/src/network/artic_base/artic_base_common.h new file mode 100644 index 000000000..671da6e99 --- /dev/null +++ b/src/network/artic_base/artic_base_common.h @@ -0,0 +1,92 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include +#include "common/common_types.h" + +namespace Network { +namespace ArticBaseCommon { +enum class MethodState : int { + PARSING_INPUT = 0, + PARAMETER_TYPE_MISMATCH = 1, + PARAMETER_COUNT_MISMATCH = 2, + BIG_BUFFER_READ_FAIL = 3, + BIG_BUFFER_WRITE_FAIL = 4, + OUT_OF_MEMORY = 5, + + GENERATING_OUTPUT = 6, + UNEXPECTED_PARSING_INPUT = 7, + OUT_OF_MEMORY_OUTPUT = 8, + + INTERNAL_METHOD_ERROR = 9, + FINISHED = 10, +}; +enum class RequestParameterType : u16 { + IN_INTEGER_8 = 0, + IN_INTEGER_16 = 1, + IN_INTEGER_32 = 2, + IN_INTEGER_64 = 3, + IN_SMALL_BUFFER = 4, + IN_BIG_BUFFER = 5, +}; +struct RequestParameter { + RequestParameterType type{}; + union { + u16 parameterSize{}; + u16 bigBufferID; + }; + + char data[0x1C]{}; +}; +struct RequestPacket { + u32 requestID{}; + std::array method{}; + u32 parameterCount{}; +}; +static_assert(sizeof(RequestPacket) == 0x28); + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4200) +#endif +struct Buffer { + u32 bufferID; + u32 bufferSize; + + char data[]; +}; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +struct ResponseMethod { + enum class ArticResult : u32 { + SUCCESS = 0, + METHOD_NOT_FOUND = 1, + METHOD_ERROR = 2, + PROVIDE_INPUT = 3, + }; + ArticResult articResult{}; + union { + int methodResult{}; + int provideInputBufferID; + }; + int bufferSize{}; + u8 padding[0x10]{}; +}; + +struct DataPacket { + DataPacket() {} + u32 requestID{}; + + union { + char dataRaw[0x1C]{}; + ResponseMethod resp; + }; +}; + +static_assert(sizeof(DataPacket) == 0x20); +}; // namespace ArticBaseCommon +} // namespace Network diff --git a/src/network/socket_manager.cpp b/src/network/socket_manager.cpp new file mode 100644 index 000000000..0157e3e84 --- /dev/null +++ b/src/network/socket_manager.cpp @@ -0,0 +1,31 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef _WIN32 +#include +#include +#endif + +#include "socket_manager.h" + +namespace Network { +std::atomic SocketManager::count = 0; + +void SocketManager::EnableSockets() { + if (count++ == 0) { +#ifdef _WIN32 + WSADATA data; + WSAStartup(MAKEWORD(2, 2), &data); +#endif + } +} + +void SocketManager::DisableSockets() { + if (--count == 0) { +#ifdef _WIN32 + WSACleanup(); +#endif + } +} +} // namespace Network \ No newline at end of file diff --git a/src/network/socket_manager.h b/src/network/socket_manager.h new file mode 100644 index 000000000..f54aef2c4 --- /dev/null +++ b/src/network/socket_manager.h @@ -0,0 +1,19 @@ +// Copyright 2024 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "atomic" +#include "common/common_types.h" + +namespace Network { +class SocketManager { +public: + static void EnableSockets(); + static void DisableSockets(); + +private: + SocketManager(); + static std::atomic count; +}; +} // namespace Network \ No newline at end of file From 71eca05af1b8784b2dc7df9f3fe2f0594424ea0d Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Mon, 13 May 2024 10:54:52 +0200 Subject: [PATCH 03/30] Fix PTM ext data creation after Artic Base changes. (#111) --- src/core/file_sys/archive_extsavedata.cpp | 18 ++++++++++++------ src/core/file_sys/archive_extsavedata.h | 4 +++- src/core/hle/service/ptm/ptm.cpp | 3 ++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index 54fc25ee5..4e9ea6ef8 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -289,8 +289,13 @@ ResultVal> ArchiveFactory_ExtSaveData::Open(cons Result ArchiveFactory_ExtSaveData::FormatAsExtData(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u8 unknown, u64 program_id, u64 total_size, - std::span icon) { + std::optional> icon) { if (IsUsingArtic()) { + if (!icon.has_value()) { + LOG_ERROR(Service_FS, "No icon provided while using Artic Base"); + return ResultUnknown; + } + ExtSaveDataArchivePath path_data; std::memcpy(&path_data, path.AsBinary().data(), sizeof(path_data)); @@ -307,7 +312,7 @@ Result ArchiveFactory_ExtSaveData::FormatAsExtData(const Path& path, req.AddParameterU32(format_info.number_directories); req.AddParameterU32(format_info.number_files); req.AddParameterU64(total_size); - req.AddParameterBuffer(icon.data(), icon.size()); + req.AddParameterBuffer(icon->data(), icon->size()); return ArticArchive::RespResult(artic_client->Send(req)); } else { @@ -330,10 +335,11 @@ Result ArchiveFactory_ExtSaveData::FormatAsExtData(const Path& path, file.WriteBytes(&format_info, sizeof(format_info)); - FileUtil::IOFile icon_file(FileSys::GetExtSaveDataPath(GetMountPoint(), path) + "icon", - "wb"); - icon_file.WriteBytes(icon.data(), icon.size()); - + if (icon.has_value()) { + FileUtil::IOFile icon_file(FileSys::GetExtSaveDataPath(GetMountPoint(), path) + "icon", + "wb"); + icon_file.WriteBytes(icon->data(), icon->size()); + } return ResultSuccess; } } diff --git a/src/core/file_sys/archive_extsavedata.h b/src/core/file_sys/archive_extsavedata.h index e89909ec3..29589d418 100644 --- a/src/core/file_sys/archive_extsavedata.h +++ b/src/core/file_sys/archive_extsavedata.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -51,7 +52,8 @@ public: }; Result FormatAsExtData(const Path& path, const FileSys::ArchiveFormatInfo& format_info, - u8 unknown, u64 program_id, u64 total_size, std::span icon); + u8 unknown, u64 program_id, u64 total_size, + std::optional> icon); Result DeleteExtData(Service::FS::MediaType media_type, u8 unknown, u32 high, u32 low); diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index fb3a51b6b..a9851a19b 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -158,7 +158,8 @@ static void WriteGameCoinData(GameCoin gamecoin_data) { // If the archive didn't exist, create the files inside if (archive_result.Code() == FileSys::ResultNotFormatted) { // Format the archive to create the directories - extdata_archive_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0); + extdata_archive_factory.FormatAsExtData(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0, + std::nullopt); // Open it again to get a valid archive now that the folder exists archive = extdata_archive_factory.Open(archive_path, 0).Unwrap(); // Create the game coin file From 5d2150c67ce7a1f2823ef36987cca62691dbdd42 Mon Sep 17 00:00:00 2001 From: Reg Tiangha Date: Wed, 15 May 2024 03:16:57 -0600 Subject: [PATCH 04/30] citra_qt: Add Open Log Folder option to Help menu (#121) --- src/citra_qt/main.cpp | 4 ++++ src/citra_qt/main.ui | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index efe2bc05b..16a41ee85 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -941,6 +941,10 @@ void GMainWindow::ConnectMenuEvents() { // Help connect_menu(ui->action_Open_Citra_Folder, &GMainWindow::OnOpenCitraFolder); + connect_menu(ui->action_Open_Log_Folder, []() { + QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + }); connect_menu(ui->action_FAQ, []() { QDesktopServices::openUrl(QUrl(QStringLiteral("https://citra-emu.org/wiki/faq/"))); }); diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 552f60866..633265fcd 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -203,6 +203,7 @@ + @@ -479,6 +480,14 @@ Fullscreen + + + Open Log Folder + + + Opens the Citra Log folder + + Modify Citra Install From 893dad57b25b193edabce1be6c13ea9f75d76cae Mon Sep 17 00:00:00 2001 From: OpenSauce Date: Wed, 15 May 2024 11:25:56 +0000 Subject: [PATCH 05/30] Android: Remember last entered Artic Base server address (#113) * Android: Remember last entered Artic Base server address * Android: Updated string key for last Artic Base address `lastArticBaseAddr` --> `last_artic_base_addr` Co-authored-by: PabloMK7 --------- Co-authored-by: PabloMK7 --- .../org/citra/citra_emu/fragments/HomeSettingsFragment.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt index 091616054..fd026a32b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/HomeSettingsFragment.kt @@ -89,7 +89,7 @@ class HomeSettingsFragment : Fragment() { { val inflater = LayoutInflater.from(context) val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater) - var textInputValue: String = "" + var textInputValue: String = preferences.getString("last_artic_base_addr", "")!! inputBinding.editTextInput.setText(textInputValue) inputBinding.editTextInput.doOnTextChanged { text, _, _, _ -> @@ -102,6 +102,9 @@ class HomeSettingsFragment : Fragment() { .setTitle(getString(R.string.artic_base_enter_address)) .setPositiveButton(android.R.string.ok) { _, _ -> if (textInputValue.isNotEmpty()) { + preferences.edit() + .putString("last_artic_base_addr", textInputValue) + .apply() val menu = Game( title = getString(R.string.artic_base), path = "articbase://$textInputValue", From 05cccb585d7c248784e547ae65ca595823c385c7 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 16 May 2024 00:03:40 +0200 Subject: [PATCH 06/30] Artic Base: Fix out of bounds cache reads (#127) --- src/common/static_lru_cache.h | 8 +++++ src/core/file_sys/artic_cache.cpp | 59 +++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/common/static_lru_cache.h b/src/common/static_lru_cache.h index bd692e94e..46c3ce4de 100644 --- a/src/common/static_lru_cache.h +++ b/src/common/static_lru_cache.h @@ -88,8 +88,16 @@ public: } } + void invalidate(const key_type& key) { + auto i = find(key); + if (i != m_list.cend()) { + m_list.erase(i); + } + } + void clear() { m_list.clear(); + m_array.fill(value_type{}); } private: diff --git a/src/core/file_sys/artic_cache.cpp b/src/core/file_sys/artic_cache.cpp index b5c963495..7ce2f0346 100644 --- a/src/core/file_sys/artic_cache.cpp +++ b/src/core/file_sys/artic_cache.cpp @@ -25,13 +25,20 @@ ResultVal ArticCache::Read(s32 file_handle, std::size_t offset, std auto res = ReadFromArtic(file_handle, reinterpret_cast(big_cache_entry.second.data()), length, offset); - if (res.Failed()) + if (res.Failed()) { + big_cache.invalidate(std::make_pair(offset, length)); return res; - length = res.Unwrap(); + } + read_progress = res.Unwrap(); } else { - LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, length); + LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, + read_progress); + } + memcpy(buffer, big_cache_entry.second.data(), read_progress); + if (read_progress < length) { + // Invalidate the entry as it is not fully read + big_cache.invalidate(std::make_pair(offset, length)); } - memcpy(buffer, big_cache_entry.second.data(), length); } else { if (segments[0].second < very_big_cache_skip) { std::unique_lock very_big_read_guard(very_big_cache_mutex); @@ -44,28 +51,39 @@ ResultVal ArticCache::Read(s32 file_handle, std::size_t offset, std auto res = ReadFromArtic( file_handle, reinterpret_cast(very_big_cache_entry.second.data()), length, offset); - if (res.Failed()) + if (res.Failed()) { + very_big_cache.invalidate(std::make_pair(offset, length)); return res; - length = res.Unwrap(); + } + read_progress = res.Unwrap(); } else { - LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, length); + LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, + read_progress); + } + memcpy(buffer, very_big_cache_entry.second.data(), read_progress); + if (read_progress < length) { + // Invalidate the entry as it is not fully read + very_big_cache.invalidate(std::make_pair(offset, length)); } - memcpy(buffer, very_big_cache_entry.second.data(), length); } else { LOG_TRACE(Service_FS, "ArticCache SKIP: offset={}, length={}", offset, length); auto res = ReadFromArtic(file_handle, buffer, length, offset); if (res.Failed()) return res; - length = res.Unwrap(); + read_progress = res.Unwrap(); } } - return length; + return read_progress; } // TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function. std::unique_lock read_guard(cache_mutex); + bool read_past_end = false; for (const auto& seg : segments) { + if (read_past_end) { + break; + } std::size_t read_size = cache_line_size; std::size_t page = OffsetToPage(seg.first); // Check if segment is in cache @@ -73,9 +91,28 @@ ResultVal ArticCache::Read(s32 file_handle, std::size_t offset, std if (!cache_entry.first) { // If not found, read from artic and cache the data auto res = ReadFromArtic(file_handle, cache_entry.second.data(), read_size, page); - if (res.Failed()) + if (res.Failed()) { + // Invalidate the requested entry as it is not populated + cache.invalidate(page); + + // In the very unlikely case the file size is a multiple of the cache size, + // and the game request more data than the file size, this will save us from + // returning an incorrect out of bounds error caused by reading at just the very end + // of the file. + constexpr u32 out_of_bounds_read = 714; + if (res.Code().description == out_of_bounds_read) { + return read_progress; + } return res; + } + size_t expected_read_size = read_size; read_size = res.Unwrap(); + // + if (read_size < expected_read_size) { + // Invalidate the requested entry as it is not fully read + cache.invalidate(page); + read_past_end = true; + } LOG_TRACE(Service_FS, "ArticCache MISS: page={}, length={}, into={}", page, seg.second, (seg.first - page)); } else { From 2e9d7bd208c9dec152ce86bb4ebb2a5bab0b1289 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 16 May 2024 00:17:25 +0200 Subject: [PATCH 07/30] Revert "Artic Base: Fix out of bounds cache reads (#127)" (#129) This reverts commit 05cccb585d7c248784e547ae65ca595823c385c7. --- src/common/static_lru_cache.h | 8 ----- src/core/file_sys/artic_cache.cpp | 59 ++++++------------------------- 2 files changed, 11 insertions(+), 56 deletions(-) diff --git a/src/common/static_lru_cache.h b/src/common/static_lru_cache.h index 46c3ce4de..bd692e94e 100644 --- a/src/common/static_lru_cache.h +++ b/src/common/static_lru_cache.h @@ -88,16 +88,8 @@ public: } } - void invalidate(const key_type& key) { - auto i = find(key); - if (i != m_list.cend()) { - m_list.erase(i); - } - } - void clear() { m_list.clear(); - m_array.fill(value_type{}); } private: diff --git a/src/core/file_sys/artic_cache.cpp b/src/core/file_sys/artic_cache.cpp index 7ce2f0346..b5c963495 100644 --- a/src/core/file_sys/artic_cache.cpp +++ b/src/core/file_sys/artic_cache.cpp @@ -25,20 +25,13 @@ ResultVal ArticCache::Read(s32 file_handle, std::size_t offset, std auto res = ReadFromArtic(file_handle, reinterpret_cast(big_cache_entry.second.data()), length, offset); - if (res.Failed()) { - big_cache.invalidate(std::make_pair(offset, length)); + if (res.Failed()) return res; - } - read_progress = res.Unwrap(); + length = res.Unwrap(); } else { - LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, - read_progress); - } - memcpy(buffer, big_cache_entry.second.data(), read_progress); - if (read_progress < length) { - // Invalidate the entry as it is not fully read - big_cache.invalidate(std::make_pair(offset, length)); + LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, length); } + memcpy(buffer, big_cache_entry.second.data(), length); } else { if (segments[0].second < very_big_cache_skip) { std::unique_lock very_big_read_guard(very_big_cache_mutex); @@ -51,39 +44,28 @@ ResultVal ArticCache::Read(s32 file_handle, std::size_t offset, std auto res = ReadFromArtic( file_handle, reinterpret_cast(very_big_cache_entry.second.data()), length, offset); - if (res.Failed()) { - very_big_cache.invalidate(std::make_pair(offset, length)); + if (res.Failed()) return res; - } - read_progress = res.Unwrap(); + length = res.Unwrap(); } else { - LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, - read_progress); - } - memcpy(buffer, very_big_cache_entry.second.data(), read_progress); - if (read_progress < length) { - // Invalidate the entry as it is not fully read - very_big_cache.invalidate(std::make_pair(offset, length)); + LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, length); } + memcpy(buffer, very_big_cache_entry.second.data(), length); } else { LOG_TRACE(Service_FS, "ArticCache SKIP: offset={}, length={}", offset, length); auto res = ReadFromArtic(file_handle, buffer, length, offset); if (res.Failed()) return res; - read_progress = res.Unwrap(); + length = res.Unwrap(); } } - return read_progress; + return length; } // TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function. std::unique_lock read_guard(cache_mutex); - bool read_past_end = false; for (const auto& seg : segments) { - if (read_past_end) { - break; - } std::size_t read_size = cache_line_size; std::size_t page = OffsetToPage(seg.first); // Check if segment is in cache @@ -91,28 +73,9 @@ ResultVal ArticCache::Read(s32 file_handle, std::size_t offset, std if (!cache_entry.first) { // If not found, read from artic and cache the data auto res = ReadFromArtic(file_handle, cache_entry.second.data(), read_size, page); - if (res.Failed()) { - // Invalidate the requested entry as it is not populated - cache.invalidate(page); - - // In the very unlikely case the file size is a multiple of the cache size, - // and the game request more data than the file size, this will save us from - // returning an incorrect out of bounds error caused by reading at just the very end - // of the file. - constexpr u32 out_of_bounds_read = 714; - if (res.Code().description == out_of_bounds_read) { - return read_progress; - } + if (res.Failed()) return res; - } - size_t expected_read_size = read_size; read_size = res.Unwrap(); - // - if (read_size < expected_read_size) { - // Invalidate the requested entry as it is not fully read - cache.invalidate(page); - read_past_end = true; - } LOG_TRACE(Service_FS, "ArticCache MISS: page={}, length={}, into={}", page, seg.second, (seg.first - page)); } else { From 64e3e9ffaaf13c6f08901225b6e3312f6b18b217 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 16 May 2024 00:30:18 +0200 Subject: [PATCH 08/30] Do not use the cache if the read would result in OOB (#130) --- src/core/file_sys/archive_artic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/file_sys/archive_artic.cpp b/src/core/file_sys/archive_artic.cpp index 36f395a23..f0bd658e4 100644 --- a/src/core/file_sys/archive_artic.cpp +++ b/src/core/file_sys/archive_artic.cpp @@ -383,7 +383,7 @@ ResultVal ArticFileBackend::Read(u64 offset, std::size_t length, u8 auto cache = cache_provider->ProvideCache( client, cache_provider->PathsToVector(archive_path, file_path), true); - if (cache != nullptr) { + if (cache != nullptr && (offset + static_cast(length)) < GetSize()) { return cache->Read(file_handle, offset, length, buffer); } From 54aee70e6841a5f4dcc218f9b773bed8f5371a3e Mon Sep 17 00:00:00 2001 From: Reg Tiangha Date: Thu, 23 May 2024 12:21:27 -0600 Subject: [PATCH 09/30] citra_qt: Add scroll bar to System tab (#135) --- .../configuration/configure_system.ui | 1283 +++++++++-------- 1 file changed, 657 insertions(+), 626 deletions(-) diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index 20f585637..71819029e 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -13,640 +13,671 @@ Form - + - - - - - System Settings - - - - - - Enable New 3DS mode - - - - - - - Use LLE applets (if installed) - - - - - - - - 0 - 0 - - - - 10 - - - - - - - Username - - - - - - - Birthday - - - - - - - - - - January - - - - - February - - - - - March - - - - - April - - - - - May - - - - - June - - - - - July - - - - - August - - - - - September - - - - - October - - - - - November - - - - - December - - - - - - - - - - - - - Language - - - - - - - Note: this can be overridden when region setting is auto-select - - - - Japanese (日本語) + + + + 0 + 480 + + + + QFrame::NoFrame + + + 1 + + + true + + + + + 0 + 0 + 422 + 500 + + + + + + + + + System Settings - - - - English - - - - - French (français) - - - - - German (Deutsch) - - - - - Italian (italiano) - - - - - Spanish (español) - - - - - Simplified Chinese (简体中文) - - - - - Korean (한국어) - - - - - Dutch (Nederlands) - - - - - Portuguese (português) - - - - - Russian (Русский) - - - - - Traditional Chinese (正體中文) - - - - - - - - Sound output mode - - - - - - - - Mono - - - - - Stereo - - - - - Surround - - - - - - - - Country - - - - - - - - - - Clock - - - - - - - - System Clock - - - - - Fixed Time - - - - - - - - Startup time - - - - - - - yyyy-MM-ddTHH:mm:ss - - - - - - - Offset time - - - - - - - - - days - - - -2147483648 - - - 2147483647 - - - - - - - HH:mm:ss - - - - - - - - - Initial System Ticks - - - - - - - - Random - - - - - Fixed - - - - - - - - Initial System Ticks Override - - - - - - - - 0 - 0 - - - - 20 - - - - - - - Play Coins: - - - - - - - 300 - - - - - - - Run System Setup when Home Menu is launched - - - - - - - Console ID: - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Regenerate - - - - - - - 3GX Plugin Loader: - - - - - - - Enable 3GX plugin loader - - - - - - - Allow games to change plugin loader state - - - - - - - Download System Files from Nitendo servers - - - - - - - - - - - Minimal - - - - - Old 3DS - - - - - New 3DS - - - - - - - - - JPN - - - - - USA - - - - - EUR - - - - - AUS - - - - - CHN - - - - - KOR - - - - - TWN - - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Download - - - - - - - - - - - - - Real Console Unique Data - - + - - - SecureInfo_A/B - - - - - - - - - - - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Choose - - - - - + + + Enable New 3DS mode + + - - - LocalFriendCodeSeed_A/B - - - - - - - - - - - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Choose - - - - - - - - - - CTCert - - + + + Use LLE applets (if installed) + + - - - - - - - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Choose - - - - - + + + + 0 + 0 + + + + 10 + + - - - - - - - System settings are available only when game is not running. - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + + + Username + + + + + + + Birthday + + + + + + + + + + January + + + + + February + + + + + March + + + + + April + + + + + May + + + + + June + + + + + July + + + + + August + + + + + September + + + + + October + + + + + November + + + + + December + + + + + + + + + + + + + Language + + + + + + + Note: this can be overridden when region setting is auto-select + + + + Japanese (日本語) + + + + + English + + + + + French (français) + + + + + German (Deutsch) + + + + + Italian (italiano) + + + + + Spanish (español) + + + + + Simplified Chinese (简体中文) + + + + + Korean (한국어) + + + + + Dutch (Nederlands) + + + + + Portuguese (português) + + + + + Russian (Русский) + + + + + Traditional Chinese (正體中文) + + + + + + + + Sound output mode + + + + + + + + Mono + + + + + Stereo + + + + + Surround + + + + + + + + Country + + + + + + + + + + Clock + + + + + + + + System Clock + + + + + Fixed Time + + + + + + + + Startup time + + + + + + + yyyy-MM-ddTHH:mm:ss + + + + + + + Offset time + + + + + + + + + days + + + -2147483648 + + + 2147483647 + + + + + + + HH:mm:ss + + + + + + + + + Initial System Ticks + + + + + + + + Random + + + + + Fixed + + + + + + + + Initial System Ticks Override + + + + + + + + 0 + 0 + + + + 20 + + + + + + + Play Coins: + + + + + + + 300 + + + + + + + Run System Setup when Home Menu is launched + + + + + + + Console ID: + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Regenerate + + + + + + + 3GX Plugin Loader: + + + + + + + Enable 3GX plugin loader + + + + + + + Allow games to change plugin loader state + + + + + + + Download System Files from Nitendo servers + + + + + + + + + + + Minimal + + + + + Old 3DS + + + + + New 3DS + + + + + + + + + JPN + + + + + USA + + + + + EUR + + + + + AUS + + + + + CHN + + + + + KOR + + + + + TWN + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Download + + + + + + + + + + + + + Real Console Unique Data + + + + + + SecureInfo_A/B + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + + + + LocalFriendCodeSeed_A/B + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + + + + CTCert + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + + + + + + + System settings are available only when game is not running. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + From e15d4c0d4ae963c721f34fe128713b5176af9a37 Mon Sep 17 00:00:00 2001 From: RocketRobz Date: Thu, 23 May 2024 12:21:54 -0600 Subject: [PATCH 10/30] Run screenshot capture function in paused state by capturing next frame (#116) * Allow screenshot capture in paused state by unpausing to capture next frame * Change `QMessageBox::No` to `QMessageBox::Yes` * Fix formatting * Fix formatting --- src/citra_qt/main.cpp | 62 ++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 16a41ee85..5d3d395a2 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -974,7 +974,7 @@ void GMainWindow::UpdateMenuState() { action->setEnabled(emulation_running); } - ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused); + ui->action_Capture_Screenshot->setEnabled(emulation_running); if (emulation_running && is_paused) { ui->action_Pause->setText(tr("&Continue")); @@ -2412,33 +2412,47 @@ void GMainWindow::OnSaveMovie() { } void GMainWindow::OnCaptureScreenshot() { - if (!emu_thread || !emu_thread->IsRunning()) [[unlikely]] { + if (!emu_thread) [[unlikely]] { return; } - OnPauseGame(); - std::string path = UISettings::values.screenshot_path.GetValue(); - if (!FileUtil::IsDirectory(path)) { - if (!FileUtil::CreateFullPath(path)) { - QMessageBox::information(this, tr("Invalid Screenshot Directory"), - tr("Cannot create specified screenshot directory. Screenshot " - "path is set back to its default value.")); - path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir); - path.append("screenshots/"); - UISettings::values.screenshot_path = path; - }; + const bool was_running = emu_thread->IsRunning(); + + if (was_running || + (QMessageBox::question( + this, tr("Game will unpause"), + tr("The game will be unpaused, and the next frame will be captured. Is this okay?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)) { + if (was_running) { + OnPauseGame(); + } + std::string path = UISettings::values.screenshot_path.GetValue(); + if (!FileUtil::IsDirectory(path)) { + if (!FileUtil::CreateFullPath(path)) { + QMessageBox::information( + this, tr("Invalid Screenshot Directory"), + tr("Cannot create specified screenshot directory. Screenshot " + "path is set back to its default value.")); + path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir); + path.append("screenshots/"); + UISettings::values.screenshot_path = path; + }; + } + + static QRegularExpression expr(QStringLiteral("[\\/:?\"<>|]")); + const std::string filename = game_title.remove(expr).toStdString(); + const std::string timestamp = QDateTime::currentDateTime() + .toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z")) + .toStdString(); + path.append(fmt::format("/{}_{}.png", filename, timestamp)); + + auto* const screenshot_window = + secondary_window->HasFocus() ? secondary_window : render_window; + screenshot_window->CaptureScreenshot( + UISettings::values.screenshot_resolution_factor.GetValue(), + QString::fromStdString(path)); + OnStartGame(); } - - static QRegularExpression expr(QStringLiteral("[\\/:?\"<>|]")); - const std::string filename = game_title.remove(expr).toStdString(); - const std::string timestamp = - QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z")).toStdString(); - path.append(fmt::format("/{}_{}.png", filename, timestamp)); - - auto* const screenshot_window = secondary_window->HasFocus() ? secondary_window : render_window; - screenshot_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor.GetValue(), - QString::fromStdString(path)); - OnStartGame(); } void GMainWindow::OnDumpVideo() { From de1f082e75660c34eb56302b401f195f3a959166 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 1 Jun 2024 23:26:37 +0200 Subject: [PATCH 11/30] Fix HTTP RequestState values (fixes Pretendo Network support with HLE http) (#143) * Fix http RequestState values * Fix formatting --- src/core/hle/service/http/http_c.cpp | 9 +++++---- src/core/hle/service/http/http_c.h | 29 ++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/core/hle/service/http/http_c.cpp b/src/core/hle/service/http/http_c.cpp index 039a43044..8fb9e4300 100644 --- a/src/core/hle/service/http/http_c.cpp +++ b/src/core/hle/service/http/http_c.cpp @@ -381,10 +381,10 @@ void Context::MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_in if (!client->send(request, response, error)) { LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); - state = RequestState::TimedOut; + state = RequestState::Completed; } else { LOG_DEBUG(Service_HTTP, "Request successful"); - state = RequestState::ReadyToDownloadContent; + state = RequestState::ReceivingBody; } } @@ -439,10 +439,10 @@ void Context::MakeRequestSSL(httplib::Request& request, const URLInfo& url_info, if (!client->send(request, response, error)) { LOG_ERROR(Service_HTTP, "Request failed: {}: {}", error, httplib::to_string(error)); - state = RequestState::TimedOut; + state = RequestState::Completed; } else { LOG_DEBUG(Service_HTTP, "Request successful"); - state = RequestState::ReadyToDownloadContent; + state = RequestState::ReceivingBody; } } @@ -696,6 +696,7 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) { http_context.current_copied_data, 0, remaining_data); http_context.current_copied_data += remaining_data; + http_context.state = RequestState::Completed; rb.Push(ResultSuccess); } else { async_data->buffer->Write(http_context.response.body.data() + diff --git a/src/core/hle/service/http/http_c.h b/src/core/hle/service/http/http_c.h index 7f71aebcd..4a89bac27 100644 --- a/src/core/hle/service/http/http_c.h +++ b/src/core/hle/service/http/http_c.h @@ -48,12 +48,29 @@ enum class RequestMethod : u8 { constexpr u32 TotalRequestMethods = 8; enum class RequestState : u8 { - NotStarted = 0x1, // Request has not started yet. - ConnectingToServer = 0x5, // Request in progress, connecting to server. - SendingRequest = 0x6, // Request in progress, sending HTTP request. - ReceivingResponse = 0x7, // Request in progress, receiving HTTP response. - ReadyToDownloadContent = 0x8, // Ready to download the content. - TimedOut = 0xA, // Request timed out? + /// Request has not started yet. + NotStarted = 0x1, + + /// Request in progress, connecting to server. + ConnectingToServer = 0x5, + + /// Request in progress, sending HTTP request. + SendingRequest = 0x6, + + // Request in progress, receiving HTTP response and headers. + ReceivingResponse = 0x7, + + /// Request in progress, receiving HTTP body. The HTTP module may + /// get stuck in this state if the internal receive buffer gets full. + /// Once the user calls ReceiveData it will get unstuck. + ReceivingBody = 0x8, + + /// Request is finished and all data has been received. HTTP transitions + /// to the Completed state shortly afterwards after some cleanup. + Received = 0x9, + + /// Request is completed. + Completed = 0xA, }; enum class PostDataEncoding : u8 { From 09dc3a5592a1a4b4c341e10a0579783c0d74d6d8 Mon Sep 17 00:00:00 2001 From: kongfl888 K Date: Fri, 14 Jun 2024 19:23:07 +0800 Subject: [PATCH 12/30] Fix gcc 13+ compilation and update fmt. (#142) * Soc and artic_bass: gcc 13+ compatibility fix. * externals/fmt: update to HEAD fcd3e1e19. It will fix error. integer_sequence [-Werror=tautological-compare] The updating is helpful and needed. Fmt has gone through two public versions since its last update and has fixed many bugs, including new compiler optimizations. But neither of these two public versions can fix the errors encountered above. We need to switch to a working version. It can be fixed after fmt/8e62172.There are still many optimizations, Such as this one: Std. h c++23 build fix (# 3856) And these: C++23 compatibility: basicstring_view cannot be constructed from nullptr (# 3846) Fix warning C4702 emitted from format.h (MSVC) (#3866) Of course, there are other functional improvements as well. Very helpful. The selected version is the one that has been checked and works well. And synchronously updating local code. * citra_qt/ui: clean up duplicate naming warnings. --- externals/fmt | 2 +- src/citra_qt/configuration/configure_debug.ui | 6 +++--- src/common/logging/formatter.h | 4 ++-- src/core/hle/service/cfg/cfg.cpp | 2 +- src/core/hle/service/ir/extra_hid.cpp | 2 +- src/core/hle/service/ir/ir_user.cpp | 2 +- src/core/hle/service/soc/soc_u.cpp | 11 ++++------- src/core/hle/service/soc/soc_u.h | 4 ++-- src/core/movie.cpp | 1 + src/core/savestate.cpp | 2 +- src/network/artic_base/artic_base_client.cpp | 8 ++++---- src/video_core/renderer_software/sw_clipper.cpp | 1 + src/video_core/renderer_vulkan/vk_instance.cpp | 2 +- src/video_core/renderer_vulkan/vk_shader_util.cpp | 1 + 14 files changed, 24 insertions(+), 24 deletions(-) diff --git a/externals/fmt b/externals/fmt index 2dd4fa874..fcd3e1e19 160000 --- a/externals/fmt +++ b/externals/fmt @@ -1 +1 @@ -Subproject commit 2dd4fa8742fdac36468f8d8ea3e06e78215551f8 +Subproject commit fcd3e1e19c8d2df94bb6cb40d7f1c97a9872cf2b diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index eacf85be9..860df0ffe 100644 --- a/src/citra_qt/configuration/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui @@ -86,7 +86,7 @@ - + @@ -100,7 +100,7 @@ - + @@ -125,7 +125,7 @@ CPU - + diff --git a/src/common/logging/formatter.h b/src/common/logging/formatter.h index ad6adb143..1bfd534b5 100644 --- a/src/common/logging/formatter.h +++ b/src/common/logging/formatter.h @@ -12,9 +12,9 @@ #if FMT_VERSION >= 80100 template struct fmt::formatter, char>> - : formatter> { + : fmt::formatter> { template - auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { + auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { return fmt::formatter>::format( static_cast>(value), ctx); } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 864369215..1fa50e1df 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include "common/archives.h" #include "common/file_util.h" #include "common/logging/log.h" diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp index e159a70fe..2483a27f0 100644 --- a/src/core/hle/service/ir/extra_hid.cpp +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include +#include #include "common/alignment.h" #include "common/settings.h" #include "core/core_timing.h" diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index add0eb3d0..597fdc4b6 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include "common/archives.h" #include "common/swap.h" #include "core/core.h" diff --git a/src/core/hle/service/soc/soc_u.cpp b/src/core/hle/service/soc/soc_u.cpp index 1ef35da26..a40084249 100644 --- a/src/core/hle/service/soc/soc_u.cpp +++ b/src/core/hle/service/soc/soc_u.cpp @@ -2236,18 +2236,15 @@ std::optional SOC_U::GetDefaultInterfaceInfo() { } InterfaceInfo ret; -#ifdef _WIN32 - SOCKET sock_fd = -1; -#else - int sock_fd = -1; -#endif + + SocketHolder::SOCKET sock_fd = -1; bool interface_found = false; struct sockaddr_in s_in = {.sin_family = AF_INET, .sin_port = htons(53), .sin_addr = {}}; s_in.sin_addr.s_addr = inet_addr("8.8.8.8"); socklen_t s_info_len = sizeof(struct sockaddr_in); sockaddr_in s_info; - if (static_cast(sock_fd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) { + if ((sock_fd = ::socket(AF_INET, SOCK_STREAM, 0)) == static_cast(-1)) { return std::nullopt; } @@ -2265,7 +2262,7 @@ std::optional SOC_U::GetDefaultInterfaceInfo() { #ifdef _WIN32 sock_fd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); - if (static_cast(sock_fd) == SOCKET_ERROR) { + if (sock_fd == static_cast(SOCKET_ERROR)) { return std::nullopt; } diff --git a/src/core/hle/service/soc/soc_u.h b/src/core/hle/service/soc/soc_u.h index 2a7cfa8e8..b66607698 100644 --- a/src/core/hle/service/soc/soc_u.h +++ b/src/core/hle/service/soc/soc_u.h @@ -25,11 +25,11 @@ namespace Service::SOC { struct SocketHolder { #ifdef _WIN32 using SOCKET = unsigned long long; - SOCKET socket_fd; ///< The socket descriptor #else - int socket_fd; ///< The socket descriptor + using SOCKET = int; #endif // _WIN32 + SOCKET socket_fd; ///< The socket descriptor bool blocking = true; ///< Whether the socket is blocking or not. bool isGlobal = false; bool shutdown_rd = false; diff --git a/src/core/movie.cpp b/src/core/movie.cpp index c7dd23c90..ee6bf3577 100644 --- a/src/core/movie.cpp +++ b/src/core/movie.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "common/archives.h" #include "common/bit_field.h" #include "common/file_util.h" diff --git a/src/core/savestate.cpp b/src/core/savestate.cpp index bb750d0c9..ad8e57ffb 100644 --- a/src/core/savestate.cpp +++ b/src/core/savestate.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include "common/archives.h" #include "common/file_util.h" #include "common/logging/log.h" diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp index 5de531662..460d8f9ee 100644 --- a/src/network/artic_base/artic_base_client.cpp +++ b/src/network/artic_base/artic_base_client.cpp @@ -163,7 +163,7 @@ bool Client::Connect() { } main_socket = ::socket(AF_INET, SOCK_STREAM, 0); - if (main_socket == -1) { + if (main_socket == static_cast(-1)) { LOG_ERROR(Network, "Failed to create socket"); SignalCommunicationError(); return false; @@ -249,7 +249,7 @@ bool Client::Connect() { std::stringstream ss_port(worker_ports.value()); while (std::getline(ss_port, str_port, ',')) { int port = str_to_int(str_port); - if (port < 0 || port > USHRT_MAX) { + if (port < 0 || port > static_cast(USHRT_MAX)) { shutdown(main_socket, SHUT_RDWR); closesocket(main_socket); LOG_ERROR(Network, "Couldn't parse server worker ports"); @@ -518,7 +518,7 @@ std::optional Client::SendRequestPacket( const std::chrono::nanoseconds& read_timeout) { std::scoped_lock l(send_mutex); - if (main_socket == -1) { + if (main_socket == static_cast(-1)) { return std::nullopt; } @@ -586,7 +586,7 @@ Client::Handler::Handler(Client& _client, u32 _addr, u16 _port, int _id) void Client::Handler::RunLoop() { handler_socket = ::socket(AF_INET, SOCK_STREAM, 0); - if (handler_socket == -1) { + if (handler_socket == static_cast(-1)) { LOG_ERROR(Network, "Failed to create socket"); return; } diff --git a/src/video_core/renderer_software/sw_clipper.cpp b/src/video_core/renderer_software/sw_clipper.cpp index 03a287a37..531eb0175 100644 --- a/src/video_core/renderer_software/sw_clipper.cpp +++ b/src/video_core/renderer_software/sw_clipper.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "video_core/pica/regs_texturing.h" #include "video_core/renderer_software/sw_clipper.h" diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 8f77f25e0..2d91ff220 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "common/assert.h" #include "common/settings.h" diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp index 09e3eb883..3d2f2f1d1 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include From 4f174f1c0b81266170aa8dac226be1b772f4c94d Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 15 Jun 2024 20:49:08 +0200 Subject: [PATCH 13/30] Fix crash when cubemap face id is invalid (#154) * Fix crash when cubemap face id is invalid * make variable const --- src/video_core/rasterizer_cache/rasterizer_cache.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index cd9c080ef..0144cbd61 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -647,10 +647,11 @@ typename T::Surface& RasterizerCache::GetTextureCube(const TextureCubeConfig& Surface& cube_surface = slot_surfaces[cube.surface_id]; for (u32 i = 0; i < addresses.size(); i++) { - if (!addresses[i]) { + const SurfaceId& face_id = cube.face_ids[i]; + if (!addresses[i] || !face_id) { continue; } - Surface& surface = slot_surfaces[cube.face_ids[i]]; + Surface& surface = slot_surfaces[face_id]; if (cube.ticks[i] == surface.modification_tick) { continue; } From f782903587de11a0245039adbbaa4d5a804c81e5 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 4 Jul 2024 06:41:34 +0000 Subject: [PATCH 14/30] Y2R: Set is_busy_conversion to false when stopping conversion (#165) --- src/core/hle/service/cam/y2r_u.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/hle/service/cam/y2r_u.cpp b/src/core/hle/service/cam/y2r_u.cpp index 10194f853..0c6f97a14 100644 --- a/src/core/hle/service/cam/y2r_u.cpp +++ b/src/core/hle/service/cam/y2r_u.cpp @@ -544,6 +544,7 @@ void Y2R_U::StopConversion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); if (is_busy_conversion) { + is_busy_conversion = false; system.CoreTiming().RemoveEvent(completion_signal_event); } From d2bd58287ca08af46830cea1f17ae4593871a4b8 Mon Sep 17 00:00:00 2001 From: Wunk Date: Wed, 3 Jul 2024 23:42:31 -0700 Subject: [PATCH 15/30] shader_jit/tests: Test both the shader interpreter and jit (#72) * shader_jit/tests: Test both the shader interpreter and jit Uses Catch2's `TEMPLATE_TEST_CASE`-feature to test both the JIT and the interpreter. * shader_jit/tests: Use generator-expressions for nested-loop test cases Tests more permutations of inputs than just the two it had before * shader/tests: Refactor `shader_jit` tests to just `shader` tests Since these tests will test both the interpreter and the jit, they are no longer jit-specific tests and are more general shader-tests. * shaders/tests: Disable Nested-Loop `ShaderInterpreterTest` test Restoring loop-state on nested loops is bugged on the ShaderInterpreter. * shader/tests: Fix Nested Loop generator expressions --- src/tests/CMakeLists.txt | 2 +- .../shader_jit_compiler.cpp => shader.cpp} | 200 +++++++++--------- 2 files changed, 106 insertions(+), 96 deletions(-) rename src/tests/video_core/{shader/shader_jit_compiler.cpp => shader.cpp} (86%) diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index baddc1ada..4ac9368b1 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -13,7 +13,7 @@ add_executable(tests audio_core/lle/lle.cpp audio_core/audio_fixures.h audio_core/decoder_tests.cpp - video_core/shader/shader_jit_compiler.cpp + video_core/shader.cpp audio_core/merryhime_3ds_audio/merry_audio/merry_audio.cpp audio_core/merryhime_3ds_audio/merry_audio/merry_audio.h audio_core/merryhime_3ds_audio/merry_audio/service_fixture.cpp diff --git a/src/tests/video_core/shader/shader_jit_compiler.cpp b/src/tests/video_core/shader.cpp similarity index 86% rename from src/tests/video_core/shader/shader_jit_compiler.cpp rename to src/tests/video_core/shader.cpp index 01698a2e1..28cb1ec44 100644 --- a/src/tests/video_core/shader/shader_jit_compiler.cpp +++ b/src/tests/video_core/shader.cpp @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include #include "video_core/pica/shader_setup.h" @@ -73,18 +75,19 @@ static std::unique_ptr CompileShaderSetup( class ShaderTest { public: explicit ShaderTest(std::initializer_list code) - : shader_setup(CompileShaderSetup(code)) { - shader_jit.Compile(&shader_setup->program_code, &shader_setup->swizzle_data); - } + : shader_setup(CompileShaderSetup(code)) {} explicit ShaderTest(std::unique_ptr input_shader_setup) - : shader_setup(std::move(input_shader_setup)) { - shader_jit.Compile(&shader_setup->program_code, &shader_setup->swizzle_data); - } + : shader_setup(std::move(input_shader_setup)) {} + + virtual ~ShaderTest() = default; + + virtual void RunShader(Pica::ShaderUnit& shader_unit, + std::span inputs) = 0; Common::Vec4f Run(std::span inputs) { Pica::ShaderUnit shader_unit; - RunJit(shader_unit, inputs); + RunShader(shader_unit, inputs); return {shader_unit.output[0].x.ToFloat32(), shader_unit.output[0].y.ToFloat32(), shader_unit.output[0].z.ToFloat32(), shader_unit.output[0].w.ToFloat32()}; } @@ -105,24 +108,23 @@ public: return Run(std::vector{inputs}); } - void RunJit(Pica::ShaderUnit& shader_unit, std::span inputs) { - for (std::size_t i = 0; i < inputs.size(); ++i) { - const Common::Vec4f& input = inputs[i]; - shader_unit.input[i].x = Pica::f24::FromFloat32(input.x); - shader_unit.input[i].y = Pica::f24::FromFloat32(input.y); - shader_unit.input[i].z = Pica::f24::FromFloat32(input.z); - shader_unit.input[i].w = Pica::f24::FromFloat32(input.w); - } - shader_unit.temporary.fill(Common::Vec4::AssignToAll(Pica::f24::Zero())); - shader_jit.Run(*shader_setup, shader_unit, 0); - } - - void RunJit(Pica::ShaderUnit& shader_unit, float input) { + void Run(Pica::ShaderUnit& shader_unit, float input) { const Common::Vec4f input_vec(input, 0, 0, 0); - RunJit(shader_unit, {&input_vec, 1}); + RunShader(shader_unit, {&input_vec, 1}); } - void RunInterpreter(Pica::ShaderUnit& shader_unit, std::span inputs) { + std::unique_ptr shader_setup; +}; + +class ShaderInterpreterTest : public ShaderTest { +public: + explicit ShaderInterpreterTest(std::initializer_list code) + : ShaderTest(code) {} + + explicit ShaderInterpreterTest(std::unique_ptr input_shader_setup) + : ShaderTest(std::move(input_shader_setup)) {} + + void RunShader(Pica::ShaderUnit& shader_unit, std::span inputs) override { for (std::size_t i = 0; i < inputs.size(); ++i) { const Common::Vec4f& input = inputs[i]; shader_unit.input[i].x = Pica::f24::FromFloat32(input.x); @@ -134,23 +136,46 @@ public: shader_interpreter.Run(*shader_setup, shader_unit); } - void RunInterpreter(Pica::ShaderUnit& shader_unit, float input) { - const Common::Vec4f input_vec(input, 0, 0, 0); - RunInterpreter(shader_unit, {&input_vec, 1}); - } - -public: - JitShader shader_jit; +private: ShaderInterpreter shader_interpreter; - std::unique_ptr shader_setup; }; -TEST_CASE("ADD", "[video_core][shader][shader_jit]") { +class ShaderJitTest : public ShaderTest { +public: + explicit ShaderJitTest(std::initializer_list code) : ShaderTest(code) { + shader_jit.Compile(&shader_setup->program_code, &shader_setup->swizzle_data); + } + + explicit ShaderJitTest(std::unique_ptr input_shader_setup) + : ShaderTest(std::move(input_shader_setup)) { + shader_jit.Compile(&shader_setup->program_code, &shader_setup->swizzle_data); + } + + void RunShader(Pica::ShaderUnit& shader_unit, std::span inputs) override { + for (std::size_t i = 0; i < inputs.size(); ++i) { + const Common::Vec4f& input = inputs[i]; + shader_unit.input[i].x = Pica::f24::FromFloat32(input.x); + shader_unit.input[i].y = Pica::f24::FromFloat32(input.y); + shader_unit.input[i].z = Pica::f24::FromFloat32(input.z); + shader_unit.input[i].w = Pica::f24::FromFloat32(input.w); + } + shader_unit.temporary.fill(Common::Vec4::AssignToAll(Pica::f24::Zero())); + shader_jit.Run(*shader_setup, shader_unit, 0); + } + +private: + JitShader shader_jit; +}; + +#define SHADER_TEST_CASE(NAME, TAG) \ + TEMPLATE_TEST_CASE(NAME, TAG, ShaderInterpreterTest, ShaderJitTest) + +SHADER_TEST_CASE("ADD", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::ADD, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -162,7 +187,7 @@ TEST_CASE("ADD", "[video_core][shader][shader_jit]") { REQUIRE(std::isinf(shader.Run({INFINITY, -1.0f}).x)); } -TEST_CASE("CALL", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("CALL", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); @@ -192,17 +217,17 @@ TEST_CASE("CALL", "[video_core][shader][shader_jit]") { CALL.flow_control.num_instructions = 1; shader_setup->program_code[2] = CALL.hex; - auto shader = ShaderTest(std::move(shader_setup)); + auto shader = TestType(std::move(shader_setup)); REQUIRE(shader.Run(0.f).x == Catch::Approx(1.f)); } -TEST_CASE("DP3", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("DP3", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::DP3, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -213,12 +238,12 @@ TEST_CASE("DP3", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({vec4_one, vec4_one}).x == 3.0f); } -TEST_CASE("DP4", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("DP4", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::DP4, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -229,12 +254,12 @@ TEST_CASE("DP4", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({vec4_one, vec4_one}).x == 4.0f); } -TEST_CASE("DPH", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("DPH", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::DPH, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -246,11 +271,11 @@ TEST_CASE("DPH", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({vec4_zero, vec4_one}).x == 1.0f); } -TEST_CASE("LG2", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("LG2", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::LG2, sh_output, sh_input}, {OpCode::Id::END}, }); @@ -263,11 +288,11 @@ TEST_CASE("LG2", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run(1.e24f).x == Catch::Approx(79.7262742773f)); } -TEST_CASE("EX2", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("EX2", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::EX2, sh_output, sh_input}, {OpCode::Id::END}, }); @@ -281,12 +306,12 @@ TEST_CASE("EX2", "[video_core][shader][shader_jit]") { REQUIRE(std::isinf(shader.Run(800.f).x)); } -TEST_CASE("MUL", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("MUL", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::MUL, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -300,12 +325,12 @@ TEST_CASE("MUL", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({+INFINITY, -INFINITY}).x == -INFINITY); } -TEST_CASE("SGE", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("SGE", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::SGE, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -321,12 +346,12 @@ TEST_CASE("SGE", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({-1.0f, +1.0f}).x == 0.0f); } -TEST_CASE("SLT", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("SLT", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::SLT, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -342,11 +367,11 @@ TEST_CASE("SLT", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({-1.0f, +1.0f}).x == 1.0f); } -TEST_CASE("FLR", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("FLR", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::FLR, sh_output, sh_input1}, {OpCode::Id::END}, }); @@ -359,12 +384,12 @@ TEST_CASE("FLR", "[video_core][shader][shader_jit]") { REQUIRE(std::isinf(shader.Run({INFINITY}).x)); } -TEST_CASE("MAX", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("MAX", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::MAX, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -378,12 +403,12 @@ TEST_CASE("MAX", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({-INFINITY, +INFINITY}).x == +INFINITY); } -TEST_CASE("MIN", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("MIN", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::MIN, sh_output, sh_input1, sh_input2}, {OpCode::Id::END}, }); @@ -397,11 +422,11 @@ TEST_CASE("MIN", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({-INFINITY, +INFINITY}).x == -INFINITY); } -TEST_CASE("RCP", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("RCP", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::RCP, sh_output, sh_input}, {OpCode::Id::END}, }); @@ -422,11 +447,11 @@ TEST_CASE("RCP", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({0.0625f}).x == Catch::Approx(16.0f).margin(0.004f)); } -TEST_CASE("RSQ", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("RSQ", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ {OpCode::Id::RSQ, sh_output, sh_input}, {OpCode::Id::END}, }); @@ -448,12 +473,12 @@ TEST_CASE("RSQ", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({0.0625f}).x == Catch::Approx(4.0f).margin(0.004f)); } -TEST_CASE("Uniform Read", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("Uniform Read", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_c0 = SourceRegister::MakeFloat(0); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ // mova a0.x, sh_input.x {OpCode::Id::MOVA, DestRegister{}, "x", sh_input, "x", SourceRegister{}, "", nihstro::InlineAsm::RelativeAddress::A1}, @@ -481,12 +506,12 @@ TEST_CASE("Uniform Read", "[video_core][shader][shader_jit]") { } } -TEST_CASE("Address Register Offset", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("Address Register Offset", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_c40 = SourceRegister::MakeFloat(40); const auto sh_output = DestRegister::MakeOutput(0); - auto shader = ShaderTest({ + auto shader = TestType({ // mova a0.x, sh_input.x {OpCode::Id::MOVA, DestRegister{}, "x", sh_input, "x", SourceRegister{}, "", nihstro::InlineAsm::RelativeAddress::A1}, @@ -531,12 +556,12 @@ TEST_CASE("Address Register Offset", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run(-129.f) == f_uniforms[40]); } -TEST_CASE("Dest Mask", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("Dest Mask", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); const auto shader = [&sh_input, &sh_output](const char* dest_mask) { - return std::unique_ptr(new ShaderTest{ + return std::unique_ptr(new TestType{ {OpCode::Id::MOV, sh_output, dest_mask, sh_input, "xyzw", SourceRegister{}, ""}, {OpCode::Id::END}, }); @@ -561,7 +586,7 @@ TEST_CASE("Dest Mask", "[video_core][shader][shader_jit]") { REQUIRE(shader("xyzw")->Run({iota_vec}) == iota_vec); } -TEST_CASE("MAD", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("MAD", "[video_core][shader]") { const auto sh_input1 = SourceRegister::MakeInput(0); const auto sh_input2 = SourceRegister::MakeInput(1); const auto sh_input3 = SourceRegister::MakeInput(2); @@ -601,7 +626,7 @@ TEST_CASE("MAD", "[video_core][shader][shader_jit]") { swizzle.SetSelectorSrc3(3, SwizzlePattern::Selector::w); shader_setup->swizzle_data[0] = swizzle.hex; - auto shader = ShaderTest(std::move(shader_setup)); + auto shader = TestType(std::move(shader_setup)); REQUIRE(shader.Run({vec4_zero, vec4_zero, vec4_zero}) == vec4_zero); REQUIRE(shader.Run({vec4_one, vec4_one, vec4_one}) == (vec4_one * 2.0f)); @@ -609,12 +634,14 @@ TEST_CASE("MAD", "[video_core][shader][shader_jit]") { REQUIRE(shader.Run({vec4_nan, vec4_zero, vec4_zero}) == vec4_nan); } -TEST_CASE("Nested Loop", "[video_core][shader][shader_jit]") { +// Nested Loops are bugged on on the Shader-Interpreter at the moment +// SHADER_TEST_CASE("Nested Loop", "[video_core][shader]") { +TEMPLATE_TEST_CASE("Nested Loop", "[video_core][shader]", ShaderJitTest) { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_temp = SourceRegister::MakeTemporary(0); const auto sh_output = DestRegister::MakeOutput(0); - auto shader_test = ShaderTest({ + auto shader_test = TestType({ // clang-format off {OpCode::Id::MOV, sh_temp, sh_input}, {OpCode::Id::LOOP, 0}, @@ -628,8 +655,8 @@ TEST_CASE("Nested Loop", "[video_core][shader][shader_jit]") { }); { - shader_test.shader_setup->uniforms.i[0] = {4, 0, 1, 0}; - shader_test.shader_setup->uniforms.i[1] = {4, 0, 1, 0}; + shader_test.shader_setup->uniforms.i[0] = {(u8)GENERATE(4, 9), 0, (u8)GENERATE(1, 2), 0}; + shader_test.shader_setup->uniforms.i[1] = {(u8)GENERATE(4, 7), 0, (u8)GENERATE(1, 1), 0}; Common::Vec4 loop_parms{shader_test.shader_setup->uniforms.i[0]}; const int expected_aL = loop_parms[1] + ((loop_parms[0] + 1) * loop_parms[2]); @@ -639,37 +666,20 @@ TEST_CASE("Nested Loop", "[video_core][shader][shader_jit]") { input) + input; - Pica::ShaderUnit shader_unit_jit; - shader_test.RunJit(shader_unit_jit, input); + Pica::ShaderUnit shader_unit; + shader_test.Run(shader_unit, input); - REQUIRE(shader_unit_jit.address_registers[2] == expected_aL); - REQUIRE(shader_unit_jit.output[0].x.ToFloat32() == Catch::Approx(expected_out)); - } - { - shader_test.shader_setup->uniforms.i[0] = {9, 0, 2, 0}; - shader_test.shader_setup->uniforms.i[1] = {7, 0, 1, 0}; - - const Common::Vec4 loop_parms{shader_test.shader_setup->uniforms.i[0]}; - const int expected_aL = loop_parms[1] + ((loop_parms[0] + 1) * loop_parms[2]); - const float input = 1.0f; - const float expected_out = (((shader_test.shader_setup->uniforms.i[0][0] + 1) * - (shader_test.shader_setup->uniforms.i[1][0] + 1)) * - input) + - input; - Pica::ShaderUnit shader_unit_jit; - shader_test.RunJit(shader_unit_jit, input); - - REQUIRE(shader_unit_jit.address_registers[2] == expected_aL); - REQUIRE(shader_unit_jit.output[0].x.ToFloat32() == Catch::Approx(expected_out)); + REQUIRE(shader_unit.address_registers[2] == expected_aL); + REQUIRE(shader_unit.output[0].x.ToFloat32() == Catch::Approx(expected_out)); } } -TEST_CASE("Source Swizzle", "[video_core][shader][shader_jit]") { +SHADER_TEST_CASE("Source Swizzle", "[video_core][shader]") { const auto sh_input = SourceRegister::MakeInput(0); const auto sh_output = DestRegister::MakeOutput(0); const auto shader = [&sh_input, &sh_output](const char* swizzle) { - return std::unique_ptr(new ShaderTest{ + return std::unique_ptr(new TestType{ {OpCode::Id::MOV, sh_output, "xyzw", sh_input, swizzle, SourceRegister{}, ""}, {OpCode::Id::END}, }); From 9b39b43e907e5cf08343818b05d4175718ee092b Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sun, 7 Jul 2024 15:10:35 +0200 Subject: [PATCH 16/30] Update dynarmic to the latest version. (#170) --- externals/dynarmic | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/dynarmic b/externals/dynarmic index 30f1a3c62..a41c38024 160000 --- a/externals/dynarmic +++ b/externals/dynarmic @@ -1 +1 @@ -Subproject commit 30f1a3c6289075ef4af08f5ec502be2fc8627a0c +Subproject commit a41c380246d3d9f9874f0f792d234dc0cc17c180 From 1e2be72e5e9a3f9df6940d9a780e45b65b44fdca Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sun, 7 Jul 2024 19:10:47 +0200 Subject: [PATCH 17/30] Artic Base: Fix fallback read/write not being chunked (#171) --- src/core/file_sys/archive_artic.cpp | 79 ++++++++++++++++++----------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/core/file_sys/archive_artic.cpp b/src/core/file_sys/archive_artic.cpp index f0bd658e4..ee36a1cae 100644 --- a/src/core/file_sys/archive_artic.cpp +++ b/src/core/file_sys/archive_artic.cpp @@ -387,24 +387,34 @@ ResultVal ArticFileBackend::Read(u64 offset, std::size_t length, u8 return cache->Read(file_handle, offset, length, buffer); } - auto req = client->NewRequest("FSFILE_Read"); + size_t read_amount = 0; + while (read_amount != length) { + size_t to_read = + std::min(client->GetServerRequestMaxSize() - 0x100, length - read_amount); - req.AddParameterS32(file_handle); - req.AddParameterU64(offset); - req.AddParameterU32(static_cast(length)); + auto req = client->NewRequest("FSFILE_Read"); + req.AddParameterS32(file_handle); + req.AddParameterS64(static_cast(offset + read_amount)); + req.AddParameterS32(static_cast(to_read)); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded()) + return Result(-1); - auto resp = client->Send(req); - auto res = ArticArchive::RespResult(resp); - if (res.IsError()) - return res; + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) + return res; - auto read_buf = resp->GetResponseBuffer(0); - if (!read_buf || read_buf->second > length) { - return std::size_t(0); + auto read_buff = resp->GetResponseBuffer(0); + if (!read_buff.has_value()) + return Result(-1); + size_t actually_read = read_buff->second; + + memcpy(buffer + read_amount, read_buff->first, actually_read); + read_amount += actually_read; + if (actually_read != to_read) + break; } - - memcpy(buffer, read_buf->first, read_buf->second); - return read_buf->second; + return read_amount; } ResultVal ArticFileBackend::Write(u64 offset, std::size_t length, bool flush, @@ -415,25 +425,36 @@ ResultVal ArticFileBackend::Write(u64 offset, std::size_t length, b if (cache != nullptr) { return cache->Write(file_handle, offset, length, buffer, flags); } else { - auto req = client->NewRequest("FSFILE_Write"); + size_t written_amount = 0; + while (written_amount != length) { + size_t to_write = std::min(client->GetServerRequestMaxSize() - 0x100, + length - written_amount); - req.AddParameterS32(file_handle); - req.AddParameterU64(offset); - req.AddParameterU32(static_cast(length)); - req.AddParameterU32(flags); - req.AddParameterBuffer(buffer, length); + auto req = client->NewRequest("FSFILE_Write"); + req.AddParameterS32(file_handle); + req.AddParameterS64(static_cast(offset + written_amount)); + req.AddParameterS32(static_cast(to_write)); + req.AddParameterS32(static_cast(flags)); + req.AddParameterBuffer(buffer + written_amount, to_write); + auto resp = client->Send(req); + if (!resp.has_value() || !resp->Succeeded()) + return Result(-1); - auto resp = client->Send(req); - auto res = ArticArchive::RespResult(resp); - if (res.IsError()) - return res; + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) + return res; - auto writen_buf = resp->GetResponseS32(0); - if (!writen_buf) { - return std::size_t(0); + auto actually_written_opt = resp->GetResponseS32(0); + if (!actually_written_opt.has_value()) + return Result(-1); + + size_t actually_written = static_cast(actually_written_opt.value()); + + written_amount += actually_written; + if (actually_written != to_write) + break; } - - return std::size_t(*writen_buf); + return written_amount; } } From 4780a7134d850e86bee4b4575aa66bcf262ea83c Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 9 Jul 2024 23:35:29 +0200 Subject: [PATCH 18/30] Artic Base: Implement DLC support and other fixes (#173) * Artic Base: Implement DLC support and other fixes * Fix per game settings not working with artic loader * Fix compilation error --- .../configuration/configure_per_game.cpp | 9 +- src/citra_qt/main.cpp | 17 +- src/core/file_sys/archive_systemsavedata.cpp | 81 +- src/core/file_sys/archive_systemsavedata.h | 23 +- src/core/hle/service/am/am.cpp | 947 ++++++++++++++---- src/core/hle/service/am/am.h | 10 + src/core/hle/service/cfg/cfg.cpp | 41 +- src/core/hle/service/cfg/cfg.h | 7 + src/core/hle/service/fs/archive.cpp | 32 +- src/core/hle/service/fs/archive.h | 9 +- src/core/hle/service/fs/fs_user.cpp | 7 +- src/core/loader/artic.cpp | 24 +- src/core/loader/artic.h | 5 +- src/core/perf_stats.h | 1 + src/network/artic_base/artic_base_client.cpp | 7 +- src/network/artic_base/artic_base_client.h | 8 +- 16 files changed, 992 insertions(+), 236 deletions(-) diff --git a/src/citra_qt/configuration/configure_per_game.cpp b/src/citra_qt/configuration/configure_per_game.cpp index c11847a0d..f9c249911 100644 --- a/src/citra_qt/configuration/configure_per_game.cpp +++ b/src/citra_qt/configuration/configure_per_game.cpp @@ -151,7 +151,14 @@ void ConfigurePerGame::LoadConfiguration() { ui->display_title_id->setText( QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); - const auto loader = Loader::GetLoader(filename); + std::unique_ptr loader_ptr; + Loader::AppLoader* loader; + if (system.IsPoweredOn()) { + loader = &system.GetAppLoader(); + } else { + loader_ptr = Loader::GetLoader(filename); + loader = loader_ptr.get(); + } std::string title; if (loader->ReadTitle(title) == Loader::ResultStatus::Success) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 5d3d395a2..0b5b16695 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -1216,7 +1216,10 @@ bool GMainWindow::LoadROM(const QString& filename) { case Core::System::ResultStatus::ErrorArticDisconnected: QMessageBox::critical( this, tr("Artic Base Server"), - tr("An error has occurred whilst communicating with the Artic Base Server.")); + tr(fmt::format( + "An error has occurred whilst communicating with the Artic Base Server.\n{}", + system.GetStatusDetails()) + .c_str())); break; default: QMessageBox::critical( @@ -1238,6 +1241,10 @@ bool GMainWindow::LoadROM(const QString& filename) { } void GMainWindow::BootGame(const QString& filename) { + if (emu_thread) { + ShutdownGame(); + } + const bool is_artic = filename.startsWith(QString::fromStdString("articbase://")); if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) { @@ -2640,10 +2647,12 @@ void GMainWindow::UpdateStatusBar() { const bool do_mb = results.artic_transmitted >= (1000.0 * 1000.0); const double value = do_mb ? (results.artic_transmitted / (1000.0 * 1000.0)) : (results.artic_transmitted / 1000.0); - static const std::array, 4> + static const std::array, 5> perf_events = { std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA, tr("(Accessing SharedExtData)")), + std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SYSTEM_SAVE_DATA, + tr("(Accessing SystemSaveData)")), std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA, tr("(Accessing BossExtData)")), std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA, @@ -2868,7 +2877,9 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det error_severity_icon = QMessageBox::Icon::Warning; } else if (result == Core::System::ResultStatus::ErrorArticDisconnected) { title = tr("Artic Base Server"); - message = tr("A communication error has occurred. The game will quit."); + message = + tr(fmt::format("A communication error has occurred. The game will quit.\n{}", details) + .c_str()); error_severity_icon = QMessageBox::Icon::Critical; can_continue = false; } else { diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index 79ae33cd7..9020666b1 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp @@ -10,6 +10,7 @@ #include "common/archives.h" #include "common/common_types.h" #include "common/file_util.h" +#include "core/file_sys/archive_artic.h" #include "core/file_sys/archive_systemsavedata.h" #include "core/file_sys/errors.h" #include "core/file_sys/savedata_archive.h" @@ -52,24 +53,45 @@ Path ConstructSystemSaveDataBinaryPath(u32 high, u32 low) { ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string& nand_path) : base_path(GetSystemSaveDataContainerPath(nand_path)) {} +static bool AllowArticSystemSaveData(const Path& path) { + constexpr u32 APP_SYSTEM_SAVE_DATA_MASK = 0x00020000; + if (path.GetType() == FileSys::LowPathType::Binary) { + std::vector path_data = path.AsBinary(); + return path_data.size() == 8 && + (*reinterpret_cast(path_data.data() + 4) & APP_SYSTEM_SAVE_DATA_MASK) != 0; + } + return false; +} + ResultVal> ArchiveFactory_SystemSaveData::Open(const Path& path, u64 program_id) { - std::string fullpath = GetSystemSaveDataPath(base_path, path); - if (!FileUtil::Exists(fullpath)) { - // TODO(Subv): Check error code, this one is probably wrong - return ResultNotFound; + if (IsUsingArtic() && AllowArticSystemSaveData(path)) { + EnsureCacheCreated(); + return ArticArchive::Open(artic_client, Service::FS::ArchiveIdCode::SystemSaveData, path, + Core::PerfStats::PerfArticEventBits::ARTIC_SYSTEM_SAVE_DATA, + *this, false); + } else { + std::string fullpath = GetSystemSaveDataPath(base_path, path); + if (!FileUtil::Exists(fullpath)) { + // TODO(Subv): Check error code, this one is probably wrong + return ResultNotFound; + } + return std::make_unique(fullpath); } - return std::make_unique(fullpath); } Result ArchiveFactory_SystemSaveData::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id, u32 directory_buckets, u32 file_buckets) { - std::string fullpath = GetSystemSaveDataPath(base_path, path); - FileUtil::DeleteDirRecursively(fullpath); - FileUtil::CreateFullPath(fullpath); - return ResultSuccess; + const std::vector vec_data = path.AsBinary(); + u32 save_low; + u32 save_high; + std::memcpy(&save_low, &vec_data[4], sizeof(u32)); + std::memcpy(&save_high, &vec_data[0], sizeof(u32)); + return FormatAsSysData(save_high, save_low, format_info.total_size, 0x1000, + format_info.number_directories, format_info.number_files, + directory_buckets, file_buckets, format_info.duplicate_data); } ResultVal ArchiveFactory_SystemSaveData::GetFormatInfo(const Path& path, @@ -79,4 +101,45 @@ ResultVal ArchiveFactory_SystemSaveData::GetFormatInfo(const return ResultUnknown; } +Result ArchiveFactory_SystemSaveData::FormatAsSysData(u32 high, u32 low, u32 total_size, + u32 block_size, u32 number_directories, + u32 number_files, + u32 number_directory_buckets, + u32 number_file_buckets, u8 duplicate_data) { + if (IsUsingArtic() && + AllowArticSystemSaveData(FileSys::ConstructSystemSaveDataBinaryPath(high, low))) { + auto req = artic_client->NewRequest("FSUSER_CreateSysSaveData"); + + req.AddParameterU32(high); + req.AddParameterU32(low); + req.AddParameterU32(total_size); + req.AddParameterU32(block_size); + req.AddParameterU32(number_directories); + req.AddParameterU32(number_files); + req.AddParameterU32(number_directory_buckets); + req.AddParameterU32(number_file_buckets); + req.AddParameterU8(duplicate_data); + + auto resp = artic_client->Send(req); + if (!resp.has_value() || !resp->Succeeded()) { + return ResultUnknown; + } + + Result res(static_cast(resp->GetMethodResult())); + return res; + + } else { + // Construct the binary path to the archive first + const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); + + const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); + const std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory); + const std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path); + if (!FileUtil::CreateFullPath(systemsavedata_path)) { + return ResultUnknown; // TODO(Subv): Find the right error code + } + return ResultSuccess; + } +} + } // namespace FileSys diff --git a/src/core/file_sys/archive_systemsavedata.h b/src/core/file_sys/archive_systemsavedata.h index 5cb109122..2c86f51e6 100644 --- a/src/core/file_sys/archive_systemsavedata.h +++ b/src/core/file_sys/archive_systemsavedata.h @@ -10,12 +10,15 @@ #include #include "common/common_types.h" #include "core/file_sys/archive_backend.h" +#include "core/file_sys/artic_cache.h" #include "core/hle/result.h" +#include "core/hle/service/fs/archive.h" +#include "network/artic_base/artic_base_client.h" namespace FileSys { /// File system interface to the SystemSaveData archive -class ArchiveFactory_SystemSaveData final : public ArchiveFactory { +class ArchiveFactory_SystemSaveData final : public ArchiveFactory, public ArticCacheProvider { public: explicit ArchiveFactory_SystemSaveData(const std::string& mount_point); @@ -24,13 +27,31 @@ public: u32 directory_buckets, u32 file_buckets) override; ResultVal GetFormatInfo(const Path& path, u64 program_id) const override; + Result FormatAsSysData(u32 high, u32 low, u32 total_size, u32 block_size, + u32 number_directories, u32 number_files, u32 number_directory_buckets, + u32 number_file_buckets, u8 duplicate_data); + std::string GetName() const override { return "SystemSaveData"; } + bool IsSlow() override { + return IsUsingArtic(); + } + + void RegisterArtic(std::shared_ptr& client) { + artic_client = client; + } + + bool IsUsingArtic() const { + return artic_client.get() != nullptr; + } + private: std::string base_path; + std::shared_ptr artic_client = nullptr; + ArchiveFactory_SystemSaveData() = default; template void serialize(Archive& ar, const unsigned int) { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index cd3f49651..aee587734 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -804,76 +804,181 @@ Module::Interface::~Interface() = default; void Module::Interface::GetNumPrograms(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - u32 media_type = rp.Pop(); + u8 media_type = rp.Pop(); - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(ResultSuccess); - rb.Push(static_cast(am->am_title_list[media_type].size())); + if (artic_client.get()) { + struct AsyncData { + u8 media_type; + + ResultVal res; + }; + auto async_data = std::make_shared(); + async_data->media_type = media_type; + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AM_GetTitleCount"); + + req.AddParameterU8(async_data->media_type); + + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + + auto count = resp->GetResponseS32(0); + if (!count.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->res = *count; + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 2, 0); + + rb.Push(async_data->res.Code()); + rb.Push( + static_cast(async_data->res.Succeeded() ? async_data->res.Unwrap() : 0)); + }, + true); + } else { + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(static_cast(am->am_title_list[media_type].size())); + } } void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); + IPC::RequestParser rp(ctx); auto media_type = static_cast(rp.Pop()); u64 title_id = rp.Pop(); u32 content_count = rp.Pop(); auto& content_requested_in = rp.PopMappedBuffer(); - auto& content_info_out = rp.PopMappedBuffer(); + if (artic_client.get()) { + struct AsyncData { + u8 media_type; + u64 title_id; + std::vector content_requested; - // Validate that only DLC TIDs are passed in - u32 tid_high = static_cast(title_id >> 32); - if (tid_high != TID_HIGH_DLC) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, ErrorSummary::InvalidArgument, - ErrorLevel::Usage)); - rb.PushMappedBuffer(content_requested_in); - rb.PushMappedBuffer(content_info_out); - return; - } + Result res{0}; + std::vector out; + Kernel::MappedBuffer* content_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = static_cast(media_type); + async_data->title_id = title_id; + async_data->content_requested.resize(content_count); + content_requested_in.Read(async_data->content_requested.data(), 0, + content_count * sizeof(u16)); + async_data->content_info_out = &rp.PopMappedBuffer(); - std::vector content_requested(content_count); - content_requested_in.Read(content_requested.data(), 0, content_count * sizeof(u16)); + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AMAPP_FindDLCContentInfos"); - std::string tmd_path = GetTitleMetadataPath(media_type, title_id); + req.AddParameterU8(async_data->media_type); + req.AddParameterU64(async_data->title_id); + req.AddParameterBuffer(async_data->content_requested.data(), + async_data->content_requested.size() * sizeof(u16)); - FileSys::TitleMetadata tmd; - if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { - std::size_t write_offset = 0; - // Get info for each content index requested - for (std::size_t i = 0; i < content_count; i++) { - if (content_requested[i] >= tmd.GetContentCount()) { - LOG_ERROR(Service_AM, - "Attempted to get info for non-existent content index {:04x}.", - content_requested[i]); + auto resp = artic_client->Send(req); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(-1); // TODO(Steveice10): Find the right error code - rb.PushMappedBuffer(content_requested_in); - rb.PushMappedBuffer(content_info_out); - return; - } + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } - ContentInfo content_info = {}; - content_info.index = content_requested[i]; - content_info.type = tmd.GetContentTypeByIndex(content_requested[i]); - content_info.content_id = tmd.GetContentIDByIndex(content_requested[i]); - content_info.size = tmd.GetContentSizeByIndex(content_requested[i]); - content_info.ownership = - OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket. + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } - if (FileUtil::Exists(GetTitleContentPath(media_type, title_id, content_requested[i]))) { - content_info.ownership |= OWNERSHIP_DOWNLOADED; - } + auto content_info = resp->GetResponseBuffer(0); + if (!content_info.has_value()) { + async_data->res = Result(-1); + return 0; + } - content_info_out.Write(&content_info, write_offset, sizeof(ContentInfo)); - write_offset += sizeof(ContentInfo); + async_data->out.resize(content_info->second); + memcpy(async_data->out.data(), content_info->first, content_info->second); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.IsSuccess()) { + async_data->content_info_out->Write(async_data->out.data(), 0, + async_data->out.size()); + } + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(async_data->res); + }, + true); + } else { + + auto& content_info_out = rp.PopMappedBuffer(); + + // Validate that only DLC TIDs are passed in + u32 tid_high = static_cast(title_id >> 32); + if (tid_high != TID_HIGH_DLC) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage)); + return; } - } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(ResultSuccess); - rb.PushMappedBuffer(content_requested_in); - rb.PushMappedBuffer(content_info_out); + std::vector content_requested(content_count); + content_requested_in.Read(content_requested.data(), 0, content_count * sizeof(u16)); + + std::string tmd_path = GetTitleMetadataPath(media_type, title_id); + + FileSys::TitleMetadata tmd; + if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { + std::size_t write_offset = 0; + // Get info for each content index requested + for (std::size_t i = 0; i < content_count; i++) { + if (content_requested[i] >= tmd.GetContentCount()) { + LOG_ERROR(Service_AM, + "Attempted to get info for non-existent content index {:04x}.", + content_requested[i]); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(-1); // TODO(Steveice10): Find the right error code + return; + } + + ContentInfo content_info = {}; + content_info.index = content_requested[i]; + content_info.type = tmd.GetContentTypeByIndex(content_requested[i]); + content_info.content_id = tmd.GetContentIDByIndex(content_requested[i]); + content_info.size = tmd.GetContentSizeByIndex(content_requested[i]); + content_info.ownership = + OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket. + + if (FileUtil::Exists( + GetTitleContentPath(media_type, title_id, content_requested[i]))) { + content_info.ownership |= OWNERSHIP_DOWNLOADED; + } + + content_info_out.Write(&content_info, write_offset, sizeof(ContentInfo)); + write_offset += sizeof(ContentInfo); + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); + } } void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) { @@ -883,50 +988,112 @@ void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) { auto media_type = static_cast(rp.Pop()); u64 title_id = rp.Pop(); u32 start_index = rp.Pop(); - auto& content_info_out = rp.PopMappedBuffer(); - // Validate that only DLC TIDs are passed in - u32 tid_high = static_cast(title_id >> 32); - if (tid_high != TID_HIGH_DLC) { - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, ErrorSummary::InvalidArgument, - ErrorLevel::Usage)); - rb.Push(0); - rb.PushMappedBuffer(content_info_out); - return; - } + if (artic_client.get()) { + struct AsyncData { + u8 media_type; + u64 title_id; + u32 content_count; + u32 start_index; - std::string tmd_path = GetTitleMetadataPath(media_type, title_id); + Result res{0}; + std::vector out; + Kernel::MappedBuffer* content_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = static_cast(media_type); + async_data->title_id = title_id; + async_data->content_count = content_count; + async_data->start_index = start_index; + async_data->content_info_out = &rp.PopMappedBuffer(); - u32 copied = 0; - FileSys::TitleMetadata tmd; - if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { - u32 end_index = - std::min(start_index + content_count, static_cast(tmd.GetContentCount())); - std::size_t write_offset = 0; - for (u32 i = start_index; i < end_index; i++) { - ContentInfo content_info = {}; - content_info.index = static_cast(i); - content_info.type = tmd.GetContentTypeByIndex(i); - content_info.content_id = tmd.GetContentIDByIndex(i); - content_info.size = tmd.GetContentSizeByIndex(i); - content_info.ownership = - OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket. + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AMAPP_ListDLCContentInfos"); - if (FileUtil::Exists(GetTitleContentPath(media_type, title_id, i))) { - content_info.ownership |= OWNERSHIP_DOWNLOADED; - } + req.AddParameterU32(async_data->content_count); + req.AddParameterU8(async_data->media_type); + req.AddParameterU64(async_data->title_id); + req.AddParameterU32(async_data->start_index); - content_info_out.Write(&content_info, write_offset, sizeof(ContentInfo)); - write_offset += sizeof(ContentInfo); - copied++; + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + + auto content_info = resp->GetResponseBuffer(0); + if (!content_info.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->out.resize(content_info->second); + memcpy(async_data->out.data(), content_info->first, content_info->second); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.IsSuccess()) { + async_data->content_info_out->Write(async_data->out.data(), 0, + async_data->out.size()); + } + IPC::RequestBuilder rb(ctx, 2, 0); + rb.Push(async_data->res); + rb.Push(static_cast(async_data->out.size() / sizeof(ContentInfo))); + }, + true); + } else { + + auto& content_info_out = rp.PopMappedBuffer(); + + // Validate that only DLC TIDs are passed in + u32 tid_high = static_cast(title_id >> 32); + if (tid_high != TID_HIGH_DLC) { + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage)); + rb.Push(0); + return; } - } - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(ResultSuccess); - rb.Push(copied); - rb.PushMappedBuffer(content_info_out); + std::string tmd_path = GetTitleMetadataPath(media_type, title_id); + + u32 copied = 0; + FileSys::TitleMetadata tmd; + if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { + u32 end_index = + std::min(start_index + content_count, static_cast(tmd.GetContentCount())); + std::size_t write_offset = 0; + for (u32 i = start_index; i < end_index; i++) { + ContentInfo content_info = {}; + content_info.index = static_cast(i); + content_info.type = tmd.GetContentTypeByIndex(i); + content_info.content_id = tmd.GetContentIDByIndex(i); + content_info.size = tmd.GetContentSizeByIndex(i); + content_info.ownership = + OWNERSHIP_OWNED; // TODO(Steveice10): Pull this from the ticket. + + if (FileUtil::Exists(GetTitleContentPath(media_type, title_id, i))) { + content_info.ownership |= OWNERSHIP_DOWNLOADED; + } + + content_info_out.Write(&content_info, write_offset, sizeof(ContentInfo)); + write_offset += sizeof(ContentInfo); + copied++; + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(copied); + } } void Module::Interface::DeleteContents(Kernel::HLERequestContext& ctx) { @@ -945,28 +1112,89 @@ void Module::Interface::DeleteContents(Kernel::HLERequestContext& ctx) { void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - u32 count = rp.Pop(); u8 media_type = rp.Pop(); - auto& title_ids_output = rp.PopMappedBuffer(); - if (media_type > 2) { - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(-1); // TODO(shinyquagsire23): Find the right error code - rb.Push(0); - rb.PushMappedBuffer(title_ids_output); - return; + if (artic_client.get()) { + struct AsyncData { + u32 count; + u8 media_type; + + Result res{0}; + std::vector out; + Kernel::MappedBuffer* title_ids_output; + }; + auto async_data = std::make_shared(); + async_data->count = count; + async_data->media_type = media_type; + async_data->title_ids_output = &rp.PopMappedBuffer(); + + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AM_GetTitleList"); + + req.AddParameterU32(async_data->count); + req.AddParameterU8(async_data->media_type); + + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + async_data->res = res; + + auto title_ids = resp->GetResponseBuffer(0); + if (!title_ids.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->out.resize(title_ids->second); + memcpy(async_data->out.data(), title_ids->first, title_ids->second); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (!async_data->res.IsSuccess()) { + IPC::RequestBuilder rb(ctx, 2, 0); + rb.Push(async_data->res); + rb.Push(0); + } else { + async_data->title_ids_output->Write(async_data->out.data(), 0, + async_data->out.size()); + + IPC::RequestBuilder rb(ctx, 2, 0); + rb.Push(async_data->res); + rb.Push(static_cast(async_data->out.size() / sizeof(u64))); + } + }, + true); + + } else { + auto& title_ids_output = rp.PopMappedBuffer(); + + if (media_type > 2) { + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(-1); // TODO(shinyquagsire23): Find the right error code + rb.Push(0); + return; + } + + u32 media_count = static_cast(am->am_title_list[media_type].size()); + u32 copied = std::min(media_count, count); + + title_ids_output.Write(am->am_title_list[media_type].data(), 0, copied * sizeof(u64)); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(copied); } - - u32 media_count = static_cast(am->am_title_list[media_type].size()); - u32 copied = std::min(media_count, count); - - title_ids_output.Write(am->am_title_list[media_type].data(), 0, copied * sizeof(u64)); - - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(ResultSuccess); - rb.Push(copied); - rb.PushMappedBuffer(title_ids_output); } Result GetTitleInfoFromList(std::span title_id_list, Service::FS::MediaType media_type, @@ -996,27 +1224,111 @@ Result GetTitleInfoFromList(std::span title_id_list, Service::FS::Med return ResultSuccess; } -void Module::Interface::GetProgramInfos(Kernel::HLERequestContext& ctx) { +void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform) { IPC::RequestParser rp(ctx); auto media_type = static_cast(rp.Pop()); u32 title_count = rp.Pop(); - auto& title_id_list_buffer = rp.PopMappedBuffer(); - auto& title_info_out = rp.PopMappedBuffer(); - std::vector title_id_list(title_count); - title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + if (artic_client.get()) { + struct AsyncData { + u8 media_type; + bool ignore_platform; + std::vector title_id_list; - Result result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); + Result res{0}; + std::vector out; + Kernel::MappedBuffer* title_id_list_buffer; + Kernel::MappedBuffer* title_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = static_cast(media_type); + async_data->ignore_platform = ignore_platform; + async_data->title_id_list.resize(title_count); + async_data->title_id_list_buffer = &rp.PopMappedBuffer(); + async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0, + title_count * sizeof(u64)); + async_data->title_info_out = &rp.PopMappedBuffer(); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(result); - rb.PushMappedBuffer(title_id_list_buffer); - rb.PushMappedBuffer(title_info_out); + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AM_GetTitleInfo"); + + req.AddParameterU8(async_data->media_type); + req.AddParameterBuffer(async_data->title_id_list.data(), + async_data->title_id_list.size() * sizeof(u64)); + req.AddParameterU8(async_data->ignore_platform ? 1 : 0); + + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + async_data->res = res; + + auto title_infos = resp->GetResponseBuffer(0); + if (!title_infos.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->out.resize(title_infos->second); + memcpy(async_data->out.data(), title_infos->first, title_infos->second); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (!async_data->res.IsSuccess()) { + IPC::RequestBuilder rb(ctx, 1, async_data->ignore_platform ? 0 : 4); + rb.Push(async_data->res); + if (!async_data->ignore_platform) { + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + } + } else { + async_data->title_info_out->Write(async_data->out.data(), 0, + async_data->out.size()); + + IPC::RequestBuilder rb(ctx, 1, async_data->ignore_platform ? 0 : 4); + rb.Push(async_data->res); + if (!async_data->ignore_platform) { + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + } + } + }, + true); + + } else { + auto& title_id_list_buffer = rp.PopMappedBuffer(); + auto& title_info_out = rp.PopMappedBuffer(); + + std::vector title_id_list(title_count); + title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + + Result result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, ignore_platform ? 0 : 4); + rb.Push(result); + if (!ignore_platform) { + rb.PushMappedBuffer(title_id_list_buffer); + rb.PushMappedBuffer(title_info_out); + } + } +} + +void Module::Interface::GetProgramInfos(Kernel::HLERequestContext& ctx) { + GetProgramInfosImpl(ctx, false); } void Module::Interface::GetProgramInfosIgnorePlatform(Kernel::HLERequestContext& ctx) { - GetProgramInfos(ctx); + GetProgramInfosImpl(ctx, true); } void Module::Interface::DeleteUserProgram(Kernel::HLERequestContext& ctx) { @@ -1078,32 +1390,102 @@ void Module::Interface::GetDLCTitleInfos(Kernel::HLERequestContext& ctx) { auto media_type = static_cast(rp.Pop()); u32 title_count = rp.Pop(); - auto& title_id_list_buffer = rp.PopMappedBuffer(); - auto& title_info_out = rp.PopMappedBuffer(); - std::vector title_id_list(title_count); - title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + if (artic_client.get()) { + struct AsyncData { + u8 media_type; + std::vector title_id_list; - Result result = ResultSuccess; + Result res{0}; + std::vector out; + Kernel::MappedBuffer* title_id_list_buffer; + Kernel::MappedBuffer* title_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = static_cast(media_type); + async_data->title_id_list.resize(title_count); + async_data->title_id_list_buffer = &rp.PopMappedBuffer(); + async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0, + title_count * sizeof(u64)); + async_data->title_info_out = &rp.PopMappedBuffer(); - // Validate that DLC TIDs were passed in - for (u32 i = 0; i < title_count; i++) { - u32 tid_high = static_cast(title_id_list[i] >> 32); - if (tid_high != TID_HIGH_DLC) { - result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, - ErrorSummary::InvalidArgument, ErrorLevel::Usage); - break; + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AMAPP_GetDLCTitleInfos"); + + req.AddParameterU8(async_data->media_type); + req.AddParameterBuffer(async_data->title_id_list.data(), + async_data->title_id_list.size() * sizeof(u64)); + + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + async_data->res = res; + + auto title_infos = resp->GetResponseBuffer(0); + if (!title_infos.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->out.resize(title_infos->second); + memcpy(async_data->out.data(), title_infos->first, title_infos->second); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (!async_data->res.IsSuccess()) { + IPC::RequestBuilder rb(ctx, 1, 4); + rb.Push(async_data->res); + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + } else { + async_data->title_info_out->Write(async_data->out.data(), 0, + async_data->out.size()); + + IPC::RequestBuilder rb(ctx, 1, 4); + rb.Push(async_data->res); + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + } + }, + true); + } else { + auto& title_id_list_buffer = rp.PopMappedBuffer(); + auto& title_info_out = rp.PopMappedBuffer(); + + std::vector title_id_list(title_count); + title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + + Result result = ResultSuccess; + + // Validate that DLC TIDs were passed in + for (u32 i = 0; i < title_count; i++) { + u32 tid_high = static_cast(title_id_list[i] >> 32); + if (tid_high != TID_HIGH_DLC) { + result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + break; + } } - } - if (result.IsSuccess()) { - result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); - } + if (result.IsSuccess()) { + result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); + } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(result); - rb.PushMappedBuffer(title_id_list_buffer); - rb.PushMappedBuffer(title_info_out); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(result); + rb.PushMappedBuffer(title_id_list_buffer); + rb.PushMappedBuffer(title_info_out); + } } void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) { @@ -1111,32 +1493,102 @@ void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) { auto media_type = static_cast(rp.Pop()); u32 title_count = rp.Pop(); - auto& title_id_list_buffer = rp.PopMappedBuffer(); - auto& title_info_out = rp.PopMappedBuffer(); - std::vector title_id_list(title_count); - title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + if (artic_client.get()) { + struct AsyncData { + u8 media_type; + std::vector title_id_list; - Result result = ResultSuccess; + Result res{0}; + std::vector out; + Kernel::MappedBuffer* title_id_list_buffer; + Kernel::MappedBuffer* title_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = static_cast(media_type); + async_data->title_id_list.resize(title_count); + async_data->title_id_list_buffer = &rp.PopMappedBuffer(); + async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0, + title_count * sizeof(u64)); + async_data->title_info_out = &rp.PopMappedBuffer(); - // Validate that update TIDs were passed in - for (u32 i = 0; i < title_count; i++) { - u32 tid_high = static_cast(title_id_list[i] >> 32); - if (tid_high != TID_HIGH_UPDATE) { - result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, - ErrorSummary::InvalidArgument, ErrorLevel::Usage); - break; + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AMAPP_GetPatchTitleInfos"); + + req.AddParameterU8(async_data->media_type); + req.AddParameterBuffer(async_data->title_id_list.data(), + async_data->title_id_list.size() * sizeof(u64)); + + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + async_data->res = res; + + auto title_infos = resp->GetResponseBuffer(0); + if (!title_infos.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->out.resize(title_infos->second); + memcpy(async_data->out.data(), title_infos->first, title_infos->second); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (!async_data->res.IsSuccess()) { + IPC::RequestBuilder rb(ctx, 1, 4); + rb.Push(async_data->res); + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + } else { + async_data->title_info_out->Write(async_data->out.data(), 0, + async_data->out.size()); + + IPC::RequestBuilder rb(ctx, 1, 4); + rb.Push(async_data->res); + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + } + }, + true); + } else { + auto& title_id_list_buffer = rp.PopMappedBuffer(); + auto& title_info_out = rp.PopMappedBuffer(); + + std::vector title_id_list(title_count); + title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + + Result result = ResultSuccess; + + // Validate that update TIDs were passed in + for (u32 i = 0; i < title_count; i++) { + u32 tid_high = static_cast(title_id_list[i] >> 32); + if (tid_high != TID_HIGH_UPDATE) { + result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + break; + } } - } - if (result.IsSuccess()) { - result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); - } + if (result.IsSuccess()) { + result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); + } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(result); - rb.PushMappedBuffer(title_id_list_buffer); - rb.PushMappedBuffer(title_info_out); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(result); + rb.PushMappedBuffer(title_id_list_buffer); + rb.PushMappedBuffer(title_info_out); + } } void Module::Interface::ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx) { @@ -1144,56 +1596,165 @@ void Module::Interface::ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx) u32 ticket_count = rp.Pop(); u64 title_id = rp.Pop(); u32 start_index = rp.Pop(); - auto& ticket_info_out = rp.PopMappedBuffer(); + if (artic_client.get()) { + struct AsyncData { + u64 title_id; + u32 ticket_count; + u32 start_index; - std::size_t write_offset = 0; - for (u32 i = 0; i < ticket_count; i++) { - TicketInfo ticket_info = {}; - ticket_info.title_id = title_id; - ticket_info.version = 0; // TODO - ticket_info.size = 0; // TODO + Result res{0}; + std::vector out; + Kernel::MappedBuffer* ticket_info_out; + }; + auto async_data = std::make_shared(); + async_data->title_id = title_id; + async_data->ticket_count = ticket_count; + async_data->start_index = start_index; + async_data->ticket_info_out = &rp.PopMappedBuffer(); - ticket_info_out.Write(&ticket_info, write_offset, sizeof(TicketInfo)); - write_offset += sizeof(TicketInfo); + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AMAPP_ListDataTitleTicketInfos"); + + req.AddParameterU32(async_data->ticket_count); + req.AddParameterU64(async_data->title_id); + req.AddParameterU32(async_data->start_index); + + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + + auto content_info = resp->GetResponseBuffer(0); + if (!content_info.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->out.resize(content_info->second); + memcpy(async_data->out.data(), content_info->first, content_info->second); + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.IsSuccess()) { + async_data->ticket_info_out->Write(async_data->out.data(), 0, + async_data->out.size()); + } + IPC::RequestBuilder rb(ctx, 2, 0); + rb.Push(async_data->res); + rb.Push(static_cast(async_data->out.size() / sizeof(TicketInfo))); + }, + true); + } else { + auto& ticket_info_out = rp.PopMappedBuffer(); + + std::size_t write_offset = 0; + for (u32 i = 0; i < ticket_count; i++) { + TicketInfo ticket_info = {}; + ticket_info.title_id = title_id; + ticket_info.version = 0; // TODO + ticket_info.size = 0; // TODO + + ticket_info_out.Write(&ticket_info, write_offset, sizeof(TicketInfo)); + write_offset += sizeof(TicketInfo); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(ResultSuccess); + rb.Push(ticket_count); + rb.PushMappedBuffer(ticket_info_out); + + LOG_WARNING(Service_AM, + "(STUBBED) ticket_count=0x{:08X}, title_id=0x{:016x}, start_index=0x{:08X}", + ticket_count, title_id, start_index); } - - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(ResultSuccess); - rb.Push(ticket_count); - rb.PushMappedBuffer(ticket_info_out); - - LOG_WARNING(Service_AM, - "(STUBBED) ticket_count=0x{:08X}, title_id=0x{:016x}, start_index=0x{:08X}", - ticket_count, title_id, start_index); } void Module::Interface::GetDLCContentInfoCount(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); auto media_type = static_cast(rp.Pop()); u64 title_id = rp.Pop(); + if (artic_client.get()) { + struct AsyncData { + u8 media_type; + u64 title_id; - // Validate that only DLC TIDs are passed in - u32 tid_high = static_cast(title_id >> 32); - if (tid_high != TID_HIGH_DLC) { - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(Result(ErrCodes::InvalidTID, ErrorModule::AM, ErrorSummary::InvalidArgument, - ErrorLevel::Usage)); - rb.Push(0); - return; - } + ResultVal res; + }; + auto async_data = std::make_shared(); + async_data->media_type = static_cast(media_type); + async_data->title_id = title_id; - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(ResultSuccess); // No error + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + auto req = artic_client->NewRequest("AMAPP_GetDLCContentInfoCount"); - std::string tmd_path = GetTitleMetadataPath(media_type, title_id); + req.AddParameterU8(async_data->media_type); + req.AddParameterU64(async_data->title_id); - FileSys::TitleMetadata tmd; - if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { - rb.Push(static_cast(tmd.GetContentCount())); + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) { + async_data->res = Result(-1); + return 0; + } + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) { + async_data->res = res; + return 0; + } + + auto count = resp->GetResponseS32(0); + if (!count.has_value()) { + async_data->res = Result(-1); + return 0; + } + + async_data->res = *count; + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 2, 0); + + rb.Push(async_data->res.Code()); + rb.Push( + static_cast(async_data->res.Succeeded() ? async_data->res.Unwrap() : 0)); + }, + true); } else { - rb.Push(1); // Number of content infos plus one - LOG_WARNING(Service_AM, "(STUBBED) called media_type={}, title_id=0x{:016x}", media_type, - title_id); + + // Validate that only DLC TIDs are passed in + u32 tid_high = static_cast(title_id >> 32); + if (tid_high != TID_HIGH_DLC) { + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(Result(ErrCodes::InvalidTID, ErrorModule::AM, ErrorSummary::InvalidArgument, + ErrorLevel::Usage)); + rb.Push(0); + return; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); // No error + + std::string tmd_path = GetTitleMetadataPath(media_type, title_id); + + FileSys::TitleMetadata tmd; + if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { + rb.Push(static_cast(tmd.GetContentCount())); + } else { + rb.Push(1); // Number of content infos plus one + LOG_WARNING(Service_AM, "(STUBBED) called media_type={}, title_id=0x{:016x}", + media_type, title_id); + } } } diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 7ef96f985..c56fa0888 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -20,6 +20,7 @@ #include "core/hle/kernel/mutex.h" #include "core/hle/result.h" #include "core/hle/service/service.h" +#include "network/artic_base/artic_base_client.h" namespace Core { class System; @@ -245,7 +246,13 @@ public: return am; } + void UseArticClient(std::shared_ptr& client) { + artic_client = client; + } + protected: + void GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform); + /** * AM::GetNumPrograms service function * Gets the number of installed titles in the requested media type @@ -753,6 +760,9 @@ public: protected: std::shared_ptr am; + + // Placed on the interface level so that only am:net and am:app have it. + std::shared_ptr artic_client = nullptr; }; /** diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 1fa50e1df..0eef32aca 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -278,7 +278,7 @@ void Module::Interface::GetTransferableId(Kernel::HLERequestContext& ctx) { std::array buffer; const Result result = - cfg->GetConfigBlock(ConsoleUniqueID2BlockID, 8, AccessFlag::SystemRead, buffer.data()); + cfg->GetConfigBlock(ConsoleUniqueID2BlockID, 8, AccessFlag::Global, buffer.data()); rb.Push(result); if (result.IsSuccess()) { std::memcpy(&buffer[8], &app_id_salt, sizeof(u32)); @@ -502,11 +502,42 @@ ResultVal Module::GetConfigBlockPointer(u32 block_id, u32 size, AccessFla } Result Module::GetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, void* output) { - void* pointer = nullptr; - CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag)); - std::memcpy(output, pointer, size); + bool get_from_artic = + block_id == ConsoleUniqueID2BlockID && + (static_cast(accesss_flag) & static_cast(AccessFlag::UserRead)) != 0; - return ResultSuccess; + if (get_from_artic && artic_client.get()) { + auto req = artic_client->NewRequest("CFGU_GetConfigInfoBlk2"); + + req.AddParameterS32(block_id); + req.AddParameterU32(size); + + auto resp = artic_client->Send(req); + + if (!resp.has_value() || !resp->Succeeded()) + return Result(-1); + + auto res = Result(static_cast(resp->GetMethodResult())); + if (res.IsError()) + return res; + + auto buff = resp->GetResponseBuffer(0); + if (!buff.has_value()) + return Result(-1); + size_t actually_read = buff->second; + if (actually_read > size) + return Result(-1); + + memcpy(output, buff->first, actually_read); + return ResultSuccess; + + } else { + void* pointer = nullptr; + CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag)); + std::memcpy(output, pointer, size); + + return ResultSuccess; + } } Result Module::SetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, const void* input) { diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 7bd6a1310..263cd51e6 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -11,6 +11,7 @@ #include #include "common/common_types.h" #include "core/hle/service/service.h" +#include "network/artic_base/artic_base_client.h" namespace FileSys { class ArchiveBackend; @@ -210,6 +211,10 @@ public: std::shared_ptr GetModule() const; + void UseArticClient(std::shared_ptr& client) { + GetModule()->artic_client = client; + } + /** * CFG::GetCountryCodeString service function * Inputs: @@ -680,6 +685,8 @@ private: bool preferred_region_chosen = false; MCUData mcu_data{}; + std::shared_ptr artic_client = nullptr; + template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index f70f03c7e..2a48a741a 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -298,18 +298,22 @@ Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) { return ResultSuccess; } -Result ArchiveManager::CreateSystemSaveData(u32 high, u32 low) { - // Construct the binary path to the archive first - const FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); +Result ArchiveManager::CreateSystemSaveData(u32 high, u32 low, u32 total_size, u32 block_size, + u32 number_directories, u32 number_files, + u32 number_directory_buckets, u32 number_file_buckets, + u8 duplicate_data) { - const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); - const std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory); - const std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path); - if (!FileUtil::CreateFullPath(systemsavedata_path)) { - return ResultUnknown; // TODO(Subv): Find the right error code + auto archive = id_code_map.find(ArchiveIdCode::SystemSaveData); + + if (archive == id_code_map.end()) { + return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error } - return ResultSuccess; + auto sys_savedata = static_cast(archive->second.get()); + + return sys_savedata->FormatAsSysData(high, low, total_size, block_size, number_directories, + number_files, number_directory_buckets, + number_file_buckets, duplicate_data); } ResultVal ArchiveManager::GetArchiveResource(MediaType media_type) const { @@ -454,6 +458,16 @@ void ArchiveManager::RegisterArticNCCH(std::shared_ptr(itr->second.get())->RegisterArtic(client); } +void ArchiveManager::RegisterArticSystemSaveData( + std::shared_ptr& client) { + auto itr = id_code_map.find(ArchiveIdCode::SystemSaveData); + if (itr == id_code_map.end() || itr->second.get() == nullptr) { + return; + } + reinterpret_cast(itr->second.get()) + ->RegisterArtic(client); +} + ArchiveManager::ArchiveManager(Core::System& system) : system(system) { RegisterArchiveTypes(); } diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index a20713066..c9d5df87c 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -264,11 +264,12 @@ public: /** * Creates the SystemSaveData archive folder for the specified save data id - * @param high The high word of the SystemSaveData archive to create - * @param low The low word of the SystemSaveData archive to create * @return Result 0 on success or the corresponding code on error */ - Result CreateSystemSaveData(u32 high, u32 low); + Result CreateSystemSaveData(u32 high, u32 low, u32 total_size, u32 block_size, + u32 number_directories, u32 number_files, + u32 number_directory_buckets, u32 number_file_buckets, + u8 duplicate_data); /** * Returns capacity and free space information about the given media type. @@ -296,6 +297,8 @@ public: void RegisterArticNCCH(std::shared_ptr& client); + void RegisterArticSystemSaveData(std::shared_ptr& client); + private: Core::System& system; diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index e5b4cb240..ac1c70b2d 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -1026,7 +1026,9 @@ void FS_USER::CreateSystemSaveData(Kernel::HLERequestContext& ctx) { file_buckets, duplicate); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(archives.CreateSystemSaveData(savedata_high, savedata_low)); + rb.Push(archives.CreateSystemSaveData(savedata_high, savedata_low, total_size, block_size, + directories, files, directory_buckets, file_buckets, + duplicate ? 1 : 0)); } void FS_USER::CreateLegacySystemSaveData(Kernel::HLERequestContext& ctx) { @@ -1048,7 +1050,8 @@ void FS_USER::CreateLegacySystemSaveData(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); // With this command, the SystemSaveData always has save_high = 0 (Always created in the NAND) - rb.Push(archives.CreateSystemSaveData(0, savedata_id)); + rb.Push(archives.CreateSystemSaveData(0, savedata_id, total_size, block_size, directories, + files, directory_buckets, file_buckets, duplicate)); } void FS_USER::InitializeWithSdkVersion(Kernel::HLERequestContext& ctx) { diff --git a/src/core/loader/artic.cpp b/src/core/loader/artic.cpp index f2e74b9a6..d4ba70060 100644 --- a/src/core/loader/artic.cpp +++ b/src/core/loader/artic.cpp @@ -21,7 +21,10 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/am/am.h" +#include "core/hle/service/am/am_app.h" +#include "core/hle/service/am/am_net.h" #include "core/hle/service/cfg/cfg.h" +#include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" #include "core/loader/artic.h" @@ -335,9 +338,28 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { system.ArchiveManager().RegisterArticSaveDataSource(client); system.ArchiveManager().RegisterArticExtData(client); system.ArchiveManager().RegisterArticNCCH(client); + system.ArchiveManager().RegisterArticSystemSaveData(client); auto fs_user = system.ServiceManager().GetService("fs:USER"); - fs_user->RegisterSecureValueBackend(std::make_shared(client)); + if (fs_user.get()) { + fs_user->RegisterSecureValueBackend( + std::make_shared(client)); + } + + auto cfg = system.ServiceManager().GetService("cfg:u"); + if (cfg.get()) { + cfg->UseArticClient(client); + } + + auto amnet = system.ServiceManager().GetService("am:net"); + if (amnet.get()) { + amnet->UseArticClient(client); + } + + auto amapp = system.ServiceManager().GetService("am:app"); + if (amapp.get()) { + amapp->UseArticClient(client); + } ParseRegionLockoutInfo(ncch_program_id); diff --git a/src/core/loader/artic.h b/src/core/loader/artic.h index d51202b2e..477153f08 100644 --- a/src/core/loader/artic.h +++ b/src/core/loader/artic.h @@ -21,8 +21,9 @@ public: Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port) : AppLoader(system_, FileUtil::IOFile()) { client = std::make_shared(server_addr, server_port); - client->SetCommunicationErrorCallback([&system_]() { - system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected); + client->SetCommunicationErrorCallback([&system_](const std::string& msg) { + system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected, + msg.empty() ? nullptr : msg.c_str()); }); client->SetArticReportTrafficCallback( [&system_](u32 bytes) { system_.ReportArticTraffic(bytes); }); diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 5cd69afb2..d68ff4dda 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -32,6 +32,7 @@ public: ARTIC_EXT_DATA = (1 << 1), ARTIC_BOSS_EXT_DATA = (1 << 2), ARTIC_SHARED_EXT_DATA = (1 << 3), + ARTIC_SYSTEM_SAVE_DATA = (1 << 4), }; union PerfArticEvents { u32 raw{}; diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp index 460d8f9ee..fd5974d11 100644 --- a/src/network/artic_base/artic_base_client.cpp +++ b/src/network/artic_base/artic_base_client.cpp @@ -197,7 +197,8 @@ bool Client::Connect() { shutdown(main_socket, SHUT_RDWR); closesocket(main_socket); LOG_ERROR(Network, "Incompatible server version: {}", version_value); - SignalCommunicationError(); + SignalCommunicationError("\nIncompatible Artic Base Server version.\nCheck for updates " + "to Artic Base Server or Citra."); return false; } } else { @@ -369,11 +370,11 @@ std::optional Client::Send(Request& request) { return std::optional(std::move(resp.response)); } -void Client::SignalCommunicationError() { +void Client::SignalCommunicationError(const std::string& msg) { StopImpl(true); LOG_CRITICAL(Network, "Communication error"); if (communication_error_callback) - communication_error_callback(); + communication_error_callback(msg); } void Client::PingFunction() { diff --git a/src/network/artic_base/artic_base_client.h b/src/network/artic_base/artic_base_client.h index 4e9bb4ec8..23079f832 100644 --- a/src/network/artic_base/artic_base_client.h +++ b/src/network/artic_base/artic_base_client.h @@ -80,7 +80,7 @@ public: StopImpl(false); } - void SetCommunicationErrorCallback(const std::function& callback) { + void SetCommunicationErrorCallback(const std::function& callback) { communication_error_callback = callback; } @@ -98,7 +98,7 @@ public: } private: - static constexpr const int SERVER_VERSION = 0; + static constexpr const int SERVER_VERSION = 1; std::string address; u16 port; @@ -109,8 +109,8 @@ private: return currRequestID++; } - void SignalCommunicationError(); - std::function communication_error_callback; + void SignalCommunicationError(const std::string& msg = ""); + std::function communication_error_callback; std::function report_artic_event_callback; From cc220928bda5c16410dfa811a4bd4ba09d8ddec4 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Fri, 12 Jul 2024 13:22:31 +0200 Subject: [PATCH 19/30] Fixup logic for GSP_GPU::TriggerCmdReqQueue (#177) --- src/core/hle/service/gsp/gsp_command.h | 14 +++++++++++++- src/core/hle/service/gsp/gsp_gpu.cpp | 26 ++++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/core/hle/service/gsp/gsp_command.h b/src/core/hle/service/gsp/gsp_command.h index ddc203615..c7d0da927 100644 --- a/src/core/hle/service/gsp/gsp_command.h +++ b/src/core/hle/service/gsp/gsp_command.h @@ -71,7 +71,12 @@ struct CacheFlushCommand { /// GSP command struct Command { - BitField<0, 8, CommandId> id; + union { + BitField<0, 8, CommandId> id; + BitField<8, 8, u32> unknown1; + BitField<16, 8, u32> stop; + BitField<24, 8, u32> unknown2; + }; union { DmaCommand dma_request; SubmitCmdListCommand submit_gpu_cmdlist; @@ -86,6 +91,8 @@ static_assert(sizeof(Command) == 0x20, "Command struct has incorrect size"); /// GSP shared memory GX command buffer header struct CommandBuffer { + static constexpr u32 STATUS_STOPPED = 0x1; + static constexpr u32 STATUS_CMD_FAILED = 0x80; union { u32 hex; @@ -99,6 +106,11 @@ struct CommandBuffer { // application when writing a command to shared memory, after increasing this value // TriggerCmdReqQueue is only used if this field is value 1. BitField<8, 8, u32> number_commands; + + // When any of the following flags are set to 1, the GSP module stops processing the + // commands in the command buffer. + BitField<16, 8, u32> status; + BitField<24, 8, u32> should_stop; }; u32 unk[7]; diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index 374bbe3c6..329c653c8 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -408,17 +408,31 @@ void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) { void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - // Iterate through each command. auto* command_buffer = GetCommandBuffer(active_thread_id); auto& gpu = system.GPU(); - for (u32 i = 0; i < command_buffer->number_commands; i++) { - gpu.Debugger().GXCommandProcessed(command_buffer->commands[i]); + while (command_buffer->number_commands) { + if (command_buffer->should_stop) { + command_buffer->status.Assign(CommandBuffer::STATUS_STOPPED); + break; + } + if (command_buffer->status == CommandBuffer::STATUS_STOPPED) { + break; + } + + Command command = command_buffer->commands[command_buffer->index]; + + // Decrease the number of commands remaining and increase the current index + command_buffer->number_commands.Assign(command_buffer->number_commands - 1); + command_buffer->index.Assign((command_buffer->index + 1) % 0xF); + + gpu.Debugger().GXCommandProcessed(command); // Decode and execute command - gpu.Execute(command_buffer->commands[i]); + gpu.Execute(command); - // Indicates that command has completed - command_buffer->number_commands.Assign(command_buffer->number_commands - 1); + if (command.stop) { + command_buffer->should_stop.Assign(1); + } } IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); From e90795b6169ee18c271fb83ee7307347ca920614 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 13 Jul 2024 00:57:03 +0200 Subject: [PATCH 20/30] Implement game render thread delay (#180) More details: https://www.reddit.com/r/Citra/comments/1e1v4e1/fixing_luigis_mansion_2_performance_issues_once/ --- .../features/settings/model/IntSetting.kt | 3 +- .../settings/ui/SettingsFragmentPresenter.kt | 12 +++ src/android/app/src/main/jni/config.cpp | 1 + src/android/app/src/main/jni/default_ini.h | 4 + .../app/src/main/res/values-es/strings.xml | 2 + .../app/src/main/res/values/strings.xml | 2 + src/citra/config.cpp | 1 + src/citra_qt/configuration/config.cpp | 4 + .../configuration/configure_graphics.cpp | 37 +++++++++ .../configuration/configure_graphics.ui | 77 +++++++++++++++++++ src/common/settings.cpp | 2 + src/common/settings.h | 2 + src/core/hle/service/gsp/gsp_gpu.cpp | 24 +++++- 13 files changed, 168 insertions(+), 3 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index b2c378397..378c0eb05 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -40,7 +40,8 @@ enum class IntSetting( VSYNC("use_vsync_new", Settings.SECTION_RENDERER, 1), DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), - USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1); + USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), + DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0); override var int: Int = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 9f504e603..f949950bc 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -729,6 +729,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.TEXTURE_FILTER.defaultValue ) ) + add( + SliderSetting( + IntSetting.DELAY_RENDER_THREAD_US, + R.string.delay_render_thread, + R.string.delay_render_thread_description, + 0, + 16000, + " μs", + IntSetting.DELAY_RENDER_THREAD_US.key, + IntSetting.DELAY_RENDER_THREAD_US.defaultValue.toFloat() + ) + ) add(HeaderSetting(R.string.stereoscopy)) add( diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 4e862f3a0..bafac3129 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -169,6 +169,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_green); ReadSetting("Renderer", Settings::values.bg_blue); + ReadSetting("Renderer", Settings::values.delay_game_render_thread_us); // Layout Settings::values.layout_option = static_cast(sdl2_config->GetInteger( diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index c46395fea..7bf546e03 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -175,6 +175,10 @@ anaglyph_shader_name = # 0: Nearest, 1 (default): Linear filter_mode = +# Delays the game render thread by the specified amount of microseconds +# Set to 0 for no delay, only useful in dynamic-fps games to simulate GPU delay. +delay_game_render_thread_us = + [Layout] # Layout for the screen inside the render window. # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 7c7ba9e53..c4c9db23a 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -663,5 +663,7 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Conectar con Artic Base Conectar con una consola real que esté ejecutando un servidor Artic Base Introduce la dirección del servidor Artic Base + Retrasa el hilo de dibujado del juego + Retrasa el hilo de dibujado del juego cuando envía datos a la GPU. Ayuda con problemas de rendimiento en los (muy pocos) juegos de fps dinámicos. diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 6614e97f1..4de0ba43b 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -689,5 +689,7 @@ Connect to a real console that is running an Artic Base server Connect to Artic Base Enter Artic Base server address + Delay game render thread + Delay the game render thread when it submits data to the GPU. Helps with performance issues in the (very few) dynamic-fps games. diff --git a/src/citra/config.cpp b/src/citra/config.cpp index e47421102..bd60d4764 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -147,6 +147,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.use_vsync_new); ReadSetting("Renderer", Settings::values.texture_filter); ReadSetting("Renderer", Settings::values.texture_sampling); + ReadSetting("Renderer", Settings::values.delay_game_render_thread_us); ReadSetting("Renderer", Settings::values.mono_render_option); ReadSetting("Renderer", Settings::values.render_3d); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index e54fcee87..76ae4934d 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -667,6 +667,8 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.texture_filter); ReadGlobalSetting(Settings::values.texture_sampling); + ReadGlobalSetting(Settings::values.delay_game_render_thread_us); + if (global) { ReadBasicSetting(Settings::values.use_shader_jit); } @@ -1168,6 +1170,8 @@ void Config::SaveRendererValues() { WriteGlobalSetting(Settings::values.texture_filter); WriteGlobalSetting(Settings::values.texture_sampling); + WriteGlobalSetting(Settings::values.delay_game_render_thread_us); + if (global) { WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(), true); diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 80c2d138d..2e244b33a 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -26,6 +26,10 @@ ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::spangraphics_api_combo->setCurrentIndex(-1); + const auto width = static_cast(QString::fromStdString("000000000").size() * 6); + ui->delay_render_display_label->setMinimumWidth(width); + ui->delay_render_combo->setVisible(!Settings::IsConfiguringGlobal()); + auto graphics_api_combo_model = qobject_cast(ui->graphics_api_combo->model()); #ifndef ENABLE_SOFTWARE_RENDERER @@ -82,12 +86,25 @@ ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::spangraphics_api_combo, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureGraphics::SetPhysicalDeviceComboVisibility); + connect(ui->delay_render_slider, &QSlider::valueChanged, this, [&](int value) { + ui->delay_render_display_label->setText( + QStringLiteral("%1 ms") + .arg(((double)value) / 1000.f, 0, 'f', 3) + .rightJustified(QString::fromStdString("000000000").size())); + }); + SetConfiguration(); } ConfigureGraphics::~ConfigureGraphics() = default; void ConfigureGraphics::SetConfiguration() { + ui->delay_render_slider->setValue(Settings::values.delay_game_render_thread_us.GetValue()); + ui->delay_render_display_label->setText( + QStringLiteral("%1 ms") + .arg(((double)ui->delay_render_slider->value()) / 1000, 0, 'f', 3) + .rightJustified(QString::fromStdString("000000000").size())); + if (!Settings::IsConfiguringGlobal()) { ConfigurationShared::SetHighlight(ui->graphics_api_group, !Settings::values.graphics_api.UsingGlobal()); @@ -101,6 +118,16 @@ void ConfigureGraphics::SetConfiguration() { &Settings::values.texture_sampling); ConfigurationShared::SetHighlight(ui->widget_texture_sampling, !Settings::values.texture_sampling.UsingGlobal()); + ConfigurationShared::SetHighlight( + ui->delay_render_layout, !Settings::values.delay_game_render_thread_us.UsingGlobal()); + + if (Settings::values.delay_game_render_thread_us.UsingGlobal()) { + ui->delay_render_combo->setCurrentIndex(0); + ui->delay_render_slider->setEnabled(false); + } else { + ui->delay_render_combo->setCurrentIndex(1); + ui->delay_render_slider->setEnabled(true); + } } else { ui->graphics_api_combo->setCurrentIndex( static_cast(Settings::values.graphics_api.GetValue())); @@ -144,6 +171,9 @@ void ConfigureGraphics::ApplyConfiguration() { ui->toggle_disk_shader_cache, use_disk_shader_cache); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new, use_vsync_new); + ConfigurationShared::ApplyPerGameSetting( + &Settings::values.delay_game_render_thread_us, ui->delay_render_combo, + [this](s32) { return ui->delay_render_slider->value(); }); if (Settings::IsConfiguringGlobal()) { Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); @@ -170,9 +200,16 @@ void ConfigureGraphics::SetupPerGameUI() { ui->toggle_async_present->setEnabled(Settings::values.async_presentation.UsingGlobal()); ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal()); ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal()); + ui->delay_render_combo->setEnabled( + Settings::values.delay_game_render_thread_us.UsingGlobal()); return; } + connect(ui->delay_render_combo, qOverload(&QComboBox::activated), this, [this](int index) { + ui->delay_render_slider->setEnabled(index == 1); + ConfigurationShared::SetHighlight(ui->delay_render_layout, index == 1); + }); + ui->toggle_shader_jit->setVisible(false); ConfigurationShared::SetColoredComboBox( diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index a052186cd..122fdddcd 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -307,6 +307,83 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Use global + + + + + Use per-game + + + + + + + + Delay game render thread: + + + <html><head/><body><p>Delays the emulated game render thread the specified amount of milliseconds every time it submits render commands to the GPU.</p><p>Adjust this feature in the (very few) dynamic-fps games to fix performance issues.</p></body></html> + + + + + + + 0 + + + 16000 + + + 100 + + + 250 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 657747b61..e2f432a71 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -100,6 +100,7 @@ void LogSettings() { log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.GetValue())); log_setting("Renderer_TextureSampling", GetTextureSamplingName(values.texture_sampling.GetValue())); + log_setting("Renderer_DelayGameRenderThreasUs", values.delay_game_render_thread_us.GetValue()); log_setting("Stereoscopy_Render3d", values.render_3d.GetValue()); log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue()); log_setting("Stereoscopy_MonoRenderOption", values.mono_render_option.GetValue()); @@ -192,6 +193,7 @@ void RestoreGlobalState(bool is_powered_on) { values.frame_limit.SetGlobal(true); values.texture_filter.SetGlobal(true); values.texture_sampling.SetGlobal(true); + values.delay_game_render_thread_us.SetGlobal(true); values.layout_option.SetGlobal(true); values.swap_screen.SetGlobal(true); values.upright_screen.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 64bba90ee..b7ac91a0b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -479,6 +479,8 @@ struct Values { SwitchableSetting texture_filter{TextureFilter::None, "texture_filter"}; SwitchableSetting texture_sampling{TextureSampling::GameControlled, "texture_sampling"}; + SwitchableSetting delay_game_render_thread_us{0, 0, 16000, + "delay_game_render_thread_us"}; SwitchableSetting layout_option{LayoutOption::Default, "layout_option"}; SwitchableSetting swap_screen{false, "swap_screen"}; diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index 329c653c8..1b2dd29f1 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -9,6 +9,7 @@ #include #include "common/archives.h" #include "common/bit_field.h" +#include "common/settings.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/shared_memory.h" @@ -410,6 +411,9 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { auto* command_buffer = GetCommandBuffer(active_thread_id); auto& gpu = system.GPU(); + + bool requires_delay = false; + while (command_buffer->number_commands) { if (command_buffer->should_stop) { command_buffer->status.Assign(CommandBuffer::STATUS_STOPPED); @@ -420,6 +424,10 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { } Command command = command_buffer->commands[command_buffer->index]; + if (command.id == CommandId::SubmitCmdList && !requires_delay && + Settings::values.delay_game_render_thread_us.GetValue() != 0) { + requires_delay = true; + } // Decrease the number of commands remaining and increase the current index command_buffer->number_commands.Assign(command_buffer->number_commands - 1); @@ -435,8 +443,20 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { } } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); + if (requires_delay) { + ctx.RunAsync( + [](Kernel::HLERequestContext& ctx) { + return Settings::values.delay_game_render_thread_us.GetValue() * 1000; + }, + [](Kernel::HLERequestContext& ctx) { + IPC::RequestBuilder rb(ctx, 1, 0); + rb.Push(ResultSuccess); + }, + false); + } else { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); + } } void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { From 40851682cede3b94b5ea0fb964d6c0701c984634 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 13 Jul 2024 23:47:11 +0200 Subject: [PATCH 21/30] Workaround a performance bug in older Mali GPUs (#185) --- src/video_core/renderer_opengl/gl_driver.cpp | 9 +++++++++ src/video_core/renderer_opengl/gl_driver.h | 3 +++ .../renderer_opengl/gl_rasterizer.cpp | 19 ++++++++++++------- .../renderer_opengl/gl_rasterizer.h | 1 - 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_driver.cpp b/src/video_core/renderer_opengl/gl_driver.cpp index 63b05bd85..1e110fd58 100644 --- a/src/video_core/renderer_opengl/gl_driver.cpp +++ b/src/video_core/renderer_opengl/gl_driver.cpp @@ -199,6 +199,15 @@ void Driver::FindBugs() { if (vendor == Vendor::Intel && !is_linux) { bugs |= DriverBug::BrokenClearTexture; } + + if (vendor == Vendor::ARM && gpu_model.find("Mali") != gpu_model.npos) { + constexpr GLint MIN_TEXTURE_BUFFER_SIZE = static_cast((1 << 16)); + GLint max_texel_buffer_size; + glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max_texel_buffer_size); + if (max_texel_buffer_size == MIN_TEXTURE_BUFFER_SIZE) { + bugs |= DriverBug::SlowTextureBufferWithBigSize; + } + } } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_driver.h b/src/video_core/renderer_opengl/gl_driver.h index 2d67c3dc9..3801babad 100644 --- a/src/video_core/renderer_opengl/gl_driver.h +++ b/src/video_core/renderer_opengl/gl_driver.h @@ -36,6 +36,9 @@ enum class DriverBug { BrokenTextureView = 1 << 2, // On Haswell and Broadwell Intel drivers glClearTexSubImage produces a black screen BrokenClearTexture = 1 << 3, + // On some Mali GPUs, the texture buffer size is small and has reduced performance + // if the buffer is close to the maximum texture size + SlowTextureBufferWithBigSize = 1 << 4, }; /** diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 0487905de..3a9dc7831 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -63,12 +63,19 @@ GLenum MakeAttributeType(Pica::PipelineRegs::VertexAttributeFormat format) { return GL_UNSIGNED_BYTE; } -[[nodiscard]] GLsizeiptr TextureBufferSize() { +[[nodiscard]] GLsizeiptr TextureBufferSize(const Driver& driver, bool is_lf) { // Use the smallest texel size from the texel views // which corresponds to GL_RG32F GLint max_texel_buffer_size; glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max_texel_buffer_size); - return std::min(max_texel_buffer_size * 8ULL, TEXTURE_BUFFER_SIZE); + GLsizeiptr candidate = std::min(max_texel_buffer_size * 8ULL, TEXTURE_BUFFER_SIZE); + + if (driver.HasBug(DriverBug::SlowTextureBufferWithBigSize) && !is_lf) { + constexpr GLsizeiptr FIXUP_TEXTURE_BUFFER_SIZE = static_cast(1 << 14); // 16384 + return FIXUP_TEXTURE_BUFFER_SIZE; + } + + return candidate; } } // Anonymous namespace @@ -79,13 +86,11 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, Pica::PicaCore& : VideoCore::RasterizerAccelerated{memory, pica}, driver{driver_}, shader_manager{renderer.GetRenderWindow(), driver, !driver.IsOpenGLES()}, runtime{driver, renderer}, res_cache{memory, custom_tex_manager, runtime, regs, renderer}, - texture_buffer_size{TextureBufferSize()}, vertex_buffer{driver, GL_ARRAY_BUFFER, - VERTEX_BUFFER_SIZE}, + vertex_buffer{driver, GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE}, uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE}, index_buffer{driver, GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE}, - texture_buffer{driver, GL_TEXTURE_BUFFER, texture_buffer_size}, texture_lf_buffer{ - driver, GL_TEXTURE_BUFFER, - texture_buffer_size} { + texture_buffer{driver, GL_TEXTURE_BUFFER, TextureBufferSize(driver, false)}, + texture_lf_buffer{driver, GL_TEXTURE_BUFFER, TextureBufferSize(driver, true)} { // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0 state.clip_distance[0] = true; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 3fe5e8dde..b63b34e86 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -145,7 +145,6 @@ private: OGLVertexArray hw_vao; // VAO for hardware shader / accelerate draw std::array hw_vao_enabled_attributes{}; - GLsizeiptr texture_buffer_size; OGLStreamBuffer vertex_buffer; OGLStreamBuffer uniform_buffer; OGLStreamBuffer index_buffer; From c697aaf656dae133e560d1b1ee27e566d581a5d7 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 13 Jul 2024 23:49:26 +0200 Subject: [PATCH 22/30] renderer_vulkan: Reduce amount of wait semaphores --- src/video_core/renderer_vulkan/vk_master_semaphore.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index f8542524d..0e5ec6d97 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -68,9 +68,9 @@ void MasterSemaphoreTimeline::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore const std::array signal_values{signal_value, u64(0)}; const std::array signal_semaphores{Handle(), signal}; - const u32 num_wait_semaphores = wait ? 2U : 1U; - const std::array wait_values{signal_value - 1, u64(1)}; - const std::array wait_semaphores{Handle(), wait}; + const u32 num_wait_semaphores = wait ? 1U : 0U; + const std::array wait_values{u64(1)}; + const std::array wait_semaphores{wait}; static constexpr std::array wait_stage_masks = { vk::PipelineStageFlagBits::eAllCommands, From ed3d5a9f7fe7fa44affaaf20b95109e3928a1d68 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sun, 14 Jul 2024 00:00:21 +0200 Subject: [PATCH 23/30] renderer_vulkan: Wait for window to present before destroying instance --- src/video_core/renderer_vulkan/renderer_vulkan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index bb81a1037..f00966085 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -73,6 +73,7 @@ RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_, RendererVulkan::~RendererVulkan() { vk::Device device = instance.GetDevice(); scheduler.Finish(); + main_window.WaitPresent(); device.waitIdle(); device.destroyShaderModule(present_vertex_shader); From 45f52709a6b448221fa94c8dab424f4d5bb6ce27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 14 Jul 2024 00:54:19 +0200 Subject: [PATCH 24/30] android: add quicksave hotkeys (#181) --- .../java/org/citra/citra_emu/NativeLibrary.kt | 18 +++++++- .../citra_emu/activities/EmulationActivity.kt | 2 +- .../citra_emu/features/hotkeys/Hotkey.kt | 4 +- .../features/hotkeys/HotkeyUtility.kt | 23 +++++++++- .../features/settings/model/Settings.kt | 10 ++++- .../model/view/InputBindingSetting.kt | 2 + .../citra_emu/fragments/EmulationFragment.kt | 42 ++++++++++++++----- .../app/src/main/res/values/strings.xml | 9 ++++ src/core/savestate.cpp | 2 +- src/core/savestate.h | 2 +- 10 files changed, 96 insertions(+), 18 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index 84a62d898..135794d65 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -527,12 +527,28 @@ object NativeLibrary { external fun removeAmiibo() - const val SAVESTATE_SLOT_COUNT = 10 + const val SAVESTATE_SLOT_COUNT = 11 + const val QUICKSAVE_SLOT = 0 external fun getSavestateInfo(): Array? external fun saveState(slot: Int) + fun loadStateIfAvailable(slot: Int): Boolean { + var available = false + getSavestateInfo()?.forEach { + if (it.slot == slot){ + available = true + return@forEach + } + } + if (available) { + loadState(slot) + return true + } + return false + } + external fun loadState(slot: Int) /** diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index a911b0c31..6b9c89c9d 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -66,7 +66,7 @@ class EmulationActivity : AppCompatActivity() { binding = ActivityEmulationBinding.inflate(layoutInflater) screenAdjustmentUtil = ScreenAdjustmentUtil(windowManager, settingsViewModel.settings) - hotkeyUtility = HotkeyUtility(screenAdjustmentUtil) + hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) setContentView(binding.root) val navHostFragment = diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt index b19cb03da..db99abf67 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/Hotkey.kt @@ -8,5 +8,7 @@ enum class Hotkey(val button: Int) { SWAP_SCREEN(10001), CYCLE_LAYOUT(10002), CLOSE_GAME(10003), - PAUSE_OR_RESUME(10004); + PAUSE_OR_RESUME(10004), + QUICKSAVE(10005), + QUICKLOAD(10006); } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt index 830b57b29..25f6a493b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/hotkeys/HotkeyUtility.kt @@ -4,10 +4,14 @@ package org.citra.citra_emu.features.hotkeys +import android.content.Context +import android.widget.Toast +import org.citra.citra_emu.NativeLibrary +import org.citra.citra_emu.R import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil -class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) { +class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val context: Context) { val hotkeyButtons = Hotkey.entries.map { it.button } @@ -18,6 +22,23 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil) { Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() + Hotkey.QUICKSAVE.button -> { + NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT) + Toast.makeText(context, + context.getString(R.string.quicksave_saving), + Toast.LENGTH_SHORT).show() + } + Hotkey.QUICKLOAD.button -> { + val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT) + val stringRes = if(wasLoaded) { + R.string.quickload_loading + } else { + R.string.quickload_not_found + } + Toast.makeText(context, + context.getString(stringRes), + Toast.LENGTH_SHORT).show() + } else -> {} } return true diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt index d94489021..0e92138cb 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt @@ -136,6 +136,8 @@ class Settings { const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout" const val HOTKEY_CLOSE_GAME = "hotkey_close_game" const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game" + const val HOTKEY_QUICKSAVE = "hotkey_quickload" + const val HOTKEY_QUICKlOAD = "hotkey_quickpause" val buttonKeys = listOf( KEY_BUTTON_A, @@ -187,13 +189,17 @@ class Settings { HOTKEY_SCREEN_SWAP, HOTKEY_CYCLE_LAYOUT, HOTKEY_CLOSE_GAME, - HOTKEY_PAUSE_OR_RESUME + HOTKEY_PAUSE_OR_RESUME, + HOTKEY_QUICKSAVE, + HOTKEY_QUICKlOAD ) val hotkeyTitles = listOf( R.string.emulation_swap_screens, R.string.emulation_cycle_landscape_layouts, R.string.emulation_close_game, - R.string.emulation_toggle_pause + R.string.emulation_toggle_pause, + R.string.emulation_quicksave, + R.string.emulation_quickload, ) const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt index 55b454093..8165830f6 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt @@ -133,6 +133,8 @@ class InputBindingSetting( Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button + Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button + Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button else -> -1 } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index eeff4ff1b..7aa8ce7b7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -481,12 +481,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.setOnMenuItemClickListener { when (it.itemId) { R.id.menu_emulation_save_state -> { - showSaveStateSubmenu() + showStateSubmenu(true) true } R.id.menu_emulation_load_state -> { - showLoadStateSubmenu() + showStateSubmenu(false) true } @@ -497,7 +497,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.show() } - private fun showSaveStateSubmenu() { + private fun showStateSubmenu(isSaving: Boolean) { + val savestates = NativeLibrary.getSavestateInfo() val popupMenu = PopupMenu( @@ -507,19 +508,40 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.menu.apply { for (i in 0 until NativeLibrary.SAVESTATE_SLOT_COUNT) { - val slot = i + 1 - val text = getString(R.string.emulation_empty_state_slot, slot) - add(text).setEnabled(true).setOnMenuItemClickListener { - displaySavestateWarning() - NativeLibrary.saveState(slot) + val slot = i + var enableClick = isSaving + val text = if (slot == NativeLibrary.QUICKSAVE_SLOT) { + enableClick = false + getString(R.string.emulation_quicksave_slot) + } else { + getString(R.string.emulation_empty_state_slot, slot) + } + + add(text).setEnabled(enableClick).setOnMenuItemClickListener { + if(isSaving) { + NativeLibrary.saveState(slot) + } else { + NativeLibrary.loadState(slot) + binding.drawerLayout.close() + Toast.makeText(context, + getString(R.string.quickload_loading), + Toast.LENGTH_SHORT).show() + } true } } } savestates?.forEach { - val text = getString(R.string.emulation_occupied_state_slot, it.slot, it.time) - popupMenu.menu.getItem(it.slot - 1).setTitle(text) + var enableClick = true + val text = if(it.slot == NativeLibrary.QUICKSAVE_SLOT) { + // do not allow saving in quicksave slot + enableClick = !isSaving + getString(R.string.emulation_occupied_quicksave_slot, it.time) + } else{ + getString(R.string.emulation_occupied_state_slot, it.slot, it.time) + } + popupMenu.menu.getItem(it.slot).setTitle(text).setEnabled(enableClick) } popupMenu.show() diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 4de0ba43b..a01418870 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -692,4 +692,13 @@ Delay game render thread Delay the game render thread when it submits data to the GPU. Helps with performance issues in the (very few) dynamic-fps games. + + Quicksave + Quicksave + Quickload + Quicksave - %1$tF %1$tR + Saving… + Loading… + No Quicksave available. + diff --git a/src/core/savestate.cpp b/src/core/savestate.cpp index ad8e57ffb..3390af273 100644 --- a/src/core/savestate.cpp +++ b/src/core/savestate.cpp @@ -90,7 +90,7 @@ static bool ValidateSaveState(const CSTHeader& header, SaveStateInfo& info, u64 std::vector ListSaveStates(u64 program_id, u64 movie_id) { std::vector result; result.reserve(SaveStateSlotCount); - for (u32 slot = 1; slot <= SaveStateSlotCount; ++slot) { + for (u32 slot = 0; slot <= SaveStateSlotCount; ++slot) { const auto path = GetSaveStatePath(program_id, movie_id, slot); if (!FileUtil::Exists(path)) { continue; diff --git a/src/core/savestate.h b/src/core/savestate.h index 2962cce86..5232e98c0 100644 --- a/src/core/savestate.h +++ b/src/core/savestate.h @@ -20,7 +20,7 @@ struct SaveStateInfo { std::string build_name; }; -constexpr u32 SaveStateSlotCount = 10; // Maximum count of savestate slots +constexpr u32 SaveStateSlotCount = 11; // Maximum count of savestate slots std::vector ListSaveStates(u64 program_id, u64 movie_id); From 93025c95f2ce4baed37a7b587236f12ef667c540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 14 Jul 2024 00:54:57 +0200 Subject: [PATCH 25/30] android: implement device rotation options (#182) * android: implement device rotation options * fix naming * move orientation-setting to different section --- .../citra_emu/activities/EmulationActivity.kt | 14 +++++++++++--- .../features/settings/model/IntSetting.kt | 5 ++++- .../settings/ui/SettingsFragmentPresenter.kt | 14 ++++++++++++++ src/android/app/src/main/res/values/arrays.xml | 15 +++++++++++++++ src/android/app/src/main/res/values/strings.xml | 10 ++++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 6b9c89c9d..68e173e7c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -35,6 +35,8 @@ import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.features.hotkeys.HotkeyUtility import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.model.view.InputBindingSetting +import org.citra.citra_emu.features.settings.model.IntSetting +import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.utils.ControllerMappingHelper import org.citra.citra_emu.utils.FileBrowserHelper @@ -60,7 +62,10 @@ class EmulationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ThemeUtil.setTheme(this) - settingsViewModel.settings.loadSettings() + isActivityRecreated = savedInstanceState != null + if (!isActivityRecreated) { + settingsViewModel.settings.loadSettings() + } super.onCreate(savedInstanceState) @@ -74,8 +79,6 @@ class EmulationActivity : AppCompatActivity() { val navController = navHostFragment.navController navController.setGraph(R.navigation.emulation_navigation, intent.extras) - isActivityRecreated = savedInstanceState != null - // Set these options now so that the SurfaceView the game renders into is the right size. enableFullscreenImmersive() @@ -175,6 +178,11 @@ class EmulationActivity : AppCompatActivity() { controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } + + + val orientation = settingsViewModel.settings.getSection(Settings.SECTION_RENDERER) + ?.getSetting(IntSetting.DEVICE_ORIENTATION.key) as IntSetting + this.requestedOrientation = orientation.int } // Gets button presses diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 378c0eb05..7e921cc3c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -4,6 +4,8 @@ package org.citra.citra_emu.features.settings.model +import android.content.pm.ActivityInfo + enum class IntSetting( override val key: String, override val section: String, @@ -41,7 +43,8 @@ enum class IntSetting( DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), - DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0); + DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0), + DEVICE_ORIENTATION("default_device_orientation", Settings.SECTION_RENDERER, ActivityInfo.SCREEN_ORIENTATION_USER); override var int: Int = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index f949950bc..7d43031ae 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -173,6 +173,20 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun addGeneralSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general)) sl.apply { + add(HeaderSetting(R.string.graphics_ui)) + add( + SingleChoiceSetting( + IntSetting.DEVICE_ORIENTATION, + R.string.device_orientation_title, + R.string.device_orientation_description, + R.array.deviceOrientationEntries, + R.array.deviceOrientationValues, + IntSetting.DEVICE_ORIENTATION.key, + IntSetting.DEVICE_ORIENTATION.defaultValue, + ) + ) + + add(HeaderSetting(R.string.emulator_speed)) add( SwitchSetting( IntSetting.USE_FRAME_LIMIT, diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index cd542bae8..5f71b7cb5 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -191,6 +191,21 @@ 2 + + @string/device_orientation_system + @string/device_orientation_landscape + @string/device_orientation_portrait + @string/device_orientation_auto + + + + + 2 + 6 + 7 + 4 + + @string/system_region_jpn @string/system_region_usa diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index a01418870..e1f9f75a3 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -204,6 +204,8 @@ Flip + UI + Speed Renderer Graphics API Enable SPIR-V shader generation @@ -465,6 +467,14 @@ Black Backgrounds When using the dark theme, apply black backgrounds. + + Screen Orientation + How should your games be displayed? + Follow System Setting + Landscape + Portrait + Automatic + Device Clock Simulated Clock From eafb03ad38ebd2e051f426ba5d4582819d33d974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Mon, 15 Jul 2024 19:16:03 +0200 Subject: [PATCH 26/30] allow remapping of unknown keycodes (#189) --- .../citra_emu/activities/EmulationActivity.kt | 3 +- .../model/view/InputBindingSetting.kt | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 68e173e7c..0f3a5fb81 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -194,8 +194,7 @@ class EmulationActivity : AppCompatActivity() { return false } - val button = - preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode) + val button = preferences.getInt(InputBindingSetting.getInputButtonKey(event), event.scanCode) val action: Int = when (event.action) { KeyEvent.ACTION_DOWN -> { // On some devices, the back gesture / button press is not intercepted by androidx diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt index 8165830f6..c3756abf7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/InputBindingSetting.kt @@ -224,8 +224,10 @@ class InputBindingSetting( Toast.makeText(context, R.string.input_message_analog_only, Toast.LENGTH_LONG).show() return } - writeButtonMapping(getInputButtonKey(keyEvent.keyCode)) - val uiString = "${keyEvent.device.name}: Button ${keyEvent.keyCode}" + + val code = translateEventToKeyId(keyEvent) + writeButtonMapping(getInputButtonKey(code)) + val uiString = "${keyEvent.device.name}: Button $code" value = uiString } @@ -285,9 +287,17 @@ class InputBindingSetting( /** * Helper function to get the settings key for an gamepad button. + * */ + @Deprecated("Use the new getInputButtonKey(keyEvent) method to handle unknown keys") fun getInputButtonKey(keyCode: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyCode}" + /** + * Helper function to get the settings key for an gamepad button. + * + */ + fun getInputButtonKey(event: KeyEvent): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${translateEventToKeyId(event)}" + /** * Helper function to get the settings key for an gamepad axis. */ @@ -303,5 +313,23 @@ class InputBindingSetting( */ fun getInputAxisOrientationKey(axis: Int): String = "${getInputAxisKey(axis)}_GuestOrientation" + + + /** + * This function translates a keyEvent into an "keyid" + * This key id is either the keyCode from the event, or + * the raw scanCode. + * Only when the keyCode itself is 0, (so it is an unknown key) + * we fall back to the raw scan code. + * This handles keys like the media-keys on google statia-controllers + * that don't have a conventional "mapping" and report as "unknown" + */ + fun translateEventToKeyId(event: KeyEvent): Int { + return if (event.keyCode == 0) { + event.scanCode + } else { + event.keyCode + } + } } } From 9de19ff7a136f274397be7756bf0f69a34eb342f Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 16 Jul 2024 12:50:12 +0200 Subject: [PATCH 27/30] Revert "android: implement device rotation options (#182)" (#194) This reverts commit 93025c95f2ce4baed37a7b587236f12ef667c540. --- .../citra_emu/activities/EmulationActivity.kt | 14 +++----------- .../features/settings/model/IntSetting.kt | 5 +---- .../settings/ui/SettingsFragmentPresenter.kt | 14 -------------- src/android/app/src/main/res/values/arrays.xml | 15 --------------- src/android/app/src/main/res/values/strings.xml | 10 ---------- 5 files changed, 4 insertions(+), 54 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 0f3a5fb81..c22547f81 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -35,8 +35,6 @@ import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.features.hotkeys.HotkeyUtility import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.model.view.InputBindingSetting -import org.citra.citra_emu.features.settings.model.IntSetting -import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.utils.ControllerMappingHelper import org.citra.citra_emu.utils.FileBrowserHelper @@ -62,10 +60,7 @@ class EmulationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ThemeUtil.setTheme(this) - isActivityRecreated = savedInstanceState != null - if (!isActivityRecreated) { - settingsViewModel.settings.loadSettings() - } + settingsViewModel.settings.loadSettings() super.onCreate(savedInstanceState) @@ -79,6 +74,8 @@ class EmulationActivity : AppCompatActivity() { val navController = navHostFragment.navController navController.setGraph(R.navigation.emulation_navigation, intent.extras) + isActivityRecreated = savedInstanceState != null + // Set these options now so that the SurfaceView the game renders into is the right size. enableFullscreenImmersive() @@ -178,11 +175,6 @@ class EmulationActivity : AppCompatActivity() { controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } - - - val orientation = settingsViewModel.settings.getSection(Settings.SECTION_RENDERER) - ?.getSetting(IntSetting.DEVICE_ORIENTATION.key) as IntSetting - this.requestedOrientation = orientation.int } // Gets button presses diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 7e921cc3c..378c0eb05 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -4,8 +4,6 @@ package org.citra.citra_emu.features.settings.model -import android.content.pm.ActivityInfo - enum class IntSetting( override val key: String, override val section: String, @@ -43,8 +41,7 @@ enum class IntSetting( DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), - DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0), - DEVICE_ORIENTATION("default_device_orientation", Settings.SECTION_RENDERER, ActivityInfo.SCREEN_ORIENTATION_USER); + DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0); override var int: Int = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 7d43031ae..f949950bc 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -173,20 +173,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) private fun addGeneralSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general)) sl.apply { - add(HeaderSetting(R.string.graphics_ui)) - add( - SingleChoiceSetting( - IntSetting.DEVICE_ORIENTATION, - R.string.device_orientation_title, - R.string.device_orientation_description, - R.array.deviceOrientationEntries, - R.array.deviceOrientationValues, - IntSetting.DEVICE_ORIENTATION.key, - IntSetting.DEVICE_ORIENTATION.defaultValue, - ) - ) - - add(HeaderSetting(R.string.emulator_speed)) add( SwitchSetting( IntSetting.USE_FRAME_LIMIT, diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 5f71b7cb5..cd542bae8 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -191,21 +191,6 @@ 2 - - @string/device_orientation_system - @string/device_orientation_landscape - @string/device_orientation_portrait - @string/device_orientation_auto - - - - - 2 - 6 - 7 - 4 - - @string/system_region_jpn @string/system_region_usa diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index e1f9f75a3..a01418870 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -204,8 +204,6 @@ Flip - UI - Speed Renderer Graphics API Enable SPIR-V shader generation @@ -467,14 +465,6 @@ Black Backgrounds When using the dark theme, apply black backgrounds. - - Screen Orientation - How should your games be displayed? - Follow System Setting - Landscape - Portrait - Automatic - Device Clock Simulated Clock From 55748d7d1ab79ff12338cc00a9a0ffc266d0c7a8 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 16 Jul 2024 22:00:21 +0200 Subject: [PATCH 28/30] Artic Base: Add Artic Controller support (#195) --- .../features/settings/model/IntSetting.kt | 6 +- .../settings/ui/SettingsFragmentPresenter.kt | 10 + src/android/app/src/main/jni/config.cpp | 2 + src/android/app/src/main/jni/default_ini.h | 3 + .../app/src/main/res/values-es/strings.xml | 3 + .../app/src/main/res/values/strings.xml | 3 + src/citra_qt/configuration/config.cpp | 4 + .../configuration/configure_dialog.cpp | 2 +- .../configuration/configure_input.cpp | 12 +- src/citra_qt/configuration/configure_input.h | 3 +- src/citra_qt/configuration/configure_input.ui | 7 + src/common/settings.cpp | 1 + src/common/settings.h | 1 + src/core/hle/service/hid/hid.cpp | 472 +++++++++++++----- src/core/hle/service/hid/hid.h | 49 +- src/core/hle/service/ir/extra_hid.cpp | 47 +- src/core/hle/service/ir/extra_hid.h | 10 + src/core/hle/service/ir/ir_rst.cpp | 46 +- src/core/hle/service/ir/ir_rst.h | 10 + src/core/hle/service/ir/ir_user.cpp | 6 + src/core/hle/service/ir/ir_user.h | 6 + src/core/loader/artic.cpp | 8 + src/network/artic_base/artic_base_client.cpp | 109 +++- src/network/artic_base/artic_base_client.h | 79 ++- 24 files changed, 741 insertions(+), 158 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 378c0eb05..f0726f665 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -41,7 +41,8 @@ enum class IntSetting( DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), - DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0); + DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0), + USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0); override var int: Int = defaultValue @@ -69,7 +70,8 @@ enum class IntSetting( DEBUG_RENDERER, CPU_JIT, ASYNC_CUSTOM_LOADING, - AUDIO_INPUT_TYPE + AUDIO_INPUT_TYPE, + USE_ARTIC_BASE_CONTROLLER ) fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index f949950bc..8f104f0b5 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -626,6 +626,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) val button = getInputObject(key) add(InputBindingSetting(button, Settings.hotkeyTitles[i])) } + add(HeaderSetting(R.string.miscellaneous)) + add( + SwitchSetting( + IntSetting.USE_ARTIC_BASE_CONTROLLER, + R.string.use_artic_base_controller, + R.string.use_artic_base_controller_desc, + IntSetting.USE_ARTIC_BASE_CONTROLLER.key, + IntSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue + ) + ) } } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index bafac3129..e565c8ec8 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -128,6 +128,8 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger("Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT)); + ReadSetting("Controls", Settings::values.use_artic_base_controller); + // Core ReadSetting("Core", Settings::values.use_cpu_jit); ReadSetting("Core", Settings::values.cpu_clock_percentage); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 7bf546e03..acf42a73f 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -86,6 +86,9 @@ udp_input_port= # The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) udp_pad_index= +# Use Artic Controller when connected to Artic Base Server. (Default 0) +use_artic_base_controller= + [Core] # Whether to use the Just-In-Time (JIT) compiler for CPU emulation # 0: Interpreter (slow), 1 (default): JIT (fast) diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index c4c9db23a..2b83bc36d 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -665,5 +665,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Introduce la dirección del servidor Artic Base Retrasa el hilo de dibujado del juego Retrasa el hilo de dibujado del juego cuando envía datos a la GPU. Ayuda con problemas de rendimiento en los (muy pocos) juegos de fps dinámicos. + Misceláneo + Usar Artic Controller cuando se está conectado a Artic Base Server + Usa los controles proporcionados por Artic Base Server cuando esté conectado a él en lugar del dispositivo de entrada configurado. diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index a01418870..aad33db15 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -700,5 +700,8 @@ Saving… Loading… No Quicksave available. + Miscellaneous + Use Artic Controller when connected to Artic Base Server + Use the controls provided by Artic Base Server when connected to it instead of the configured input device. diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 76ae4934d..886b66aa2 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -327,6 +327,8 @@ void Config::ReadCameraValues() { void Config::ReadControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); + ReadBasicSetting(Settings::values.use_artic_base_controller); + int num_touch_from_button_maps = qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); @@ -924,6 +926,8 @@ void Config::SaveCameraValues() { void Config::SaveControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); + WriteBasicSetting(Settings::values.use_artic_base_controller); + WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0); qt_config->beginWriteArray(QStringLiteral("profiles")); for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) { diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 896009352..dd00e932b 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -29,7 +29,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor system{system_}, is_powered_on{system.IsPoweredOn()}, general_tab{std::make_unique(this)}, system_tab{std::make_unique(system, this)}, - input_tab{std::make_unique(this)}, + input_tab{std::make_unique(system, this)}, hotkeys_tab{std::make_unique(this)}, graphics_tab{ std::make_unique(gl_renderer, physical_devices, is_powered_on, this)}, diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index da98da0d0..158885ca5 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -16,6 +16,7 @@ #include "citra_qt/configuration/configure_input.h" #include "citra_qt/configuration/configure_motion_touch.h" #include "common/param_package.h" +#include "core/core.h" #include "ui_configure_input.h" const std::array @@ -145,8 +146,8 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return QObject::tr("[unknown]"); } -ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique()), +ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) + : QWidget(parent), system(_system), ui(std::make_unique()), timeout_timer(std::make_unique()), poll_timer(std::make_unique()) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -400,6 +401,9 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; void ConfigureInput::ApplyConfiguration() { + + Settings::values.use_artic_base_controller = ui->use_artic_controller->isChecked(); + std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.current_input_profile.buttons.begin(), [](const Common::ParamPackage& param) { return param.Serialize(); }); @@ -444,6 +448,10 @@ QList ConfigureInput::GetUsedKeyboardKeys() { } void ConfigureInput::LoadConfiguration() { + + ui->use_artic_controller->setChecked(Settings::values.use_artic_base_controller.GetValue()); + ui->use_artic_controller->setEnabled(!system.IsPoweredOn()); + std::transform(Settings::values.current_input_profile.buttons.begin(), Settings::values.current_input_profile.buttons.end(), buttons_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index fb00444c6..45a7a8329 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -30,7 +30,7 @@ class ConfigureInput : public QWidget { Q_OBJECT public: - explicit ConfigureInput(QWidget* parent = nullptr); + explicit ConfigureInput(Core::System& system, QWidget* parent = nullptr); ~ConfigureInput() override; /// Save all button configurations to settings file @@ -50,6 +50,7 @@ signals: void InputKeysChanged(QList new_key_list); private: + Core::System& system; std::unique_ptr ui; std::unique_ptr timeout_timer; diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 2d199e667..7168b7190 100644 --- a/src/citra_qt/configuration/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui @@ -841,6 +841,13 @@ + + + + Use Artic Controller when connected to Artic Base Server + + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index e2f432a71..69cc6f4ef 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -83,6 +83,7 @@ void LogSettings() { LOG_INFO(Config, "Citra Configuration:"); log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue()); log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue()); + log_setting("Controller_UseArticController", values.use_artic_base_controller.GetValue()); log_setting("Renderer_UseGLES", values.use_gles.GetValue()); log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue())); log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue()); diff --git a/src/common/settings.h b/src/common/settings.h index b7ac91a0b..b61957a6a 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -425,6 +425,7 @@ struct Values { int current_input_profile_index; ///< The current input profile index std::vector input_profiles; ///< The list of input profiles std::vector touch_from_button_maps; + Setting use_artic_base_controller{false, "use_artic_base_controller"}; SwitchableSetting enable_gamemode{true, "enable_gamemode"}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 9013fd4b1..1d11acd20 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -20,6 +20,8 @@ #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_spvr.h" #include "core/hle/service/hid/hid_user.h" +#include "core/hle/service/ir/ir_rst.h" +#include "core/hle/service/ir/ir_user.h" #include "core/hle/service/service.h" #include "core/movie.h" @@ -53,6 +55,32 @@ void Module::serialize(Archive& ar, const unsigned int file_version) { } SERIALIZE_IMPL(Module) +ArticBaseController::ArticBaseController( + const std::shared_ptr& client) { + + udp_stream = + client->NewUDPStream("ArticController", sizeof(ArticBaseController::ControllerData), + std::chrono::milliseconds(2)); + if (udp_stream.get()) { + udp_stream->Start(); + } +} + +ArticBaseController::ControllerData ArticBaseController::GetControllerData() { + + if (udp_stream.get() && udp_stream->IsReady()) { + auto data = udp_stream->GetLastPacket(); + if (data.size() == sizeof(ControllerData)) { + u32 id = *reinterpret_cast(data.data()); + if ((id - last_packet_id) < (std::numeric_limits::max() / 2)) { + last_packet_id = id; + memcpy(&last_controller_data, data.data(), data.size()); + } + } + } + return last_controller_data; +} + constexpr float accelerometer_coef = 512.0f; // measured from hw test result constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call @@ -111,96 +139,151 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { LoadInputDevices(); using namespace Settings::NativeButton; - state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); - state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); - state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); - state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); - state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); - state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); - state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); - state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); - // Get current circle pad position and update circle pad direction - float circle_pad_x_f, circle_pad_y_f; - std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + if (artic_controller.get() && artic_controller->IsReady()) { + constexpr u32 HID_VALID_KEYS = 0xF0003FFF; + constexpr u32 LIBCTRU_TOUCH_KEY = (1 << 20); - // xperia64: 0x9A seems to be the calibrated limit of the circle pad - // Verified by using Input Redirector with very large-value digital inputs - // on the circle pad and calibrating using the system settings application - constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); - // These are rounded rather than truncated on actual hardware - s16 circle_pad_new_x = static_cast(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); - s16 circle_pad_new_y = static_cast(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); - s16 circle_pad_x = - (circle_pad_new_x + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / - CIRCLE_PAD_AVERAGING; - s16 circle_pad_y = - (circle_pad_new_y + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / - CIRCLE_PAD_AVERAGING; - circle_pad_old_x.erase(circle_pad_old_x.begin()); - circle_pad_old_x.push_back(circle_pad_new_x); - circle_pad_old_y.erase(circle_pad_old_y.begin()); - circle_pad_old_y.push_back(circle_pad_new_y); + state.hex = data.pad & HID_VALID_KEYS; - system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); + s16 circle_pad_x = data.c_pad_x; + s16 circle_pad_y = data.c_pad_y; - const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); - state.circle_up.Assign(direction.up); - state.circle_down.Assign(direction.down); - state.circle_left.Assign(direction.left); - state.circle_right.Assign(direction.right); + system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); - mem->pad.current_state.hex = state.hex; - mem->pad.index = next_pad_index; - next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); - // Get the previous Pad state - u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); - PadState old_state = mem->pad.entries[last_entry_index].current_state; + // Get the previous Pad state + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; - // Compute bitmask with 1s for bits different from the old state - PadState changed = {{(state.hex ^ old_state.hex)}}; + // Compute bitmask with 1s for bits different from the old state + PadState changed = {{(state.hex ^ old_state.hex)}}; - // Get the current Pad entry - PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; + // Get the current Pad entry + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; - // Update entry properties - pad_entry.current_state.hex = state.hex; - pad_entry.delta_additions.hex = changed.hex & state.hex; - pad_entry.delta_removals.hex = changed.hex & old_state.hex; - pad_entry.circle_pad_x = circle_pad_x; - pad_entry.circle_pad_y = circle_pad_y; + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.circle_pad_x = circle_pad_x; + pad_entry.circle_pad_y = circle_pad_y; - // If we just updated index 0, provide a new timestamp - if (mem->pad.index == 0) { - mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; - mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + // If we just updated index 0, provide a new timestamp + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + } + + mem->touch.index = next_touch_index; + next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); + + // Get the current touch entry + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; + bool pressed = (data.pad & LIBCTRU_TOUCH_KEY) != 0; + + touch_entry.x = static_cast(data.touch_x); + touch_entry.y = static_cast(data.touch_y); + touch_entry.valid.Assign(pressed ? 1 : 0); + + system.Movie().HandleTouchStatus(touch_entry); + } else { + state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); + state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); + state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); + state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); + state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); + state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); + state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); + state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); + + // Get current circle pad position and update circle pad direction + float circle_pad_x_f, circle_pad_y_f; + std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + + // xperia64: 0x9A seems to be the calibrated limit of the circle pad + // Verified by using Input Redirector with very large-value digital inputs + // on the circle pad and calibrating using the system settings application + constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position + + // These are rounded rather than truncated on actual hardware + s16 circle_pad_new_x = static_cast(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); + s16 circle_pad_new_y = static_cast(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); + s16 circle_pad_x = (circle_pad_new_x + + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / + CIRCLE_PAD_AVERAGING; + s16 circle_pad_y = (circle_pad_new_y + + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / + CIRCLE_PAD_AVERAGING; + circle_pad_old_x.erase(circle_pad_old_x.begin()); + circle_pad_old_x.push_back(circle_pad_new_x); + circle_pad_old_y.erase(circle_pad_old_y.begin()); + circle_pad_old_y.push_back(circle_pad_new_y); + + system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); + + const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); + state.circle_up.Assign(direction.up); + state.circle_down.Assign(direction.down); + state.circle_left.Assign(direction.left); + state.circle_right.Assign(direction.right); + + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); + + // Get the previous Pad state + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; + + // Compute bitmask with 1s for bits different from the old state + PadState changed = {{(state.hex ^ old_state.hex)}}; + + // Get the current Pad entry + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; + + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.circle_pad_x = circle_pad_x; + pad_entry.circle_pad_y = circle_pad_y; + + // If we just updated index 0, provide a new timestamp + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + } + + mem->touch.index = next_touch_index; + next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); + + // Get the current touch entry + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; + bool pressed = false; + float x, y; + std::tie(x, y, pressed) = touch_device->GetStatus(); + if (!pressed && touch_btn_device) { + std::tie(x, y, pressed) = touch_btn_device->GetStatus(); + } + touch_entry.x = static_cast(x * Core::kScreenBottomWidth); + touch_entry.y = static_cast(y * Core::kScreenBottomHeight); + touch_entry.valid.Assign(pressed ? 1 : 0); + + system.Movie().HandleTouchStatus(touch_entry); } - mem->touch.index = next_touch_index; - next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); - - // Get the current touch entry - TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; - bool pressed = false; - float x, y; - std::tie(x, y, pressed) = touch_device->GetStatus(); - if (!pressed && touch_btn_device) { - std::tie(x, y, pressed) = touch_btn_device->GetStatus(); - } - touch_entry.x = static_cast(x * Core::kScreenBottomWidth); - touch_entry.y = static_cast(y * Core::kScreenBottomHeight); - touch_entry.valid.Assign(pressed ? 1 : 0); - - system.Movie().HandleTouchStatus(touch_entry); - // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). @@ -231,19 +314,27 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la mem->accelerometer.index = next_accelerometer_index; next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - Common::Vec3 accel; - std::tie(accel, std::ignore) = motion_device->GetStatus(); - accel *= accelerometer_coef; - // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback - // The time stretch formula should be like - // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity - AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; - accelerometer_entry.x = static_cast(accel.x); - accelerometer_entry.y = static_cast(accel.y); - accelerometer_entry.z = static_cast(accel.z); + if (artic_controller.get() && artic_controller->IsReady()) { + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); + + accelerometer_entry.x = data.accel_x; + accelerometer_entry.y = data.accel_y; + accelerometer_entry.z = data.accel_z; + } else { + Common::Vec3 accel; + std::tie(accel, std::ignore) = motion_device->GetStatus(); + accel *= accelerometer_coef; + // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback + // The time stretch formula should be like + // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity + + accelerometer_entry.x = static_cast(accel.x); + accelerometer_entry.y = static_cast(accel.y); + accelerometer_entry.z = static_cast(accel.z); + } system.Movie().HandleAccelerometerStatus(accelerometer_entry); @@ -278,13 +369,21 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late) GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; - Common::Vec3 gyro; - std::tie(std::ignore, gyro) = motion_device->GetStatus(); - double stretch = system.perf_stats->GetLastFrameTimeScale(); - gyro *= gyroscope_coef * static_cast(stretch); - gyroscope_entry.x = static_cast(gyro.x); - gyroscope_entry.y = static_cast(gyro.y); - gyroscope_entry.z = static_cast(gyro.z); + if (artic_controller.get() && artic_controller->IsReady()) { + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); + + gyroscope_entry.x = data.gyro_x; + gyroscope_entry.y = data.gyro_y; + gyroscope_entry.z = data.gyro_z; + } else { + Common::Vec3 gyro; + std::tie(std::ignore, gyro) = motion_device->GetStatus(); + double stretch = system.perf_stats->GetLastFrameTimeScale(); + gyro *= gyroscope_coef * static_cast(stretch); + gyroscope_entry.x = static_cast(gyro.x); + gyroscope_entry.y = static_cast(gyro.y); + gyroscope_entry.z = static_cast(gyro.z); + } system.Movie().HandleGyroscopeStatus(gyroscope_entry); @@ -316,6 +415,23 @@ void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) { void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_EnableAccelerometer"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + ++hid->enable_accelerometer_count; // Schedules the accelerometer update event if the accelerometer was just enabled @@ -324,15 +440,29 @@ void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { hid->accelerometer_update_event); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_DisableAccelerometer"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + --hid->enable_accelerometer_count; // Unschedules the accelerometer update event if the accelerometer was just disabled @@ -340,15 +470,29 @@ void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().UnscheduleEvent(hid->accelerometer_update_event, 0); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_EnableGyroscope"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + ++hid->enable_gyroscope_count; // Schedules the gyroscope update event if the gyroscope was just enabled @@ -356,15 +500,29 @@ void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_DisableGyroscope"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + --hid->enable_gyroscope_count; // Unschedules the gyroscope update event if the gyroscope was just disabled @@ -372,9 +530,6 @@ void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().UnscheduleEvent(hid->gyroscope_update_event, 0); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } @@ -382,25 +537,90 @@ void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestCon IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(ResultSuccess); - rb.Push(gyroscope_coef); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_GetGyroRawToDpsCoef"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + rb.Push(0.f); + return; + } + + Result res = Result{static_cast(resp->GetMethodResult())}; + if (res.IsError()) { + rb.Push(res); + rb.Push(0.f); + return; + } + + auto coef = resp->GetResponseFloat(0); + if (!coef.has_value()) { + rb.Push(ResultUnknown); + rb.Push(0.f); + return; + } + + rb.Push(res); + rb.Push(*coef); + } else { + rb.Push(ResultSuccess); + rb.Push(gyroscope_coef); + } } void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); - rb.Push(ResultSuccess); - const s16 param_unit = 6700; // an approximate value taken from hw - GyroscopeCalibrateParam param = { - {0, param_unit, -param_unit}, - {0, param_unit, -param_unit}, - {0, param_unit, -param_unit}, - }; - rb.PushRaw(param); + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + GyroscopeCalibrateParam param; - LOG_WARNING(Service_HID, "(STUBBED) called"); + auto req = artic_client->NewRequest("HIDUSER_GetGyroCalibrateParam"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + rb.PushRaw(param); + return; + } + + Result res = Result{static_cast(resp->GetMethodResult())}; + if (res.IsError()) { + rb.Push(res); + rb.PushRaw(param); + return; + } + + auto param_buf = resp->GetResponseBuffer(0); + if (!param_buf.has_value() || param_buf->second != sizeof(param)) { + rb.Push(ResultUnknown); + rb.PushRaw(param); + return; + } + memcpy(¶m, param_buf->first, sizeof(param)); + + rb.Push(res); + rb.PushRaw(param); + } else { + rb.Push(ResultSuccess); + + const s16 param_unit = 6700; // an approximate value taken from hw + GyroscopeCalibrateParam param = { + {0, param_unit, -param_unit}, + {0, param_unit, -param_unit}, + {0, param_unit, -param_unit}, + }; + rb.PushRaw(param); + + LOG_WARNING(Service_HID, "(STUBBED) called"); + } } void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) { @@ -454,6 +674,24 @@ Module::Module(Core::System& system) : system(system) { timing.ScheduleEvent(pad_update_ticks, pad_update_event); } +void Module::UseArticClient(const std::shared_ptr& client) { + artic_client = client; + artic_controller = std::make_shared(client); + if (!artic_controller->IsCreated()) { + artic_controller.reset(); + } else { + auto ir_user = system.ServiceManager().GetService("ir:USER"); + if (ir_user.get()) { + ir_user->UseArticController(artic_controller); + } + + auto ir_rst = system.ServiceManager().GetService("ir:rst"); + if (ir_rst.get()) { + ir_rst->UseArticController(artic_controller); + } + } +} + void Module::ReloadInputDevices() { is_device_reload_pending.store(true); } diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 609cb9276..79713ca69 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -14,13 +14,11 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/settings.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/frontend/input.h" #include "core/hle/service/service.h" - -namespace Core { -class System; -} +#include "network/artic_base/artic_base_client.h" namespace Kernel { class Event; @@ -199,6 +197,44 @@ struct DirectionState { /// Translates analog stick axes to directions. This is exposed for ir_rst module to use. DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); +class ArticBaseController { +public: + struct ControllerData { + u32 index{}; + u32 pad{}; + s16 c_pad_x{}; + s16 c_pad_y{}; + u16 touch_x{}; + u16 touch_y{}; + s16 c_stick_x{}; + s16 c_stick_y{}; + s16 accel_x{}; + s16 accel_y{}; + s16 accel_z{}; + s16 gyro_x{}; + s16 gyro_y{}; + s16 gyro_z{}; + }; + static_assert(sizeof(ControllerData) == 0x20, "Incorrect ControllerData size"); + + ArticBaseController(const std::shared_ptr& client); + + bool IsCreated() { + return udp_stream.get(); + } + + bool IsReady() { + return udp_stream.get() ? udp_stream->IsReady() : false; + } + + ControllerData GetControllerData(); + +private: + std::shared_ptr udp_stream; + u32 last_packet_id{}; + ControllerData last_controller_data{}; +}; + class Module final { public: explicit Module(Core::System& system); @@ -296,6 +332,8 @@ public: std::shared_ptr hid; }; + void UseArticClient(const std::shared_ptr& client); + void ReloadInputDevices(); const PadState& GetState() const; @@ -355,6 +393,9 @@ private: std::unique_ptr touch_device; std::unique_ptr touch_btn_device; + std::shared_ptr artic_controller; + std::shared_ptr artic_client; + template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp index 2483a27f0..e5dc486cb 100644 --- a/src/core/hle/service/ir/extra_hid.cpp +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -6,6 +6,7 @@ #include "common/alignment.h" #include "common/settings.h" #include "core/core_timing.h" +#include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/extra_hid.h" #include "core/movie.h" @@ -230,23 +231,47 @@ void ExtraHID::SendHIDStatus() { if (is_device_reload_pending.exchange(false)) LoadInputDevices(); + constexpr u32 ZL_BUTTON = (1 << 14); + constexpr u32 ZR_BUTTON = (1 << 15); + constexpr int C_STICK_CENTER = 0x800; // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can // take values in the whole range of a 12-bit integer. constexpr int C_STICK_RADIUS = 0x7FF; - float x, y; - std::tie(x, y) = c_stick->GetStatus(); - ExtraHIDResponse response{}; - response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); - response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); - response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); - response.buttons.battery_level.Assign(0x1F); - response.buttons.zl_not_held.Assign(!zl->GetStatus()); - response.buttons.zr_not_held.Assign(!zr->GetStatus()); - response.buttons.r_not_held.Assign(1); - response.unknown = 0; + + if (artic_controller.get() && artic_controller->IsReady()) { + Service::HID::ArticBaseController::ControllerData data = + artic_controller->GetControllerData(); + + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + + response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast( + (static_cast(data.c_stick_x) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS + + C_STICK_CENTER)); + response.c_stick.c_stick_y.Assign(static_cast( + (static_cast(data.c_stick_y) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS + + C_STICK_CENTER)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign((data.pad & ZL_BUTTON) == 0); + response.buttons.zr_not_held.Assign((data.pad & ZR_BUTTON) == 0); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + } else { + float x, y; + std::tie(x, y) = c_stick->GetStatus(); + + response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); + response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign(!zl->GetStatus()); + response.buttons.zr_not_held.Assign(!zr->GetStatus()); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + } movie.HandleExtraHidResponse(response); diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h index 5dc36b4cd..4cceec580 100644 --- a/src/core/hle/service/ir/extra_hid.h +++ b/src/core/hle/service/ir/extra_hid.h @@ -19,6 +19,10 @@ class Timing; class Movie; } // namespace Core +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { struct ExtraHIDResponse { @@ -54,6 +58,10 @@ public: /// Requests input devices reload from current settings. Called when the input settings change. void RequestInputDevicesReload(); + void UseArticController(const std::shared_ptr& ac) { + artic_controller = ac; + } + private: void SendHIDStatus(); void HandleConfigureHIDPollingRequest(std::span request); @@ -70,6 +78,8 @@ private: std::unique_ptr c_stick; std::atomic is_device_reload_pending; + std::shared_ptr artic_controller = nullptr; + template void serialize(Archive& ar, const unsigned int) { ar& hid_period; diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 2e2cd5b94..be7408619 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -72,25 +72,41 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) { if (is_device_reload_pending.exchange(false)) LoadInputDevices(); + constexpr u32 VALID_EXTRAHID_KEYS = 0xF00C000; + PadState state; - state.zl.Assign(zl_button->GetStatus()); - state.zr.Assign(zr_button->GetStatus()); + s16 c_stick_x, c_stick_y; - // Get current c-stick position and update c-stick direction - float c_stick_x_f, c_stick_y_f; - std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); - constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius - s16 c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_RADIUS); - s16 c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_RADIUS); + if (artic_controller.get() && artic_controller->IsReady()) { + Service::HID::ArticBaseController::ControllerData data = + artic_controller->GetControllerData(); - system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + state.hex = data.pad & VALID_EXTRAHID_KEYS; - if (!raw_c_stick) { - const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); - state.c_stick_up.Assign(direction.up); - state.c_stick_down.Assign(direction.down); - state.c_stick_left.Assign(direction.left); - state.c_stick_right.Assign(direction.right); + c_stick_x = data.c_stick_x; + c_stick_y = data.c_stick_y; + + system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + } else { + state.zl.Assign(zl_button->GetStatus()); + state.zr.Assign(zr_button->GetStatus()); + + // Get current c-stick position and update c-stick direction + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_RADIUS); + c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_RADIUS); + + system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + + if (!raw_c_stick) { + const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); + state.c_stick_up.Assign(direction.up); + state.c_stick_down.Assign(direction.down); + state.c_stick_left.Assign(direction.left); + state.c_stick_right.Assign(direction.right); + } } // TODO (wwylele): implement raw C-stick data for raw_c_stick = true diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 2514ab6f9..d02d34aa3 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,6 +21,10 @@ namespace Core { struct TimingEventType; }; +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { union PadState { @@ -42,6 +46,10 @@ public: ~IR_RST(); void ReloadInputDevices(); + void UseArticController(const std::shared_ptr& ac) { + artic_controller = ac; + } + private: /** * GetHandles service function @@ -88,6 +96,8 @@ private: bool raw_c_stick{false}; int update_period{0}; + std::shared_ptr artic_controller = nullptr; + template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index 597fdc4b6..d6ced4b4c 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -480,6 +480,12 @@ void IR_USER::ReloadInputDevices() { extra_hid->RequestInputDevicesReload(); } +void IR_USER::UseArticController(const std::shared_ptr& ac) { + if (extra_hid.get()) { + extra_hid->UseArticController(ac); + } +} + IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {} IRDevice::~IRDevice() = default; diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index e724e9dc5..fac49edfa 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -14,6 +14,10 @@ class Event; class SharedMemory; } // namespace Kernel +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { class BufferManager; @@ -57,6 +61,8 @@ public: void ReloadInputDevices(); + void UseArticController(const std::shared_ptr& ac); + private: /** * InitializeIrNopShared service function diff --git a/src/core/loader/artic.cpp b/src/core/loader/artic.cpp index d4ba70060..80365ed6e 100644 --- a/src/core/loader/artic.cpp +++ b/src/core/loader/artic.cpp @@ -27,6 +27,7 @@ #include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" +#include "core/hle/service/hid/hid_user.h" #include "core/loader/artic.h" #include "core/loader/smdh.h" #include "core/memory.h" @@ -361,6 +362,13 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { amapp->UseArticClient(client); } + if (Settings::values.use_artic_base_controller.GetValue()) { + auto hid_user = system.ServiceManager().GetService("hid:USER"); + if (hid_user.get()) { + hid_user->GetModule()->UseArticClient(client); + } + } + ParseRegionLockoutInfo(ncch_program_id); return ResultStatus::Success; diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp index fd5974d11..edc587f35 100644 --- a/src/network/artic_base/artic_base_client.cpp +++ b/src/network/artic_base/artic_base_client.cpp @@ -121,6 +121,81 @@ Client::Request::Request(u32 request_id, const std::string& method, size_t max_p std::min(request_packet.method.size(), method.size())); } +void Client::UDPStream::Start() { + thread_run = true; + handle_thread = std::thread(&Client::UDPStream::Handle, this); +} + +void Client::UDPStream::Handle() { + struct sockaddr_in* servaddr = reinterpret_cast(serv_sockaddr_in.data()); + socklen_t serv_sockaddr_len = static_cast(serv_sockaddr_in.size()); + memcpy(servaddr, client.GetServerAddr().data(), client.GetServerAddr().size()); + servaddr->sin_port = htons(port); + + main_socket = ::socket(AF_INET, SOCK_DGRAM, 0); + if (main_socket == static_cast(-1) || !thread_run) { + LOG_ERROR(Network, "Failed to create socket"); + return; + } + + if (!SetNonBlock(main_socket, true) || !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot set non-blocking socket mode"); + return; + } + + // Limit receive buffer so that packets don't get qeued and are dropped instead. + int buffer_size_int = static_cast(buffer_size); + if (::setsockopt(main_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&buffer_size_int), + sizeof(buffer_size_int)) || + !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot change receive buffer size"); + return; + } + + // Send data to server so that it knows client address. + char zero = '\0'; + int send_res = + ::sendto(main_socket, &zero, sizeof(char), 0, + reinterpret_cast(serv_sockaddr_in.data()), serv_sockaddr_len); + if (send_res < 0 || !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot send data to socket"); + return; + } + + ready = true; + std::vector buffer(buffer_size); + while (thread_run) { + std::chrono::steady_clock::time_point before = std::chrono::steady_clock::now(); + + int packet_size = ::recvfrom( + main_socket, reinterpret_cast(buffer.data()), static_cast(buffer.size()), 0, + reinterpret_cast(serv_sockaddr_in.data()), &serv_sockaddr_len); + if (packet_size > 0) { + if (client.report_traffic_callback) { + client.report_traffic_callback(packet_size); + } + + buffer.resize(packet_size); + { + std::scoped_lock l(current_buffer_mutex); + current_buffer = buffer; + } + } + + auto elapsed = std::chrono::steady_clock::now() - before; + + std::unique_lock lk(thread_cv_mutex); + thread_cv.wait_for(lk, elapsed < read_interval ? (read_interval - elapsed) + : std::chrono::microseconds(50)); + } + ready = false; + + closesocket(main_socket); +} + Client::~Client() { StopImpl(false); @@ -182,6 +257,7 @@ bool Client::Connect() { servaddr.sin_addr.s_addr = ((struct sockaddr_in*)(addrinfo->ai_addr))->sin_addr.s_addr; servaddr.sin_port = htons(port); freeaddrinfo(addrinfo); + memcpy(last_sockaddr_in.data(), &servaddr, last_sockaddr_in.size()); if (!ConnectWithTimeout(main_socket, &servaddr, sizeof(servaddr), 10)) { closesocket(main_socket); @@ -249,15 +325,15 @@ bool Client::Connect() { std::string str_port; std::stringstream ss_port(worker_ports.value()); while (std::getline(ss_port, str_port, ',')) { - int port = str_to_int(str_port); - if (port < 0 || port > static_cast(USHRT_MAX)) { + int port_curr = str_to_int(str_port); + if (port_curr < 0 || port_curr > static_cast(USHRT_MAX)) { shutdown(main_socket, SHUT_RDWR); closesocket(main_socket); LOG_ERROR(Network, "Couldn't parse server worker ports"); SignalCommunicationError(); return false; } - ports.push_back(static_cast(port)); + ports.push_back(static_cast(port_curr)); } if (ports.empty()) { shutdown(main_socket, SHUT_RDWR); @@ -294,6 +370,29 @@ bool Client::Connect() { return true; } +std::shared_ptr Client::NewUDPStream( + const std::string stream_id, size_t buffer_size, + const std::chrono::milliseconds& read_interval) { + + auto req = NewRequest("#" + stream_id); + + auto resp = Send(req); + + if (!resp.has_value()) { + return nullptr; + } + + auto port_udp = resp->GetResponseS32(0); + if (!port_udp.has_value()) { + return nullptr; + } + + udp_streams.push_back(std::make_shared(*this, static_cast(*port_udp), + buffer_size, read_interval)); + + return udp_streams.back(); +} + void Client::StopImpl(bool from_error) { bool expected = false; if (!stopped.compare_exchange_strong(expected, true)) @@ -303,6 +402,10 @@ void Client::StopImpl(bool from_error) { SendSimpleRequest("STOP"); } + for (auto it = udp_streams.begin(); it != udp_streams.end(); it++) { + it->get()->Stop(); + } + if (ping_thread.joinable()) { std::scoped_lock l2(ping_cv_mutex); ping_run = false; diff --git a/src/network/artic_base/artic_base_client.h b/src/network/artic_base/artic_base_client.h index 23079f832..040d50c9c 100644 --- a/src/network/artic_base/artic_base_client.h +++ b/src/network/artic_base/artic_base_client.h @@ -60,6 +60,61 @@ public: std::vector> pending_big_buffers; }; + class UDPStream { + public: + std::vector GetLastPacket() { + std::scoped_lock l(current_buffer_mutex); + return current_buffer; + } + + bool IsReady() { + return ready; + } + + void Start(); + void Stop() { + if (thread_run && handle_thread.joinable()) { + std::scoped_lock l2(thread_cv_mutex); + thread_run = false; + thread_cv.notify_one(); + } + } + + UDPStream(Client& _client, u16 _port, size_t _buffer_size, + const std::chrono::milliseconds& _read_interval) + : client(_client), port(_port), buffer_size(_buffer_size), + read_interval(_read_interval) {} + + ~UDPStream() { + Stop(); + if (handle_thread.joinable()) { + handle_thread.join(); + } + } + + private: + void Handle(); + + Client& client; + u16 port; + size_t buffer_size; + std::chrono::milliseconds read_interval; + + std::array serv_sockaddr_in{}; + bool ready = false; + + std::mutex current_buffer_mutex; + std::vector current_buffer; + + SocketHolder main_socket = -1; + + std::thread handle_thread; + std::condition_variable thread_cv; + std::mutex thread_cv_mutex; + std::atomic thread_run = true; + }; + friend class UDPStream; + Client(const std::string& _address, u16 _port) : address(_address), port(_port) { SocketManager::EnableSockets(); } @@ -76,6 +131,10 @@ public: return Request(GetNextRequestID(), method, max_parameter_count); } + std::shared_ptr NewUDPStream( + const std::string stream_id, size_t buffer_size, + const std::chrono::milliseconds& read_interval = std::chrono::milliseconds(0)); + void Stop() { StopImpl(false); } @@ -97,11 +156,17 @@ public: report_artic_event_callback = callback; } + // Returns the server address as a sockaddr_in struct + const std::array& GetServerAddr() { + return last_sockaddr_in; + } + private: - static constexpr const int SERVER_VERSION = 1; + static constexpr const int SERVER_VERSION = 2; std::string address; u16 port; + std::array last_sockaddr_in; SocketHolder main_socket = -1; std::atomic currRequestID; @@ -124,7 +189,7 @@ private: std::thread ping_thread; std::condition_variable ping_cv; std::mutex ping_cv_mutex; - bool ping_run = true; + std::atomic ping_run = true; void StopImpl(bool from_error); @@ -145,6 +210,8 @@ private: const std::chrono::nanoseconds& read_timeout = std::chrono::nanoseconds(0)); std::optional SendSimpleRequest(const std::string& method); + std::vector> udp_streams; + class Handler { public: Handler(Client& _client, u32 _addr, u16 _port, int _id); @@ -242,6 +309,14 @@ public: return *reinterpret_cast(buf->first); } + std::optional GetResponseFloat(u32 buffer_id) const { + auto buf = GetResponseBuffer(buffer_id); + if (!buf.has_value() || buf->second != sizeof(float)) { + return std::nullopt; + } + return *reinterpret_cast(buf->first); + } + private: friend class Client; friend class Client::Handler; From 959a66d839007b4d1b60fa7a9a3a768adf0c0f3d Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 16 Jul 2024 23:54:02 +0200 Subject: [PATCH 29/30] renderer_vulkan: Address vulkan surface recreation issues (#198) --- src/android/app/build.gradle.kts | 2 +- .../citra_emu/activities/EmulationActivity.kt | 15 --------------- .../citra/citra_emu/ui/main/MainActivity.kt | 4 ---- .../app/src/main/jni/emu_window/emu_window.cpp | 8 ++++++-- .../app/src/main/jni/emu_window/emu_window.h | 2 +- src/android/app/src/main/jni/native.cpp | 5 +++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../renderer_vulkan/vk_present_window.cpp | 2 +- .../renderer_vulkan/vk_swapchain.cpp | 18 +++++++++++------- 9 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index b1289ec3d..9ffd5055d 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -29,7 +29,7 @@ android { namespace = "org.citra.citra_emu" compileSdkVersion = "android-34" - ndkVersion = "26.1.10909125" + ndkVersion = "26.3.11579264" compileOptions { sourceCompatibility = JavaVersion.VERSION_17 diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index c22547f81..4681bd319 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -38,7 +38,6 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.utils.ControllerMappingHelper import org.citra.citra_emu.utils.FileBrowserHelper -import org.citra.citra_emu.utils.ForegroundService import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.ThemeUtil @@ -47,7 +46,6 @@ import org.citra.citra_emu.viewmodel.EmulationViewModel class EmulationActivity : AppCompatActivity() { private val preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) - private var foregroundService: Intent? = null var isActivityRecreated = false private val settingsViewModel: SettingsViewModel by viewModels() @@ -85,10 +83,6 @@ class EmulationActivity : AppCompatActivity() { windowManager.defaultDisplay.rotation ) - // Start a foreground service to prevent the app from getting killed in the background - foregroundService = Intent(this, ForegroundService::class.java) - startForegroundService(foregroundService) - EmulationLifecycleUtil.addShutdownHook(hook = { this.finish() }) } @@ -112,7 +106,6 @@ class EmulationActivity : AppCompatActivity() { override fun onDestroy() { EmulationLifecycleUtil.clear() - stopForegroundService(this) super.onDestroy() } @@ -452,12 +445,4 @@ class EmulationActivity : AppCompatActivity() { OnFilePickerResult(result.toString()) } - - companion object { - fun stopForegroundService(activity: Activity) { - val startIntent = Intent(activity, ForegroundService::class.java) - startIntent.action = ForegroundService.ACTION_STOP - activity.startForegroundService(startIntent) - } - } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt index c2aa87de4..3e9b48f7c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt @@ -156,9 +156,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } - // Dismiss previous notifications (should not happen unless a crash occurred) - EmulationActivity.stopForegroundService(this) - setInsets() } @@ -170,7 +167,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } override fun onDestroy() { - EmulationActivity.stopForegroundService(this) super.onDestroy() } diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 436442acd..8e1c40cd7 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -27,14 +27,18 @@ static void UpdateLandscapeScreenLayout() { IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout())); } -void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { - render_window = surface; +bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { + if (render_window == surface) { + return false; + } + render_window = surface; window_info.type = Frontend::WindowSystemType::Android; window_info.render_surface = surface; StopPresenting(); OnFramebufferSizeChanged(); + return true; } bool EmuWindow_Android::OnTouchEvent(int x, int y, bool pressed) { diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index 64ead5c7f..4266fd1bb 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -17,7 +17,7 @@ public: ~EmuWindow_Android(); /// Called by the onSurfaceChanges() method to change the surface - void OnSurfaceChanged(ANativeWindow* surface); + bool OnSurfaceChanged(ANativeWindow* surface); /// Handles touch event that occur.(Touched or released) bool OnTouchEvent(int x, int y, bool pressed); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index d84562e1a..393ce906d 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -294,12 +294,13 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject surf) { s_surf = ANativeWindow_fromSurface(env, surf); + bool notify = false; if (window) { - window->OnSurfaceChanged(s_surf); + notify = window->OnSurfaceChanged(s_surf); } auto& system = Core::System::GetInstance(); - if (system.IsPoweredOn()) { + if (notify && system.IsPoweredOn()) { system.GPU().Renderer().NotifySurfaceChanged(); } diff --git a/src/android/gradle/wrapper/gradle-wrapper.properties b/src/android/gradle/wrapper/gradle-wrapper.properties index 10cba3572..03ef85ab3 100644 --- a/src/android/gradle/wrapper/gradle-wrapper.properties +++ b/src/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/src/video_core/renderer_vulkan/vk_present_window.cpp b/src/video_core/renderer_vulkan/vk_present_window.cpp index ef879776a..62178c690 100644 --- a/src/video_core/renderer_vulkan/vk_present_window.cpp +++ b/src/video_core/renderer_vulkan/vk_present_window.cpp @@ -473,7 +473,7 @@ void PresentWindow::CopyToSwapchain(Frame* frame) { .pSignalSemaphores = &present_ready, }; - std::scoped_lock submit_lock{scheduler.submit_mutex}; + std::scoped_lock submit_lock{scheduler.submit_mutex, recreate_surface_mutex}; try { graphics_queue.submit(submit_info, frame->present_done); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 498a921a8..12b12d27d 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -78,9 +78,13 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { } bool Swapchain::AcquireNextImage() { + if (needs_recreation) { + return false; + } + MICROPROFILE_SCOPE(Vulkan_Acquire); - vk::Device device = instance.GetDevice(); - vk::Result result = + const vk::Device device = instance.GetDevice(); + const vk::Result result = device.acquireNextImageKHR(swapchain, std::numeric_limits::max(), image_acquired[frame_index], VK_NULL_HANDLE, &image_index); @@ -102,10 +106,6 @@ bool Swapchain::AcquireNextImage() { } void Swapchain::Present() { - if (needs_recreation) { - return; - } - const vk::PresentInfoKHR present_info = { .waitSemaphoreCount = 1, .pWaitSemaphores = &present_ready[image_index], @@ -119,6 +119,10 @@ void Swapchain::Present() { [[maybe_unused]] vk::Result result = instance.GetPresentQueue().presentKHR(present_info); } catch (vk::OutOfDateKHRError&) { needs_recreation = true; + return; + } catch (vk::SurfaceLostKHRError&) { + needs_recreation = true; + return; } catch (const vk::SystemError& err) { LOG_CRITICAL(Render_Vulkan, "Swapchain presentation failed {}", err.what()); UNREACHABLE(); @@ -268,4 +272,4 @@ void Swapchain::SetupImages() { } } -} // namespace Vulkan +} // namespace Vulkan \ No newline at end of file From 518f7234f79bb6d423fc6060f1c2c71c4dc5f9d9 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Wed, 17 Jul 2024 14:37:55 +0200 Subject: [PATCH 30/30] Artic Base: Fix issue when 0 bytes are read from file (#199) --- src/core/file_sys/archive_artic.cpp | 9 +++++---- src/core/file_sys/artic_cache.cpp | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/core/file_sys/archive_artic.cpp b/src/core/file_sys/archive_artic.cpp index ee36a1cae..a4eb4a599 100644 --- a/src/core/file_sys/archive_artic.cpp +++ b/src/core/file_sys/archive_artic.cpp @@ -405,11 +405,12 @@ ResultVal ArticFileBackend::Read(u64 offset, std::size_t length, u8 return res; auto read_buff = resp->GetResponseBuffer(0); - if (!read_buff.has_value()) - return Result(-1); - size_t actually_read = read_buff->second; + size_t actually_read = 0; + if (read_buff.has_value()) { + actually_read = read_buff->second; + memcpy(buffer + read_amount, read_buff->first, actually_read); + } - memcpy(buffer + read_amount, read_buff->first, actually_read); read_amount += actually_read; if (actually_read != to_read) break; diff --git a/src/core/file_sys/artic_cache.cpp b/src/core/file_sys/artic_cache.cpp index b5c963495..1aa770e93 100644 --- a/src/core/file_sys/artic_cache.cpp +++ b/src/core/file_sys/artic_cache.cpp @@ -200,11 +200,12 @@ ResultVal ArticCache::ReadFromArtic(s32 file_handle, u8* buffer, size_t return res; auto read_buff = resp->GetResponseBuffer(0); - if (!read_buff.has_value()) - return Result(-1); - size_t actually_read = read_buff->second; + size_t actually_read = 0; + if (read_buff.has_value()) { + actually_read = read_buff->second; + memcpy(buffer + read_amount, read_buff->first, actually_read); + } - memcpy(buffer + read_amount, read_buff->first, actually_read); read_amount += actually_read; if (actually_read != to_read) break;