diff --git a/dist/license.md b/dist/license.md index 207fc638e..801b0a519 100644 --- a/dist/license.md +++ b/dist/license.md @@ -16,6 +16,7 @@ qt_themes/default/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/default/icons/128x128/cartridge.png | CC0 1.0 | Designed by PabloMK7 qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com diff --git a/dist/qt_themes/default/icons/128x128/cartridge.png b/dist/qt_themes/default/icons/128x128/cartridge.png new file mode 100644 index 000000000..cb669ee29 Binary files /dev/null and b/dist/qt_themes/default/icons/128x128/cartridge.png differ diff --git a/dist/qt_themes/default/icons/index.theme b/dist/qt_themes/default/icons/index.theme index 1edbe6408..a7cfb3306 100644 --- a/dist/qt_themes/default/icons/index.theme +++ b/dist/qt_themes/default/icons/index.theme @@ -1,13 +1,16 @@ [Icon Theme] Name=default Comment=default theme -Directories=16x16,48x48,256x256 +Directories=16x16,48x48,128x128,256x256 [16x16] Size=16 [48x48] Size=48 - + +[128x128] +Size=128 + [256x256] Size=256 \ No newline at end of file diff --git a/dist/qt_themes/default/icons_light/128x128/cartridge.png b/dist/qt_themes/default/icons_light/128x128/cartridge.png new file mode 100644 index 000000000..cb669ee29 Binary files /dev/null and b/dist/qt_themes/default/icons_light/128x128/cartridge.png differ diff --git a/dist/qt_themes/default/icons_light/index.theme b/dist/qt_themes/default/icons_light/index.theme index 1edbe6408..a7cfb3306 100644 --- a/dist/qt_themes/default/icons_light/index.theme +++ b/dist/qt_themes/default/icons_light/index.theme @@ -1,13 +1,16 @@ [Icon Theme] Name=default Comment=default theme -Directories=16x16,48x48,256x256 +Directories=16x16,48x48,128x128,256x256 [16x16] Size=16 [48x48] Size=48 - + +[128x128] +Size=128 + [256x256] Size=256 \ No newline at end of file diff --git a/dist/qt_themes/default/theme_default.qrc b/dist/qt_themes/default/theme_default.qrc index c8339f86d..90ae777aa 100644 --- a/dist/qt_themes/default/theme_default.qrc +++ b/dist/qt_themes/default/theme_default.qrc @@ -13,6 +13,7 @@ icons/48x48/no_avatar.png icons/48x48/plus.png icons/48x48/sd_card.png + icons/128x128/cartridge.png icons/256x256/azahar.png icons/48x48/star.png icons/256x256/plus_folder.png @@ -31,6 +32,7 @@ icons_light/48x48/no_avatar.png icons_light/48x48/plus.png icons_light/48x48/sd_card.png + icons_light/128x128/cartridge.png icons_light/256x256/azahar.png icons_light/48x48/star.png icons_light/256x256/plus_folder.png diff --git a/license.txt b/license.txt index f94c73f1a..7b0e5ad1b 100644 --- a/license.txt +++ b/license.txt @@ -357,3 +357,4 @@ plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 fro plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com sd_card.png | CC BY-ND 3.0 | https://icons8.com star.png | CC BY-ND 3.0 | https://icons8.com +cartridge.png | CC0 1.0 | Designed by PabloMK7 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 56a47bbb6..20c3c0868 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 @@ -139,6 +139,12 @@ object NativeLibrary { external fun createLogFile() external fun logUserDirectory(directory: String) + /** + * Set the inserted cartridge that will appear + * in the home menu. Empty string to clear. + */ + external fun setInsertedCartridge(path: String) + /** * Begins emulation. */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt index a92f93401..dae538d73 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt @@ -13,6 +13,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.content.Context +import android.content.SharedPreferences import android.widget.TextView import android.widget.ImageView import android.widget.Toast @@ -69,6 +70,9 @@ class GameAdapter( private var imagePath: String? = null private var dialogShortcutBinding: DialogShortcutBinding? = null + private val preferences: SharedPreferences + get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) + fun handleShortcutImageResult(uri: Uri?) { val path = uri?.toString() if (path != null) { @@ -196,6 +200,11 @@ class GameAdapter( binding.textGameTitle.text = game.title binding.textCompany.text = game.company binding.textGameRegion.text = game.regions + binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) { + View.GONE + } else { + View.VISIBLE + } val backgroundColorId = if ( @@ -345,12 +354,29 @@ class GameAdapter( val bottomSheetDialog = BottomSheetDialog(context) bottomSheetDialog.setContentView(bottomSheetView) + val insertable = game.isInsertable + val inserted = insertable && (preferences.getString("insertedCartridge", "") == game.path) + bottomSheetView.findViewById(R.id.about_game_title).text = game.title bottomSheetView.findViewById(R.id.about_game_company).text = game.company bottomSheetView.findViewById(R.id.about_game_region).text = game.regions bottomSheetView.findViewById(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId) bottomSheetView.findViewById(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename bottomSheetView.findViewById(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType + + val insertButton = bottomSheetView.findViewById(R.id.insert_cartridge_button) + insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) } + insertButton.visibility = if (insertable) View.VISIBLE else View.GONE + insertButton.setOnClickListener { + if (inserted) { + preferences.edit().putString("insertedCartridge", "").apply() + } else { + preferences.edit().putString("insertedCartridge", game.path).apply() + } + bottomSheetDialog.dismiss() + notifyItemRangeChanged(0, currentList.size) + } + GameIconUtils.loadGameIcon(activity, game, bottomSheetView.findViewById(R.id.game_icon)) bottomSheetView.findViewById(R.id.about_game_play).setOnClickListener { 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 419919527..c27d49be7 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 @@ -144,6 +144,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } } + val insertedCartridge = preferences.getString("insertedCartridge", "") + NativeLibrary.setInsertedCartridge(insertedCartridge ?: "") + try { game = args.game ?: intentGame!! } catch (e: NullPointerException) { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/model/Game.kt b/src/android/app/src/main/java/org/citra/citra_emu/model/Game.kt index b74635e96..797b7a262 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/model/Game.kt @@ -25,6 +25,7 @@ class Game( val isInstalled: Boolean = false, val isSystemTitle: Boolean = false, val isVisibleSystemTitle: Boolean = false, + val isInsertable: Boolean = false, val icon: IntArray? = null, val fileType: String = "", val isCompressed: Boolean = false, diff --git a/src/android/app/src/main/java/org/citra/citra_emu/model/GameInfo.kt b/src/android/app/src/main/java/org/citra/citra_emu/model/GameInfo.kt index 817e5fdec..494d7bf75 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/model/GameInfo.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/model/GameInfo.kt @@ -37,6 +37,8 @@ class GameInfo(path: String) { external fun getFileType(): String + external fun getIsInsertable(): Boolean + companion object { @JvmStatic private external fun initialize(path: String): Long diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/GameHelper.kt index 7b7ca9fdb..90b011114 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/GameHelper.kt @@ -88,6 +88,7 @@ object GameHelper { isInstalled, gameInfo?.isSystemTitle() ?: false, gameInfo?.getIsVisibleSystemTitle() ?: false, + gameInfo?.getIsInsertable() ?: false, gameInfo?.getIcon(), gameInfo?.getFileType() ?: "", gameInfo?.getFileType()?.contains("(Z)") ?: false, diff --git a/src/android/app/src/main/jni/game_info.cpp b/src/android/app/src/main/jni/game_info.cpp index ebf61820a..7ff043fff 100644 --- a/src/android/app/src/main/jni/game_info.cpp +++ b/src/android/app/src/main/jni/game_info.cpp @@ -25,6 +25,7 @@ struct GameInfoData { bool loaded = false; bool is_encrypted = false; std::string file_type = ""; + bool is_insertable = false; }; GameInfoData* GetNewGameInfoData(const std::string& path) { @@ -89,6 +90,7 @@ GameInfoData* GetNewGameInfoData(const std::string& path) { gid->is_encrypted = is_encrypted; gid->title_id = program_id; gid->file_type = Loader::GetFileTypeString(loader->GetFileType(), loader->IsFileCompressed()); + gid->is_insertable = loader->GetFileType() == Loader::FileType::CCI; return gid; } @@ -230,4 +232,7 @@ jstring Java_org_citra_citra_1emu_model_GameInfo_getFileType(JNIEnv* env, jobjec return ToJString(env, file_type); } +jboolean Java_org_citra_citra_1emu_model_GameInfo_getIsInsertable(JNIEnv* env, jobject obj) { + return GetPointer(env, obj)->is_insertable; +} } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index a0dc4d864..779163cfc 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -101,6 +101,8 @@ std::mutex paused_mutex; std::mutex running_mutex; std::condition_variable running_cv; +std::string inserted_cartridge; + } // Anonymous namespace static jobject ToJavaCoreError(Core::System::ResultStatus result) { @@ -148,7 +150,10 @@ static void TryShutdown() { secondary_window->DoneCurrent(); } - Core::System::GetInstance().Shutdown(); + Core::System& system{Core::System::GetInstance()}; + + system.Shutdown(); + system.EjectCartridge(); window.reset(); if (secondary_window) { @@ -179,6 +184,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { Core::System& system{Core::System::GetInstance()}; + if (!inserted_cartridge.empty()) { + system.InsertCartridge(inserted_cartridge); + } + const auto graphics_api = Settings::values.graphics_api.GetValue(); EGLContext* shared_context; switch (graphics_api) { @@ -1090,4 +1099,9 @@ jlong Java_org_citra_citra_1emu_NativeLibrary_playTimeManagerGetCurrentTitleId(J return ptm_current_title_id; } +void Java_org_citra_citra_1emu_NativeLibrary_setInsertedCartridge(JNIEnv* env, jobject obj, + jstring path) { + inserted_cartridge = GetJString(env, path); +} + } // extern "C" diff --git a/src/android/app/src/main/res/drawable/cartridge.png b/src/android/app/src/main/res/drawable/cartridge.png new file mode 100644 index 000000000..cb669ee29 Binary files /dev/null and b/src/android/app/src/main/res/drawable/cartridge.png differ diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml index 85392a813..5b1d558ea 100644 --- a/src/android/app/src/main/res/layout/card_game.xml +++ b/src/android/app/src/main/res/layout/card_game.xml @@ -28,6 +28,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + ID: File: Type: + Insert Cartridge + Eject Cartridge Show Performance Overlay diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index 6398d1f22..d8d37a5e6 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -1243,6 +1243,10 @@ bool GMainWindow::LoadROM(const QString& filename) { const auto scope = render_window->Acquire(); + if (!UISettings::values.inserted_cartridge.GetValue().empty()) { + system.InsertCartridge(UISettings::values.inserted_cartridge.GetValue()); + } + const Core::System::ResultStatus result{ system.Load(*render_window, filename.toStdString(), secondary_window)}; @@ -1532,6 +1536,8 @@ void GMainWindow::ShutdownGame() { emu_thread->wait(); emu_thread = nullptr; + system.EjectCartridge(); + OnCloseMovie(); discord_rpc->Update(); @@ -3813,6 +3819,9 @@ void GMainWindow::closeEvent(QCloseEvent* event) { ShutdownGame(); } + // Save settings in case they were changed from outside the configuration menu. + config->Save(); + render_window->close(); secondary_window->close(); multiplayer_state->Close(); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 4c7d99c93..2eb891a75 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -670,6 +670,11 @@ void QtConfig::ReadPathValues() { ReadSetting(QStringLiteral("last_artic_base_addr"), QString{}).toString(); UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString(); + + ReadBasicSetting(UISettings::values.inserted_cartridge); + if (!FileUtil::Exists(UISettings::values.inserted_cartridge.GetValue())) { + UISettings::values.inserted_cartridge.SetValue(""); + } } qt_config->endGroup(); @@ -1204,6 +1209,7 @@ void QtConfig::SavePathValues() { UISettings::values.last_artic_base_addr, QString{}); WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{}); + WriteBasicSetting(UISettings::values.inserted_cartridge); } qt_config->endGroup(); diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index c275090dc..d6e3efdb5 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -307,6 +309,42 @@ void GameList::OnFilterCloseClicked() { main_window->filterBarSetChecked(false); } +class CartridgeIconDelegate : public QStyledItemDelegate { +public: + using QStyledItemDelegate::QStyledItemDelegate; + + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override { + QStyleOptionViewItem opt(option); + initStyleOption(&opt, index); + + QStyle* style = opt.widget ? opt.widget->style() : QApplication::style(); + + // Draw the default item (background, text, selection, etc.) + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); + + // Draw cartridge inserted icon + quint32 can_insert = index.data(GameListItemPath::CanInsertRole).value(); + QString game_path = index.data(GameListItemPath::FullPathRole).value(); + + bool is_inserted = can_insert && UISettings::values.inserted_cartridge.GetValue() == + game_path.toStdString(); + + if (is_inserted) { + QPixmap pixmap = QIcon::fromTheme(QStringLiteral("cartridge")).pixmap(24); + + const int margin = 12; + QSize pmSize = pixmap.size() / pixmap.devicePixelRatio(); + + QRect pmRect(opt.rect.right() - pmSize.width() - margin, + opt.rect.center().y() - pmSize.height() / 2, pmSize.width(), + pmSize.height()); + + painter->drawPixmap(pmRect, pixmap); + } + } +}; + GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* parent) : QWidget{parent}, play_time_manager{play_time_manager_} { watcher = new QFileSystemWatcher(this); @@ -329,6 +367,7 @@ GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* p tree_view->setEditTriggers(QHeaderView::NoEditTriggers); tree_view->setContextMenuPolicy(Qt::CustomContextMenu); tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); + tree_view->setItemDelegateForColumn(0, new CartridgeIconDelegate(tree_view)); tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); UpdateColumnVisibility(); @@ -534,7 +573,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { selected.data(GameListItemPath::ProgramIdRole).toULongLong(), selected.data(GameListItemPath::ExtdataIdRole).toULongLong(), static_cast( - selected.data(GameListItemPath::MediaTypeRole).toUInt())); + selected.data(GameListItemPath::MediaTypeRole).toUInt()), + selected.data(GameListItemPath::CanInsertRole).toUInt() != 0); break; case GameListItemType::CustomDir: AddPermDirPopup(context_menu, selected); @@ -604,8 +644,16 @@ void ForEachOpenGLCacheFile(u64 program_id, auto func) { #endif void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QString& name, - u64 program_id, u64 extdata_id, Service::FS::MediaType media_type) { + u64 program_id, u64 extdata_id, Service::FS::MediaType media_type, + bool can_insert) { QAction* favorite = context_menu.addAction(tr("Favorite")); + bool is_inserted = + can_insert && UISettings::values.inserted_cartridge.GetValue() == path.toStdString(); + QAction* cartridge_insert = nullptr; + if (can_insert) { + cartridge_insert = + context_menu.addAction(is_inserted ? tr("Eject Cartridge") : tr("Insert Cartridge")); + } context_menu.addSeparator(); QMenu* open_menu = context_menu.addMenu(tr("Open")); QAction* open_application_location = open_menu->addAction(tr("Application Location")); @@ -719,6 +767,16 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr connect(open_extdata_location, &QAction::triggered, this, [this, extdata_id] { emit OpenFolderRequested(extdata_id, GameListOpenTarget::EXT_DATA); }); + if (cartridge_insert) { + connect(cartridge_insert, &QAction::triggered, this, [this, path, is_inserted] { + if (is_inserted) { + UISettings::values.inserted_cartridge.SetValue(""); + } else { + UISettings::values.inserted_cartridge.SetValue(path.toStdString()); + } + tree_view->viewport()->update(); + }); + } connect(open_application_location, &QAction::triggered, this, [this, program_id] { emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION); }); diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index d563f74b1..a28cf4290 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -124,7 +124,7 @@ private: void PopupContextMenu(const QPoint& menu_location); void PopupHeaderContextMenu(const QPoint& menu_location); void AddGamePopup(QMenu& context_menu, const QString& path, const QString& name, u64 program_id, - u64 extdata_id, Service::FS::MediaType media_type); + u64 extdata_id, Service::FS::MediaType media_type, bool can_insert); void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); void AddFavoritesPopup(QMenu& context_menu); diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 046ec7267..45df2a483 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -159,15 +159,18 @@ public: static constexpr int ExtdataIdRole = SortRole + 4; static constexpr int LongTitleRole = SortRole + 5; static constexpr int MediaTypeRole = SortRole + 6; + static constexpr int CanInsertRole = SortRole + 7; GameListItemPath() = default; GameListItemPath(const QString& game_path, std::span smdh_data, u64 program_id, - u64 extdata_id, Service::FS::MediaType media_type, bool is_encrypted) { + u64 extdata_id, Service::FS::MediaType media_type, bool is_encrypted, + bool can_insert) { setData(type(), TypeRole); setData(game_path, FullPathRole); setData(qulonglong(program_id), ProgramIdRole); setData(qulonglong(extdata_id), ExtdataIdRole); setData(quint32(media_type), MediaTypeRole); + setData(quint32(can_insert), CanInsertRole); if (UISettings::values.game_list_icon_size.GetValue() == UISettings::GameListIconSize::NoIcon) { diff --git a/src/citra_qt/game_list_worker.cpp b/src/citra_qt/game_list_worker.cpp index 7e5c4650f..cc65b5082 100644 --- a/src/citra_qt/game_list_worker.cpp +++ b/src/citra_qt/game_list_worker.cpp @@ -113,7 +113,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign { new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id, extdata_id, media_type, - res == Loader::ResultStatus::ErrorEncrypted), + res == Loader::ResultStatus::ErrorEncrypted, + loader->GetFileType() == Loader::FileType::CCI), new GameListItemCompat(compatibility), new GameListItemRegion(smdh), new GameListItem(QString::fromStdString(Loader::GetFileTypeString( diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h index 393e20dd4..f9f42353a 100644 --- a/src/citra_qt/uisettings.h +++ b/src/citra_qt/uisettings.h @@ -85,6 +85,8 @@ struct Values { Settings::Setting hide_mouse{false, "hideInactiveMouse"}; Settings::Setting check_for_update_on_start{true, "check_for_update_on_start"}; + Settings::Setting inserted_cartridge{"", "inserted_cartridge"}; + // Discord RPC Settings::Setting enable_discord_presence{true, "enable_discord_presence"}; diff --git a/src/core/core.cpp b/src/core/core.cpp index d00de677c..1622f3941 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -24,6 +24,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/dumping/backend.h" +#include "core/file_sys/ncch_container.h" #include "core/frontend/image_interface.h" #include "core/gdbstub/gdbstub.h" #include "core/global.h" @@ -812,6 +813,18 @@ void System::RegisterAppLoaderEarly(std::unique_ptr& loader) early_app_loader = std::move(loader); } +void System::InsertCartridge(const std::string& path) { + FileSys::NCCHContainer cartridge_container(path); + if (cartridge_container.LoadHeader() == Loader::ResultStatus::Success && + cartridge_container.IsNCSD()) { + inserted_cartridge = path; + } +} + +void System::EjectCartridge() { + inserted_cartridge.clear(); +} + bool System::IsInitialSetup() { return app_loader && app_loader->DoingInitialSetup(); } diff --git a/src/core/core.h b/src/core/core.h index 122401eed..b33fea174 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -376,6 +376,14 @@ public: void RegisterAppLoaderEarly(std::unique_ptr& loader); + void InsertCartridge(const std::string& path); + + void EjectCartridge(); + + const std::string& GetCartridge() const { + return inserted_cartridge; + } + bool IsInitialSetup(); private: @@ -399,6 +407,9 @@ private: // Temporary app loader passed from frontend std::unique_ptr early_app_loader; + /// Path for current inserted cartridge + std::string inserted_cartridge; + /// ARM11 CPU core std::vector> cpu_cores; ARM_Interface* running_core = nullptr; diff --git a/src/core/file_sys/archive_ncch.cpp b/src/core/file_sys/archive_ncch.cpp index 345ba6a0f..514a8736e 100644 --- a/src/core/file_sys/archive_ncch.cpp +++ b/src/core/file_sys/archive_ncch.cpp @@ -88,14 +88,31 @@ ResultVal> NCCHArchive::OpenFile(const Path& path, std::memcpy(&openfile_path, binary.data(), sizeof(NCCHFilePath)); std::string file_path; - if (Settings::values.is_new_3ds) { - // Try the New 3DS specific variant first. - file_path = Service::AM::GetTitleContentPath(media_type, title_id | 0x20000000, - openfile_path.content_index); - } - if (!Settings::values.is_new_3ds || !FileUtil::Exists(file_path)) { - file_path = - Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index); + + if (media_type == Service::FS::MediaType::GameCard) { + const auto& cartridge = Core::System::GetInstance().GetCartridge(); + if (cartridge.empty()) { + return ResultNotFound; + } + + u64 card_program_id; + auto cartridge_loader = Loader::GetLoader(cartridge); + FileSys::NCCHContainer cartridge_ncch(cartridge); + if (cartridge_ncch.ReadProgramId(card_program_id) != Loader::ResultStatus::Success || + card_program_id != title_id) { + return ResultNotFound; + } + file_path = cartridge; + } else { + if (Settings::values.is_new_3ds) { + // Try the New 3DS specific variant first. + file_path = Service::AM::GetTitleContentPath(media_type, title_id | 0x20000000, + openfile_path.content_index); + } + if (!Settings::values.is_new_3ds || !FileUtil::Exists(file_path)) { + file_path = + Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index); + } } auto ncch_container = NCCHContainer(file_path, 0, openfile_path.content_index); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3bae37cfa..645a015b6 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1285,7 +1285,7 @@ std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, std: auto fs_user = Core::System::GetInstance().ServiceManager().GetService( "fs:USER"); - return fs_user->GetCurrentGamecardPath(); + return fs_user->GetRegisteredGamecardPath(); } std::string content_path = GetTitlePath(media_type, tid) + "content/"; @@ -1330,7 +1330,7 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid) { auto fs_user = Core::System::GetInstance().ServiceManager().GetService( "fs:USER"); - return fs_user->GetCurrentGamecardPath(); + return fs_user->GetRegisteredGamecardPath(); } return ""; @@ -1351,7 +1351,7 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) { auto fs_user = Core::System::GetInstance().ServiceManager().GetService( "fs:USER"); - return fs_user->GetCurrentGamecardPath(); + return fs_user->GetRegisteredGamecardPath(); } return ""; @@ -1414,39 +1414,52 @@ void Module::ScanForTitlesImpl(Service::FS::MediaType media_type) { LOG_DEBUG(Service_AM, "Starting title scan for media_type={}", static_cast(media_type)); - std::string title_path = GetMediaTitlePath(media_type); - - FileUtil::FSTEntry entries; - FileUtil::ScanDirectoryTree(title_path, entries, 1, &stop_scan_flag); - for (const FileUtil::FSTEntry& tid_high : entries.children) { - if (stop_scan_flag) { - break; + if (media_type == FS::MediaType::GameCard) { + const auto& cartridge = system.GetCartridge(); + if (!cartridge.empty()) { + u64 program_id = 0; + FileSys::NCCHContainer cartridge_ncch(cartridge); + Loader::ResultStatus res = cartridge_ncch.ReadProgramId(program_id); + if (res == Loader::ResultStatus::Success) { + am_title_list[static_cast(media_type)].push_back(program_id); + } } - for (const FileUtil::FSTEntry& tid_low : tid_high.children) { + } else { + std::string title_path = GetMediaTitlePath(media_type); + + FileUtil::FSTEntry entries; + FileUtil::ScanDirectoryTree(title_path, entries, 1, &stop_scan_flag); + for (const FileUtil::FSTEntry& tid_high : entries.children) { if (stop_scan_flag) { break; } - std::string tid_string = tid_high.virtualName + tid_low.virtualName; + for (const FileUtil::FSTEntry& tid_low : tid_high.children) { + if (stop_scan_flag) { + break; + } + std::string tid_string = tid_high.virtualName + tid_low.virtualName; - if (tid_string.length() == TITLE_ID_VALID_LENGTH) { - const u64 tid = std::stoull(tid_string, nullptr, 16); + if (tid_string.length() == TITLE_ID_VALID_LENGTH) { + const u64 tid = std::stoull(tid_string, nullptr, 16); - if (tid & TWL_TITLE_ID_FLAG) { - // TODO(PabloMK7) Move to TWL Nand, for now only check that - // the contents exists in CTR Nand as this is a SRL file - // instead of NCCH. - if (FileUtil::Exists(GetTitleContentPath(media_type, tid))) { - am_title_list[static_cast(media_type)].push_back(tid); - } - } else { - FileSys::NCCHContainer container(GetTitleContentPath(media_type, tid)); - if (container.Load() == Loader::ResultStatus::Success) { - am_title_list[static_cast(media_type)].push_back(tid); + if (tid & TWL_TITLE_ID_FLAG) { + // TODO(PabloMK7) Move to TWL Nand, for now only check that + // the contents exists in CTR Nand as this is a SRL file + // instead of NCCH. + if (FileUtil::Exists(GetTitleContentPath(media_type, tid))) { + am_title_list[static_cast(media_type)].push_back(tid); + } + } else { + FileSys::NCCHContainer container(GetTitleContentPath(media_type, tid)); + if (container.Load() == Loader::ResultStatus::Success) { + am_title_list[static_cast(media_type)].push_back(tid); + } } } } } } + LOG_DEBUG(Service_AM, "Finished title scan for media_type={}", static_cast(media_type)); } @@ -1455,6 +1468,7 @@ void Module::ScanForAllTitles() { ScanForTicketsImpl(); ScanForTitlesImpl(Service::FS::MediaType::NAND); ScanForTitlesImpl(Service::FS::MediaType::SDMC); + ScanForTitlesImpl(Service::FS::MediaType::GameCard); } else { scan_all_future = std::async([this]() { std::scoped_lock lock(am_lists_mutex); @@ -1465,6 +1479,9 @@ void Module::ScanForAllTitles() { if (!stop_scan_flag) { ScanForTitlesImpl(Service::FS::MediaType::SDMC); } + if (!stop_scan_flag) { + ScanForTitlesImpl(Service::FS::MediaType::GameCard); + } }); } } @@ -1987,30 +2004,75 @@ void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) { } } -Result GetTitleInfoFromList(std::span title_id_list, Service::FS::MediaType media_type, +Result GetTitleInfoFromList(Core::System& system, std::span title_id_list, + Service::FS::MediaType media_type, std::vector& title_info_out) { title_info_out.reserve(title_id_list.size()); for (u32 i = 0; i < title_id_list.size(); i++) { - std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]); + if (media_type == Service::FS::MediaType::GameCard) { + auto& cartridge = system.GetCartridge(); + if (cartridge.empty()) { + LOG_DEBUG(Service_AM, "cartridge not inserted"); + return Result(ErrorDescription::NotFound, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } - TitleInfo title_info = {}; - title_info.tid = title_id_list[i]; + FileSys::NCCHContainer ncch_container(cartridge); + if (ncch_container.Load() != Loader::ResultStatus::Success || + !ncch_container.IsNCSD()) { + LOG_ERROR(Service_AM, "failed to load cartridge card"); + return Result(ErrorDescription::NotFound, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } - FileSys::TitleMetadata tmd; - if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { - // TODO(shinyquagsire23): This is the total size of all files this process owns, - // including savefiles and other content. This comes close but is off. - title_info.size = tmd.GetContentSizeByIndex(FileSys::TMDContentIndex::Main); - title_info.version = tmd.GetTitleVersion(); - title_info.type = tmd.GetTitleType(); + // This is what Process9 does for getting the information, from disassembly. + // It is still unclear what do those values mean, like the title info type. + if (ncch_container.exheader_header.arm11_system_local_caps.program_id != + title_id_list[i]) { + LOG_DEBUG(Service_AM, + "cartridge has different title ID than requested title_id={:016X} != " + "cartridge_title_id={:016X}", + title_id_list[i], + ncch_container.exheader_header.arm11_system_local_caps.program_id); + return Result(ErrorDescription::NotFound, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } + + TitleInfo title_info = {}; + title_info.tid = title_id_list[i]; + title_info.version = + (*reinterpret_cast( + &ncch_container.exheader_header.codeset_info.flags.remaster_version) + << 10) & + 0xFC00; + title_info.size = 0; + title_info.type = 0x40; + + LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i], + title_info.version); + title_info_out.push_back(title_info); } else { - LOG_DEBUG(Service_AM, "not found title_id={:016X}", title_id_list[i]); - return Result(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState, - ErrorLevel::Permanent); + std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]); + + TitleInfo title_info = {}; + title_info.tid = title_id_list[i]; + + FileSys::TitleMetadata tmd; + if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) { + // TODO(shinyquagsire23): This is the total size of all files this process owns, + // including savefiles and other content. This comes close but is off. + title_info.size = tmd.GetContentSizeByIndex(FileSys::TMDContentIndex::Main); + title_info.version = tmd.GetTitleVersion(); + title_info.type = tmd.GetTitleType(); + } else { + LOG_DEBUG(Service_AM, "not found title_id={:016X}", title_id_list[i]); + return Result(ErrorDescription::NotFound, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } + LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i], + title_info.version); + title_info_out.push_back(title_info); } - LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i], - title_info.version); - title_info_out.push_back(title_info); } return ResultSuccess; @@ -2142,7 +2204,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool } if (async_data->res.IsSuccess()) { - async_data->res = GetTitleInfoFromList(async_data->title_id_list, + async_data->res = GetTitleInfoFromList(am->system, async_data->title_id_list, async_data->media_type, async_data->out); } return 0; @@ -2356,7 +2418,7 @@ void Module::Interface::GetDLCTitleInfos(Kernel::HLERequestContext& ctx) { } if (async_data->res.IsSuccess()) { - async_data->res = GetTitleInfoFromList(async_data->title_id_list, + async_data->res = GetTitleInfoFromList(am->system, async_data->title_id_list, async_data->media_type, async_data->out); } return 0; @@ -2503,7 +2565,7 @@ void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) { } if (async_data->res.IsSuccess()) { - async_data->res = GetTitleInfoFromList(async_data->title_id_list, + async_data->res = GetTitleInfoFromList(am->system, async_data->title_id_list, async_data->media_type, async_data->out); } return 0; diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp index 47c0792c6..1dcbb430e 100644 --- a/src/core/hle/service/apt/applet_manager.cpp +++ b/src/core/hle/service/apt/applet_manager.cpp @@ -396,6 +396,10 @@ ResultVal AppletManager::Initialize(AppletId ap // Note: In the real console the title id of a given applet slot is set by the APT module when // calling StartApplication. slot_data->title_id = system.Kernel().GetCurrentProcess()->codeset->program_id; + if (app_id == AppletId::Application) { + slot_data->media_type = next_app_mediatype; + next_app_mediatype = static_cast(UINT32_MAX); + } slot_data->attributes.raw = attributes.raw; // Applications need to receive a Wakeup signal to actually start up, this signal is usually @@ -1207,7 +1211,14 @@ ResultVal AppletManager::GetAppletInfo(AppletId app_i ErrorLevel::Status); } - auto media_type = Service::AM::GetTitleMediaType(slot_data->title_id); + FS::MediaType media_type; + if (slot_data->media_type != static_cast(UINT32_MAX)) { + media_type = slot_data->media_type; + } else { + // Applet was not started from StartApplication, so we need to guess. + media_type = Service::AM::GetTitleMediaType(slot_data->title_id); + } + return AppletInfo{ .title_id = slot_data->title_id, .media_type = media_type, @@ -1234,7 +1245,13 @@ ResultVal AppletManager::Unknown54(u32 in_param) { in_param >= 0x40 ? Service::FS::MediaType::GameCard : Service::FS::MediaType::SDMC; auto check_update = in_param == 0x01 || in_param == 0x42; - auto app_media_type = Service::AM::GetTitleMediaType(slot_data->title_id); + FS::MediaType app_media_type; + if (slot_data->media_type != static_cast(UINT32_MAX)) { + app_media_type = slot_data->media_type; + } else { + // Applet was not started from StartApplication, so we need to guess. + app_media_type = Service::AM::GetTitleMediaType(slot_data->title_id); + } auto app_update_media_type = Service::AM::GetTitleMediaType(Service::AM::GetTitleUpdateId(slot_data->title_id)); if (app_media_type == check_target || (check_update && app_update_media_type == check_target)) { @@ -1283,8 +1300,16 @@ Result AppletManager::PrepareToDoApplicationJump(u64 title_id, FS::MediaType med // Save the title data to send it to the Home Menu when DoApplicationJump is called. auto application_slot_data = GetAppletSlot(AppletSlot::Application); app_jump_parameters.current_title_id = application_slot_data->title_id; - app_jump_parameters.current_media_type = - Service::AM::GetTitleMediaType(application_slot_data->title_id); + + FS::MediaType curr_media_type; + if (application_slot_data->media_type != static_cast(UINT32_MAX)) { + curr_media_type = application_slot_data->media_type; + } else { + // Applet was not started from StartApplication, so we need to guess. + curr_media_type = Service::AM::GetTitleMediaType(application_slot_data->title_id); + } + + app_jump_parameters.current_media_type = curr_media_type; if (flags == ApplicationJumpFlags::UseCurrentParameters) { app_jump_parameters.next_title_id = app_jump_parameters.current_title_id; app_jump_parameters.next_media_type = app_jump_parameters.current_media_type; @@ -1369,7 +1394,15 @@ Result AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType medi title_id = ConvertTitleID(system, title_id); - std::string path = AM::GetTitleContentPath(media_type, title_id); + std::string path; + if (media_type == FS::MediaType::GameCard) { + path = system.GetCartridge(); + } else { + path = AM::GetTitleContentPath(media_type, title_id); + } + + next_app_mediatype = media_type; + auto loader = Loader::GetLoader(path); if (!loader) { diff --git a/src/core/hle/service/apt/applet_manager.h b/src/core/hle/service/apt/applet_manager.h index d4d99895b..3924ec510 100644 --- a/src/core/hle/service/apt/applet_manager.h +++ b/src/core/hle/service/apt/applet_manager.h @@ -456,6 +456,7 @@ private: AppletId applet_id; AppletSlot slot; u64 title_id; + FS::MediaType media_type; bool registered; bool loaded; AppletAttributes attributes; @@ -476,6 +477,7 @@ private: ar & applet_id; ar & slot; ar & title_id; + ar & media_type; ar & registered; ar & loaded; ar & attributes.raw; @@ -514,6 +516,8 @@ private: bool last_home_button_state = false; bool last_power_button_state = false; + FS::MediaType next_app_mediatype = static_cast(UINT32_MAX); + Core::System& system; AppletSlotData* GetAppletSlot(AppletSlot slot) { @@ -569,6 +573,7 @@ private: ar & capture_info; ar & applet_slots; ar & library_applet_closing_command; + ar & next_app_mediatype; if (Archive::is_loading::value) { LoadInputDevices(); diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 8e34bb0b7..14035af1a 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -75,6 +75,20 @@ void Module::NSInterface::SetWirelessRebootInfo(Kernel::HLERequestContext& ctx) LOG_WARNING(Service_APT, "called size={}", size); } +void Module::NSInterface::CardUpdateInitialize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + rp.Pop(); // Shared mem size + rp.Pop(); // Always 0 + rp.Pop(); // Shared mem handle + + LOG_WARNING(Service_APT, "(stubbed) called"); + const Result update_not_needed(11, ErrorModule::CUP, ErrorSummary::NothingHappened, + ErrorLevel::Status); + + auto rb = rp.MakeBuilder(1, 0); + rb.Push(update_not_needed); +} + void Module::NSInterface::ShutdownAsync(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 9676a94f0..376b113f0 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -72,6 +72,8 @@ public: */ void SetWirelessRebootInfo(Kernel::HLERequestContext& ctx); + void CardUpdateInitialize(Kernel::HLERequestContext& ctx); + /** * NS::ShutdownAsync service function. * Inputs: diff --git a/src/core/hle/service/apt/ns.cpp b/src/core/hle/service/apt/ns.cpp index 785b83198..6b36d97d1 100644 --- a/src/core/hle/service/apt/ns.cpp +++ b/src/core/hle/service/apt/ns.cpp @@ -11,11 +11,21 @@ namespace Service::NS { std::shared_ptr LaunchTitle(Core::System& system, FS::MediaType media_type, u64 title_id) { - std::string path = AM::GetTitleContentPath(media_type, title_id); - auto loader = Loader::GetLoader(path); + std::string path; - if (!loader) { - LOG_WARNING(Service_NS, "Could not find .app for title 0x{:016x}", title_id); + if (media_type == FS::MediaType::GameCard) { + path = system.GetCartridge(); + } else { + path = AM::GetTitleContentPath(media_type, title_id); + } + + auto loader = Loader::GetLoader(path); + u64 program_id; + + if (!loader || loader->ReadProgramId(program_id) != Loader::ResultStatus::Success || + program_id != title_id) { + LOG_WARNING(Service_NS, "Could not load title=0x{:016x} media_type={}", title_id, + static_cast(media_type)); return nullptr; } @@ -60,7 +70,13 @@ std::shared_ptr LaunchTitle(Core::System& system, FS::MediaType void RebootToTitle(Core::System& system, FS::MediaType media_type, u64 title_id, std::optional mem_mode) { - auto new_path = AM::GetTitleContentPath(media_type, title_id); + std::string new_path; + if (media_type == FS::MediaType::GameCard) { + new_path = system.GetCartridge(); + } else { + new_path = AM::GetTitleContentPath(media_type, title_id); + } + if (new_path.empty() || !FileUtil::Exists(new_path)) { // TODO: This can happen if the requested title is not installed. Need a way to find // non-installed titles in the game list. diff --git a/src/core/hle/service/apt/ns_s.cpp b/src/core/hle/service/apt/ns_s.cpp index dfb178d7b..ab267a0d4 100644 --- a/src/core/hle/service/apt/ns_s.cpp +++ b/src/core/hle/service/apt/ns_s.cpp @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -17,7 +17,7 @@ NS_S::NS_S(std::shared_ptr apt) {0x0004, nullptr, "TerminateProcess"}, {0x0005, nullptr, "LaunchApplicationFIRM"}, {0x0006, &NS_S::SetWirelessRebootInfo, "SetWirelessRebootInfo"}, - {0x0007, nullptr, "CardUpdateInitialize"}, + {0x0007, &NS_S::CardUpdateInitialize, "CardUpdateInitialize"}, {0x0008, nullptr, "CardUpdateShutdown"}, {0x000D, nullptr, "SetTWLBannerHMAC"}, {0x000E, &NS_S::ShutdownAsync, "ShutdownAsync"}, diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 99390cd40..c9641a518 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -913,6 +913,14 @@ void FS_USER::GetFreeBytes(Kernel::HLERequestContext& ctx) { } } +void FS_USER::GetCardType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(0); // CTR Card + LOG_DEBUG(Service_FS, "(STUBBED) called"); +} + void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -999,8 +1007,8 @@ void FS_USER::CardSlotIsInserted(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); - rb.Push(false); - LOG_WARNING(Service_FS, "(STUBBED) called"); + rb.Push(!system.GetCartridge().empty()); + LOG_DEBUG(Service_FS, "called"); } void FS_USER::DeleteSystemSaveData(Kernel::HLERequestContext& ctx) { @@ -1681,14 +1689,20 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { } void FS_USER::RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath) { - const MediaType media_type = GetMediaTypeFromPath(filepath); + MediaType media_type; + if (filepath == system.GetCartridge()) { + media_type = MediaType::GameCard; + } else { + media_type = GetMediaTypeFromPath(filepath); + } + program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type}); if (media_type == MediaType::GameCard) { current_gamecard_path = filepath; } } -std::string FS_USER::GetCurrentGamecardPath() const { +std::string FS_USER::GetRegisteredGamecardPath() const { return current_gamecard_path; } @@ -1779,7 +1793,7 @@ FS_USER::FS_USER(Core::System& system) {0x0810, &FS_USER::CreateLegacySystemSaveData, "CreateLegacySystemSaveData"}, {0x0811, nullptr, "DeleteSystemSaveData"}, {0x0812, &FS_USER::GetFreeBytes, "GetFreeBytes"}, - {0x0813, nullptr, "GetCardType"}, + {0x0813, &FS_USER::GetCardType, "GetCardType"}, {0x0814, &FS_USER::GetSdmcArchiveResource, "GetSdmcArchiveResource"}, {0x0815, &FS_USER::GetNandArchiveResource, "GetNandArchiveResource"}, {0x0816, nullptr, "GetSdmcFatfsError"}, diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index 11fbef4db..bdce1b7eb 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -54,7 +54,7 @@ public: // loader and pm, which we HLEed, we can just directly use it here void RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath); - std::string GetCurrentGamecardPath() const; + std::string GetRegisteredGamecardPath() const; struct ProductInfo { std::array product_code; @@ -361,6 +361,8 @@ private: */ void GetFreeBytes(Kernel::HLERequestContext& ctx); + void GetCardType(Kernel::HLERequestContext& ctx); + /** * FS_User::GetSdmcArchiveResource service function. * Inputs: diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 392b6025d..9e4c76122 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -302,6 +302,10 @@ public: return false; } + virtual std::string GetFilePath() { + return file ? file->Filename() : ""; + } + protected: Core::System& system; std::unique_ptr file; diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index c5b1b45d5..f1bf69855 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -80,6 +80,10 @@ public: bool IsFileCompressed() override; + std::string GetFilePath() override { + return filepath; + } + private: /** * Loads .code section into memory for booting