mirror of
https://github.com/citra-emu/citra-canary.git
synced 2024-12-22 18:55:32 +00:00
qt: Add option to uninstall a game. (#7064)
* qt: Add option to uninstall a game. * Address review comments.
This commit is contained in:
parent
3d55270de6
commit
07839fb3ce
|
@ -16,6 +16,7 @@
|
|||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QModelIndex>
|
||||
#include <QStandardItem>
|
||||
#include <QStandardItemModel>
|
||||
|
@ -459,8 +460,11 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
|||
switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
|
||||
case GameListItemType::Game:
|
||||
AddGamePopup(context_menu, selected.data(GameListItemPath::FullPathRole).toString(),
|
||||
selected.data(GameListItemPath::TitleRole).toString(),
|
||||
selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
|
||||
selected.data(GameListItemPath::ExtdataIdRole).toULongLong());
|
||||
selected.data(GameListItemPath::ExtdataIdRole).toULongLong(),
|
||||
static_cast<Service::FS::MediaType>(
|
||||
selected.data(GameListItemPath::MediaTypeRole).toUInt()));
|
||||
break;
|
||||
case GameListItemType::CustomDir:
|
||||
AddPermDirPopup(context_menu, selected);
|
||||
|
@ -522,28 +526,36 @@ void ForEachOpenGLCacheFile(u64 program_id, auto func) {
|
|||
}
|
||||
}
|
||||
|
||||
void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 program_id,
|
||||
u64 extdata_id) {
|
||||
void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QString& name,
|
||||
u64 program_id, u64 extdata_id, Service::FS::MediaType media_type) {
|
||||
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
|
||||
QAction* open_extdata_location = context_menu.addAction(tr("Open Extra Data Location"));
|
||||
QAction* open_application_location = context_menu.addAction(tr("Open Application Location"));
|
||||
QAction* open_update_location = context_menu.addAction(tr("Open Update Data Location"));
|
||||
QAction* open_dlc_location = context_menu.addAction(tr("Open DLC Data Location"));
|
||||
QAction* open_texture_dump_location = context_menu.addAction(tr("Open Texture Dump Location"));
|
||||
QAction* open_texture_load_location =
|
||||
context_menu.addAction(tr("Open Custom Texture Location"));
|
||||
QAction* open_mods_location = context_menu.addAction(tr("Open Mods Location"));
|
||||
QAction* open_dlc_location = context_menu.addAction(tr("Open DLC Data Location"));
|
||||
QMenu* shader_menu = context_menu.addMenu(tr("Disk Shader Cache"));
|
||||
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
|
||||
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
||||
context_menu.addSeparator();
|
||||
QAction* properties = context_menu.addAction(tr("Properties"));
|
||||
|
||||
QMenu* shader_menu = context_menu.addMenu(tr("Disk Shader Cache"));
|
||||
QAction* open_shader_cache_location = shader_menu->addAction(tr("Open Shader Cache Location"));
|
||||
shader_menu->addSeparator();
|
||||
QAction* delete_opengl_disk_shader_cache =
|
||||
shader_menu->addAction(tr("Delete OpenGL Shader Cache"));
|
||||
|
||||
QMenu* uninstall_menu = context_menu.addMenu(tr("Uninstall"));
|
||||
QAction* uninstall_all = uninstall_menu->addAction(tr("Everything"));
|
||||
uninstall_menu->addSeparator();
|
||||
QAction* uninstall_game = uninstall_menu->addAction(tr("Game"));
|
||||
QAction* uninstall_update = uninstall_menu->addAction(tr("Update"));
|
||||
QAction* uninstall_dlc = uninstall_menu->addAction(tr("DLC"));
|
||||
|
||||
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
||||
context_menu.addSeparator();
|
||||
QAction* properties = context_menu.addAction(tr("Properties"));
|
||||
|
||||
const u32 program_id_high = (program_id >> 32) & 0xFFFFFFFF;
|
||||
const bool is_application = program_id_high == 0x00040000 || program_id_high == 0x00040010;
|
||||
|
||||
|
@ -564,22 +576,36 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
|
|||
open_extdata_location->setVisible(false);
|
||||
}
|
||||
|
||||
auto media_type = Service::AM::GetTitleMediaType(program_id);
|
||||
open_application_location->setEnabled(path.toStdString() ==
|
||||
Service::AM::GetTitleContentPath(media_type, program_id));
|
||||
open_update_location->setEnabled(
|
||||
is_application && FileUtil::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
|
||||
program_id + 0xe00000000) +
|
||||
"content/"));
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
const auto update_program_id = program_id | 0xE00000000;
|
||||
const auto dlc_program_id = program_id | 0x8C00000000;
|
||||
|
||||
const auto is_installed =
|
||||
media_type == Service::FS::MediaType::NAND || media_type == Service::FS::MediaType::SDMC;
|
||||
const auto has_update =
|
||||
is_application && FileUtil::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
|
||||
update_program_id) +
|
||||
"content/");
|
||||
const auto has_dlc =
|
||||
is_application &&
|
||||
FileUtil::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, dlc_program_id) +
|
||||
"content/");
|
||||
|
||||
open_application_location->setEnabled(is_installed);
|
||||
open_update_location->setEnabled(has_update);
|
||||
open_dlc_location->setEnabled(has_dlc);
|
||||
open_texture_dump_location->setEnabled(is_application);
|
||||
open_texture_load_location->setEnabled(is_application);
|
||||
open_mods_location->setEnabled(is_application);
|
||||
open_dlc_location->setEnabled(is_application);
|
||||
dump_romfs->setEnabled(is_application);
|
||||
|
||||
delete_opengl_disk_shader_cache->setEnabled(opengl_cache_exists);
|
||||
|
||||
uninstall_all->setEnabled(is_installed || has_update || has_dlc);
|
||||
uninstall_game->setEnabled(is_installed);
|
||||
uninstall_update->setEnabled(has_update);
|
||||
uninstall_dlc->setEnabled(has_dlc);
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
|
||||
|
||||
connect(open_save_location, &QAction::triggered, this, [this, program_id] {
|
||||
|
@ -641,7 +667,63 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
|
|||
connect(delete_opengl_disk_shader_cache, &QAction::triggered, this, [program_id] {
|
||||
ForEachOpenGLCacheFile(program_id, [](QFile& file) { file.remove(); });
|
||||
});
|
||||
};
|
||||
connect(uninstall_all, &QAction::triggered, this, [=, this] {
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(
|
||||
this, tr("Citra"),
|
||||
tr("Are you sure you want to completely uninstall '%1'?\n\nThis will "
|
||||
"delete the game if installed, as well as any installed updates or DLC.")
|
||||
.arg(name),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (answer == QMessageBox::Yes) {
|
||||
std::vector<std::tuple<Service::FS::MediaType, u64, QString>> titles;
|
||||
if (is_installed) {
|
||||
titles.emplace_back(media_type, program_id, name);
|
||||
}
|
||||
if (has_update) {
|
||||
titles.emplace_back(Service::FS::MediaType::SDMC, update_program_id,
|
||||
tr("%1 (Update)").arg(name));
|
||||
}
|
||||
if (has_dlc) {
|
||||
titles.emplace_back(Service::FS::MediaType::SDMC, dlc_program_id,
|
||||
tr("%1 (DLC)").arg(name));
|
||||
}
|
||||
main_window->UninstallTitles(titles);
|
||||
}
|
||||
});
|
||||
connect(uninstall_game, &QAction::triggered, this, [this, name, media_type, program_id] {
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(
|
||||
this, tr("Citra"), tr("Are you sure you want to uninstall '%1'?").arg(name),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (answer == QMessageBox::Yes) {
|
||||
std::vector<std::tuple<Service::FS::MediaType, u64, QString>> titles;
|
||||
titles.emplace_back(media_type, program_id, name);
|
||||
main_window->UninstallTitles(titles);
|
||||
}
|
||||
});
|
||||
connect(uninstall_update, &QAction::triggered, this, [this, name, update_program_id] {
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(
|
||||
this, tr("Citra"),
|
||||
tr("Are you sure you want to uninstall the update for '%1'?").arg(name),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (answer == QMessageBox::Yes) {
|
||||
std::vector<std::tuple<Service::FS::MediaType, u64, QString>> titles;
|
||||
titles.emplace_back(Service::FS::MediaType::SDMC, update_program_id,
|
||||
tr("%1 (Update)").arg(name));
|
||||
main_window->UninstallTitles(titles);
|
||||
}
|
||||
});
|
||||
connect(uninstall_dlc, &QAction::triggered, this, [this, name, dlc_program_id] {
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(
|
||||
this, tr("Citra"), tr("Are you sure you want to uninstall all DLC for '%1'?").arg(name),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (answer == QMessageBox::Yes) {
|
||||
std::vector<std::tuple<Service::FS::MediaType, u64, QString>> titles;
|
||||
titles.emplace_back(Service::FS::MediaType::SDMC, dlc_program_id,
|
||||
tr("%1 (DLC)").arg(name));
|
||||
main_window->UninstallTitles(titles);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
|
||||
UISettings::GameDir& game_dir =
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
#include "common/common_types.h"
|
||||
#include "uisettings.h"
|
||||
|
||||
namespace Service::FS {
|
||||
enum class MediaType : u32;
|
||||
}
|
||||
|
||||
class GameListWorker;
|
||||
class GameListDir;
|
||||
class GameListSearchField;
|
||||
|
@ -105,7 +109,8 @@ private:
|
|||
|
||||
void PopupContextMenu(const QPoint& menu_location);
|
||||
void PopupHeaderContextMenu(const QPoint& menu_location);
|
||||
void AddGamePopup(QMenu& context_menu, const QString& path, u64 program_id, u64 extdata_id);
|
||||
void AddGamePopup(QMenu& context_menu, const QString& path, const QString& name, u64 program_id,
|
||||
u64 extdata_id, Service::FS::MediaType media_type);
|
||||
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
|
||||
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
|
||||
void UpdateColumnVisibility();
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
#include "common/string_util.h"
|
||||
#include "core/loader/smdh.h"
|
||||
|
||||
namespace Service::FS {
|
||||
enum class MediaType : u32;
|
||||
}
|
||||
|
||||
enum class GameListItemType {
|
||||
Game = QStandardItem::UserType + 1,
|
||||
CustomDir = QStandardItem::UserType + 2,
|
||||
|
@ -153,14 +157,16 @@ public:
|
|||
static constexpr int ProgramIdRole = SortRole + 3;
|
||||
static constexpr int ExtdataIdRole = SortRole + 4;
|
||||
static constexpr int LongTitleRole = SortRole + 5;
|
||||
static constexpr int MediaTypeRole = SortRole + 6;
|
||||
|
||||
GameListItemPath() = default;
|
||||
GameListItemPath(const QString& game_path, std::span<const u8> smdh_data, u64 program_id,
|
||||
u64 extdata_id) {
|
||||
u64 extdata_id, Service::FS::MediaType media_type) {
|
||||
setData(type(), TypeRole);
|
||||
setData(game_path, FullPathRole);
|
||||
setData(qulonglong(program_id), ProgramIdRole);
|
||||
setData(qulonglong(extdata_id), ExtdataIdRole);
|
||||
setData(quint32(media_type), MediaTypeRole);
|
||||
|
||||
if (UISettings::values.game_list_icon_size.GetValue() ==
|
||||
UISettings::GameListIconSize::NoIcon) {
|
||||
|
|
|
@ -33,10 +33,11 @@ GameListWorker::GameListWorker(QVector<UISettings::GameDir>& game_dirs,
|
|||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion,
|
||||
GameListDir* parent_dir) {
|
||||
const auto callback = [this, recursion, parent_dir](u64* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
GameListDir* parent_dir,
|
||||
Service::FS::MediaType media_type) {
|
||||
const auto callback = [this, recursion, parent_dir,
|
||||
media_type](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
if (stop_processing) {
|
||||
// Breaks the callback loop.
|
||||
return false;
|
||||
|
@ -105,7 +106,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
|||
emit EntryReady(
|
||||
{
|
||||
new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id,
|
||||
extdata_id),
|
||||
extdata_id, media_type),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItemRegion(smdh),
|
||||
new GameListItem(
|
||||
|
@ -116,7 +117,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
|||
|
||||
} else if (is_dir && recursion > 0) {
|
||||
watch_list.append(QString::fromStdString(physical_name));
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1, parent_dir);
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1, parent_dir, media_type);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -144,8 +145,10 @@ void GameListWorker::run() {
|
|||
watch_list.append(demos_path);
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::InstalledDir);
|
||||
emit DirEntryReady(game_list_dir);
|
||||
AddFstEntriesToGameList(games_path.toStdString(), 2, game_list_dir);
|
||||
AddFstEntriesToGameList(demos_path.toStdString(), 2, game_list_dir);
|
||||
AddFstEntriesToGameList(games_path.toStdString(), 2, game_list_dir,
|
||||
Service::FS::MediaType::SDMC);
|
||||
AddFstEntriesToGameList(demos_path.toStdString(), 2, game_list_dir,
|
||||
Service::FS::MediaType::SDMC);
|
||||
} else if (game_dir.path == QStringLiteral("SYSTEM")) {
|
||||
QString path =
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) +
|
||||
|
@ -153,13 +156,14 @@ void GameListWorker::run() {
|
|||
watch_list.append(path);
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir);
|
||||
emit DirEntryReady(game_list_dir);
|
||||
AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir);
|
||||
AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir,
|
||||
Service::FS::MediaType::NAND);
|
||||
} else {
|
||||
watch_list.append(game_dir.path);
|
||||
auto* const game_list_dir = new GameListDir(game_dir);
|
||||
emit DirEntryReady(game_list_dir);
|
||||
AddFstEntriesToGameList(game_dir.path.toStdString(), game_dir.deep_scan ? 256 : 0,
|
||||
game_list_dir);
|
||||
game_list_dir, Service::FS::MediaType::GameCard);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
#include "citra_qt/compatibility_list.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::FS {
|
||||
enum class MediaType : u32;
|
||||
}
|
||||
|
||||
class QStandardItem;
|
||||
|
||||
/**
|
||||
|
@ -52,7 +56,7 @@ signals:
|
|||
|
||||
private:
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion,
|
||||
GameListDir* parent_dir);
|
||||
GameListDir* parent_dir, Service::FS::MediaType media_type);
|
||||
|
||||
QVector<UISettings::GameDir>& game_dirs;
|
||||
const CompatibilityList& compatibility_list;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QSysInfo>
|
||||
#include <QtConcurrent/QtConcurrentMap>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
|
@ -1750,6 +1751,57 @@ void GMainWindow::OnCIAInstallFinished() {
|
|||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
}
|
||||
|
||||
void GMainWindow::UninstallTitles(
|
||||
const std::vector<std::tuple<Service::FS::MediaType, u64, QString>>& titles) {
|
||||
if (titles.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the first title in the list as representative.
|
||||
const auto first_name = std::get<QString>(titles[0]);
|
||||
|
||||
QProgressDialog progress(tr("Uninstalling '%1'...").arg(first_name), tr("Cancel"), 0,
|
||||
static_cast<int>(titles.size()), this);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
|
||||
QFutureWatcher<void> future_watcher;
|
||||
QObject::connect(&future_watcher, &QFutureWatcher<void>::finished, &progress,
|
||||
&QProgressDialog::reset);
|
||||
QObject::connect(&progress, &QProgressDialog::canceled, &future_watcher,
|
||||
&QFutureWatcher<void>::cancel);
|
||||
QObject::connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &progress,
|
||||
&QProgressDialog::setValue);
|
||||
|
||||
auto failed = false;
|
||||
QString failed_name;
|
||||
|
||||
const auto uninstall_title = [&future_watcher, &failed, &failed_name](const auto& title) {
|
||||
const auto name = std::get<QString>(title);
|
||||
const auto media_type = std::get<Service::FS::MediaType>(title);
|
||||
const auto program_id = std::get<u64>(title);
|
||||
|
||||
const auto result = Service::AM::UninstallProgram(media_type, program_id);
|
||||
if (result.IsError()) {
|
||||
LOG_ERROR(Frontend, "Failed to uninstall '{}': 0x{:08X}", name.toStdString(),
|
||||
result.raw);
|
||||
failed = true;
|
||||
failed_name = name;
|
||||
future_watcher.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
future_watcher.setFuture(QtConcurrent::map(titles, uninstall_title));
|
||||
progress.exec();
|
||||
future_watcher.waitForFinished();
|
||||
|
||||
if (failed) {
|
||||
QMessageBox::critical(this, tr("Citra"), tr("Failed to uninstall '%1'.").arg(failed_name));
|
||||
} else if (!future_watcher.isCanceled()) {
|
||||
QMessageBox::information(this, tr("Citra"),
|
||||
tr("Successfully uninstalled '%1'.").arg(first_name));
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnMenuRecentFile() {
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
ASSERT(action);
|
||||
|
|
|
@ -73,6 +73,10 @@ namespace Service::AM {
|
|||
enum class InstallStatus : u32;
|
||||
}
|
||||
|
||||
namespace Service::FS {
|
||||
enum class MediaType : u32;
|
||||
}
|
||||
|
||||
class GMainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -100,6 +104,9 @@ public:
|
|||
bool DropAction(QDropEvent* event);
|
||||
void AcceptDropEvent(QDropEvent* event);
|
||||
|
||||
void UninstallTitles(
|
||||
const std::vector<std::tuple<Service::FS::MediaType, u64, QString>>& titles);
|
||||
|
||||
public slots:
|
||||
void OnAppFocusStateChanged(Qt::ApplicationState state);
|
||||
void OnLoadComplete();
|
||||
|
|
|
@ -1557,24 +1557,33 @@ void Module::Interface::GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx) {
|
|||
rb.Push(container.GetTitleMetadata().GetContentSizeByIndex(FileSys::TMDContentIndex::Main));
|
||||
}
|
||||
|
||||
ResultCode UninstallProgram(const FS::MediaType media_type, const u64 title_id) {
|
||||
// Use the content folder so we don't delete the user's save data.
|
||||
const auto path = GetTitlePath(media_type, title_id) + "content/";
|
||||
if (!FileUtil::Exists(path)) {
|
||||
return {ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
|
||||
ErrorLevel::Permanent};
|
||||
}
|
||||
if (!FileUtil::DeleteDirRecursively(path)) {
|
||||
// TODO: Determine the right error code for this.
|
||||
return {ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
|
||||
ErrorLevel::Permanent};
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void Module::Interface::DeleteProgram(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
auto media_type = rp.PopEnum<FS::MediaType>();
|
||||
u64 title_id = rp.Pop<u64>();
|
||||
LOG_INFO(Service_AM, "Deleting title 0x{:016x}", title_id);
|
||||
std::string path = GetTitlePath(media_type, title_id);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
if (!FileUtil::Exists(path)) {
|
||||
rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
|
||||
ErrorLevel::Permanent));
|
||||
LOG_ERROR(Service_AM, "Title not found");
|
||||
return;
|
||||
}
|
||||
bool success = FileUtil::DeleteDirRecursively(path);
|
||||
const auto media_type = rp.PopEnum<FS::MediaType>();
|
||||
const auto title_id = rp.Pop<u64>();
|
||||
|
||||
LOG_INFO(Service_AM, "called, title={:016x}", title_id);
|
||||
|
||||
const auto result = UninstallProgram(media_type, title_id);
|
||||
am->ScanForAllTitles();
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
if (!success)
|
||||
LOG_ERROR(Service_AM, "FileUtil::DeleteDirRecursively unexpectedly failed");
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void Module::Interface::GetSystemUpdaterMutex(Kernel::HLERequestContext& ctx) {
|
||||
|
|
|
@ -172,6 +172,14 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid);
|
|||
*/
|
||||
std::string GetMediaTitlePath(Service::FS::MediaType media_type);
|
||||
|
||||
/**
|
||||
* Uninstalls the specified title.
|
||||
* @param media_type the storage medium the title is installed to
|
||||
* @param title_id the title ID to uninstall
|
||||
* @return result of the uninstall operation
|
||||
*/
|
||||
ResultCode UninstallProgram(const FS::MediaType media_type, const u64 title_id);
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
explicit Module(Core::System& system);
|
||||
|
|
Loading…
Reference in a new issue