mirror of
https://github.com/Atmosphere-NX/hac2l.git
synced 2025-06-20 18:52:39 +02:00
hac2l: auto-load common keys from tickets inside app-fs, support pfs0/nsp as exefs-or-appfs
This commit is contained in:
parent
1c41ca7fc6
commit
60db38430a
@ -112,6 +112,8 @@ namespace ams::hactool {
|
||||
options.file_type = FileType::Xci;
|
||||
} else if (std::strcmp(arg, "appfs") == 0) {
|
||||
options.file_type = FileType::AppFs;
|
||||
} else if (std::strcmp(arg, "pfs") == 0 || std::strcmp(arg, "pfs0") == 0 || std::strcmp(arg, "nsp") == 0) {
|
||||
options.file_type = FileType::Pfs;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -28,6 +28,109 @@ namespace ams::hactool {
|
||||
|
||||
constexpr const char ContentMetaFileNameExtension[] = ".cnmt";
|
||||
|
||||
constexpr const char TicketFileNameExtension[] = ".tik";
|
||||
|
||||
struct alignas(4) CommonTicketData {
|
||||
u32 signature_type;
|
||||
u8 signature_data[0x100];
|
||||
u8 padding0[0x3C];
|
||||
char issuer[0x40];
|
||||
u8 title_key_block[0x100];
|
||||
u8 format_version;
|
||||
u8 titlekey_type;
|
||||
u16 ticket_version;
|
||||
u8 license_type;
|
||||
u8 key_generation;
|
||||
u16 property_mask;
|
||||
u8 reserved[8];
|
||||
u8 ticket_id[8];
|
||||
u8 device_id[8];
|
||||
u8 rights_id[0x10];
|
||||
u8 account_id[0x4];
|
||||
u32 total_section_size;
|
||||
u32 section_header_offset;
|
||||
u16 section_header_count;
|
||||
u16 section_header_entry_size;
|
||||
};
|
||||
static_assert(util::is_pod<CommonTicketData>::value);
|
||||
static_assert(sizeof(CommonTicketData) == 0x2C0);
|
||||
|
||||
bool IsValidCommonTicketFormat(const void *data, size_t size) {
|
||||
/* Check that the data is the right size for a ticket. */
|
||||
if (size != sizeof(CommonTicketData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check the ticket. */
|
||||
const auto &ticket = *static_cast<const CommonTicketData *>(data);
|
||||
|
||||
/* Check that the ticket is an aes key. */
|
||||
if (ticket.titlekey_type != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check that the ticket's rights id isn't all-zero. */
|
||||
size_t i;
|
||||
for (i = 0; i < util::size(ticket.rights_id); ++i) {
|
||||
if (ticket.rights_id[i] != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == util::size(ticket.rights_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check that the ticket is a proper aes-key. */
|
||||
for (i = 0; i < sizeof(spl::AesKey); ++i) {
|
||||
if (ticket.title_key_block[i] != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == sizeof(spl::AesKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = sizeof(spl::AesKey); i < util::size(ticket.title_key_block); ++i) {
|
||||
if (ticket.title_key_block[i] != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i != util::size(ticket.title_key_block)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check that the ticket's section header is proper. */
|
||||
if (ticket.section_header_offset != sizeof(CommonTicketData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ticket is good enough. */
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryLoadKeyFromCommonTicket(fssrv::impl::ExternalKeyManager &km, const void *data, size_t size) {
|
||||
if (IsValidCommonTicketFormat(data, size)) {
|
||||
/* Get the ticket. */
|
||||
const auto &ticket = *static_cast<const CommonTicketData *>(data);
|
||||
|
||||
/* Decode the rights id. */
|
||||
fs::RightsId rights_id = {};
|
||||
std::memcpy(std::addressof(rights_id), ticket.rights_id, sizeof(rights_id));
|
||||
|
||||
/* Decode the key. */
|
||||
spl::AccessKey access_key = {};
|
||||
std::memcpy(std::addressof(access_key), ticket.title_key_block, sizeof(access_key));
|
||||
|
||||
/* Register with the key manager. */
|
||||
km.Register(rights_id, access_key);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Result ReadContentMetaFile(std::unique_ptr<u8[]> *out, size_t *out_size, std::shared_ptr<fs::fsa::IFileSystem> &fs) {
|
||||
bool found = false;
|
||||
R_RETURN(fssystem::IterateDirectoryRecursively(fs.get(),
|
||||
@ -69,9 +172,9 @@ namespace ams::hactool {
|
||||
|
||||
}
|
||||
|
||||
Result Processor::ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemCtx *ctx) {
|
||||
Result Processor::ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemContext *ctx) {
|
||||
/* Ensure we have a context. */
|
||||
ProcessAsApplicationFileSystemCtx local_ctx{};
|
||||
ProcessAsApplicationFileSystemContext local_ctx{};
|
||||
if (ctx == nullptr) {
|
||||
ctx = std::addressof(local_ctx);
|
||||
}
|
||||
@ -86,6 +189,33 @@ namespace ams::hactool {
|
||||
[&] (const fs::Path &, const fs::DirectoryEntry &) -> Result { R_SUCCEED(); },
|
||||
[&] (const fs::Path &, const fs::DirectoryEntry &) -> Result { R_SUCCEED(); },
|
||||
[&] (const fs::Path &path, const fs::DirectoryEntry &entry) -> Result {
|
||||
/* If the path is a ticket, try to load it. */
|
||||
if (PathView(entry.name).HasSuffix(TicketFileNameExtension)) {
|
||||
std::shared_ptr<fs::IStorage> tik_storage;
|
||||
if (const auto res = OpenFileStorage(std::addressof(tik_storage), ctx->fs, path.GetString()); R_SUCCEEDED(res)) {
|
||||
/* Get ticket size. */
|
||||
s64 tik_size = -1;
|
||||
if (const auto res = tik_storage->GetSize(std::addressof(tik_size)); R_SUCCEEDED(res)) {
|
||||
if (tik_size >= static_cast<s64>(sizeof(CommonTicketData))) {
|
||||
CommonTicketData tik_data;
|
||||
if (const auto res = tik_storage->Read(0, std::addressof(tik_data), sizeof(tik_data)); R_SUCCEEDED(res)) {
|
||||
if (!TryLoadKeyFromCommonTicket(m_external_nca_key_manager, std::addressof(tik_data), sizeof(tik_data))) {
|
||||
fprintf(stderr, "[Warning]: Failed to load common title key from ticket file (%s). Is it not a common ticket?\n", path.GetString());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "[Warning]: Failed to read ticket file (%s): 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "[Warning]: Ticket file (%s) has incorrect size: 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "[Warning]: Failed to get size of ticket file (%s): 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "[Warning]: Failed to open ticket file (%s): 2%03d-%04d\n", path.GetString(), res.GetModule(), res.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
/* If the path isn't a meta nca, finish. */
|
||||
R_SUCCEED_IF(!PathView(entry.name).HasSuffix(MetaNcaFileNameExtension));
|
||||
|
||||
@ -200,7 +330,7 @@ namespace ams::hactool {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Processor::PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx) {
|
||||
void Processor::PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx) {
|
||||
auto _ = this->PrintHeader("Application File System");
|
||||
|
||||
{
|
||||
@ -226,7 +356,7 @@ namespace ams::hactool {
|
||||
AMS_UNUSED(ctx);
|
||||
}
|
||||
|
||||
void Processor::SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx) {
|
||||
void Processor::SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx) {
|
||||
/* TODO */
|
||||
AMS_UNUSED(ctx);
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ namespace ams::hactool {
|
||||
ProcessAsNpdmContext npdm_ctx;
|
||||
};
|
||||
|
||||
struct ProcessAsApplicationFileSystemCtx {
|
||||
struct ProcessAsApplicationFileSystemContext {
|
||||
std::shared_ptr<fs::fsa::IFileSystem> fs;
|
||||
|
||||
struct ApplicationEntryData {
|
||||
@ -114,7 +114,17 @@ namespace ams::hactool {
|
||||
PartitionData normal_partition;
|
||||
PartitionData secure_partition;
|
||||
|
||||
ProcessAsApplicationFileSystemCtx app_ctx;
|
||||
ProcessAsApplicationFileSystemContext app_ctx;
|
||||
};
|
||||
|
||||
struct ProcessAsPfsContext {
|
||||
std::shared_ptr<fs::IStorage> storage;
|
||||
std::shared_ptr<fs::fsa::IFileSystem> fs;
|
||||
|
||||
bool is_exefs;
|
||||
|
||||
ProcessAsNpdmContext npdm_ctx;
|
||||
ProcessAsApplicationFileSystemContext app_ctx;
|
||||
};
|
||||
private:
|
||||
Options m_options;
|
||||
@ -206,19 +216,22 @@ namespace ams::hactool {
|
||||
Result ProcessAsNca(std::shared_ptr<fs::IStorage> storage, ProcessAsNcaContext *ctx = nullptr);
|
||||
Result ProcessAsNpdm(std::shared_ptr<fs::IStorage> storage, ProcessAsNpdmContext *ctx = nullptr);
|
||||
Result ProcessAsXci(std::shared_ptr<fs::IStorage> storage, ProcessAsXciContext *ctx = nullptr);
|
||||
Result ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemCtx *ctx = nullptr);
|
||||
Result ProcessAsPfs(std::shared_ptr<fs::IStorage> storage, ProcessAsPfsContext *ctx = nullptr);
|
||||
Result ProcessAsApplicationFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, ProcessAsApplicationFileSystemContext *ctx = nullptr);
|
||||
|
||||
/* Printing. */
|
||||
void PrintAsNca(ProcessAsNcaContext &ctx);
|
||||
void PrintAsNpdm(ProcessAsNpdmContext &ctx);
|
||||
void PrintAsXci(ProcessAsXciContext &ctx);
|
||||
void PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx);
|
||||
void PrintAsPfs(ProcessAsPfsContext &ctx);
|
||||
void PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx);
|
||||
|
||||
/* Saving. */
|
||||
void SaveAsNca(ProcessAsNcaContext &ctx);
|
||||
void SaveAsNpdm(ProcessAsNpdmContext &ctx);
|
||||
void SaveAsXci(ProcessAsXciContext &ctx);
|
||||
void SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx);
|
||||
void SaveAsPfs(ProcessAsPfsContext &ctx);
|
||||
void SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx);
|
||||
};
|
||||
|
||||
inline void Processor::PrintLineImpl(const char *fmt, ...) const {
|
||||
|
@ -59,6 +59,9 @@ namespace ams::hactool {
|
||||
case FileType::Xci:
|
||||
R_TRY(this->ProcessAsXci(std::move(input)));
|
||||
break;
|
||||
case FileType::Pfs:
|
||||
R_TRY(this->ProcessAsPfs(std::move(input)));
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
105
source/hactool_processor.pfs.cpp
Normal file
105
source/hactool_processor.pfs.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "hactool_processor.hpp"
|
||||
#include "hactool_fs_utils.hpp"
|
||||
|
||||
namespace ams::hactool {
|
||||
|
||||
Result Processor::ProcessAsPfs(std::shared_ptr<fs::IStorage> storage, ProcessAsPfsContext *ctx) {
|
||||
/* Ensure we have a context. */
|
||||
ProcessAsPfsContext local_ctx{};
|
||||
if (ctx == nullptr) {
|
||||
ctx = std::addressof(local_ctx);
|
||||
}
|
||||
|
||||
/* Set the fs. */
|
||||
ctx->storage = std::move(storage);
|
||||
|
||||
/* Mount the partition filesystem. */
|
||||
{
|
||||
/* Allocate the fs. */
|
||||
auto fs = fssystem::AllocateShared<fssystem::PartitionFileSystem>();
|
||||
R_UNLESS(fs != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemCreatorA());
|
||||
|
||||
/* Initialize the filesystem. */
|
||||
R_TRY(fs->Initialize(std::shared_ptr<fs::IStorage>(ctx->storage)));
|
||||
|
||||
/* Set the context fs. */
|
||||
ctx->fs = std::move(fs);
|
||||
}
|
||||
|
||||
/* Try to treat the context as an exefs. */
|
||||
std::shared_ptr<fs::IStorage> npdm_storage;
|
||||
{
|
||||
bool is_exefs = false;
|
||||
const auto check_npdm_res = fssystem::HasFile(std::addressof(is_exefs), ctx->fs.get(), fs::MakeConstantPath("/main.npdm"));
|
||||
if (R_SUCCEEDED(check_npdm_res)) {
|
||||
if (is_exefs) {
|
||||
ctx->is_exefs = true;
|
||||
|
||||
if (const auto open_npdm_res = OpenFileStorage(std::addressof(npdm_storage), ctx->fs, "/main.npdm"); R_FAILED(open_npdm_res)) {
|
||||
fprintf(stderr, "[Warning]: main.npdm exists in PartitionFileSystem but could not be opened: 2%03d-%04d\n", open_npdm_res.GetModule(), open_npdm_res.GetDescription());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "[Warning]: Failed to check if PartitionFileSystem is exefs: 2%03d-%04d\n", check_npdm_res.GetModule(), check_npdm_res.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse as exefs or appfs. */
|
||||
if (ctx->is_exefs) {
|
||||
if (const auto process_npdm_res = this->ProcessAsNpdm(std::move(npdm_storage), std::addressof(ctx->npdm_ctx)); R_FAILED(process_npdm_res)) {
|
||||
fprintf(stderr, "[Warning]: Failed to process PartitionFileSystem main.npdm: 2%03d-%04d\n", process_npdm_res.GetModule(), process_npdm_res.GetDescription());
|
||||
}
|
||||
} else {
|
||||
if (const auto process_app_res = this->ProcessAsApplicationFileSystem(ctx->fs, std::addressof(ctx->app_ctx)); R_FAILED(process_app_res)) {
|
||||
fprintf(stderr, "[Warning]: Failed to process PartitionFileSystem applications: 2%03d-%04d\n", process_app_res.GetModule(), process_app_res.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Recursive processing? */
|
||||
|
||||
/* Print. */
|
||||
if (ctx == std::addressof(local_ctx)) {
|
||||
this->PrintAsPfs(*ctx);
|
||||
}
|
||||
|
||||
/* Save. */
|
||||
if (ctx == std::addressof(local_ctx)) {
|
||||
this->SaveAsPfs(*ctx);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Processor::PrintAsPfs(ProcessAsPfsContext &ctx) {
|
||||
if (ctx.is_exefs) {
|
||||
this->PrintAsNpdm(ctx.npdm_ctx);
|
||||
} else {
|
||||
this->PrintAsApplicationFileSystem(ctx.app_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void Processor::SaveAsPfs(ProcessAsPfsContext &ctx) {
|
||||
if (ctx.is_exefs) {
|
||||
this->SaveAsNpdm(ctx.npdm_ctx);
|
||||
} else {
|
||||
this->SaveAsApplicationFileSystem(ctx.app_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user