mirror of
https://github.com/azahar-emu/azahar
synced 2025-11-07 07:29:58 +01:00
am: Improve cia encrypted content detection (#1152)
This commit is contained in:
parent
eec1466b7b
commit
3c3dd2bd86
@ -448,13 +448,16 @@ ResultVal<std::size_t> CIAFile::Read(u64 offset, std::size_t length, u8* buffer)
|
|||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result CIAFile::WriteTicket() {
|
CIAFile::InstallResult CIAFile::WriteTicket() {
|
||||||
|
InstallResult res{};
|
||||||
|
res.type = InstallResult::Type::TIK;
|
||||||
auto load_result = container.LoadTicket(data, container.GetTicketOffset());
|
auto load_result = container.LoadTicket(data, container.GetTicketOffset());
|
||||||
if (load_result != Loader::ResultStatus::Success) {
|
if (load_result != Loader::ResultStatus::Success) {
|
||||||
LOG_ERROR(Service_AM, "Could not read ticket from CIA.");
|
LOG_ERROR(Service_AM, "Could not read ticket from CIA.");
|
||||||
// TODO: Correct result code.
|
// TODO: Correct result code.
|
||||||
return {ErrCodes::InvalidCIAHeader, ErrorModule::AM, ErrorSummary::InvalidArgument,
|
res.result = {ErrCodes::InvalidCIAHeader, ErrorModule::AM, ErrorSummary::InvalidArgument,
|
||||||
ErrorLevel::Permanent};
|
ErrorLevel::Permanent};
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& ticket = container.GetTicket();
|
const auto& ticket = container.GetTicket();
|
||||||
@ -466,23 +469,30 @@ Result CIAFile::WriteTicket() {
|
|||||||
FileUtil::CreateFullPath(ticket_folder);
|
FileUtil::CreateFullPath(ticket_folder);
|
||||||
|
|
||||||
// Save ticket
|
// Save ticket
|
||||||
|
res.install_full_path = ticket_path;
|
||||||
if (ticket.Save(ticket_path) != Loader::ResultStatus::Success) {
|
if (ticket.Save(ticket_path) != Loader::ResultStatus::Success) {
|
||||||
LOG_ERROR(Service_AM, "Failed to install ticket file from CIA.");
|
LOG_ERROR(Service_AM, "Failed to install ticket file from CIA.");
|
||||||
// TODO: Correct result code.
|
// TODO: Correct result code.
|
||||||
return FileSys::ResultFileNotFound;
|
res.result = FileSys::ResultFileNotFound;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
install_state = CIAInstallState::TicketLoaded;
|
install_state = CIAInstallState::TicketLoaded;
|
||||||
return ResultSuccess;
|
res.result = ResultSuccess;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result CIAFile::WriteTitleMetadata(std::span<const u8> tmd_data, std::size_t offset) {
|
CIAFile::InstallResult CIAFile::WriteTitleMetadata(std::span<const u8> tmd_data,
|
||||||
|
std::size_t offset) {
|
||||||
|
InstallResult res{};
|
||||||
|
res.type = InstallResult::Type::TMD;
|
||||||
auto load_result = container.LoadTitleMetadata(tmd_data, offset);
|
auto load_result = container.LoadTitleMetadata(tmd_data, offset);
|
||||||
if (load_result != Loader::ResultStatus::Success) {
|
if (load_result != Loader::ResultStatus::Success) {
|
||||||
LOG_ERROR(Service_AM, "Could not read title metadata.");
|
LOG_ERROR(Service_AM, "Could not read title metadata.");
|
||||||
// TODO: Correct result code.
|
// TODO: Correct result code.
|
||||||
return {ErrCodes::InvalidCIAHeader, ErrorModule::AM, ErrorSummary::InvalidArgument,
|
res.result = {ErrCodes::InvalidCIAHeader, ErrorModule::AM, ErrorSummary::InvalidArgument,
|
||||||
ErrorLevel::Permanent};
|
ErrorLevel::Permanent};
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSys::TitleMetadata tmd = container.GetTitleMetadata();
|
FileSys::TitleMetadata tmd = container.GetTitleMetadata();
|
||||||
@ -503,13 +513,16 @@ Result CIAFile::WriteTitleMetadata(std::span<const u8> tmd_data, std::size_t off
|
|||||||
FileUtil::CreateFullPath(tmd_folder);
|
FileUtil::CreateFullPath(tmd_folder);
|
||||||
|
|
||||||
// Save TMD so that we can start getting new .app paths
|
// Save TMD so that we can start getting new .app paths
|
||||||
|
res.install_full_path = tmd_path;
|
||||||
if (tmd.Save(tmd_path) != Loader::ResultStatus::Success) {
|
if (tmd.Save(tmd_path) != Loader::ResultStatus::Success) {
|
||||||
LOG_ERROR(Service_AM, "Failed to install title metadata file from CIA.");
|
LOG_ERROR(Service_AM, "Failed to install title metadata file from CIA.");
|
||||||
// TODO: Correct result code.
|
// TODO: Correct result code.
|
||||||
return FileSys::ResultFileNotFound;
|
res.result = FileSys::ResultFileNotFound;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PrepareToImportContent(tmd);
|
res.result = PrepareToImportContent(tmd);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length, const u8* buffer) {
|
ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length, const u8* buffer) {
|
||||||
@ -537,10 +550,18 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
|||||||
// to get the content paths to write to.
|
// to get the content paths to write to.
|
||||||
const FileSys::TitleMetadata& tmd = container.GetTitleMetadata();
|
const FileSys::TitleMetadata& tmd = container.GetTitleMetadata();
|
||||||
if (i != current_content_index) {
|
if (i != current_content_index) {
|
||||||
|
// A previous content file was being installed, save it first
|
||||||
|
if (current_content_install_result.type == InstallResult::Type::APP) {
|
||||||
|
install_results.push_back(current_content_install_result);
|
||||||
|
}
|
||||||
current_content_index = static_cast<u16>(i);
|
current_content_index = static_cast<u16>(i);
|
||||||
current_content_file =
|
current_content_file =
|
||||||
std::make_unique<NCCHCryptoFile>(content_file_paths[i], decryption_authorized);
|
std::make_unique<NCCHCryptoFile>(content_file_paths[i], decryption_authorized);
|
||||||
current_content_file->decryption_authorized = decryption_authorized;
|
current_content_file->decryption_authorized = decryption_authorized;
|
||||||
|
|
||||||
|
current_content_install_result.type = InstallResult::Type::APP;
|
||||||
|
current_content_install_result.install_full_path = content_file_paths[i];
|
||||||
|
current_content_install_result.result = ResultSuccess;
|
||||||
}
|
}
|
||||||
auto& file = *current_content_file;
|
auto& file = *current_content_file;
|
||||||
|
|
||||||
@ -550,8 +571,11 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
|||||||
if ((tmd.GetContentTypeByIndex(i) & FileSys::TMDContentTypeFlag::Encrypted) != 0) {
|
if ((tmd.GetContentTypeByIndex(i) & FileSys::TMDContentTypeFlag::Encrypted) != 0) {
|
||||||
if (!decryption_authorized) {
|
if (!decryption_authorized) {
|
||||||
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
|
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
|
||||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::AM,
|
current_content_install_result.result =
|
||||||
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
Result(ErrorDescription::NotAuthorized, ErrorModule::AM,
|
||||||
|
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||||
|
install_results.push_back(current_content_install_result);
|
||||||
|
return current_content_install_result.result;
|
||||||
}
|
}
|
||||||
decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size());
|
decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size());
|
||||||
}
|
}
|
||||||
@ -559,8 +583,11 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
|||||||
file.Write(temp.data(), temp.size());
|
file.Write(temp.data(), temp.size());
|
||||||
if (file.IsError()) {
|
if (file.IsError()) {
|
||||||
// This can never happen in real HW
|
// This can never happen in real HW
|
||||||
return Result(ErrCodes::InvalidImportState, ErrorModule::AM,
|
current_content_install_result.result =
|
||||||
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
Result(ErrCodes::InvalidImportState, ErrorModule::AM,
|
||||||
|
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||||
|
install_results.push_back(current_content_install_result);
|
||||||
|
return current_content_install_result.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep tabs on how much of this content ID has been written so new range_min
|
// Keep tabs on how much of this content ID has been written so new range_min
|
||||||
@ -623,14 +650,16 @@ ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush
|
|||||||
// The end of our TMD is at the beginning of Content data, so ensure we have that much
|
// The end of our TMD is at the beginning of Content data, so ensure we have that much
|
||||||
// buffered before trying to parse.
|
// buffered before trying to parse.
|
||||||
if (written >= container.GetContentOffset() && install_state != CIAInstallState::TMDLoaded) {
|
if (written >= container.GetContentOffset() && install_state != CIAInstallState::TMDLoaded) {
|
||||||
auto result = WriteTicket();
|
InstallResult result = WriteTicket();
|
||||||
if (result.IsError()) {
|
install_results.push_back(result);
|
||||||
return result;
|
if (result.result.IsError()) {
|
||||||
|
return result.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = WriteTitleMetadata(data, container.GetTitleMetadataOffset());
|
result = WriteTitleMetadata(data, container.GetTitleMetadataOffset());
|
||||||
if (result.IsError()) {
|
install_results.push_back(result);
|
||||||
return result;
|
if (result.result.IsError()) {
|
||||||
|
return result.result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -642,6 +671,7 @@ ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush
|
|||||||
// From this point forward, data will no longer be buffered in data
|
// From this point forward, data will no longer be buffered in data
|
||||||
auto result = WriteContentData(offset, length, buffer);
|
auto result = WriteContentData(offset, length, buffer);
|
||||||
if (result.Failed()) {
|
if (result.Failed()) {
|
||||||
|
current_content_install_result.type = InstallResult::Type::NONE;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,10 +786,19 @@ ResultVal<std::size_t> CIAFile::WriteContentDataIndexed(u16 content_index, u64 o
|
|||||||
tmd.GetContentSizeByIndex(content_index) - content_written[content_index];
|
tmd.GetContentSizeByIndex(content_index) - content_written[content_index];
|
||||||
|
|
||||||
if (content_index != current_content_index) {
|
if (content_index != current_content_index) {
|
||||||
|
// A previous content file was being installed, save it first
|
||||||
|
if (current_content_install_result.type == InstallResult::Type::APP) {
|
||||||
|
install_results.push_back(current_content_install_result);
|
||||||
|
}
|
||||||
|
|
||||||
current_content_index = content_index;
|
current_content_index = content_index;
|
||||||
current_content_file = std::make_unique<NCCHCryptoFile>(content_file_paths[content_index],
|
current_content_file = std::make_unique<NCCHCryptoFile>(content_file_paths[content_index],
|
||||||
decryption_authorized);
|
decryption_authorized);
|
||||||
current_content_file->decryption_authorized = decryption_authorized;
|
current_content_file->decryption_authorized = decryption_authorized;
|
||||||
|
|
||||||
|
current_content_install_result.type = InstallResult::Type::APP;
|
||||||
|
current_content_install_result.install_full_path = content_file_paths[content_index];
|
||||||
|
current_content_install_result.result = ResultSuccess;
|
||||||
}
|
}
|
||||||
auto& file = *current_content_file;
|
auto& file = *current_content_file;
|
||||||
|
|
||||||
@ -768,8 +807,11 @@ ResultVal<std::size_t> CIAFile::WriteContentDataIndexed(u16 content_index, u64 o
|
|||||||
if ((tmd.GetContentTypeByIndex(content_index) & FileSys::TMDContentTypeFlag::Encrypted) != 0) {
|
if ((tmd.GetContentTypeByIndex(content_index) & FileSys::TMDContentTypeFlag::Encrypted) != 0) {
|
||||||
if (!decryption_authorized) {
|
if (!decryption_authorized) {
|
||||||
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
|
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
|
||||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::AM,
|
current_content_install_result.result =
|
||||||
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
Result(ErrorDescription::NotAuthorized, ErrorModule::AM, ErrorSummary::InvalidState,
|
||||||
|
ErrorLevel::Permanent);
|
||||||
|
install_results.push_back(current_content_install_result);
|
||||||
|
return current_content_install_result.result;
|
||||||
}
|
}
|
||||||
decryption_state->content[content_index].ProcessData(temp.data(), temp.data(), temp.size());
|
decryption_state->content[content_index].ProcessData(temp.data(), temp.data(), temp.size());
|
||||||
}
|
}
|
||||||
@ -777,8 +819,11 @@ ResultVal<std::size_t> CIAFile::WriteContentDataIndexed(u16 content_index, u64 o
|
|||||||
file.Write(temp.data(), temp.size());
|
file.Write(temp.data(), temp.size());
|
||||||
if (file.IsError()) {
|
if (file.IsError()) {
|
||||||
// This can never happen in real HW
|
// This can never happen in real HW
|
||||||
return Result(ErrCodes::InvalidImportState, ErrorModule::AM, ErrorSummary::InvalidState,
|
current_content_install_result.result =
|
||||||
ErrorLevel::Permanent);
|
Result(ErrCodes::InvalidImportState, ErrorModule::AM, ErrorSummary::InvalidState,
|
||||||
|
ErrorLevel::Permanent);
|
||||||
|
install_results.push_back(current_content_install_result);
|
||||||
|
return current_content_install_result.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
content_written[content_index] += temp.size();
|
content_written[content_index] += temp.size();
|
||||||
@ -801,6 +846,12 @@ bool CIAFile::Close() {
|
|||||||
return true;
|
return true;
|
||||||
is_closed = true;
|
is_closed = true;
|
||||||
|
|
||||||
|
// Commit last pending install result
|
||||||
|
if (current_content_install_result.type != InstallResult::Type::NONE) {
|
||||||
|
install_results.push_back(current_content_install_result);
|
||||||
|
current_content_install_result.type = InstallResult::Type::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
bool complete =
|
bool complete =
|
||||||
from_cdn ? is_done
|
from_cdn ? is_done
|
||||||
: (install_state >= CIAInstallState::TMDLoaded &&
|
: (install_state >= CIAInstallState::TMDLoaded &&
|
||||||
@ -824,8 +875,15 @@ bool CIAFile::Close() {
|
|||||||
// Clean up older content data if we installed newer content on top
|
// Clean up older content data if we installed newer content on top
|
||||||
std::string old_tmd_path =
|
std::string old_tmd_path =
|
||||||
GetTitleMetadataPath(media_type, container.GetTitleMetadata().GetTitleID(), false);
|
GetTitleMetadataPath(media_type, container.GetTitleMetadata().GetTitleID(), false);
|
||||||
std::string new_tmd_path =
|
std::string new_tmd_path = old_tmd_path;
|
||||||
GetTitleMetadataPath(media_type, container.GetTitleMetadata().GetTitleID(), true);
|
|
||||||
|
for (auto result : install_results) {
|
||||||
|
if (result.type == InstallResult::Type::TMD && result.result.IsSuccess()) {
|
||||||
|
new_tmd_path = result.install_full_path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (FileUtil::Exists(new_tmd_path) && old_tmd_path != new_tmd_path) {
|
if (FileUtil::Exists(new_tmd_path) && old_tmd_path != new_tmd_path) {
|
||||||
FileSys::TitleMetadata old_tmd;
|
FileSys::TitleMetadata old_tmd;
|
||||||
FileSys::TitleMetadata new_tmd;
|
FileSys::TitleMetadata new_tmd;
|
||||||
@ -954,7 +1012,7 @@ bool TMDFile::Close() {
|
|||||||
void TMDFile::Flush() const {}
|
void TMDFile::Flush() const {}
|
||||||
|
|
||||||
Result TMDFile::Commit() {
|
Result TMDFile::Commit() {
|
||||||
return importing_title->cia_file.WriteTitleMetadata(data, 0);
|
return importing_title->cia_file.WriteTitleMetadata(data, 0).result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentFile::~ContentFile() {
|
ContentFile::~ContentFile() {
|
||||||
@ -1041,39 +1099,29 @@ InstallStatus InstallCIA(const std::string& path,
|
|||||||
}
|
}
|
||||||
installFile.Close();
|
installFile.Close();
|
||||||
|
|
||||||
LOG_INFO(Service_AM, "Installed {} successfully.", path);
|
InstallStatus install_res = InstallStatus::Success;
|
||||||
|
for (auto result : installFile.GetInstallResults()) {
|
||||||
const FileUtil::DirectoryEntryCallable callback =
|
if (result.type != CIAFile::InstallResult::Type::APP || result.result.IsError()) {
|
||||||
[&callback](u64* num_entries_out, const std::string& directory,
|
continue;
|
||||||
const std::string& virtual_name) -> bool {
|
}
|
||||||
const std::string physical_name = directory + DIR_SEP + virtual_name;
|
|
||||||
const bool is_dir = FileUtil::IsDirectory(physical_name);
|
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(result.install_full_path);
|
||||||
if (!is_dir) {
|
if (!loader) {
|
||||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
|
continue;
|
||||||
if (!loader) {
|
}
|
||||||
return true;
|
|
||||||
}
|
bool executable = false;
|
||||||
|
const auto res = loader->IsExecutable(executable);
|
||||||
bool executable = false;
|
if (res == Loader::ResultStatus::ErrorEncrypted) {
|
||||||
const auto res = loader->IsExecutable(executable);
|
LOG_ERROR(Service_AM, "CIA contains encrypted content: {}", path,
|
||||||
if (res == Loader::ResultStatus::ErrorEncrypted) {
|
result.install_full_path);
|
||||||
return false;
|
install_res = InstallStatus::ErrorEncrypted;
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return FileUtil::ForeachDirectoryEntry(nullptr, physical_name, callback);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if (!FileUtil::ForeachDirectoryEntry(
|
|
||||||
nullptr,
|
|
||||||
GetTitlePath(
|
|
||||||
Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID()),
|
|
||||||
container.GetTitleMetadata().GetTitleID()),
|
|
||||||
callback)) {
|
|
||||||
LOG_ERROR(Service_AM, "CIA {} contained encrypted files.", path);
|
|
||||||
return InstallStatus::ErrorEncrypted;
|
|
||||||
}
|
}
|
||||||
return InstallStatus::Success;
|
if (install_res == InstallStatus::Success) {
|
||||||
|
LOG_INFO(Service_AM, "Installed {} successfully.", path);
|
||||||
|
}
|
||||||
|
return install_res;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_ERROR(Service_AM, "CIA file {} is invalid!", path);
|
LOG_ERROR(Service_AM, "CIA file {} is invalid!", path);
|
||||||
|
|||||||
@ -170,13 +170,26 @@ void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ct
|
|||||||
// A file handled returned for CIAs to be written into and subsequently installed.
|
// A file handled returned for CIAs to be written into and subsequently installed.
|
||||||
class CIAFile final : public FileSys::FileBackend {
|
class CIAFile final : public FileSys::FileBackend {
|
||||||
public:
|
public:
|
||||||
|
class InstallResult {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
NONE,
|
||||||
|
TIK,
|
||||||
|
TMD,
|
||||||
|
APP,
|
||||||
|
};
|
||||||
|
Type type{Type::NONE};
|
||||||
|
std::string install_full_path{};
|
||||||
|
Result result{0};
|
||||||
|
};
|
||||||
|
|
||||||
explicit CIAFile(Core::System& system_, Service::FS::MediaType media_type,
|
explicit CIAFile(Core::System& system_, Service::FS::MediaType media_type,
|
||||||
bool from_cdn = false);
|
bool from_cdn = false);
|
||||||
~CIAFile();
|
~CIAFile();
|
||||||
|
|
||||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
Result WriteTicket();
|
InstallResult WriteTicket();
|
||||||
Result WriteTitleMetadata(std::span<const u8> tmd_data, std::size_t offset);
|
InstallResult WriteTitleMetadata(std::span<const u8> tmd_data, std::size_t offset);
|
||||||
ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer);
|
ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer);
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override;
|
const u8* buffer) override;
|
||||||
@ -202,6 +215,10 @@ public:
|
|||||||
is_done = true;
|
is_done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<InstallResult>& GetInstallResults() const {
|
||||||
|
return install_results;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ctx);
|
friend void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ctx);
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
@ -227,6 +244,8 @@ private:
|
|||||||
std::vector<std::string> content_file_paths;
|
std::vector<std::string> content_file_paths;
|
||||||
u16 current_content_index = -1;
|
u16 current_content_index = -1;
|
||||||
std::unique_ptr<NCCHCryptoFile> current_content_file;
|
std::unique_ptr<NCCHCryptoFile> current_content_file;
|
||||||
|
InstallResult current_content_install_result{};
|
||||||
|
std::vector<InstallResult> install_results;
|
||||||
Service::FS::MediaType media_type;
|
Service::FS::MediaType media_type;
|
||||||
|
|
||||||
class DecryptionState;
|
class DecryptionState;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user