summaryrefslogtreecommitdiff
path: root/src/yuzu/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu/main.cpp')
-rw-r--r--src/yuzu/main.cpp189
1 files changed, 163 insertions, 26 deletions
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index bef9df00d..b5bfa6741 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,6 +10,7 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
+#include "core/hle/service/acc/profile_manager.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
// defines.
@@ -29,6 +30,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL
#include <QDesktopWidget>
#include <QDialogButtonBox>
+#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QtConcurrent/QtConcurrent>
@@ -60,6 +62,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_ldr.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/settings.h"
@@ -100,6 +104,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
+constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
+
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
@@ -422,6 +428,7 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
[this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); });
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
+ connect(ui.action_Load_Amiibo, &QAction::triggered, this, &GMainWindow::OnLoadAmiibo);
// Emulation
connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
@@ -690,6 +697,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
+ ui.action_Load_Amiibo->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -751,12 +759,43 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
open_target = "Save Data";
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
ASSERT(program_id != 0);
- // TODO(tech4me): Update this to work with arbitrary user profile
- // Refer to core/hle/service/acc/profile_manager.cpp ProfileManager constructor
- constexpr u128 user_id = {1, 0};
+
+ Service::Account::ProfileManager manager{};
+ const auto user_ids = manager.GetAllUsers();
+ QStringList list;
+ for (const auto& user_id : user_ids) {
+ if (user_id == Service::Account::UUID{})
+ continue;
+ Service::Account::ProfileBase base;
+ if (!manager.GetProfileBase(user_id, base))
+ continue;
+
+ list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(base.username.data()), base.username.size())));
+ }
+
+ bool ok = false;
+ const auto index_string =
+ QInputDialog::getItem(this, tr("Select User"),
+ tr("Please select the user's save data you would like to open."),
+ list, Settings::values.current_user, false, &ok);
+ if (!ok)
+ return;
+
+ const auto index = list.indexOf(index_string);
+ ASSERT(index != -1 && index < 8);
+
+ const auto user_id = manager.GetUser(index);
+ ASSERT(user_id != std::nullopt);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData,
- program_id, user_id, 0);
+ program_id, user_id->uuid, 0);
+
+ if (!FileUtil::Exists(path)) {
+ FileUtil::CreateFullPath(path);
+ FileUtil::CreateDir(path);
+ }
+
break;
}
case GameListOpenTarget::ModData: {
@@ -823,14 +862,10 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
}
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
- const auto path = fmt::format("{}{:016X}/romfs",
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
-
- const auto failed = [this, &path] {
+ const auto failed = [this] {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
tr("There was an error copying the RomFS files or the user "
"cancelled the operation."));
- vfs->DeleteDirectory(path);
};
const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
@@ -845,10 +880,24 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto romfs =
- loader->IsRomFSUpdatable()
- ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
- : file;
+ const auto installed = Service::FileSystem::GetUnionContents();
+ auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id);
+
+ if (!romfs_title_id) {
+ failed();
+ return;
+ }
+
+ const auto path = fmt::format(
+ "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id);
+
+ FileSys::VirtualFile romfs;
+
+ if (*romfs_title_id == program_id) {
+ romfs = file;
+ } else {
+ romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
+ }
const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
if (extracted == nullptr) {
@@ -860,6 +909,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
if (out == nullptr) {
failed();
+ vfs->DeleteDirectory(path);
return;
}
@@ -870,8 +920,11 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
"files into the new directory while <br>skeleton will only create the directory "
"structure."),
{"Full", "Skeleton"}, 0, false, &ok);
- if (!ok)
+ if (!ok) {
failed();
+ vfs->DeleteDirectory(path);
+ return;
+ }
const auto full = res == "Full";
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
@@ -888,6 +941,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
} else {
progress.close();
failed();
+ vfs->DeleteDirectory(path);
}
}
@@ -1174,6 +1228,7 @@ void GMainWindow::OnStartGame() {
ui.action_Report_Compatibility->setEnabled(true);
discord_rpc->Update();
+ ui.action_Load_Amiibo->setEnabled(true);
}
void GMainWindow::OnPauseGame() {
@@ -1278,6 +1333,47 @@ void GMainWindow::OnConfigure() {
}
}
+void GMainWindow::OnLoadAmiibo() {
+ const QString extensions{"*.bin"};
+ const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
+ const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter);
+
+ if (filename.isEmpty()) {
+ return;
+ }
+
+ Core::System& system{Core::System::GetInstance()};
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+ auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
+ if (nfc == nullptr) {
+ return;
+ }
+
+ QFile nfc_file{filename};
+ if (!nfc_file.open(QIODevice::ReadOnly)) {
+ QMessageBox::warning(this, tr("Error opening Amiibo data file"),
+ tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
+ return;
+ }
+
+ const u64 nfc_file_size = nfc_file.size();
+ std::vector<u8> buffer(nfc_file_size);
+ const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
+ if (nfc_file_size != read_size) {
+ QMessageBox::warning(this, tr("Error reading Amiibo data file"),
+ tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
+ "was only able to read %2 bytes.")
+ .arg(nfc_file_size)
+ .arg(read_size));
+ return;
+ }
+
+ if (!nfc->LoadAmiibo(buffer)) {
+ QMessageBox::warning(this, tr("Error loading Amiibo data"),
+ tr("Unable to load Amiibo data."));
+ }
+}
+
void GMainWindow::OnAbout() {
AboutDialog aboutDialog(this);
aboutDialog.exec();
@@ -1318,15 +1414,17 @@ void GMainWindow::UpdateStatusBar() {
void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
QMessageBox::StandardButton answer;
QString status_message;
- const QString common_message = tr(
- "The game you are trying to load requires additional files from your Switch to be dumped "
- "before playing.<br/><br/>For more information on dumping these files, please see the "
- "following wiki page: <a "
- "href='https://yuzu-emu.org/wiki/"
- "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
- "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit "
- "back to the game list? Continuing emulation may result in crashes, corrupted save "
- "data, or other bugs.");
+ const QString common_message =
+ tr("The game you are trying to load requires additional files from your Switch to be "
+ "dumped "
+ "before playing.<br/><br/>For more information on dumping these files, please see the "
+ "following wiki page: <a "
+ "href='https://yuzu-emu.org/wiki/"
+ "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
+ "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to "
+ "quit "
+ "back to the game list? Continuing emulation may result in crashes, corrupted save "
+ "data, or other bugs.");
switch (result) {
case Core::System::ResultStatus::ErrorSystemFiles: {
QString message = "yuzu was unable to locate a Switch system archive";
@@ -1357,9 +1455,12 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
this, tr("Fatal Error"),
tr("yuzu has encountered a fatal error, please see the log for more details. "
"For more information on accessing the log, please see the following page: "
- "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to "
- "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? "
- "Continuing emulation may result in crashes, corrupted save data, or other bugs."),
+ "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
+ "to "
+ "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game "
+ "list? "
+ "Continuing emulation may result in crashes, corrupted save data, or other "
+ "bugs."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
status_message = "Fatal Error encountered";
break;
@@ -1459,6 +1560,42 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
}
}
+boost::optional<u64> GMainWindow::SelectRomFSDumpTarget(
+ const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
+ const auto dlc_entries =
+ installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
+ std::vector<FileSys::RegisteredCacheEntry> dlc_match;
+ dlc_match.reserve(dlc_entries.size());
+ std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
+ [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
+ return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
+ installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
+ });
+
+ std::vector<u64> romfs_tids;
+ romfs_tids.push_back(program_id);
+ for (const auto& entry : dlc_match)
+ romfs_tids.push_back(entry.title_id);
+
+ if (romfs_tids.size() > 1) {
+ QStringList list{"Base"};
+ for (std::size_t i = 1; i < romfs_tids.size(); ++i)
+ list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
+
+ bool ok;
+ const auto res = QInputDialog::getItem(
+ this, tr("Select RomFS Dump Target"),
+ tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
+ if (!ok) {
+ return boost::none;
+ }
+
+ return romfs_tids[list.indexOf(res)];
+ }
+
+ return program_id;
+}
+
bool GMainWindow::ConfirmClose() {
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
return true;