|
|
|
@ -6,6 +6,8 @@
|
|
|
|
|
#include <cinttypes>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <cryptopp/aes.h>
|
|
|
|
|
#include <cryptopp/modes.h>
|
|
|
|
|
#include <fmt/format.h>
|
|
|
|
|
#include "common/file_util.h"
|
|
|
|
|
#include "common/logging/log.h"
|
|
|
|
@ -74,13 +76,31 @@ struct TicketInfo {
|
|
|
|
|
|
|
|
|
|
static_assert(sizeof(TicketInfo) == 0x18, "Ticket info structure size is wrong");
|
|
|
|
|
|
|
|
|
|
class CIAFile::DecryptionState {
|
|
|
|
|
public:
|
|
|
|
|
std::vector<CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption> content;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
CIAFile::CIAFile(Service::FS::MediaType media_type)
|
|
|
|
|
: media_type(media_type), decryption_state(std::make_unique<DecryptionState>()) {}
|
|
|
|
|
|
|
|
|
|
CIAFile::~CIAFile() {
|
|
|
|
|
Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResultVal<std::size_t> CIAFile::Read(u64 offset, std::size_t length, u8* buffer) const {
|
|
|
|
|
UNIMPLEMENTED();
|
|
|
|
|
return MakeResult<std::size_t>(length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResultVal<std::size_t> CIAFile::WriteTitleMetadata(u64 offset, std::size_t length,
|
|
|
|
|
const u8* buffer) {
|
|
|
|
|
ResultCode CIAFile::WriteTicket() {
|
|
|
|
|
container.LoadTicket(data, container.GetTicketOffset());
|
|
|
|
|
|
|
|
|
|
install_state = CIAInstallState::TicketLoaded;
|
|
|
|
|
return RESULT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResultCode CIAFile::WriteTitleMetadata() {
|
|
|
|
|
container.LoadTitleMetadata(data, container.GetTitleMetadataOffset());
|
|
|
|
|
FileSys::TitleMetadata tmd = container.GetTitleMetadata();
|
|
|
|
|
tmd.Print();
|
|
|
|
@ -109,10 +129,22 @@ ResultVal<std::size_t> CIAFile::WriteTitleMetadata(u64 offset, std::size_t lengt
|
|
|
|
|
&app_folder, nullptr, nullptr);
|
|
|
|
|
FileUtil::CreateFullPath(app_folder);
|
|
|
|
|
|
|
|
|
|
content_written.resize(container.GetTitleMetadata().GetContentCount());
|
|
|
|
|
auto content_count = container.GetTitleMetadata().GetContentCount();
|
|
|
|
|
content_written.resize(content_count);
|
|
|
|
|
|
|
|
|
|
auto title_key = container.GetTicket().GetTitleKey();
|
|
|
|
|
if (title_key) {
|
|
|
|
|
decryption_state->content.resize(content_count);
|
|
|
|
|
for (std::size_t i = 0; i < content_count; ++i) {
|
|
|
|
|
auto ctr = tmd.GetContentCTRByIndex(i);
|
|
|
|
|
decryption_state->content[i].SetKeyWithIV(title_key->data(), title_key->size(),
|
|
|
|
|
ctr.data());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
install_state = CIAInstallState::TMDLoaded;
|
|
|
|
|
|
|
|
|
|
return MakeResult<std::size_t>(length);
|
|
|
|
|
return RESULT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length, const u8* buffer) {
|
|
|
|
@ -144,7 +176,15 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
|
|
|
|
if (!file.IsOpen())
|
|
|
|
|
return FileSys::ERROR_INSUFFICIENT_SPACE;
|
|
|
|
|
|
|
|
|
|
file.WriteBytes(buffer + (range_min - offset), available_to_write);
|
|
|
|
|
std::vector<u8> temp(buffer + (range_min - offset),
|
|
|
|
|
buffer + (range_min - offset) + available_to_write);
|
|
|
|
|
|
|
|
|
|
if (tmd.GetContentTypeByIndex(static_cast<u16>(i)) &
|
|
|
|
|
FileSys::TMDContentTypeFlag::Encrypted) {
|
|
|
|
|
decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file.WriteBytes(temp.data(), temp.size());
|
|
|
|
|
|
|
|
|
|
// Keep tabs on how much of this content ID has been written so new range_min
|
|
|
|
|
// values can be calculated.
|
|
|
|
@ -207,8 +247,12 @@ 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
|
|
|
|
|
// buffered before trying to parse.
|
|
|
|
|
if (written >= container.GetContentOffset() && install_state != CIAInstallState::TMDLoaded) {
|
|
|
|
|
auto result = WriteTitleMetadata(offset, length, buffer);
|
|
|
|
|
if (result.Failed())
|
|
|
|
|
auto result = WriteTicket();
|
|
|
|
|
if (result.IsError())
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
result = WriteTitleMetadata();
|
|
|
|
|
if (result.IsError())
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -296,9 +340,12 @@ InstallStatus InstallCIA(const std::string& path,
|
|
|
|
|
Service::AM::CIAFile installFile(
|
|
|
|
|
Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID()));
|
|
|
|
|
|
|
|
|
|
bool title_key_available = container.GetTicket().GetTitleKey().is_initialized();
|
|
|
|
|
|
|
|
|
|
for (std::size_t i = 0; i < container.GetTitleMetadata().GetContentCount(); i++) {
|
|
|
|
|
if (container.GetTitleMetadata().GetContentTypeByIndex(static_cast<u16>(i)) &
|
|
|
|
|
FileSys::TMDContentTypeFlag::Encrypted) {
|
|
|
|
|
if ((container.GetTitleMetadata().GetContentTypeByIndex(static_cast<u16>(i)) &
|
|
|
|
|
FileSys::TMDContentTypeFlag::Encrypted) &&
|
|
|
|
|
!title_key_available) {
|
|
|
|
|
LOG_ERROR(Service_AM, "File {} is encrypted! Aborting...", path);
|
|
|
|
|
return InstallStatus::ErrorEncrypted;
|
|
|
|
|
}
|
|
|
|
|