add gc_menu, add progress, icon, time remaining to progress bar (see full commit message).

- fix ignore distribution bit doing nothing.
- fix yati failing to parse control nca causing the transfer to abort.
- yati now uses ncm rather than ns to get the latest app version.
- improve ui::list input handling (it handles directional buttons now).
- progress bar displays speed and time remaining.
- added gc menu (taken from my gc installer nx and gci).
This commit is contained in:
ITotalJustice
2025-04-27 20:01:13 +01:00
parent f7f1254699
commit 2c2f602d14
28 changed files with 1059 additions and 930 deletions

View File

@@ -151,4 +151,30 @@ Result VerifyFixedKey(const Header& header) {
R_SUCCEED();
}
auto GetKeyGenStr(u8 key_gen) -> const char* {
switch (key_gen) {
case KeyGenerationOld_100: return "1.0.0";
case KeyGenerationOld_300: return "3.0.0";
case KeyGeneration_301: return "3.0.1";
case KeyGeneration_400: return "4.0.0";
case KeyGeneration_500: return "5.0.0";
case KeyGeneration_600: return "6.0.0";
case KeyGeneration_620: return "6.2.0";
case KeyGeneration_700: return "7.0.0";
case KeyGeneration_810: return "8.1.0";
case KeyGeneration_900: return "9.0.0";
case KeyGeneration_910: return "9.1.0";
case KeyGeneration_1210: return "12.1.0";
case KeyGeneration_1300: return "13.0.0";
case KeyGeneration_1400: return "14.0.0";
case KeyGeneration_1500: return "15.0.0";
case KeyGeneration_1600: return "16.0.0";
case KeyGeneration_1700: return "17.0.0";
case KeyGeneration_1800: return "18.0.0";
case KeyGeneration_1900: return "19.0.0";
}
return "Unknown";
}
} // namespace sphaira::nca

View File

