mirror of
https://github.com/Atmosphere-NX/hac2l.git
synced 2025-06-21 03:02: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;
|
options.file_type = FileType::Xci;
|
||||||
} else if (std::strcmp(arg, "appfs") == 0) {
|
} else if (std::strcmp(arg, "appfs") == 0) {
|
||||||
options.file_type = FileType::AppFs;
|
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 {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,109 @@ namespace ams::hactool {
|
|||||||
|
|
||||||
constexpr const char ContentMetaFileNameExtension[] = ".cnmt";
|
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) {
|
Result ReadContentMetaFile(std::unique_ptr<u8[]> *out, size_t *out_size, std::shared_ptr<fs::fsa::IFileSystem> &fs) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
R_RETURN(fssystem::IterateDirectoryRecursively(fs.get(),
|
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. */
|
/* Ensure we have a context. */
|
||||||
ProcessAsApplicationFileSystemCtx local_ctx{};
|
ProcessAsApplicationFileSystemContext local_ctx{};
|
||||||
if (ctx == nullptr) {
|
if (ctx == nullptr) {
|
||||||
ctx = std::addressof(local_ctx);
|
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 &, const fs::DirectoryEntry &) -> Result { R_SUCCEED(); },
|
[&] (const fs::Path &, const fs::DirectoryEntry &) -> Result { R_SUCCEED(); },
|
||||||
[&] (const fs::Path &path, const fs::DirectoryEntry &entry) -> Result {
|
[&] (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. */
|
/* If the path isn't a meta nca, finish. */
|
||||||
R_SUCCEED_IF(!PathView(entry.name).HasSuffix(MetaNcaFileNameExtension));
|
R_SUCCEED_IF(!PathView(entry.name).HasSuffix(MetaNcaFileNameExtension));
|
||||||
|
|
||||||
@ -200,7 +330,7 @@ namespace ams::hactool {
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Processor::PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx) {
|
void Processor::PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx) {
|
||||||
auto _ = this->PrintHeader("Application File System");
|
auto _ = this->PrintHeader("Application File System");
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -226,7 +356,7 @@ namespace ams::hactool {
|
|||||||
AMS_UNUSED(ctx);
|
AMS_UNUSED(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Processor::SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx) {
|
void Processor::SaveAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx) {
|
||||||
/* TODO */
|
/* TODO */
|
||||||
AMS_UNUSED(ctx);
|
AMS_UNUSED(ctx);
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ namespace ams::hactool {
|
|||||||
ProcessAsNpdmContext npdm_ctx;
|
ProcessAsNpdmContext npdm_ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ProcessAsApplicationFileSystemCtx {
|
struct ProcessAsApplicationFileSystemContext {
|
||||||
std::shared_ptr<fs::fsa::IFileSystem> fs;
|
std::shared_ptr<fs::fsa::IFileSystem> fs;
|
||||||
|
|
||||||
struct ApplicationEntryData {
|
struct ApplicationEntryData {
|
||||||
@ -114,7 +114,17 @@ namespace ams::hactool {
|
|||||||
PartitionData normal_partition;
|
PartitionData normal_partition;
|
||||||
PartitionData secure_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:
|
private:
|
||||||
Options m_options;
|
Options m_options;
|
||||||
@ -206,19 +216,22 @@ namespace ams::hactool {
|
|||||||
Result ProcessAsNca(std::shared_ptr<fs::IStorage> storage, ProcessAsNcaContext *ctx = nullptr);
|
Result ProcessAsNca(std::shared_ptr<fs::IStorage> storage, ProcessAsNcaContext *ctx = nullptr);
|
||||||
Result ProcessAsNpdm(std::shared_ptr<fs::IStorage> storage, ProcessAsNpdmContext *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 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. */
|
/* Printing. */
|
||||||
void PrintAsNca(ProcessAsNcaContext &ctx);
|
void PrintAsNca(ProcessAsNcaContext &ctx);
|
||||||
void PrintAsNpdm(ProcessAsNpdmContext &ctx);
|
void PrintAsNpdm(ProcessAsNpdmContext &ctx);
|
||||||
void PrintAsXci(ProcessAsXciContext &ctx);
|
void PrintAsXci(ProcessAsXciContext &ctx);
|
||||||
void PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemCtx &ctx);
|
void PrintAsPfs(ProcessAsPfsContext &ctx);
|
||||||
|
void PrintAsApplicationFileSystem(ProcessAsApplicationFileSystemContext &ctx);
|
||||||
|
|
||||||
/* Saving. */
|
/* Saving. */
|
||||||
void SaveAsNca(ProcessAsNcaContext &ctx);
|
void SaveAsNca(ProcessAsNcaContext &ctx);
|
||||||
void SaveAsNpdm(ProcessAsNpdmContext &ctx);
|
void SaveAsNpdm(ProcessAsNpdmContext &ctx);
|
||||||
void SaveAsXci(ProcessAsXciContext &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 {
|
inline void Processor::PrintLineImpl(const char *fmt, ...) const {
|
||||||
|
@ -59,6 +59,9 @@ namespace ams::hactool {
|
|||||||
case FileType::Xci:
|
case FileType::Xci:
|
||||||
R_TRY(this->ProcessAsXci(std::move(input)));
|
R_TRY(this->ProcessAsXci(std::move(input)));
|
||||||
break;
|
break;
|
||||||
|
case FileType::Pfs:
|
||||||
|
R_TRY(this->ProcessAsPfs(std::move(input)));
|
||||||
|
break;
|
||||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
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