mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-22 17:19:18 +01:00
More detailed error messages when encrypted titles fail to launch
This commit is contained in:
parent
5ad57bb0c9
commit
f6c3c96d94
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
#include "boost/range/adaptor/reversed.hpp"
|
#include "boost/range/adaptor/reversed.hpp"
|
||||||
|
|
||||||
|
#define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code
|
||||||
|
|
||||||
class FSTDataSource
|
class FSTDataSource
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -215,23 +217,22 @@ bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
|
|||||||
|
|
||||||
// open WUD image using key cache
|
// open WUD image using key cache
|
||||||
// if no matching key is found then keyFound will return false
|
// 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();
|
KeyCache_Prepare();
|
||||||
NCrypto::AesKey discTitleKey;
|
NCrypto::AesKey discTitleKey;
|
||||||
if (!FindDiscKey(path, discTitleKey))
|
if (!FindDiscKey(path, discTitleKey))
|
||||||
{
|
{
|
||||||
if(keyFound)
|
SET_FST_ERROR(DISC_KEY_MISSING);
|
||||||
*keyFound = false;
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if(keyFound)
|
return OpenFromDiscImage(path, discTitleKey, errorCodeOut);
|
||||||
*keyFound = true;
|
|
||||||
return OpenFromDiscImage(path, discTitleKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// open WUD image
|
// 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
|
// 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:
|
// 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
|
// 3) find main GM partition
|
||||||
// 4) use SI information to get titleKey for GM partition
|
// 4) use SI information to get titleKey for GM partition
|
||||||
// 5) Load FST for GM
|
// 5) Load FST for GM
|
||||||
|
SET_FST_ERROR(UNKNOWN_ERROR);
|
||||||
std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path));
|
std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path));
|
||||||
if (!dataSource)
|
if (!dataSource)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -365,11 +367,15 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
|
|||||||
|
|
||||||
// load GM partition
|
// load GM partition
|
||||||
dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
|
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<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType));
|
FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(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
|
// load TMD
|
||||||
FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd");
|
FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd");
|
||||||
if (!tmdFile)
|
if (!tmdFile)
|
||||||
@ -379,17 +385,26 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath)
|
|||||||
delete tmdFile;
|
delete tmdFile;
|
||||||
NCrypto::TMDParser tmdParser;
|
NCrypto::TMDParser tmdParser;
|
||||||
if (!tmdParser.parse(tmdData.data(), tmdData.size()))
|
if (!tmdParser.parse(tmdData.data(), tmdData.size()))
|
||||||
|
{
|
||||||
|
SET_FST_ERROR(BAD_TITLE_TMD);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
// load ticket
|
// load ticket
|
||||||
FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik");
|
FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik");
|
||||||
if (!ticketFile)
|
if (!ticketFile)
|
||||||
|
{
|
||||||
|
SET_FST_ERROR(TITLE_TIK_MISSING);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
std::vector<uint8> ticketData;
|
std::vector<uint8> ticketData;
|
||||||
ticketFile->extract(ticketData);
|
ticketFile->extract(ticketData);
|
||||||
delete ticketFile;
|
delete ticketFile;
|
||||||
NCrypto::ETicketParser ticketParser;
|
NCrypto::ETicketParser ticketParser;
|
||||||
if (!ticketParser.parse(ticketData.data(), ticketData.size()))
|
if (!ticketParser.parse(ticketData.data(), ticketData.size()))
|
||||||
|
{
|
||||||
|
SET_FST_ERROR(BAD_TITLE_TIK);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
NCrypto::AesKey titleKey;
|
NCrypto::AesKey titleKey;
|
||||||
ticketParser.GetTitleKey(titleKey);
|
ticketParser.GetTitleKey(titleKey);
|
||||||
// open data source
|
// open data source
|
||||||
@ -412,6 +427,8 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath)
|
|||||||
// load FST
|
// load FST
|
||||||
// fstSize = size of first cluster?
|
// fstSize = size of first cluster?
|
||||||
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode);
|
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode);
|
||||||
|
if (fstVolume)
|
||||||
|
SET_FST_ERROR(OK);
|
||||||
return fstVolume;
|
return fstVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +20,21 @@ private:
|
|||||||
class FSTVolume
|
class FSTVolume
|
||||||
{
|
{
|
||||||
public:
|
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 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, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut = nullptr);
|
||||||
static FSTVolume* OpenFromDiscImage(const fs::path& path, bool* keyFound = nullptr);
|
static FSTVolume* OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut = nullptr);
|
||||||
static FSTVolume* OpenFromContentFolder(fs::path folderPath);
|
static FSTVolume* OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut = nullptr);
|
||||||
|
|
||||||
~FSTVolume();
|
~FSTVolume();
|
||||||
|
|
||||||
|
@ -77,6 +77,7 @@ TitleInfo::TitleInfo(const fs::path& path, std::string_view subPath)
|
|||||||
if (!path.has_filename())
|
if (!path.has_filename())
|
||||||
{
|
{
|
||||||
m_isValid = false;
|
m_isValid = false;
|
||||||
|
SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_isValid = true;
|
m_isValid = true;
|
||||||
@ -269,6 +270,7 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SetInvalidReason(InvalidReason::UNKNOWN_FORMAT);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,6 +323,12 @@ uint64 TitleInfo::GetUID()
|
|||||||
return m_uid;
|
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::mutex sZArchivePoolMtx;
|
||||||
std::map<fs::path, std::pair<uint32, ZArchiveReader*>> sZArchivePool;
|
std::map<fs::path, std::pair<uint32, ZArchiveReader*>> sZArchivePool;
|
||||||
|
|
||||||
@ -382,21 +390,29 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder,
|
|||||||
if (!r)
|
if (!r)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder);
|
cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder);
|
||||||
|
SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS)
|
else if (m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS)
|
||||||
{
|
{
|
||||||
|
FSTVolume::ErrorCode fstError;
|
||||||
if (m_mountpoints.empty())
|
if (m_mountpoints.empty())
|
||||||
{
|
{
|
||||||
cemu_assert_debug(!m_wudVolume);
|
cemu_assert_debug(!m_wudVolume);
|
||||||
if(m_titleFormat == TitleDataFormat::WUD)
|
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
|
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 (!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;
|
return false;
|
||||||
|
}
|
||||||
bool r = FSCDeviceWUD_Mount(virtualPath, subfolder, m_wudVolume, mountPriority);
|
bool r = FSCDeviceWUD_Mount(virtualPath, subfolder, m_wudVolume, mountPriority);
|
||||||
cemu_assert_debug(r);
|
cemu_assert_debug(r);
|
||||||
if (!r)
|
if (!r)
|
||||||
@ -518,6 +534,7 @@ bool TitleInfo::ParseXmlInfo()
|
|||||||
m_parsedAppXml = nullptr;
|
m_parsedAppXml = nullptr;
|
||||||
m_parsedCosXml = nullptr;
|
m_parsedCosXml = nullptr;
|
||||||
m_isValid = false;
|
m_isValid = false;
|
||||||
|
SetInvalidReason(InvalidReason::MISSING_XML_FILES);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_isValid = true;
|
m_isValid = true;
|
||||||
|
@ -65,6 +65,16 @@ public:
|
|||||||
INVALID_STRUCTURE = 0,
|
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
|
struct CachedInfo
|
||||||
{
|
{
|
||||||
TitleDataFormat titleDataFormat;
|
TitleDataFormat titleDataFormat;
|
||||||
@ -101,6 +111,7 @@ public:
|
|||||||
CachedInfo MakeCacheEntry();
|
CachedInfo MakeCacheEntry();
|
||||||
|
|
||||||
bool IsValid() const;
|
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
|
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;
|
fs::path GetPath() const;
|
||||||
@ -182,7 +193,7 @@ private:
|
|||||||
|
|
||||||
bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut);
|
bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut);
|
||||||
void CalcUID();
|
void CalcUID();
|
||||||
|
void SetInvalidReason(InvalidReason reason);
|
||||||
bool ParseAppXml(std::vector<uint8>& appXmlData);
|
bool ParseAppXml(std::vector<uint8>& appXmlData);
|
||||||
|
|
||||||
bool m_isValid{ false };
|
bool m_isValid{ false };
|
||||||
@ -190,6 +201,7 @@ private:
|
|||||||
fs::path m_fullPath;
|
fs::path m_fullPath;
|
||||||
std::string m_subPath; // used for formats where fullPath isn't unique on its own (like WUA)
|
std::string m_subPath; // used for formats where fullPath isn't unique on its own (like WUA)
|
||||||
uint64 m_uid{};
|
uint64 m_uid{};
|
||||||
|
InvalidReason m_invalidReason{ InvalidReason::NONE }; // if m_isValid == false, this contains a more detailed error code
|
||||||
// mounting info
|
// mounting info
|
||||||
std::vector<std::pair<sint32, std::string>> m_mountpoints;
|
std::vector<std::pair<sint32, std::string>> m_mountpoints;
|
||||||
class FSTVolume* m_wudVolume{};
|
class FSTVolume* m_wudVolume{};
|
||||||
|
@ -559,6 +559,16 @@ bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY
|
|||||||
{
|
{
|
||||||
wxString t = _("Unable to launch game\nPath:\n");
|
wxString t = _("Unable to launch game\nPath:\n");
|
||||||
t.append(fileName);
|
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);
|
wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user