@@ -7,16 +7,24 @@ namespace {
} // namespace
auto GetAppId(const NcmContentMetaKey& key) -> u64 {
if (key.type == NcmContentMetaType_Patch) {
return key.id ^ 0x800;
} else if (key.type == NcmContentMetaType_AddOnContent) {
return (key.id ^ 0x1000) & ~0xFFF;
auto GetAppId(u8 meta_type, u64 id) -> u64 {
if (meta_type == NcmContentMetaType_Patch) {
return id ^ 0x800;
} else if (meta_type == NcmContentMetaType_AddOnContent) {
return (id ^ 0x1000) & ~0xFFF;
} else {
return key.id;
return id;
}
}
auto GetAppId(const NcmContentMetaKey& key) -> u64 {
return GetAppId(key.type, key.id);
}
auto GetAppId(const PackagedContentMeta& meta) -> u64 {
return GetAppId(meta.meta_type, meta.title_id);
}
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id) {
bool has;
R_TRY(ncmContentStorageHas(cs, std::addressof(has), content_id));

View File

@@ -274,7 +274,7 @@ struct Yati {
Yati(ui::ProgressBox*, std::shared_ptr<source::Base>);
~Yati();
Result Setup();
Result Setup(const ConfigOverride& override);
Result InstallNca(std::span<TikCollection> tickets, NcaCollection& nca);
Result InstallCnmtNca(std::span<TikCollection> tickets, CnmtCollection& cnmt, const container::Collections& collections);
Result InstallControlNca(std::span<TikCollection> tickets, const CnmtCollection& cnmt, NcaCollection& nca);
@@ -549,7 +549,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
t->write_size = header.size;
R_TRY(ncmContentStorageSetPlaceHolderSize(std::addressof(cs), std::addressof(t->nca->placeholder_id), header.size));
if (header.distribution_type == nca::DistributionType_GameCard) {
if (!config.ignore_distribution_bit && header.distribution_type == nca::DistributionType_GameCard) {
header.distribution_type = nca::DistributionType_System;
t->nca->modified = true;
}
@@ -792,8 +792,8 @@ Yati::~Yati() {
appletSetMediaPlaybackState(false);
}
Result Yati::Setup() {
config.sd_card_install = App::GetApp()->m_install_sd.Get();
Result Yati::Setup(const ConfigOverride& override) {
config.sd_card_install = override.sd_card_install.value_or(App::GetApp()->m_install_sd.Get());
config.allow_downgrade = App::GetApp()->m_allow_downgrade.Get();
config.skip_if_already_installed = App::GetApp()->m_skip_if_already_installed.Get();
config.ticket_only = App::GetApp()->m_ticket_only.Get();
@@ -802,13 +802,13 @@ Result Yati::Setup() {
config.skip_addon = App::GetApp()->m_skip_addon.Get();
config.skip_data_patch = App::GetApp()->m_skip_data_patch.Get();
config.skip_ticket = App::GetApp()->m_skip_ticket.Get();
config.skip_nca_hash_verify = App::GetApp()->m_skip_nca_hash_verify.Get();
config.skip_rsa_header_fixed_key_verify = App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get();
config.skip_rsa_npdm_fixed_key_verify = App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get();
config.ignore_distribution_bit = App::GetApp()->m_ignore_distribution_bit.Get();
config.convert_to_standard_crypto = App::GetApp()->m_convert_to_standard_crypto.Get();
config.lower_master_key = App::GetApp()->m_lower_master_key.Get();
config.lower_system_version = App::GetApp()->m_lower_system_version.Get();
config.skip_nca_hash_verify = override.skip_nca_hash_verify.value_or(App::GetApp()->m_skip_nca_hash_verify.Get());
config.skip_rsa_header_fixed_key_verify = override.skip_rsa_header_fixed_key_verify.value_or(App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get());
config.skip_rsa_npdm_fixed_key_verify = override.skip_rsa_npdm_fixed_key_verify.value_or(App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get());
config.ignore_distribution_bit = override.ignore_distribution_bit.value_or(App::GetApp()->m_ignore_distribution_bit.Get());
config.convert_to_standard_crypto = override.convert_to_standard_crypto.value_or(App::GetApp()->m_convert_to_standard_crypto.Get());
config.lower_master_key = override.lower_master_key.value_or(App::GetApp()->m_lower_master_key.Get());
config.lower_system_version = override.lower_system_version.value_or(App::GetApp()->m_lower_system_version.Get());
storage_id = config.sd_card_install ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser;
R_TRY(source->GetOpenResult());
@@ -925,37 +925,9 @@ Result Yati::InstallCnmtNca(std::span<TikCollection> tickets, CnmtCollection& cn
R_TRY(ncmContentStorageFlushPlaceHolder(std::addressof(cs)));
R_TRY(ncmContentStorageGetPlaceHolderPath(std::addressof(cs), path, sizeof(path), std::addressof(cnmt.placeholder_id)));
FsFileSystem fs;
R_TRY(fsOpenFileSystem(std::addressof(fs), FsFileSystemType_ContentMeta, path));
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
FsDir dir;
R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir)));
ON_SCOPE_EXIT(fsDirClose(std::addressof(dir)));
s64 total_entries;
FsDirectoryEntry buf;
R_TRY(fsDirRead(std::addressof(dir), std::addressof(total_entries), 1, std::addressof(buf)));
FsFile file;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file)));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
s64 offset{};
u64 bytes_read;
ncm::PackagedContentMeta header;
R_TRY(fsFileRead(std::addressof(file), offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read)));
offset += bytes_read;
// read extended header
cnmt.extended_header.resize(header.meta_header.extended_header_size);
R_TRY(fsFileRead(std::addressof(file), offset, cnmt.extended_header.data(), cnmt.extended_header.size(), 0, std::addressof(bytes_read)));
offset += bytes_read;
// read infos.
std::vector<NcmPackagedContentInfo> infos(header.meta_header.content_count);
R_TRY(fsFileRead(std::addressof(file), offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read)));
offset += bytes_read;
std::vector<NcmPackagedContentInfo> infos;
R_TRY(ParseCnmtNca(path, header, cnmt.extended_header, infos));
for (const auto& info : infos) {
if (info.info.content_type == NcmContentType_DeltaFragment) {
@@ -1020,21 +992,15 @@ Result Yati::InstallControlNca(std::span<TikCollection> tickets, const CnmtColle
R_TRY(ncmContentStorageFlushPlaceHolder(std::addressof(cs)));
R_TRY(ncmContentStorageGetPlaceHolderPath(std::addressof(cs), path, sizeof(path), std::addressof(nca.placeholder_id)));
log_write("got control path: %s\n", path.s);
FsFileSystem fs;
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), ncm::GetAppId(cnmt.key), FsFileSystemType_ContentControl, path, FsContentAttributes_All));
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
log_write("opened control path fs: %s\n", path.s);
FsFile file;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file)));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
log_write("got control path file: %s\n", path.s);
// this can fail if it's not a valid control nca, examples are mario 3d all stars.
// there are 4 control ncas, only 1 is valid (InvalidNcaId 0x235E02).
NacpLanguageEntry entry;
u64 bytes_read;
R_TRY(fsFileRead(&file, 0, &entry, sizeof(entry), 0, &bytes_read));
pbox->SetTitle("Installing "_i18n + entry.name);
std::vector<u8> icon;
if (R_SUCCEEDED(yati::ParseControlNca(path, ncm::GetAppId(cnmt.key), &entry, sizeof(entry), &icon))) {
pbox->SetTitle(entry.name).SetImageData(icon);
} else {
log_write("\tWARNING: failed to parse control nca!\n");
}
R_SUCCEED();
}
@@ -1070,41 +1036,37 @@ Result Yati::ParseTicketsIntoCollection(std::vector<TikCollection>& tickets, con
Result Yati::GetLatestVersion(const CnmtCollection& cnmt, u32& version_out, bool& skip) {
const auto app_id = ncm::GetAppId(cnmt.key);
bool has_records;
R_TRY(nsIsAnyApplicationEntityInstalled(app_id, &has_records));
// TODO: fix this when gamecard is inserted as it will only return records
// for the gamecard...
// may have to use ncm directly to get the keys, then parse that.
version_out = cnmt.key.version;
if (has_records) {
s32 meta_count{};
R_TRY(nsCountApplicationContentMeta(app_id, &meta_count));
R_UNLESS(meta_count > 0, 0x1);
std::vector<ncm::ContentStorageRecord> records(meta_count);
s32 count;
R_TRY(ns::ListApplicationRecordContentMeta(std::addressof(ns_app), 0, app_id, records.data(), records.size(), &count));
R_UNLESS(count == records.size(), 0x1);
for (auto& record : records) {
log_write("found record: 0x%016lX type: %u version: %u\n", record.key.id, record.key.type, record.key.version);
log_write("cnmt record: 0x%016lX type: %u version: %u\n", cnmt.key.id, cnmt.key.type, cnmt.key.version);
if (record.key.id == cnmt.key.id && cnmt.key.version == record.key.version && config.skip_if_already_installed) {
log_write("skipping as already installed\n");
skip = true;
for (auto& db : ncm_db) {
s32 db_list_total;
s32 db_list_count;
std::vector<NcmContentMetaKey> keys(1);
if (R_SUCCEEDED(ncmContentMetaDatabaseList(std::addressof(db), std::addressof(db_list_total), std::addressof(db_list_count), keys.data(), keys.size(), NcmContentMetaType_Unknown, app_id, 0, UINT64_MAX, NcmContentInstallType_Full))) {
if (db_list_total != keys.size()) {
keys.resize(db_list_total);
if (keys.size()) {
R_TRY(ncmContentMetaDatabaseList(std::addressof(db), std::addressof(db_list_total), std::addressof(db_list_count), keys.data(), keys.size(), NcmContentMetaType_Unknown, app_id, 0, UINT64_MAX, NcmContentInstallType_Full));
}
}
// check if we are downgrading
if (cnmt.key.type == NcmContentMetaType_Patch) {
if (cnmt.key.type == record.key.type && cnmt.key.version < record.key.version && !config.allow_downgrade) {
log_write("skipping due to it being lower\n");
for (auto& key : keys) {
log_write("found record: %016lX type: %u version: %u\n", key.id, key.type, key.version);
if (key.id == cnmt.key.id && cnmt.key.version == key.version && config.skip_if_already_installed) {
log_write("skipping as already installed\n");
skip = true;
}
} else {
version_out = std::max(version_out, record.key.version);
// check if we are downgrading
if (cnmt.key.type == NcmContentMetaType_Patch) {
if (cnmt.key.type == key.type && cnmt.key.version < key.version && !config.allow_downgrade) {
log_write("skipping due to it being lower\n");
skip = true;
}
} else {
version_out = std::max(version_out, key.version);
}
}
}
}
@@ -1160,7 +1122,6 @@ Result Yati::RemoveInstalledNcas(const CnmtCollection& cnmt) {
s32 db_list_count;
u64 id_min = cnmt.key.id;
u64 id_max = cnmt.key.id;
std::vector<NcmContentMetaKey> keys(1);
// if installing a patch, remove all previously installed patches.
if (cnmt.key.type == NcmContentMetaType_Patch) {
@@ -1263,9 +1224,9 @@ Result Yati::RegisterNcasAndPushRecord(const CnmtCollection& cnmt, u32 latest_ve
R_SUCCEED();
}
Result InstallInternal(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections) {
Result InstallInternal(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override) {
auto yati = std::make_unique<Yati>(pbox, source);
R_TRY(yati->Setup());
R_TRY(yati->Setup(override));
std::vector<TikCollection> tickets{};
R_TRY(yati->ParseTicketsIntoCollection(tickets, collections, true));
@@ -1318,9 +1279,9 @@ Result InstallInternal(ui::ProgressBox* pbox, std::shared_ptr<source::Base> sour
R_SUCCEED();
}
Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, container::Collections collections) {
Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, container::Collections collections, const ConfigOverride& override) {
auto yati = std::make_unique<Yati>(pbox, source);
R_TRY(yati->Setup());
R_TRY(yati->Setup(override));
// not supported with stream installs (yet).
yati->config.convert_to_standard_crypto = false;
@@ -1415,40 +1376,107 @@ Result InstallInternalStream(ui::ProgressBox* pbox, std::shared_ptr<source::Base
} // namespace
Result InstallFromFile(ui::ProgressBox* pbox, FsFileSystem* fs, const fs::FsPath& path) {
return InstallFromSource(pbox, std::make_shared<source::File>(fs, path), path);
// return InstallFromSource(pbox, std::make_shared<source::StreamFile>(fs, path), path);
Result InstallFromFile(ui::ProgressBox* pbox, FsFileSystem* fs, const fs::FsPath& path, const ConfigOverride& override) {
return InstallFromSource(pbox, std::make_shared<source::File>(fs, path), path, override);
// return InstallFromSource(pbox, std::make_shared<source::StreamFile>(fs, path), path, override);
}
Result InstallFromStdioFile(ui::ProgressBox* pbox, const fs::FsPath& path) {
return InstallFromSource(pbox, std::make_shared<source::Stdio>(path), path);
Result InstallFromStdioFile(ui::ProgressBox* pbox, const fs::FsPath& path, const ConfigOverride& override) {
return InstallFromSource(pbox, std::make_shared<source::Stdio>(path), path, override);
}
Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const fs::FsPath& path) {
Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const fs::FsPath& path, const ConfigOverride& override) {
const auto ext = std::strrchr(path.s, '.');
R_UNLESS(ext, Result_ContainerNotFound);
if (!strcasecmp(ext, ".nsp") || !strcasecmp(ext, ".nsz")) {
return InstallFromContainer(pbox, std::make_unique<container::Nsp>(source));
return InstallFromContainer(pbox, std::make_unique<container::Nsp>(source), override);
} else if (!strcasecmp(ext, ".xci") || !strcasecmp(ext, ".xcz")) {
return InstallFromContainer(pbox, std::make_unique<container::Xci>(source));
return InstallFromContainer(pbox, std::make_unique<container::Xci>(source), override);
}
R_THROW(Result_ContainerNotFound);
}
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container) {
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container, const ConfigOverride& override) {
container::Collections collections;
R_TRY(container->GetCollections(collections));
return InstallFromCollections(pbox, container->GetSource(), collections);
}
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections) {
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override) {
if (source->IsStream()) {
return InstallInternalStream(pbox, source, collections);
return InstallInternalStream(pbox, source, collections, override);
} else {
return InstallInternal(pbox, source, collections);
return InstallInternal(pbox, source, collections, override);
}
}
Result ParseCnmtNca(const fs::FsPath& path, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos) {
FsFileSystem fs;
R_TRY(fsOpenFileSystem(std::addressof(fs), FsFileSystemType_ContentMeta, path));
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
FsDir dir;
R_TRY(fsFsOpenDirectory(std::addressof(fs), fs::FsPath{"/"}, FsDirOpenMode_ReadFiles, std::addressof(dir)));
ON_SCOPE_EXIT(fsDirClose(std::addressof(dir)));
s64 total_entries;
FsDirectoryEntry buf;
R_TRY(fsDirRead(std::addressof(dir), std::addressof(total_entries), 1, std::addressof(buf)));
FsFile file;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::AppendPath("/", buf.name), FsOpenMode_Read, std::addressof(file)));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
s64 offset{};
u64 bytes_read;
R_TRY(fsFileRead(std::addressof(file), offset, std::addressof(header), sizeof(header), 0, std::addressof(bytes_read)));
offset += bytes_read;
// read extended header
extended_header.resize(header.meta_header.extended_header_size);
R_TRY(fsFileRead(std::addressof(file), offset, extended_header.data(), extended_header.size(), 0, std::addressof(bytes_read)));
offset += bytes_read;
// read infos.
infos.resize(header.meta_header.content_count);
R_TRY(fsFileRead(std::addressof(file), offset, infos.data(), infos.size() * sizeof(NcmPackagedContentInfo), 0, std::addressof(bytes_read)));
offset += bytes_read;
R_SUCCEED();
}
Result ParseControlNca(const fs::FsPath& path, u64 id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out) {
FsFileSystem fs;
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), id, FsFileSystemType_ContentControl, path, FsContentAttributes_All));
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
// read nacp.
if (nacp_out) {
FsFile file;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/control.nacp"}, FsOpenMode_Read, std::addressof(file)));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
u64 bytes_read;
R_TRY(fsFileRead(&file, 0, nacp_out, nacp_size, 0, &bytes_read));
}
// read icon.
if (icon_out) {
FsFile file;
R_TRY(fsFsOpenFile(std::addressof(fs), fs::FsPath{"/icon_AmericanEnglish.dat"}, FsOpenMode_Read, std::addressof(file)));
ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
s64 size;
R_TRY(fsFileGetSize(std::addressof(file), std::addressof(size)));
icon_out->resize(size);
u64 bytes_read;
R_TRY(fsFileRead(&file, 0, icon_out->data(), icon_out->size(), 0, &bytes_read));
}
R_SUCCEED();
}
} // namespace sphaira::yati