Add content/nca viewer for games menu, fix manual nca title fetch for chinese lang icon, rename "dump" to "export".
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
#include "ui/menus/grid_menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
|
||||
#include "yati/container/base.hpp"
|
||||
#include "yati/nx/keys.hpp"
|
||||
|
||||
#include "title_info.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
@@ -101,4 +105,69 @@ private:
|
||||
option::OptionBool m_title_cache{INI_SECTION, "title_cache", true};
|
||||
};
|
||||
|
||||
struct NcmMetaData {
|
||||
// points to global service, do not close manually!
|
||||
NcmContentStorage* cs{};
|
||||
NcmContentMetaDatabase* db{};
|
||||
u64 app_id{};
|
||||
NcmContentMetaKey key{};
|
||||
};
|
||||
|
||||
Result GetMetaEntries(const Entry& e, title::MetaEntries& out, u32 flags = title::ContentFlag_All);
|
||||
|
||||
Result GetNcmMetaFromMetaStatus(const NsApplicationContentMetaStatus& status, NcmMetaData& out);
|
||||
void DeleteMetaEntries(u64 app_id, int image, const std::string& name, const title::MetaEntries& entries);
|
||||
|
||||
struct TikEntry {
|
||||
FsRightsId id{};
|
||||
u8 key_gen{};
|
||||
std::vector<u8> tik_data{};
|
||||
std::vector<u8> cert_data{};
|
||||
};
|
||||
|
||||
struct NspEntry {
|
||||
// application name.
|
||||
std::string application_name{};
|
||||
// name of the nsp (name [id][v0][BASE].nsp).
|
||||
fs::FsPath path{};
|
||||
// tickets and cert data, will be empty if title key crypto isn't used.
|
||||
std::vector<TikEntry> tickets{};
|
||||
// all the collections for this nsp, such as nca's and tickets.
|
||||
std::vector<yati::container::CollectionEntry> collections{};
|
||||
// raw nsp data (header, file table and string table).
|
||||
std::vector<u8> nsp_data{};
|
||||
// size of the entier nsp.
|
||||
s64 nsp_size{};
|
||||
// copy of ncm cs, it is not closed.
|
||||
NcmContentStorage cs{};
|
||||
// copy of the icon, if invalid, it will use the default icon.
|
||||
int icon{};
|
||||
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read);
|
||||
|
||||
private:
|
||||
static auto InRange(s64 off, s64 offset, s64 size) -> bool {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
|
||||
static auto ClipSize(s64 off, s64 size, s64 file_size) -> s64 {
|
||||
return std::min(size, file_size - off);
|
||||
}
|
||||
};
|
||||
|
||||
struct ContentInfoEntry {
|
||||
NsApplicationContentMetaStatus status{};
|
||||
std::vector<NcmContentInfo> content_infos{};
|
||||
std::vector<NcmRightsId> ncm_rights_id{};
|
||||
};
|
||||
|
||||
auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status) -> fs::FsPath;
|
||||
Result BuildContentEntry(const NsApplicationContentMetaStatus& status, ContentInfoEntry& out);
|
||||
Result BuildNspEntry(const Entry& e, const ContentInfoEntry& info, const keys::Keys& keys, NspEntry& out);
|
||||
Result BuildNspEntries(Entry& e, const title::MetaEntries& meta_entries, std::vector<NspEntry>& out);
|
||||
Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out);
|
||||
|
||||
// dumps the array of nsp entries.
|
||||
void DumpNsp(const std::vector<NspEntry>& entries);
|
||||
|
||||
} // namespace sphaira::ui::menu::game
|
||||
|
||||
111
sphaira/include/ui/menus/game_meta_menu.hpp
Normal file
111
sphaira/include/ui/menus/game_meta_menu.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/menus/game_menu.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "yati/nx/ncm.hpp"
|
||||
#include <span>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::game::meta {
|
||||
|
||||
enum TicketType : u8 {
|
||||
TicketType_None,
|
||||
TicketType_Common,
|
||||
TicketType_Personalised,
|
||||
TicketType_Missing,
|
||||
};
|
||||
|
||||
struct MiniNacp {
|
||||
char display_version[0x10];
|
||||
};
|
||||
|
||||
struct MetaEntry {
|
||||
NsApplicationContentMetaStatus status{};
|
||||
ncm::ContentMeta content_meta{};
|
||||
// small version of nacp to speed up loading.
|
||||
MiniNacp nacp{};
|
||||
// total size of all ncas.
|
||||
s64 size{};
|
||||
// set to the key gen (if possible), only if title key encrypted.
|
||||
u8 key_gen{};
|
||||
// set to the ticket type.
|
||||
u8 ticket_type{TicketType_None};
|
||||
// set if it has missing ncas.
|
||||
u8 missing_count{};
|
||||
// set if selected.
|
||||
bool selected{};
|
||||
// set if we have checked the above meta data.
|
||||
bool checked{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(Entry& entry);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Meta"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void Scan();
|
||||
void UpdateSubheading();
|
||||
|
||||
auto GetSelectedEntries() const {
|
||||
title::MetaEntries out;
|
||||
for (auto& e : m_entries) {
|
||||
if (e.selected) {
|
||||
out.emplace_back(e.status);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_entries.empty() && out.empty()) {
|
||||
out.emplace_back(m_entries[m_index].status);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void ClearSelection() {
|
||||
for (auto& e : m_entries) {
|
||||
e.selected = false;
|
||||
}
|
||||
|
||||
m_selected_count = 0;
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) -> MetaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) const -> const MetaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry() -> MetaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto GetEntry() const -> const MetaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
void DumpGames();
|
||||
void DeleteGames();
|
||||
Result ResetRequiredSystemVersion(MetaEntry& entry) const;
|
||||
Result GetNcmSizeOfMetaStatus(MetaEntry& entry) const;
|
||||
|
||||
private:
|
||||
Entry& m_entry;
|
||||
std::vector<MetaEntry> m_entries{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
std::unique_ptr<List> m_list{};
|
||||
bool m_dirty{};
|
||||
|
||||
std::vector<FsRightsId> m_common_tickets{};
|
||||
std::vector<FsRightsId> m_personalised_tickets{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::game::meta
|
||||
91
sphaira/include/ui/menus/game_nca_menu.hpp
Normal file
91
sphaira/include/ui/menus/game_nca_menu.hpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/menus/game_meta_menu.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/nx/ncm.hpp"
|
||||
#include <span>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::game::meta_nca {
|
||||
|
||||
struct NcaEntry {
|
||||
NcmContentId content_id{};
|
||||
u64 size{};
|
||||
u8 content_type{};
|
||||
// decrypted nca header.
|
||||
nca::Header header{};
|
||||
// set if missing.
|
||||
bool missing{};
|
||||
// set if selected.
|
||||
bool selected{};
|
||||
// set if we have checked the above meta data.
|
||||
bool checked{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(Entry& entry, const meta::MetaEntry& meta_entry);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Nca"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void Scan();
|
||||
void UpdateSubheading();
|
||||
|
||||
auto GetSelectedEntries() const {
|
||||
std::vector<NcaEntry> out;
|
||||
for (auto& e : m_entries) {
|
||||
if (e.selected && !e.missing) {
|
||||
out.emplace_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_entries.empty() && out.empty()) {
|
||||
out.emplace_back(m_entries[m_index]);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void ClearSelection() {
|
||||
for (auto& e : m_entries) {
|
||||
e.selected = false;
|
||||
}
|
||||
|
||||
m_selected_count = 0;
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) -> NcaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) const -> const NcaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry() -> NcaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto GetEntry() const -> const NcaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
void DumpNcas();
|
||||
|
||||
private:
|
||||
Entry& m_entry;
|
||||
const meta::MetaEntry& m_meta_entry;
|
||||
NcmMetaData m_meta{};
|
||||
std::vector<NcaEntry> m_entries{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
std::unique_ptr<List> m_list{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::game::meta_nca
|
||||
@@ -203,4 +203,16 @@ Result ShouldPatchTicket(const TicketData& data, std::span<const u8> ticket, std
|
||||
Result ShouldPatchTicket(std::span<const u8> ticket, std::span<const u8> cert_chain, bool patch_personalised, bool& should_patch);
|
||||
Result PatchTicket(std::vector<u8>& ticket, std::span<const u8> cert_chain, u8 key_gen, const keys::Keys& keys, bool patch_personalised);
|
||||
|
||||
// fills out with the list of common / personalised rights ids.
|
||||
Result GetCommonTickets(std::vector<FsRightsId>& out);
|
||||
Result GetPersonalisedTickets(std::vector<FsRightsId>& out);
|
||||
|
||||
// checks if the rights id is found in common / personalised.
|
||||
Result IsRightsIdCommon(const FsRightsId& id, bool* out);
|
||||
Result IsRightsIdPersonalised(const FsRightsId& id, bool* out);
|
||||
|
||||
// helper for the above if the db has already been parsed.
|
||||
bool IsRightsIdValid(const FsRightsId& id);
|
||||
bool IsRightsIdFound(const FsRightsId& id, std::span<const FsRightsId> ids);
|
||||
|
||||
} // namespace sphaira::es
|
||||
|
||||
@@ -181,7 +181,15 @@ struct Header {
|
||||
u64 size;
|
||||
u64 program_id;
|
||||
u32 context_id;
|
||||
u32 sdk_version;
|
||||
union {
|
||||
u32 sdk_version;
|
||||
struct {
|
||||
u8 sdk_revision;
|
||||
u8 sdk_micro;
|
||||
u8 sdk_minor;
|
||||
u8 sdk_major;
|
||||
};
|
||||
};
|
||||
u8 key_gen; // see KeyGeneration.
|
||||
u8 sig_key_gen;
|
||||
u8 _0x222[0xE]; // empty.
|
||||
@@ -215,6 +223,9 @@ struct Header {
|
||||
};
|
||||
static_assert(sizeof(Header) == 0xC00);
|
||||
|
||||
auto GetContentTypeStr(u8 content_type) -> const char*;
|
||||
auto GetDistributionTypeStr(u8 distribution_type) -> const char*;
|
||||
|
||||
Result DecryptKeak(const keys::Keys& keys, Header& header);
|
||||
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
||||
Result VerifyFixedKey(const Header& header);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::ncm {
|
||||
|
||||
@@ -31,10 +34,19 @@ union ExtendedHeader {
|
||||
NcmDataPatchMetaExtendedHeader data_patch;
|
||||
};
|
||||
|
||||
struct ContentMeta {
|
||||
NcmContentMetaHeader header;
|
||||
ExtendedHeader extened;
|
||||
};
|
||||
|
||||
auto GetMetaTypeStr(u8 meta_type) -> const char*;
|
||||
auto GetContentTypeStr(u8 content_type) -> const char*;
|
||||
auto GetStorageIdStr(u8 storage_id) -> const char*;
|
||||
auto GetMetaTypeShortStr(u8 meta_type) -> const char*;
|
||||
|
||||
auto GetReadableMetaTypeStr(u8 meta_type) -> const char*;
|
||||
auto GetReadableStorageIdStr(u8 storage_id) -> const char*;
|
||||
|
||||
auto GetAppId(u8 meta_type, u64 id) -> u64;
|
||||
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
||||
auto GetAppId(const PackagedContentMeta& meta) -> u64;
|
||||
@@ -44,4 +56,30 @@ auto GetContentIdFromStr(const char* str) -> NcmContentId;
|
||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id);
|
||||
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id);
|
||||
|
||||
// fills out with the content header, which includes the normal and extended header.
|
||||
Result GetContentMeta(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, ContentMeta& out);
|
||||
|
||||
// fills out will a list of all content infos tied to the key.
|
||||
Result GetContentInfos(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, std::vector<NcmContentInfo>& out);
|
||||
// same as above but accepts the ncm header rather than fetching it.
|
||||
Result GetContentInfos(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, const NcmContentMetaHeader& header, std::vector<NcmContentInfo>& out);
|
||||
|
||||
// removes key from ncm, including ncas and setting the db.
|
||||
Result DeleteKey(NcmContentStorage* cs, NcmContentMetaDatabase *db, const NcmContentMetaKey *key);
|
||||
|
||||
// sets the required system version.
|
||||
Result SetRequiredSystemVersion(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, u32 version);
|
||||
|
||||
// returns true if type is application or update.
|
||||
static constexpr inline bool HasRequiredSystemVersion(u8 meta_type) {
|
||||
return meta_type == NcmContentMetaType_Application || meta_type == NcmContentMetaType_Patch;
|
||||
}
|
||||
|
||||
static constexpr inline bool HasRequiredSystemVersion(const NcmContentMetaKey *key) {
|
||||
return HasRequiredSystemVersion(key->type);
|
||||
}
|
||||
|
||||
// fills program id and out path of the control nca.
|
||||
Result GetControlPathFromContentId(NcmContentStorage* cs, const NcmContentMetaKey& key, const NcmContentId& id, u64* out_program_id, fs::FsPath* out_path);
|
||||
|
||||
} // namespace sphaira::ncm
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "ncm.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::ns {
|
||||
|
||||
enum ApplicationRecordType {
|
||||
@@ -16,9 +19,26 @@ enum ApplicationRecordType {
|
||||
ApplicationRecordType_Archived = 0xB,
|
||||
};
|
||||
|
||||
Result PushApplicationRecord(Service* srv, u64 tid, const ncm::ContentStorageRecord* records, u32 count);
|
||||
Result ListApplicationRecordContentMeta(Service* srv, u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read);
|
||||
Result DeleteApplicationRecord(Service* srv, u64 tid);
|
||||
Result InvalidateApplicationControlCache(Service* srv, u64 tid);
|
||||
Result Initialize();
|
||||
void Exit();
|
||||
|
||||
Result PushApplicationRecord(u64 tid, const ncm::ContentStorageRecord* records, u32 count);
|
||||
Result ListApplicationRecordContentMeta(u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read);
|
||||
Result DeleteApplicationRecord(u64 tid);
|
||||
Result InvalidateApplicationControlCache(u64 tid);
|
||||
|
||||
// helpers
|
||||
|
||||
// fills out with the number or records available
|
||||
Result GetApplicationRecords(u64 id, std::vector<ncm::ContentStorageRecord>& out);
|
||||
|
||||
// sets the lowest launch version based on the current record list.
|
||||
Result SetLowestLaunchVersion(u64 id);
|
||||
// same as above, but uses the provided record list.
|
||||
Result SetLowestLaunchVersion(u64 id, std::span<const ncm::ContentStorageRecord> records);
|
||||
|
||||
static inline bool IsNsControlFetchSlow() {
|
||||
return hosversionAtLeast(20,0,0);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ns
|
||||
|
||||
Reference in New Issue
Block a user