diff options
Diffstat (limited to '')
| -rw-r--r-- | src/core/file_sys/registered_cache.cpp | 2 | ||||
| -rw-r--r-- | src/core/file_sys/registered_cache.h | 2 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 9 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 7 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 145 | ||||
| -rw-r--r-- | src/yuzu/main.h | 2 |
6 files changed, 162 insertions, 5 deletions
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index dad7ae10b..0dda0b861 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -480,7 +480,7 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs | |||
| 480 | auto out = dir->CreateFileRelative(path); | 480 | auto out = dir->CreateFileRelative(path); |
| 481 | if (out == nullptr) | 481 | if (out == nullptr) |
| 482 | return InstallResult::ErrorCopyFailed; | 482 | return InstallResult::ErrorCopyFailed; |
| 483 | return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; | 483 | return copy(in, out, 0x400000) ? InstallResult::Success : InstallResult::ErrorCopyFailed; |
| 484 | } | 484 | } |
| 485 | 485 | ||
| 486 | bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { | 486 | bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { |
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index f487b0cf0..c0cd59fc5 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h | |||
| @@ -27,7 +27,7 @@ struct ContentRecord; | |||
| 27 | 27 | ||
| 28 | using NcaID = std::array<u8, 0x10>; | 28 | using NcaID = std::array<u8, 0x10>; |
| 29 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; | 29 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; |
| 30 | using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; | 30 | using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>; |
| 31 | 31 | ||
| 32 | enum class InstallResult { | 32 | enum class InstallResult { |
| 33 | Success, | 33 | Success, |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index e8b2f720a..991ae10cd 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 318 | int row = item_model->itemFromIndex(item)->row(); | 318 | int row = item_model->itemFromIndex(item)->row(); |
| 319 | QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); | 319 | QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); |
| 320 | u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); | 320 | u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); |
| 321 | std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); | ||
| 321 | 322 | ||
| 322 | QMenu context_menu; | 323 | QMenu context_menu; |
| 323 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); | 324 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); |
| 325 | QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); | ||
| 326 | context_menu.addSeparator(); | ||
| 327 | QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); | ||
| 328 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | ||
| 324 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 329 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 325 | 330 | ||
| 326 | open_save_location->setEnabled(program_id != 0); | 331 | open_save_location->setEnabled(program_id != 0); |
| @@ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 329 | 334 | ||
| 330 | connect(open_save_location, &QAction::triggered, | 335 | connect(open_save_location, &QAction::triggered, |
| 331 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); | 336 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); |
| 337 | connect(open_lfs_location, &QAction::triggered, | ||
| 338 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); | ||
| 339 | connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); | ||
| 340 | connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); | ||
| 332 | connect(navigate_to_gamedb_entry, &QAction::triggered, | 341 | connect(navigate_to_gamedb_entry, &QAction::triggered, |
| 333 | [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); | 342 | [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); |
| 334 | 343 | ||
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 2713e7b54..3bf51870e 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -28,7 +28,10 @@ namespace FileSys { | |||
| 28 | class VfsFilesystem; | 28 | class VfsFilesystem; |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | enum class GameListOpenTarget { SaveData }; | 31 | enum class GameListOpenTarget { |
| 32 | SaveData, | ||
| 33 | ModData, | ||
| 34 | }; | ||
| 32 | 35 | ||
| 33 | class GameList : public QWidget { | 36 | class GameList : public QWidget { |
| 34 | Q_OBJECT | 37 | Q_OBJECT |
| @@ -89,6 +92,8 @@ signals: | |||
| 89 | void GameChosen(QString game_path); | 92 | void GameChosen(QString game_path); |
| 90 | void ShouldCancelWorker(); | 93 | void ShouldCancelWorker(); |
| 91 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target); | 94 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target); |
| 95 | void DumpRomFSRequested(u64 program_id, const std::string& game_path); | ||
| 96 | void CopyTIDRequested(u64 program_id); | ||
| 92 | void NavigateToGamedbEntryRequested(u64 program_id, | 97 | void NavigateToGamedbEntryRequested(u64 program_id, |
| 93 | const CompatibilityList& compatibility_list); | 98 | const CompatibilityList& compatibility_list); |
| 94 | 99 | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 45bb1d1d1..7cfe8a32f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -7,6 +7,22 @@ | |||
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include <thread> | 8 | #include <thread> |
| 9 | 9 | ||
| 10 | // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. | ||
| 11 | #include "core/file_sys/vfs.h" | ||
| 12 | #include "core/file_sys/vfs_real.h" | ||
| 13 | |||
| 14 | // These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows | ||
| 15 | // defines. | ||
| 16 | static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( | ||
| 17 | const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { | ||
| 18 | return vfs->CreateDirectory(path, mode); | ||
| 19 | } | ||
| 20 | |||
| 21 | static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir, | ||
| 22 | const std::string& path) { | ||
| 23 | return dir->CreateFile(path); | ||
| 24 | } | ||
| 25 | |||
| 10 | #include <fmt/ostream.h> | 26 | #include <fmt/ostream.h> |
| 11 | #include <glad/glad.h> | 27 | #include <glad/glad.h> |
| 12 | 28 | ||
| @@ -30,16 +46,18 @@ | |||
| 30 | #include "common/telemetry.h" | 46 | #include "common/telemetry.h" |
| 31 | #include "core/core.h" | 47 | #include "core/core.h" |
| 32 | #include "core/crypto/key_manager.h" | 48 | #include "core/crypto/key_manager.h" |
| 49 | #include "core/file_sys/bis_factory.h" | ||
| 33 | #include "core/file_sys/card_image.h" | 50 | #include "core/file_sys/card_image.h" |
| 34 | #include "core/file_sys/content_archive.h" | 51 | #include "core/file_sys/content_archive.h" |
| 35 | #include "core/file_sys/control_metadata.h" | 52 | #include "core/file_sys/control_metadata.h" |
| 36 | #include "core/file_sys/patch_manager.h" | 53 | #include "core/file_sys/patch_manager.h" |
| 37 | #include "core/file_sys/registered_cache.h" | 54 | #include "core/file_sys/registered_cache.h" |
| 55 | #include "core/file_sys/romfs.h" | ||
| 38 | #include "core/file_sys/savedata_factory.h" | 56 | #include "core/file_sys/savedata_factory.h" |
| 39 | #include "core/file_sys/submission_package.h" | 57 | #include "core/file_sys/submission_package.h" |
| 40 | #include "core/file_sys/vfs_real.h" | ||
| 41 | #include "core/hle/kernel/process.h" | 58 | #include "core/hle/kernel/process.h" |
| 42 | #include "core/hle/service/filesystem/filesystem.h" | 59 | #include "core/hle/service/filesystem/filesystem.h" |
| 60 | #include "core/hle/service/filesystem/fsp_ldr.h" | ||
| 43 | #include "core/loader/loader.h" | 61 | #include "core/loader/loader.h" |
| 44 | #include "core/perf_stats.h" | 62 | #include "core/perf_stats.h" |
| 45 | #include "core/settings.h" | 63 | #include "core/settings.h" |
| @@ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() { | |||
| 362 | void GMainWindow::ConnectWidgetEvents() { | 380 | void GMainWindow::ConnectWidgetEvents() { |
| 363 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); | 381 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); |
| 364 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | 382 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); |
| 383 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | ||
| 384 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | ||
| 365 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 385 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| 366 | &GMainWindow::OnGameListNavigateToGamedbEntry); | 386 | &GMainWindow::OnGameListNavigateToGamedbEntry); |
| 367 | 387 | ||
| @@ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target | |||
| 713 | program_id, user_id, 0); | 733 | program_id, user_id, 0); |
| 714 | break; | 734 | break; |
| 715 | } | 735 | } |
| 736 | case GameListOpenTarget::ModData: { | ||
| 737 | open_target = "Mod Data"; | ||
| 738 | const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir); | ||
| 739 | path = fmt::format("{}{:016X}", load_dir, program_id); | ||
| 740 | break; | ||
| 741 | } | ||
| 716 | default: | 742 | default: |
| 717 | UNIMPLEMENTED(); | 743 | UNIMPLEMENTED(); |
| 718 | } | 744 | } |
| @@ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target | |||
| 730 | QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); | 756 | QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); |
| 731 | } | 757 | } |
| 732 | 758 | ||
| 759 | void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { | ||
| 760 | const auto path = fmt::format("{}{:016X}/romfs", | ||
| 761 | FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id); | ||
| 762 | |||
| 763 | auto failed = [this, &path]() { | ||
| 764 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), | ||
| 765 | tr("There was an error copying the RomFS files or the user " | ||
| 766 | "cancelled the operation.")); | ||
| 767 | vfs->DeleteDirectory(path); | ||
| 768 | }; | ||
| 769 | |||
| 770 | const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 771 | if (loader == nullptr) { | ||
| 772 | failed(); | ||
| 773 | return; | ||
| 774 | } | ||
| 775 | |||
| 776 | FileSys::VirtualFile file; | ||
| 777 | if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { | ||
| 778 | failed(); | ||
| 779 | return; | ||
| 780 | } | ||
| 781 | |||
| 782 | const auto romfs = | ||
| 783 | loader->IsRomFSUpdatable() | ||
| 784 | ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset()) | ||
| 785 | : file; | ||
| 786 | |||
| 787 | const auto extracted = FileSys::ExtractRomFS(romfs, false); | ||
| 788 | if (extracted == nullptr) { | ||
| 789 | failed(); | ||
| 790 | return; | ||
| 791 | } | ||
| 792 | |||
| 793 | const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); | ||
| 794 | |||
| 795 | if (out == nullptr) { | ||
| 796 | failed(); | ||
| 797 | return; | ||
| 798 | } | ||
| 799 | |||
| 800 | bool ok; | ||
| 801 | const auto res = QInputDialog::getItem( | ||
| 802 | this, tr("Select RomFS Dump Mode"), | ||
| 803 | tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the " | ||
| 804 | "files into the new directory while <br>skeleton will only create the directory " | ||
| 805 | "structure."), | ||
| 806 | {"Full", "Skeleton"}, 0, false, &ok); | ||
| 807 | if (!ok) | ||
| 808 | failed(); | ||
| 809 | |||
| 810 | const auto full = res == "Full"; | ||
| 811 | |||
| 812 | const static std::function<size_t(const FileSys::VirtualDir&, bool)> calculate_entry_size = | ||
| 813 | [](const FileSys::VirtualDir& dir, bool full) { | ||
| 814 | size_t out = 0; | ||
| 815 | for (const auto& subdir : dir->GetSubdirectories()) | ||
| 816 | out += 1 + calculate_entry_size(subdir, full); | ||
| 817 | return out + full ? dir->GetFiles().size() : 0; | ||
| 818 | }; | ||
| 819 | const auto entry_size = calculate_entry_size(extracted, full); | ||
| 820 | |||
| 821 | QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this); | ||
| 822 | progress.setWindowModality(Qt::WindowModal); | ||
| 823 | progress.setMinimumDuration(100); | ||
| 824 | |||
| 825 | const static std::function<bool(QProgressDialog&, const FileSys::VirtualDir&, | ||
| 826 | const FileSys::VirtualDir&, size_t, bool)> | ||
| 827 | qt_raw_copy = [](QProgressDialog& dialog, const FileSys::VirtualDir& src, | ||
| 828 | const FileSys::VirtualDir& dest, size_t block_size, bool full) { | ||
| 829 | if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) | ||
| 830 | return false; | ||
| 831 | if (dialog.wasCanceled()) | ||
| 832 | return false; | ||
| 833 | |||
| 834 | if (full) { | ||
| 835 | for (const auto& file : src->GetFiles()) { | ||
| 836 | const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); | ||
| 837 | if (!FileSys::VfsRawCopy(file, out, block_size)) | ||
| 838 | return false; | ||
| 839 | dialog.setValue(dialog.value() + 1); | ||
| 840 | if (dialog.wasCanceled()) | ||
| 841 | return false; | ||
| 842 | } | ||
| 843 | } | ||
| 844 | |||
| 845 | for (const auto& dir : src->GetSubdirectories()) { | ||
| 846 | const auto out = dest->CreateSubdirectory(dir->GetName()); | ||
| 847 | if (!qt_raw_copy(dialog, dir, out, block_size, full)) | ||
| 848 | return false; | ||
| 849 | dialog.setValue(dialog.value() + 1); | ||
| 850 | if (dialog.wasCanceled()) | ||
| 851 | return false; | ||
| 852 | } | ||
| 853 | |||
| 854 | return true; | ||
| 855 | }; | ||
| 856 | |||
| 857 | if (qt_raw_copy(progress, extracted, out, 0x400000, full)) { | ||
| 858 | progress.close(); | ||
| 859 | QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), | ||
| 860 | tr("The operation completed successfully.")); | ||
| 861 | QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path))); | ||
| 862 | } else { | ||
| 863 | progress.close(); | ||
| 864 | failed(); | ||
| 865 | } | ||
| 866 | } | ||
| 867 | |||
| 868 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | ||
| 869 | QClipboard* clipboard = QGuiApplication::clipboard(); | ||
| 870 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); | ||
| 871 | } | ||
| 872 | |||
| 733 | void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | 873 | void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, |
| 734 | const CompatibilityList& compatibility_list) { | 874 | const CompatibilityList& compatibility_list) { |
| 735 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | 875 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); |
| @@ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 790 | return; | 930 | return; |
| 791 | } | 931 | } |
| 792 | 932 | ||
| 793 | const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) { | 933 | const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, |
| 934 | const FileSys::VirtualFile& dest, size_t block_size) { | ||
| 794 | if (src == nullptr || dest == nullptr) | 935 | if (src == nullptr || dest == nullptr) |
| 795 | return false; | 936 | return false; |
| 796 | if (!dest->Resize(src->GetSize())) | 937 | if (!dest->Resize(src->GetSize())) |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 552e3e61c..8ee9242b1 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -138,6 +138,8 @@ private slots: | |||
| 138 | /// Called whenever a user selects a game in the game list widget. | 138 | /// Called whenever a user selects a game in the game list widget. |
| 139 | void OnGameListLoadFile(QString game_path); | 139 | void OnGameListLoadFile(QString game_path); |
| 140 | void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); | 140 | void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); |
| 141 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); | ||
| 142 | void OnGameListCopyTID(u64 program_id); | ||
| 141 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 143 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 142 | const CompatibilityList& compatibility_list); | 144 | const CompatibilityList& compatibility_list); |
| 143 | void OnMenuLoadFile(); | 145 | void OnMenuLoadFile(); |