forked from moonpower/azahar-UWP
Encrypt downloaded apps with a console unique key
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -174,7 +174,7 @@ static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Co
|
||||
u32 tmp = (first ? cheat_lines[state.current_line_nr].first
|
||||
: cheat_lines[state.current_line_nr].value) >>
|
||||
bit_offset;
|
||||
system.Memory().Write8(addr, tmp);
|
||||
system.Memory().Write8(addr, static_cast<u8>(tmp));
|
||||
addr += 1;
|
||||
num_bytes -= 1;
|
||||
bit_offset += 8;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "core/file_sys/patch.h"
|
||||
#include "core/file_sys/seed_db.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
#include "core/hw/unique_data.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -112,7 +113,7 @@ static bool LZSS_Decompress(std::span<const u8> compressed, std::span<u8> decomp
|
||||
|
||||
NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset, u32 partition)
|
||||
: ncch_offset(ncch_offset), partition(partition), filepath(filepath) {
|
||||
file = FileUtil::IOFile(filepath, "rb");
|
||||
file = std::make_unique<FileUtil::IOFile>(filepath, "rb");
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath_, u32 ncch_offset_,
|
||||
@@ -120,9 +121,9 @@ Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath_, u32 n
|
||||
filepath = filepath_;
|
||||
ncch_offset = ncch_offset_;
|
||||
partition = partition_;
|
||||
file = FileUtil::IOFile(filepath_, "rb");
|
||||
file = std::make_unique<FileUtil::IOFile>(filepath_, "rb");
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
if (!file->IsOpen()) {
|
||||
LOG_WARNING(Service_FS, "Failed to open {}", filepath);
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
@@ -135,33 +136,47 @@ Loader::ResultStatus NCCHContainer::LoadHeader() {
|
||||
if (has_header) {
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
if (!file.IsOpen()) {
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!file->IsOpen()) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file->Seek(ncch_offset, SEEK_SET);
|
||||
|
||||
if (file->ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
|
||||
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
|
||||
NCSD_Header ncsd_header;
|
||||
file->Seek(ncch_offset, SEEK_SET);
|
||||
file->ReadBytes(&ncsd_header, sizeof(NCSD_Header));
|
||||
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
|
||||
ASSERT(partition < 8);
|
||||
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
|
||||
LOG_ERROR(Service_FS, "{}", ncch_offset);
|
||||
file->Seek(ncch_offset, SEEK_SET);
|
||||
file->ReadBytes(&ncch_header, sizeof(NCCH_Header));
|
||||
}
|
||||
|
||||
// Verify we are loading the correct file type...
|
||||
if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) {
|
||||
// We may be loading a crypto file, try again
|
||||
if (i == 0) {
|
||||
file.reset();
|
||||
file = HW::UniqueData::OpenUniqueCryptoFile(
|
||||
filepath, "rb", HW::UniqueData::UniqueCryptoFileID::NCCH);
|
||||
} else {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
|
||||
if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
|
||||
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
|
||||
NCSD_Header ncsd_header;
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncsd_header, sizeof(NCSD_Header));
|
||||
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
|
||||
ASSERT(partition < 8);
|
||||
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
|
||||
LOG_ERROR(Service_FS, "{}", ncch_offset);
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
|
||||
}
|
||||
|
||||
// Verify we are loading the correct file type...
|
||||
if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
if (file->IsCrypto()) {
|
||||
LOG_DEBUG(Service_FS, "NCCH file has console unique crypto");
|
||||
}
|
||||
|
||||
has_header = true;
|
||||
@@ -174,30 +189,45 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
|
||||
int block_size = kBlockSize;
|
||||
|
||||
if (file.IsOpen()) {
|
||||
size_t file_size = file.GetSize();
|
||||
if (file->IsOpen()) {
|
||||
size_t file_size;
|
||||
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
file_size = file->GetSize();
|
||||
|
||||
if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file->Seek(ncch_offset, SEEK_SET);
|
||||
|
||||
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
|
||||
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
|
||||
NCSD_Header ncsd_header;
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncsd_header, sizeof(NCSD_Header));
|
||||
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
|
||||
ASSERT(partition < 8);
|
||||
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
|
||||
if (file->ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
|
||||
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
|
||||
NCSD_Header ncsd_header;
|
||||
file->Seek(ncch_offset, SEEK_SET);
|
||||
file->ReadBytes(&ncsd_header, sizeof(NCSD_Header));
|
||||
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
|
||||
ASSERT(partition < 8);
|
||||
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
|
||||
file->Seek(ncch_offset, SEEK_SET);
|
||||
file->ReadBytes(&ncch_header, sizeof(NCCH_Header));
|
||||
}
|
||||
|
||||
// Verify we are loading the correct file type...
|
||||
if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) {
|
||||
// We may be loading a crypto file, try again
|
||||
if (i == 0) {
|
||||
file = HW::UniqueData::OpenUniqueCryptoFile(
|
||||
filepath, "rb", HW::UniqueData::UniqueCryptoFileID::NCCH);
|
||||
} else {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify we are loading the correct file type...
|
||||
if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic)
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
if (file->IsCrypto()) {
|
||||
LOG_DEBUG(Service_FS, "NCCH file has console unique crypto");
|
||||
}
|
||||
|
||||
has_header = true;
|
||||
|
||||
@@ -215,12 +245,12 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
// System archives and DLC don't have an extended header but have RomFS
|
||||
// Proto apps don't have an ext header size
|
||||
if (ncch_header.extended_header_size || is_proto) {
|
||||
auto read_exheader = [this](FileUtil::IOFile& file) {
|
||||
auto read_exheader = [this](FileUtil::IOFile* file) {
|
||||
const std::size_t size = sizeof(exheader_header);
|
||||
return file && file.ReadBytes(&exheader_header, size) == size;
|
||||
return file && file->ReadBytes(&exheader_header, size) == size;
|
||||
};
|
||||
|
||||
if (!read_exheader(file)) {
|
||||
if (!read_exheader(file.get())) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
@@ -235,7 +265,7 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
bool has_exheader_override = false;
|
||||
for (const auto& path : exheader_override_paths) {
|
||||
FileUtil::IOFile exheader_override_file{path, "rb"};
|
||||
if (read_exheader(exheader_override_file)) {
|
||||
if (read_exheader(&exheader_override_file)) {
|
||||
has_exheader_override = true;
|
||||
break;
|
||||
}
|
||||
@@ -289,11 +319,17 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
LOG_DEBUG(Service_FS, "ExeFS offset: 0x{:08X}", exefs_offset);
|
||||
LOG_DEBUG(Service_FS, "ExeFS size: 0x{:08X}", exefs_size);
|
||||
|
||||
file.Seek(exefs_offset + ncch_offset, SEEK_SET);
|
||||
if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
|
||||
file->Seek(exefs_offset + ncch_offset, SEEK_SET);
|
||||
if (file->ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
exefs_file = FileUtil::IOFile(filepath, "rb");
|
||||
if (file->IsCrypto()) {
|
||||
exefs_file = HW::UniqueData::OpenUniqueCryptoFile(
|
||||
filepath, "rb", HW::UniqueData::UniqueCryptoFileID::NCCH);
|
||||
} else {
|
||||
exefs_file = std::make_unique<FileUtil::IOFile>(filepath, "rb");
|
||||
}
|
||||
|
||||
has_exefs = true;
|
||||
}
|
||||
|
||||
@@ -322,15 +358,15 @@ Loader::ResultStatus NCCHContainer::LoadOverrides() {
|
||||
std::string exefs_override = filepath + ".exefs";
|
||||
std::string exefsdir_override = filepath + ".exefsdir/";
|
||||
if (FileUtil::Exists(exefs_override)) {
|
||||
exefs_file = FileUtil::IOFile(exefs_override, "rb");
|
||||
exefs_file = std::make_unique<FileUtil::IOFile>(exefs_override, "rb");
|
||||
|
||||
if (exefs_file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) == sizeof(ExeFs_Header)) {
|
||||
if (exefs_file->ReadBytes(&exefs_header, sizeof(ExeFs_Header)) == sizeof(ExeFs_Header)) {
|
||||
LOG_DEBUG(Service_FS, "Loading ExeFS section from {}", exefs_override);
|
||||
exefs_offset = 0;
|
||||
is_tainted = true;
|
||||
has_exefs = true;
|
||||
} else {
|
||||
exefs_file = FileUtil::IOFile(filepath, "rb");
|
||||
exefs_file = std::make_unique<FileUtil::IOFile>(filepath, "rb");
|
||||
}
|
||||
} else if (FileUtil::Exists(exefsdir_override) && FileUtil::IsDirectory(exefsdir_override)) {
|
||||
is_tainted = true;
|
||||
@@ -385,9 +421,9 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
|
||||
std::size_t logo_size = ncch_header.logo_region_size * block_size;
|
||||
|
||||
buffer.resize(logo_size);
|
||||
file.Seek(ncch_offset + logo_offset, SEEK_SET);
|
||||
file->Seek(ncch_offset + logo_offset, SEEK_SET);
|
||||
|
||||
if (file.ReadBytes(buffer.data(), logo_size) != logo_size) {
|
||||
if (file->ReadBytes(buffer.data(), logo_size) != logo_size) {
|
||||
LOG_ERROR(Service_FS, "Could not read NCCH logo");
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
@@ -398,7 +434,7 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
|
||||
}
|
||||
|
||||
// If we don't have any separate files, we'll need a full ExeFS
|
||||
if (!exefs_file.IsOpen())
|
||||
if (!exefs_file->IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
LOG_DEBUG(Service_FS, "{} sections:", kMaxSections);
|
||||
@@ -414,14 +450,14 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
|
||||
s64 section_offset =
|
||||
is_proto ? section.offset
|
||||
: (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset);
|
||||
exefs_file.Seek(section_offset, SEEK_SET);
|
||||
exefs_file->Seek(section_offset, SEEK_SET);
|
||||
|
||||
size_t section_size = is_proto ? Common::AlignUp(section.size, 0x10) : section.size;
|
||||
|
||||
if (strcmp(section.name, ".code") == 0 && is_compressed) {
|
||||
// Section is compressed, read compressed .code section...
|
||||
std::vector<u8> temp_buffer(section_size);
|
||||
if (exefs_file.ReadBytes(temp_buffer.data(), temp_buffer.size()) !=
|
||||
if (exefs_file->ReadBytes(temp_buffer.data(), temp_buffer.size()) !=
|
||||
temp_buffer.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
@@ -433,7 +469,7 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
|
||||
} else {
|
||||
// Section is uncompressed...
|
||||
buffer.resize(section_size);
|
||||
if (exefs_file.ReadBytes(buffer.data(), section_size) != section_size)
|
||||
if (exefs_file->ReadBytes(buffer.data(), section_size) != section_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
@@ -552,7 +588,7 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
|
||||
return Loader::ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
if (!file.IsOpen())
|
||||
if (!file->IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * block_size) + 0x1000;
|
||||
@@ -561,17 +597,22 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
|
||||
LOG_DEBUG(Service_FS, "RomFS offset: 0x{:08X}", romfs_offset);
|
||||
LOG_DEBUG(Service_FS, "RomFS size: 0x{:08X}", romfs_size);
|
||||
|
||||
if (file.GetSize() < romfs_offset + romfs_size)
|
||||
if (file->GetSize() < romfs_offset + romfs_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
// We reopen the file, to allow its position to be independent from file's
|
||||
FileUtil::IOFile romfs_file_inner(filepath, "rb");
|
||||
if (!romfs_file_inner.IsOpen())
|
||||
std::unique_ptr<FileUtil::IOFile> romfs_file_inner;
|
||||
if (file->IsCrypto()) {
|
||||
romfs_file_inner = HW::UniqueData::OpenUniqueCryptoFile(
|
||||
filepath, "rb", HW::UniqueData::UniqueCryptoFileID::NCCH);
|
||||
} else {
|
||||
romfs_file_inner = std::make_unique<FileUtil::IOFile>(filepath, "rb");
|
||||
}
|
||||
|
||||
if (!romfs_file_inner->IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
std::shared_ptr<RomFSReader> direct_romfs;
|
||||
|
||||
direct_romfs =
|
||||
std::shared_ptr<RomFSReader> direct_romfs =
|
||||
std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset, romfs_size);
|
||||
|
||||
const auto path =
|
||||
@@ -590,6 +631,9 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCCHContainer::DumpRomFS(const std::string& target_path) {
|
||||
if (file->IsCrypto())
|
||||
return Loader::ResultStatus::ErrorEncrypted;
|
||||
|
||||
std::shared_ptr<RomFSReader> direct_romfs;
|
||||
Loader::ResultStatus result = ReadRomFS(direct_romfs, false);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
@@ -608,12 +652,13 @@ Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReade
|
||||
// Check for RomFS overrides
|
||||
std::string split_filepath = filepath + ".romfs";
|
||||
if (FileUtil::Exists(split_filepath)) {
|
||||
FileUtil::IOFile romfs_file_inner(split_filepath, "rb");
|
||||
if (romfs_file_inner.IsOpen()) {
|
||||
std::unique_ptr<FileUtil::IOFile> romfs_file_inner =
|
||||
std::make_unique<FileUtil::IOFile>(split_filepath, "rb");
|
||||
if (romfs_file_inner->IsOpen()) {
|
||||
LOG_WARNING(Service_FS, "File {} overriding built-in RomFS; LayeredFS not enabled",
|
||||
split_filepath);
|
||||
romfs_file = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), 0,
|
||||
romfs_file_inner.GetSize());
|
||||
romfs_file_inner->GetSize());
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -353,8 +353,8 @@ private:
|
||||
u32 partition = 0;
|
||||
|
||||
std::string filepath;
|
||||
FileUtil::IOFile file;
|
||||
FileUtil::IOFile exefs_file;
|
||||
std::unique_ptr<FileUtil::IOFile> file;
|
||||
std::unique_ptr<FileUtil::IOFile> exefs_file;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cryptopp/aes.h>
|
||||
@@ -24,7 +28,7 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
|
||||
|
||||
// Skip cache if the read is too big
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
length = file.ReadAtBytes(buffer, length, file_offset + offset);
|
||||
length = file->ReadAtBytes(buffer, length, file_offset + offset);
|
||||
LOG_TRACE(Service_FS, "RomFS Cache SKIP: offset={}, length={}", offset, length);
|
||||
return length;
|
||||
}
|
||||
@@ -38,7 +42,7 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
|
||||
auto cache_entry = cache.request(page);
|
||||
if (!cache_entry.first) {
|
||||
// If not found, read from disk and cache the data
|
||||
read_size = file.ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page);
|
||||
read_size = file->ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page);
|
||||
LOG_TRACE(Service_FS, "RomFS Cache MISS: page={}, length={}, into={}", page, seg.second,
|
||||
(seg.first - page));
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
@@ -41,7 +45,8 @@ private:
|
||||
*/
|
||||
class DirectRomFSReader : public RomFSReader {
|
||||
public:
|
||||
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size)
|
||||
DirectRomFSReader(std::unique_ptr<FileUtil::IOFile>&& file, std::size_t file_offset,
|
||||
std::size_t data_size)
|
||||
: file(std::move(file)), file_offset(file_offset), data_size(data_size) {}
|
||||
|
||||
~DirectRomFSReader() override = default;
|
||||
@@ -57,7 +62,7 @@ public:
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override;
|
||||
|
||||
private:
|
||||
FileUtil::IOFile file;
|
||||
std::unique_ptr<FileUtil::IOFile> file;
|
||||
u64 file_offset;
|
||||
u64 data_size;
|
||||
|
||||
|
||||
@@ -95,13 +95,24 @@ public:
|
||||
};
|
||||
|
||||
NCCHCryptoFile::NCCHCryptoFile(const std::string& out_file) {
|
||||
file = FileUtil::IOFile(out_file, "wb");
|
||||
// A console unique crypto file is used to store the decrypted NCCH file. This is done
|
||||
// to prevent Azahar being used as a tool to download easy shareable decrypted contents
|
||||
// from the eshop.
|
||||
file = HW::UniqueData::OpenUniqueCryptoFile(out_file, "wb",
|
||||
HW::UniqueData::UniqueCryptoFileID::NCCH);
|
||||
if (!file->IsOpen()) {
|
||||
is_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NCCHCryptoFile::Write(const u8* buffer, std::size_t length) {
|
||||
if (is_error)
|
||||
return;
|
||||
|
||||
if (is_not_ncch) {
|
||||
file->WriteBytes(buffer, length);
|
||||
}
|
||||
|
||||
const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes)
|
||||
|
||||
if (header_size != sizeof(NCCH_Header)) {
|
||||
@@ -114,7 +125,10 @@ void NCCHCryptoFile::Write(const u8* buffer, std::size_t length) {
|
||||
|
||||
if (!header_parsed && header_size == sizeof(NCCH_Header)) {
|
||||
if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) {
|
||||
is_error = true;
|
||||
// Most likely DS contents, store without additional operations
|
||||
is_not_ncch = true;
|
||||
file->WriteBytes(&ncch_header, sizeof(ncch_header));
|
||||
file->WriteBytes(buffer, length);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,7 +292,7 @@ void NCCHCryptoFile::Write(const u8* buffer, std::size_t length) {
|
||||
|
||||
u8 prev_crypto = ncch_header.no_crypto;
|
||||
ncch_header.no_crypto.Assign(1);
|
||||
file.WriteBytes(&ncch_header, sizeof(ncch_header));
|
||||
file->WriteBytes(&ncch_header, sizeof(ncch_header));
|
||||
written += sizeof(ncch_header);
|
||||
ncch_header.no_crypto.Assign(prev_crypto);
|
||||
}
|
||||
@@ -305,7 +319,7 @@ void NCCHCryptoFile::Write(const u8* buffer, std::size_t length) {
|
||||
if (reg == nullptr) {
|
||||
// This file has no encryption
|
||||
size_t to_write = length;
|
||||
file.WriteBytes(buffer, to_write);
|
||||
file->WriteBytes(buffer, to_write);
|
||||
written += to_write;
|
||||
buffer += to_write;
|
||||
length -= to_write;
|
||||
@@ -313,7 +327,7 @@ void NCCHCryptoFile::Write(const u8* buffer, std::size_t length) {
|
||||
if (written < reg->offset) {
|
||||
// Not inside a crypto region
|
||||
size_t to_write = std::min(length, reg->offset - written);
|
||||
file.WriteBytes(buffer, to_write);
|
||||
file->WriteBytes(buffer, to_write);
|
||||
written += to_write;
|
||||
buffer += to_write;
|
||||
length -= to_write;
|
||||
@@ -347,7 +361,7 @@ void NCCHCryptoFile::Write(const u8* buffer, std::size_t length) {
|
||||
d.Seek(offset);
|
||||
}
|
||||
d.ProcessData(temp.data(), buffer, to_write);
|
||||
file.WriteBytes(temp.data(), to_write);
|
||||
file->WriteBytes(temp.data(), to_write);
|
||||
|
||||
if (reg->type == CryptoRegion::EXEFS_HDR) {
|
||||
if (exefs_header_written != sizeof(ExeFs_Header)) {
|
||||
@@ -376,7 +390,7 @@ void NCCHCryptoFile::Write(const u8* buffer, std::size_t length) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
file.WriteBytes(buffer, to_write);
|
||||
file->WriteBytes(buffer, to_write);
|
||||
}
|
||||
written += to_write;
|
||||
buffer += to_write;
|
||||
@@ -538,7 +552,6 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
||||
// has been written since we might get a written buffer which contains multiple .app
|
||||
// contents or only part of a larger .app's contents.
|
||||
const u64 offset_max = offset + length;
|
||||
bool success = true;
|
||||
for (std::size_t i = 0; i < content_written.size(); i++) {
|
||||
if (content_written[i] < container.GetContentSize(i)) {
|
||||
// The size, minimum unwritten offset, and maximum unwritten offset of this content
|
||||
@@ -573,8 +586,11 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
||||
}
|
||||
|
||||
file.Write(temp.data(), temp.size());
|
||||
if (file.IsError())
|
||||
success = false;
|
||||
if (file.IsError()) {
|
||||
// This can never happen in real HW
|
||||
return Result(ErrCodes::InvalidImportState, ErrorModule::AM,
|
||||
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||
}
|
||||
|
||||
// Keep tabs on how much of this content ID has been written so new range_min
|
||||
// values can be calculated.
|
||||
@@ -584,7 +600,7 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
||||
}
|
||||
}
|
||||
|
||||
return success ? length : 0;
|
||||
return length;
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush,
|
||||
@@ -705,13 +721,17 @@ ResultVal<std::size_t> CIAFile::WriteContentDataIndexed(u16 content_index, u64 o
|
||||
}
|
||||
|
||||
file.Write(temp.data(), temp.size());
|
||||
bool success = !file.IsError();
|
||||
if (file.IsError()) {
|
||||
// This can never happen in real HW
|
||||
return Result(ErrCodes::InvalidImportState, ErrorModule::AM, ErrorSummary::InvalidState,
|
||||
ErrorLevel::Permanent);
|
||||
}
|
||||
|
||||
content_written[content_index] += temp.size();
|
||||
LOG_DEBUG(Service_AM, "Wrote {} to content {}, total {}", temp.size(), content_index,
|
||||
content_written[content_index]);
|
||||
|
||||
return success ? temp.size() : 0;
|
||||
return temp.size();
|
||||
}
|
||||
|
||||
u64 CIAFile::GetSize() const {
|
||||
@@ -2313,8 +2333,6 @@ void Module::Interface::GetNumImportTitleContextsImpl(IPC::RequestParser& rp,
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
// TODO: Make this actually do something:
|
||||
/*
|
||||
u32 count = 0;
|
||||
for (auto it = am->import_title_contexts.begin(); it != am->import_title_contexts.end(); it++) {
|
||||
if ((include_installing &&
|
||||
@@ -2325,9 +2343,8 @@ void Module::Interface::GetNumImportTitleContextsImpl(IPC::RequestParser& rp,
|
||||
count++;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
rb.Push<u32>(static_cast<u32>(am->import_title_contexts.size()));
|
||||
rb.Push<u32>(count);
|
||||
}
|
||||
|
||||
void Module::Interface::GetImportTitleContextListImpl(IPC::RequestParser& rp,
|
||||
@@ -3672,6 +3689,8 @@ void Module::Interface::Sign(Kernel::HLERequestContext& ctx) {
|
||||
template <class Archive>
|
||||
void Module::serialize(Archive& ar, const unsigned int) {
|
||||
ar & cia_installing;
|
||||
ar & force_old_device_id;
|
||||
ar & force_new_device_id;
|
||||
ar & am_title_list;
|
||||
ar & system_updater_mutex;
|
||||
}
|
||||
|
||||
@@ -121,8 +121,9 @@ public:
|
||||
|
||||
private:
|
||||
friend class CIAFile;
|
||||
FileUtil::IOFile file;
|
||||
std::unique_ptr<FileUtil::IOFile> file;
|
||||
bool is_error = false;
|
||||
bool is_not_ncch = false;
|
||||
bool decryption_authorized = false;
|
||||
|
||||
std::size_t written = 0;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cryptopp/sha.h>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/certificate.h"
|
||||
@@ -105,6 +106,7 @@ SecureDataLoadStatus LoadOTP() {
|
||||
|
||||
const std::string filepath = GetOTPPath();
|
||||
|
||||
HW::AES::InitKeys();
|
||||
auto otp_keyiv = HW::AES::GetOTPKeyIV();
|
||||
|
||||
auto loader_status = otp.Load(filepath, otp_keyiv.first, otp_keyiv.second);
|
||||
@@ -226,4 +228,34 @@ void InvalidateSecureData() {
|
||||
movable.Invalidate();
|
||||
}
|
||||
|
||||
std::unique_ptr<FileUtil::IOFile> OpenUniqueCryptoFile(const std::string& filename,
|
||||
const char openmode[], UniqueCryptoFileID id,
|
||||
int flags) {
|
||||
LoadOTP();
|
||||
|
||||
if (!ct_cert.IsValid() || !otp.Valid()) {
|
||||
return std::make_unique<FileUtil::IOFile>();
|
||||
}
|
||||
|
||||
struct {
|
||||
ECC::PublicKey pkey;
|
||||
u32 device_id;
|
||||
u32 id;
|
||||
} hash_data;
|
||||
hash_data.pkey = ct_cert.GetPublicKeyECC();
|
||||
hash_data.device_id = otp.GetDeviceID();
|
||||
hash_data.id = static_cast<u32>(id);
|
||||
|
||||
CryptoPP::SHA256 hash;
|
||||
u8 digest[CryptoPP::SHA256::DIGESTSIZE];
|
||||
hash.CalculateDigest(digest, reinterpret_cast<CryptoPP::byte*>(&hash_data), sizeof(hash_data));
|
||||
|
||||
std::vector<u8> key(0x10);
|
||||
std::vector<u8> ctr(0x10);
|
||||
memcpy(key.data(), digest, 0x10);
|
||||
memcpy(ctr.data(), digest + 0x10, 12);
|
||||
|
||||
return std::make_unique<FileUtil::CryptoIOFile>(filename, openmode, key, ctr, flags);
|
||||
}
|
||||
|
||||
} // namespace HW::UniqueData
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Azahar Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
|
||||
namespace FileSys {
|
||||
class Certificate;
|
||||
@@ -129,5 +130,13 @@ FileSys::Certificate& GetCTCert();
|
||||
FileSys::OTP& GetOTP();
|
||||
MovableSedFull& GetMovableSed();
|
||||
|
||||
enum class UniqueCryptoFileID {
|
||||
NCCH = 0,
|
||||
};
|
||||
|
||||
void InvalidateSecureData();
|
||||
|
||||
std::unique_ptr<FileUtil::IOFile> OpenUniqueCryptoFile(const std::string& filename,
|
||||
const char openmode[], UniqueCryptoFileID id,
|
||||
int flags = 0);
|
||||
} // namespace HW::UniqueData
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -314,8 +314,9 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>
|
||||
LOG_DEBUG(Loader, "RomFS size: {:#010X}", romfs_size);
|
||||
|
||||
// We reopen the file, to allow its position to be independent from file's
|
||||
FileUtil::IOFile romfs_file_inner(filepath, "rb");
|
||||
if (!romfs_file_inner.IsOpen())
|
||||
std::unique_ptr<FileUtil::IOFile> romfs_file_inner =
|
||||
std::make_unique<FileUtil::IOFile>(filepath, "rb");
|
||||
if (!romfs_file_inner->IsOpen())
|
||||
return ResultStatus::Error;
|
||||
|
||||
romfs_file = std::make_shared<FileSys::DirectRomFSReader>(std::move(romfs_file_inner),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/fs/fs_user.h"
|
||||
#include "core/hw/unique_data.h"
|
||||
#include "core/loader/ncch.h"
|
||||
#include "core/loader/smdh.h"
|
||||
#include "core/memory.h"
|
||||
@@ -45,6 +46,19 @@ FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) {
|
||||
if (MakeMagic('N', 'C', 'C', 'H') == magic)
|
||||
return FileType::CXI;
|
||||
|
||||
std::unique_ptr<FileUtil::IOFile> file_crypto = HW::UniqueData::OpenUniqueCryptoFile(
|
||||
file.Filename(), "rb", HW::UniqueData::UniqueCryptoFileID::NCCH);
|
||||
|
||||
file_crypto->Seek(0x100, SEEK_SET);
|
||||
if (1 != file_crypto->ReadArray<u32>(&magic, 1))
|
||||
return FileType::Error;
|
||||
|
||||
if (MakeMagic('N', 'C', 'S', 'D') == magic)
|
||||
return FileType::CCI;
|
||||
|
||||
if (MakeMagic('N', 'C', 'C', 'H') == magic)
|
||||
return FileType::CXI;
|
||||
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user