diff --git a/source/hactool_application_list.hpp b/source/hactool_application_list.hpp index 2bb38e0..5f0f356 100644 --- a/source/hactool_application_list.hpp +++ b/source/hactool_application_list.hpp @@ -25,9 +25,10 @@ namespace ams::hactool { u32 m_version; u8 m_id_offset; ncm::ContentType m_type; + ncm::ContentMetaType m_meta_type; UserData m_data; public: - ApplicationContentTreeEntry(ncm::ApplicationId id, u32 v, u8 o, ncm::ContentType t) : m_id(id), m_version(v), m_id_offset(o), m_type(t), m_data() { + ApplicationContentTreeEntry(ncm::ApplicationId id, u32 v, u8 o, ncm::ContentType t, ncm::ContentMetaType m) : m_id(id), m_version(v), m_id_offset(o), m_type(t), m_meta_type(m), m_data() { /* ... */ } @@ -47,6 +48,10 @@ namespace ams::hactool { return m_type; } + ncm::ContentMetaType GetMetaType() const { + return m_meta_type; + } + const UserData &GetData() const { return m_data; } UserData &GetData() { return m_data; } @@ -59,13 +64,15 @@ namespace ams::hactool { const auto a_v = a.GetVersion(); const auto a_o = a.GetIdOffset(); const auto a_t = a.GetType(); + const auto a_m = a.GetMetaType(); const auto b_i = b.GetId(); const auto b_v = b.GetVersion(); const auto b_o = b.GetIdOffset(); const auto b_t = b.GetType(); - if (std::tie(a_i, a_v, a_o, a_t) < std::tie(b_i, b_v, b_o, b_t)) { + const auto b_m = b.GetMetaType(); + if (std::tie(a_i, a_v, a_o, a_t, a_m) < std::tie(b_i, b_v, b_o, b_t, b_m)) { return -1; - } else if (std::tie(a_i, a_v, a_o, a_t) > std::tie(b_i, b_v, b_o, b_t)) { + } else if (std::tie(a_i, a_v, a_o, a_t, a_m) > std::tie(b_i, b_v, b_o, b_t, b_m)) { return 1; } else { return 0; @@ -96,8 +103,8 @@ namespace ams::hactool { } } - ApplicationContentTreeEntry *Insert(ncm::ApplicationId id, u32 v, u8 o, ncm::ContentType t) { - auto *entry = new ApplicationContentTreeEntry(id, v, o, t); + ApplicationContentTreeEntry *Insert(ncm::ApplicationId id, u32 v, u8 o, ncm::ContentType t, ncm::ContentMetaType m) { + auto *entry = new ApplicationContentTreeEntry(id, v, o, t, m); m_tree.insert(*entry); return entry; } @@ -105,8 +112,8 @@ namespace ams::hactool { auto begin() const { return m_tree.begin(); } auto end() const { return m_tree.end(); } - auto Find(ncm::ApplicationId id, u32 v, u8 o, ncm::ContentType t) { - ApplicationContentTreeEntry dummy(id, v, o, t); + auto Find(ncm::ApplicationId id, u32 v, u8 o, ncm::ContentType t, ncm::ContentMetaType m) { + ApplicationContentTreeEntry dummy(id, v, o, t, m); return m_tree.find(dummy); } }; diff --git a/source/hactool_options.cpp b/source/hactool_options.cpp index 9dfe3c8..cb5cca3 100644 --- a/source/hactool_options.cpp +++ b/source/hactool_options.cpp @@ -63,6 +63,17 @@ namespace ams::hactool { return true; } + bool ParseIntegerArgument(int *out, const char *argument) { + char *parse_end = nullptr; + const auto val = std::strtol(argument, std::addressof(parse_end), 0); + if (parse_end != nullptr && parse_end != argument) { + *out = val; + return true; + } else { + return false; + } + } + using OptionHandlerFunction = util::IFunction; struct OptionHandler { @@ -153,6 +164,9 @@ namespace ams::hactool { MakeOptionHandler("logodir", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.logo_partition_out_dir), arg); }), MakeOptionHandler("listromfs", [] (Options &options) { options.list_romfs = true; }), MakeOptionHandler("listupdate", [] (Options &options) { options.list_update = true; }), + MakeOptionHandler("appindex", [] (Options &options, const char *arg) { return ParseIntegerArgument(std::addressof(options.preferred_app_index), arg); }), + MakeOptionHandler("programindex", [] (Options &options, const char *arg) { return ParseIntegerArgument(std::addressof(options.preferred_program_index), arg); }), + MakeOptionHandler("appversion", [] (Options &options, const char *arg) { return ParseIntegerArgument(std::addressof(options.preferred_version), arg); }), }; } diff --git a/source/hactool_processor.app_fs.cpp b/source/hactool_processor.app_fs.cpp index 9fe7741..e713f0f 100644 --- a/source/hactool_processor.app_fs.cpp +++ b/source/hactool_processor.app_fs.cpp @@ -264,9 +264,6 @@ namespace ams::hactool { /* Get the version. */ const auto version = meta_header->version; - /* We'll want to insert into the appropriate tracking holder. */ - auto &target = meta_header->type == ncm::ContentMetaType::Patch ? ctx->patches : ctx->apps; - /* Add all the content metas. */ for (size_t i = 0; i < meta_reader.GetContentCount(); ++i) { const auto &info = *meta_reader.GetContentInfo(i); @@ -277,8 +274,8 @@ namespace ams::hactool { } /* Check that we don't already have an info for the content. */ - if (auto existing = target.Find(*app_id, version, info.GetIdOffset(), info.GetType()); existing != target.end()) { - fprintf(stderr, "[Warning]: Ignoring duplicate entry { %016" PRIX64 ", %" PRIu32 ", %d, %d }\n", app_id->value, version, static_cast(info.GetIdOffset()), static_cast(info.GetType())); + if (auto existing = ctx->apps.Find(*app_id, version, info.GetIdOffset(), info.GetType(), meta_header->type); existing != ctx->apps.end()) { + fprintf(stderr, "[Warning]: Ignoring duplicate entry { %016" PRIX64 ", %" PRIu32 ", %d, %d, %s }\n", app_id->value, version, static_cast(info.GetIdOffset()), static_cast(info.GetType()), meta_header->type == ncm::ContentMetaType::Patch ? "Patch" : "App"); continue; } @@ -304,7 +301,7 @@ namespace ams::hactool { } /* Add the new version for the content. */ - auto *entry = target.Insert(*app_id, version, info.GetIdOffset(), info.GetType()); + auto *entry = ctx->apps.Insert(*app_id, version, info.GetIdOffset(), info.GetType(), meta_header->type); entry->GetData().storage = std::move(storage); } @@ -316,6 +313,59 @@ namespace ams::hactool { } } + /* Determine the target. */ + { + /* Start with no target. */ + ctx->has_target = false; + + s32 app_idx = m_options.preferred_app_index; + s32 prog_idx = m_options.preferred_program_index; + s32 version = m_options.preferred_version; + + /* Determine the application id. */ + if (app_idx < 0) { + app_idx = 0; + } + { + s32 cur_app_idx = -1; + ncm::ApplicationId cur_app_id{}; + for (const auto &entry : ctx->apps) { + if (entry.GetType() != ncm::ContentType::Program) { + continue; + } + + if (cur_app_idx == -1 || cur_app_id != entry.GetId()) { + ++cur_app_idx; + cur_app_id = entry.GetId(); + } + + if (app_idx == cur_app_idx) { + ctx->target_app_id = entry.GetId(); + if (prog_idx < 0) { + prog_idx = entry.GetIdOffset(); + } + break; + } + } + } + + /* Find a matching version. */ + if (ctx->target_app_id != ncm::ApplicationId{}) { + for (const auto &entry : ctx->apps) { + /* We only care about matching program entries. */ + if (entry.GetType() != ncm::ContentType::Program || entry.GetId() != ctx->target_app_id || entry.GetIdOffset() != prog_idx) { + continue; + } + + if ((version < 0 && entry.GetVersion() >= ctx->target_version) || (version >= 0 && static_cast(version) == entry.GetVersion())) { + ctx->has_target = true; + ctx->target_version = entry.GetVersion(); + ctx->target_index = entry.GetIdOffset(); + } + } + } + } + /* TODO: Recursive processing? */ /* Print. */ @@ -348,20 +398,12 @@ namespace ams::hactool { cur_app_id = entry.GetId(); } - this->PrintFormat(field_name, "{ Idx=%d, ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 " }", app_idx, entry.GetId().value, entry.GetVersion(), entry.GetIdOffset()); + this->PrintFormat(field_name, "{ Idx=%d, ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 ", MetaType=%s }", app_idx, entry.GetId().value, entry.GetVersion(), entry.GetIdOffset(), entry.GetMetaType() == ncm::ContentMetaType::Patch ? "Patch" : "App"); field_name = ""; } - if (ctx.patches.begin() != ctx.patches.end()) { - field_name = "Patches"; - for (const auto &entry : ctx.patches) { - if (entry.GetType() != ncm::ContentType::Program) { - continue; - } - - this->PrintFormat(field_name, "{ ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 " }", entry.GetId().value, entry.GetVersion(), entry.GetIdOffset()); - field_name = ""; - } + if (ctx.has_target) { + this->PrintFormat("Target", "{ ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 " }", ctx.target_app_id.value, ctx.target_version, ctx.target_index); } } diff --git a/source/hactool_processor.hpp b/source/hactool_processor.hpp index 0301136..263fe62 100644 --- a/source/hactool_processor.hpp +++ b/source/hactool_processor.hpp @@ -83,7 +83,11 @@ namespace ams::hactool { }; ApplicationContentsHolder apps; - ApplicationContentsHolder patches; + + bool has_target; + ncm::ApplicationId target_app_id; + u32 target_version; + u8 target_index; }; struct ProcessAsXciContext { diff --git a/source/hactool_processor.xci.cpp b/source/hactool_processor.xci.cpp index 0c241b4..40a52d9 100644 --- a/source/hactool_processor.xci.cpp +++ b/source/hactool_processor.xci.cpp @@ -372,20 +372,12 @@ namespace ams::hactool { cur_app_id = entry.GetId(); } - this->PrintFormat(field_name, "{ Idx=%d, ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 " }", app_idx, entry.GetId().value, entry.GetVersion(), entry.GetIdOffset()); + this->PrintFormat(field_name, "{ Idx=%d, ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 ", MetaType=%s }", app_idx, entry.GetId().value, entry.GetVersion(), entry.GetIdOffset(), entry.GetMetaType() == ncm::ContentMetaType::Patch ? "Patch" : "App"); field_name = ""; } - if (ctx.app_ctx.patches.begin() != ctx.app_ctx.patches.end()) { - field_name = "Patches"; - for (const auto &entry : ctx.app_ctx.patches) { - if (entry.GetType() != ncm::ContentType::Program) { - continue; - } - - this->PrintFormat(field_name, "{ ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 " }", entry.GetId().value, entry.GetVersion(), entry.GetIdOffset()); - field_name = ""; - } + if (ctx.app_ctx.has_target) { + this->PrintFormat("Target", "{ ProgramId=%016" PRIX64 ", Version=0x%08" PRIX32 ", IdOffset=%02" PRIX32 " }", ctx.app_ctx.target_app_id.value, ctx.app_ctx.target_version, ctx.app_ctx.target_index); } }