From f6c3c96d9448b3903d2656074cf3817c4893cdde Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:38:51 +0200 Subject: [PATCH] More detailed error messages when encrypted titles fail to launch --- src/Cafe/Filesystem/FST/FST.cpp | 35 ++++++++++++++++++++++++-------- src/Cafe/Filesystem/FST/FST.h | 16 ++++++++++++--- src/Cafe/TitleList/TitleInfo.cpp | 21 +++++++++++++++++-- src/Cafe/TitleList/TitleInfo.h | 14 ++++++++++++- src/gui/MainWindow.cpp | 10 +++++++++ 5 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/Cafe/Filesystem/FST/FST.cpp b/src/Cafe/Filesystem/FST/FST.cpp index a4bbfeed..10ae659d 100644 --- a/src/Cafe/Filesystem/FST/FST.cpp +++ b/src/Cafe/Filesystem/FST/FST.cpp @@ -12,6 +12,8 @@ #include "boost/range/adaptor/reversed.hpp" +#define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code + class FSTDataSource { public: @@ -215,23 +217,22 @@ bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey) // open WUD image using key cache // if no matching key is found then keyFound will return false -FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, bool* keyFound) +FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut) { + SET_FST_ERROR(UNKNOWN_ERROR); KeyCache_Prepare(); NCrypto::AesKey discTitleKey; if (!FindDiscKey(path, discTitleKey)) { - if(keyFound) - *keyFound = false; + SET_FST_ERROR(DISC_KEY_MISSING); return nullptr; } - if(keyFound) - *keyFound = true; - return OpenFromDiscImage(path, discTitleKey); + return OpenFromDiscImage(path, discTitleKey, errorCodeOut); } // open WUD image -FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey) +FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut) + { // WUD images support multiple partitions, each with their own key and FST // the process for loading game data FSTVolume from a WUD image is as follows: @@ -240,6 +241,7 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d // 3) find main GM partition // 4) use SI information to get titleKey for GM partition // 5) Load FST for GM + SET_FST_ERROR(UNKNOWN_ERROR); std::unique_ptr dataSource(FSTDataSourceWUD::Open(path)); if (!dataSource) return nullptr; @@ -365,11 +367,15 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d // load GM partition dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); - return OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast(partitionHeaderGM.fstHashType)); + FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast(partitionHeaderGM.fstHashType)); + if (r) + SET_FST_ERROR(OK); + return r; } -FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath) +FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut) { + SET_FST_ERROR(UNKNOWN_ERROR); // load TMD FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd"); if (!tmdFile) @@ -379,17 +385,26 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath) delete tmdFile; NCrypto::TMDParser tmdParser; if (!tmdParser.parse(tmdData.data(), tmdData.size())) + { + SET_FST_ERROR(BAD_TITLE_TMD); return nullptr; + } // load ticket FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik"); if (!ticketFile) + { + SET_FST_ERROR(TITLE_TIK_MISSING); return nullptr; + } std::vector ticketData; ticketFile->extract(ticketData); delete ticketFile; NCrypto::ETicketParser ticketParser; if (!ticketParser.parse(ticketData.data(), ticketData.size())) + { + SET_FST_ERROR(BAD_TITLE_TIK); return nullptr; + } NCrypto::AesKey titleKey; ticketParser.GetTitleKey(titleKey); // open data source @@ -412,6 +427,8 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath) // load FST // fstSize = size of first cluster? FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode); + if (fstVolume) + SET_FST_ERROR(OK); return fstVolume; } diff --git a/src/Cafe/Filesystem/FST/FST.h b/src/Cafe/Filesystem/FST/FST.h index 3f59152f..98bf1ae6 100644 --- a/src/Cafe/Filesystem/FST/FST.h +++ b/src/Cafe/Filesystem/FST/FST.h @@ -20,11 +20,21 @@ private: class FSTVolume { public: + enum class ErrorCode + { + OK = 0, + UNKNOWN_ERROR = 1, + DISC_KEY_MISSING = 2, + TITLE_TIK_MISSING = 3, + BAD_TITLE_TMD = 4, + BAD_TITLE_TIK = 5, + }; + static bool FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey); - static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey); - static FSTVolume* OpenFromDiscImage(const fs::path& path, bool* keyFound = nullptr); - static FSTVolume* OpenFromContentFolder(fs::path folderPath); + static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut = nullptr); + static FSTVolume* OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut = nullptr); + static FSTVolume* OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut = nullptr); ~FSTVolume(); diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 867e0a7f..ff457575 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -77,6 +77,7 @@ TitleInfo::TitleInfo(const fs::path& path, std::string_view subPath) if (!path.has_filename()) { m_isValid = false; + SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE); return; } m_isValid = true; @@ -269,6 +270,7 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF return true; } } + SetInvalidReason(InvalidReason::UNKNOWN_FORMAT); return false; } @@ -321,6 +323,12 @@ uint64 TitleInfo::GetUID() return m_uid; } +void TitleInfo::SetInvalidReason(InvalidReason reason) +{ + if(m_invalidReason == InvalidReason::NONE) + m_invalidReason = reason; // only update reason when it hasn't been set before +} + std::mutex sZArchivePoolMtx; std::map> sZArchivePool; @@ -382,21 +390,29 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder, if (!r) { cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); + SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE); return false; } } else if (m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS) { + FSTVolume::ErrorCode fstError; if (m_mountpoints.empty()) { cemu_assert_debug(!m_wudVolume); if(m_titleFormat == TitleDataFormat::WUD) - m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath); // open wud/wux + m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath, &fstError); // open wud/wux else - m_wudVolume = FSTVolume::OpenFromContentFolder(m_fullPath.parent_path()); // open from .app files directory, the path points to /title.tmd + m_wudVolume = FSTVolume::OpenFromContentFolder(m_fullPath.parent_path(), &fstError); // open from .app files directory, the path points to /title.tmd } if (!m_wudVolume) + { + if (fstError == FSTVolume::ErrorCode::DISC_KEY_MISSING) + SetInvalidReason(InvalidReason::NO_DISC_KEY); + else if (fstError == FSTVolume::ErrorCode::TITLE_TIK_MISSING) + SetInvalidReason(InvalidReason::NO_TITLE_TIK); return false; + } bool r = FSCDeviceWUD_Mount(virtualPath, subfolder, m_wudVolume, mountPriority); cemu_assert_debug(r); if (!r) @@ -518,6 +534,7 @@ bool TitleInfo::ParseXmlInfo() m_parsedAppXml = nullptr; m_parsedCosXml = nullptr; m_isValid = false; + SetInvalidReason(InvalidReason::MISSING_XML_FILES); return false; } m_isValid = true; diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index 536e9ccb..eca6624d 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -65,6 +65,16 @@ public: INVALID_STRUCTURE = 0, }; + enum class InvalidReason : uint8 + { + NONE = 0, + BAD_PATH_OR_INACCESSIBLE = 1, + UNKNOWN_FORMAT = 2, + NO_DISC_KEY = 3, + NO_TITLE_TIK = 4, + MISSING_XML_FILES = 4, + }; + struct CachedInfo { TitleDataFormat titleDataFormat; @@ -101,6 +111,7 @@ public: CachedInfo MakeCacheEntry(); bool IsValid() const; + InvalidReason GetInvalidReason() const { return m_invalidReason; } uint64 GetUID(); // returns a unique identifier derived from the absolute canonical title location which can be used to identify this title by its location. May not persist across sessions, especially when Cemu is used portable fs::path GetPath() const; @@ -182,7 +193,7 @@ private: bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut); void CalcUID(); - + void SetInvalidReason(InvalidReason reason); bool ParseAppXml(std::vector& appXmlData); bool m_isValid{ false }; @@ -190,6 +201,7 @@ private: fs::path m_fullPath; std::string m_subPath; // used for formats where fullPath isn't unique on its own (like WUA) uint64 m_uid{}; + InvalidReason m_invalidReason{ InvalidReason::NONE }; // if m_isValid == false, this contains a more detailed error code // mounting info std::vector> m_mountpoints; class FSTVolume* m_wudVolume{}; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index e8e90f02..69159df2 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -559,6 +559,16 @@ bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY { wxString t = _("Unable to launch game\nPath:\n"); t.append(fileName); + if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_DISC_KEY) + { + t.append(_("\n\n")); + t.append(_("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title.")); + } + if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_TITLE_TIK) + { + t.append(_("")); + t.append(_("\n\nCould not decrypt title because title.tik is missing.")); + } wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; }