optimise game menu for fw 20
- loading the control data is ran on its own thread, it does not block the main thread. allows for smooth scrolling like nintendos home menu. - on fw20+, sphaira manually parses the control data, rather than using ns. manually parsing takes 20-40ms, which is faster than ms which can take 50-500ms. - on fw19 and below, if the control data is not in ns cache, sphaira will manually parse the data as its twice as fast as ns. You can see how fast this is by loading the gamecard menu as that manually parses everything, and it loads the gamecard faster than the home menu
This commit is contained in:
@@ -5,12 +5,15 @@
|
|||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include "option.hpp"
|
#include "option.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace sphaira::ui::menu::game {
|
namespace sphaira::ui::menu::game {
|
||||||
|
|
||||||
enum class NacpLoadStatus {
|
enum class NacpLoadStatus {
|
||||||
// not yet attempted to be loaded.
|
// not yet attempted to be loaded.
|
||||||
None,
|
None,
|
||||||
|
// started loading.
|
||||||
|
Progress,
|
||||||
// loaded, ready to parse.
|
// loaded, ready to parse.
|
||||||
Loaded,
|
Loaded,
|
||||||
// failed to load, do not attempt to load again!
|
// failed to load, do not attempt to load again!
|
||||||
@@ -19,12 +22,11 @@ enum class NacpLoadStatus {
|
|||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
u64 app_id{};
|
u64 app_id{};
|
||||||
s64 size{};
|
|
||||||
char display_version[0x10]{};
|
char display_version[0x10]{};
|
||||||
NacpLanguageEntry lang{};
|
NacpLanguageEntry lang{};
|
||||||
int image{};
|
int image{};
|
||||||
|
|
||||||
std::unique_ptr<NsApplicationControlData> control{};
|
std::shared_ptr<NsApplicationControlData> control{};
|
||||||
u64 control_size{};
|
u64 control_size{};
|
||||||
NacpLoadStatus status{NacpLoadStatus::None};
|
NacpLoadStatus status{NacpLoadStatus::None};
|
||||||
|
|
||||||
@@ -41,6 +43,38 @@ struct Entry {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ThreadResultData {
|
||||||
|
u64 id{};
|
||||||
|
std::shared_ptr<NsApplicationControlData> control{};
|
||||||
|
u64 control_size{};
|
||||||
|
char display_version[0x10]{};
|
||||||
|
NacpLanguageEntry lang{};
|
||||||
|
NacpLoadStatus status{NacpLoadStatus::None};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThreadData {
|
||||||
|
ThreadData();
|
||||||
|
|
||||||
|
auto IsRunning() const -> bool;
|
||||||
|
void Run();
|
||||||
|
void Close();
|
||||||
|
void Push(u64 id);
|
||||||
|
void Push(std::span<const Entry> entries);
|
||||||
|
void Pop(std::vector<ThreadResultData>& out);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UEvent m_uevent{};
|
||||||
|
Mutex m_mutex_id{};
|
||||||
|
Mutex m_mutex_result{};
|
||||||
|
|
||||||
|
// app_ids pushed to the queue, signal uevent when pushed.
|
||||||
|
std::vector<u64> m_ids{};
|
||||||
|
// control data pushed to the queue.
|
||||||
|
std::vector<ThreadResultData> m_result{};
|
||||||
|
|
||||||
|
std::atomic_bool m_running{};
|
||||||
|
};
|
||||||
|
|
||||||
enum SortType {
|
enum SortType {
|
||||||
SortType_Updated,
|
SortType_Updated,
|
||||||
};
|
};
|
||||||
@@ -79,6 +113,9 @@ private:
|
|||||||
bool m_is_reversed{};
|
bool m_is_reversed{};
|
||||||
bool m_dirty{};
|
bool m_dirty{};
|
||||||
|
|
||||||
|
ThreadData m_thread_data{};
|
||||||
|
Thread m_thread{};
|
||||||
|
|
||||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
|
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
|
||||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||||
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
|
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <switch.h>
|
#include "fs.hpp"
|
||||||
#include "keys.hpp"
|
#include "keys.hpp"
|
||||||
|
#include "ncm.hpp"
|
||||||
|
#include <switch.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace sphaira::nca {
|
namespace sphaira::nca {
|
||||||
|
|
||||||
@@ -216,6 +219,10 @@ Result DecryptKeak(const keys::Keys& keys, Header& header);
|
|||||||
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
||||||
Result VerifyFixedKey(const Header& header);
|
Result VerifyFixedKey(const Header& header);
|
||||||
|
|
||||||
|
// helpers that parse an nca.
|
||||||
|
Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos);
|
||||||
|
Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector<u8>* icon_out = nullptr);
|
||||||
|
|
||||||
auto GetKeyGenStr(u8 key_gen) -> const char*;
|
auto GetKeyGenStr(u8 key_gen) -> const char*;
|
||||||
|
|
||||||
} // namespace sphaira::nca
|
} // namespace sphaira::nca
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include "source/base.hpp"
|
#include "source/base.hpp"
|
||||||
#include "container/base.hpp"
|
#include "container/base.hpp"
|
||||||
#include "nx/ncm.hpp"
|
|
||||||
#include "ui/progress_box.hpp"
|
#include "ui/progress_box.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -136,7 +135,4 @@ Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr<source::Base> so
|
|||||||
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container, const ConfigOverride& override = {});
|
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container, const ConfigOverride& override = {});
|
||||||
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override = {});
|
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override = {});
|
||||||
|
|
||||||
Result ParseCnmtNca(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos);
|
|
||||||
Result ParseControlNca(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector<u8>* icon_out = nullptr);
|
|
||||||
|
|
||||||
} // namespace sphaira::yati
|
} // namespace sphaira::yati
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "i18n.hpp"
|
#include "i18n.hpp"
|
||||||
#include "yati/nx/ncm.hpp"
|
#include "yati/nx/ncm.hpp"
|
||||||
|
#include "yati/nx/nca.hpp"
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -19,20 +20,27 @@
|
|||||||
namespace sphaira::ui::menu::game {
|
namespace sphaira::ui::menu::game {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// thank you Shchmue ^^
|
constexpr NcmStorageId NCM_STORAGE_IDS[]{
|
||||||
struct ApplicationOccupiedSizeEntry {
|
NcmStorageId_BuiltInUser,
|
||||||
u8 storageId;
|
NcmStorageId_SdCard,
|
||||||
u8 padding[0x7];
|
|
||||||
u64 sizeApplication;
|
|
||||||
u64 sizePatch;
|
|
||||||
u64 sizeAddOnContent;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ApplicationOccupiedSize {
|
NcmContentStorage ncm_cs[2];
|
||||||
ApplicationOccupiedSizeEntry entry[4];
|
NcmContentMetaDatabase ncm_db[2];
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(ApplicationOccupiedSize) == sizeof(NsApplicationOccupiedSize));
|
auto& GetNcmCs(u8 storage_id) {
|
||||||
|
if (storage_id == NcmStorageId_SdCard) {
|
||||||
|
return ncm_cs[1];
|
||||||
|
}
|
||||||
|
return ncm_cs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& GetNcmDb(u8 storage_id) {
|
||||||
|
if (storage_id == NcmStorageId_SdCard) {
|
||||||
|
return ncm_db[1];
|
||||||
|
}
|
||||||
|
return ncm_db[0];
|
||||||
|
}
|
||||||
|
|
||||||
using MetaEntries = std::vector<NsApplicationContentMetaStatus>;
|
using MetaEntries = std::vector<NsApplicationContentMetaStatus>;
|
||||||
|
|
||||||
@@ -48,19 +56,23 @@ Result Notify(Result rc, const std::string& error_message) {
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetMetaEntries(const Entry& e, MetaEntries& out) {
|
Result GetMetaEntries(u64 id, MetaEntries& out) {
|
||||||
s32 count;
|
s32 count;
|
||||||
R_TRY(nsCountApplicationContentMeta(e.app_id, &count));
|
R_TRY(nsCountApplicationContentMeta(id, &count));
|
||||||
|
|
||||||
out.resize(count);
|
out.resize(count);
|
||||||
R_TRY(nsListApplicationContentMetaStatus(e.app_id, 0, out.data(), out.size(), &count));
|
R_TRY(nsListApplicationContentMetaStatus(id, 0, out.data(), out.size(), &count));
|
||||||
|
|
||||||
out.resize(count);
|
out.resize(count);
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result GetMetaEntries(const Entry& e, MetaEntries& out) {
|
||||||
|
return GetMetaEntries(e.app_id, out);
|
||||||
|
}
|
||||||
|
|
||||||
// also sets the status to error.
|
// also sets the status to error.
|
||||||
void FakeNacpEntry(Entry& e) {
|
void FakeNacpEntry(ThreadResultData& e) {
|
||||||
e.status = NacpLoadStatus::Error;
|
e.status = NacpLoadStatus::Error;
|
||||||
// fake the nacp entry
|
// fake the nacp entry
|
||||||
std::strcpy(e.lang.name, "Corrupted");
|
std::strcpy(e.lang.name, "Corrupted");
|
||||||
@@ -71,34 +83,113 @@ void FakeNacpEntry(Entry& e) {
|
|||||||
|
|
||||||
bool LoadControlImage(Entry& e) {
|
bool LoadControlImage(Entry& e) {
|
||||||
if (!e.image && e.control) {
|
if (!e.image && e.control) {
|
||||||
|
TimeStamp ts;
|
||||||
const auto jpeg_size = e.control_size - sizeof(NacpStruct);
|
const auto jpeg_size = e.control_size - sizeof(NacpStruct);
|
||||||
e.image = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, jpeg_size);
|
e.image = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, jpeg_size);
|
||||||
e.control.reset();
|
e.control.reset();
|
||||||
|
log_write("\t\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result LoadControlManual(u64 id, ThreadResultData& data) {
|
||||||
|
TimeStamp ts;
|
||||||
|
|
||||||
|
MetaEntries entries;
|
||||||
|
R_TRY(GetMetaEntries(id, entries));
|
||||||
|
R_UNLESS(!entries.empty(), 0x1);
|
||||||
|
|
||||||
|
const auto& ee = entries.back();
|
||||||
|
if (ee.storageID != NcmStorageId_SdCard && ee.storageID != NcmStorageId_BuiltInUser) {
|
||||||
|
return 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& db = GetNcmDb(ee.storageID);
|
||||||
|
auto& cs = GetNcmCs(ee.storageID);
|
||||||
|
|
||||||
|
NcmContentMetaKey key;
|
||||||
|
R_TRY(ncmContentMetaDatabaseGetLatestContentMetaKey(&db, &key, ee.application_id));
|
||||||
|
|
||||||
|
NcmContentId content_id;
|
||||||
|
R_TRY(ncmContentMetaDatabaseGetContentIdByType(&db, &content_id, &key, NcmContentType_Control));
|
||||||
|
|
||||||
|
u64 program_id;
|
||||||
|
R_TRY(ncmContentStorageGetProgramId(&cs, &program_id, &content_id, FsContentAttributes_All));
|
||||||
|
|
||||||
|
fs::FsPath path;
|
||||||
|
R_TRY(ncmContentStorageGetPath(&cs, path, sizeof(path), &content_id));
|
||||||
|
|
||||||
|
std::vector<u8> icon;
|
||||||
|
R_TRY(nca::ParseControl(path, program_id, &data.control->nacp, sizeof(data.control->nacp), &icon));
|
||||||
|
std::memcpy(data.control->icon, icon.data(), icon.size());
|
||||||
|
|
||||||
|
data.control_size = sizeof(data.control->nacp) + icon.size();
|
||||||
|
log_write("\t\t[manual control] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LoadControlEntry(u64 id) -> ThreadResultData {
|
||||||
|
ThreadResultData data{};
|
||||||
|
data.id = id;
|
||||||
|
data.control = std::make_shared<NsApplicationControlData>();
|
||||||
|
data.status = NacpLoadStatus::Error;
|
||||||
|
|
||||||
|
bool manual_load = false;
|
||||||
|
if (hosversionBefore(20,0,0)) {
|
||||||
|
TimeStamp ts;
|
||||||
|
if (R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_CacheOnly, id, data.control.get(), sizeof(NsApplicationControlData), &data.control_size))) {
|
||||||
|
manual_load = false;
|
||||||
|
log_write("\t\t[ns control cache] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manual_load) {
|
||||||
|
manual_load = R_SUCCEEDED(LoadControlManual(id, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc{};
|
||||||
|
if (!manual_load) {
|
||||||
|
TimeStamp ts;
|
||||||
|
rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, id, data.control.get(), sizeof(NsApplicationControlData), &data.control_size);
|
||||||
|
log_write("\t\t[ns control storage] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
NacpLanguageEntry* lang{};
|
||||||
|
if (R_SUCCEEDED(rc = nsGetApplicationDesiredLanguage(&data.control->nacp, &lang)) && lang) {
|
||||||
|
data.lang = *lang;
|
||||||
|
std::memcpy(data.display_version, data.control->nacp.display_version, sizeof(data.display_version));
|
||||||
|
data.status = NacpLoadStatus::Loaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
FakeNacpEntry(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadResultIntoEntry(Entry& e, const ThreadResultData& result) {
|
||||||
|
e.status = result.status;
|
||||||
|
e.control = result.control;
|
||||||
|
e.control_size = result.control_size;
|
||||||
|
std::memcpy(e.display_version, result.display_version, sizeof(result.display_version));
|
||||||
|
e.lang = result.lang;
|
||||||
|
e.status = result.status;
|
||||||
|
}
|
||||||
|
|
||||||
void LoadControlEntry(Entry& e, bool force_image_load = false) {
|
void LoadControlEntry(Entry& e, bool force_image_load = false) {
|
||||||
if (e.status == NacpLoadStatus::None) {
|
if (e.status == NacpLoadStatus::None) {
|
||||||
e.control = std::make_unique<NsApplicationControlData>();
|
const auto result = LoadControlEntry(e.app_id);
|
||||||
if (R_FAILED(nsGetApplicationControlData(NsApplicationControlSource_Storage, e.app_id, e.control.get(), sizeof(NsApplicationControlData), &e.control_size))) {
|
LoadResultIntoEntry(e, result);
|
||||||
FakeNacpEntry(e);
|
}
|
||||||
} else {
|
|
||||||
NacpLanguageEntry* lang{};
|
|
||||||
if (R_FAILED(nsGetApplicationDesiredLanguage(&e.control->nacp, &lang)) || !lang) {
|
|
||||||
FakeNacpEntry(e);
|
|
||||||
} else {
|
|
||||||
e.lang = *lang;
|
|
||||||
std::memcpy(e.display_version, e.control->nacp.display_version, sizeof(e.display_version));
|
|
||||||
e.status = NacpLoadStatus::Loaded;
|
|
||||||
|
|
||||||
if (force_image_load) {
|
if (force_image_load && e.status == NacpLoadStatus::Loaded) {
|
||||||
LoadControlImage(e);
|
LoadControlImage(e);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,8 +203,91 @@ void LaunchEntry(const Entry& e) {
|
|||||||
Notify(rc, "Failed to launch application");
|
Notify(rc, "Failed to launch application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ThreadFunc(void* user) {
|
||||||
|
auto data = static_cast<ThreadData*>(user);
|
||||||
|
|
||||||
|
while (data->IsRunning()) {
|
||||||
|
data->Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
ThreadData::ThreadData() {
|
||||||
|
ueventCreate(&m_uevent, true);
|
||||||
|
mutexInit(&m_mutex_id);
|
||||||
|
mutexInit(&m_mutex_result);
|
||||||
|
m_running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ThreadData::IsRunning() const -> bool {
|
||||||
|
return m_running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadData::Run() {
|
||||||
|
while (IsRunning()) {
|
||||||
|
const auto waiter = waiterForUEvent(&m_uevent);
|
||||||
|
waitSingle(waiter, UINT64_MAX);
|
||||||
|
|
||||||
|
if (!IsRunning()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u64> ids;
|
||||||
|
{
|
||||||
|
mutexLock(&m_mutex_id);
|
||||||
|
ON_SCOPE_EXIT(mutexUnlock(&m_mutex_id));
|
||||||
|
std::swap(ids, m_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u64 i = 0; i < std::size(ids); i++) {
|
||||||
|
if (!IsRunning()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep after every other entry loaded.
|
||||||
|
if (i) {
|
||||||
|
svcSleepThread(1e+6*2); // 2ms
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = LoadControlEntry(ids[i]);
|
||||||
|
mutexLock(&m_mutex_result);
|
||||||
|
ON_SCOPE_EXIT(mutexUnlock(&m_mutex_result));
|
||||||
|
m_result.emplace_back(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadData::Close() {
|
||||||
|
m_running = false;
|
||||||
|
ueventSignal(&m_uevent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadData::Push(u64 id) {
|
||||||
|
mutexLock(&m_mutex_id);
|
||||||
|
ON_SCOPE_EXIT(mutexUnlock(&m_mutex_id));
|
||||||
|
|
||||||
|
const auto it = std::find(m_ids.begin(), m_ids.end(), id);
|
||||||
|
if (it == m_ids.end()) {
|
||||||
|
m_ids.emplace_back(id);
|
||||||
|
ueventSignal(&m_uevent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadData::Push(std::span<const Entry> entries) {
|
||||||
|
for (auto& e : entries) {
|
||||||
|
Push(e.app_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadData::Pop(std::vector<ThreadResultData>& out) {
|
||||||
|
mutexLock(&m_mutex_result);
|
||||||
|
ON_SCOPE_EXIT(mutexUnlock(&m_mutex_result));
|
||||||
|
|
||||||
|
std::swap(out, m_result);
|
||||||
|
m_result.clear();
|
||||||
|
}
|
||||||
|
|
||||||
Menu::Menu() : grid::Menu{"Games"_i18n} {
|
Menu::Menu() : grid::Menu{"Games"_i18n} {
|
||||||
this->SetActions(
|
this->SetActions(
|
||||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||||
@@ -256,12 +430,30 @@ Menu::Menu() : grid::Menu{"Games"_i18n} {
|
|||||||
|
|
||||||
nsInitialize();
|
nsInitialize();
|
||||||
nsGetApplicationRecordUpdateSystemEvent(&m_event);
|
nsGetApplicationRecordUpdateSystemEvent(&m_event);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
||||||
|
ncmOpenContentMetaDatabase(std::addressof(ncm_db[i]), NCM_STORAGE_IDS[i]);
|
||||||
|
ncmOpenContentStorage(std::addressof(ncm_cs[i]), NCM_STORAGE_IDS[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
threadCreate(&m_thread, ThreadFunc, &m_thread_data, nullptr, 1024*32, 0x30, 1);
|
||||||
|
threadStart(&m_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
|
m_thread_data.Close();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < std::size(NCM_STORAGE_IDS); i++) {
|
||||||
|
ncmContentMetaDatabaseClose(std::addressof(ncm_db[i]));
|
||||||
|
ncmContentStorageClose(std::addressof(ncm_cs[i]));
|
||||||
|
}
|
||||||
|
|
||||||
FreeEntries();
|
FreeEntries();
|
||||||
eventClose(&m_event);
|
eventClose(&m_event);
|
||||||
nsExit();
|
nsExit();
|
||||||
|
|
||||||
|
threadWaitForExit(&m_thread);
|
||||||
|
threadClose(&m_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||||
@@ -293,12 +485,26 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
const int image_load_max = 2;
|
const int image_load_max = 2;
|
||||||
int image_load_count = 0;
|
int image_load_count = 0;
|
||||||
|
|
||||||
|
std::vector<ThreadResultData> data;
|
||||||
|
m_thread_data.Pop(data);
|
||||||
|
|
||||||
|
for (const auto& d : data) {
|
||||||
|
const auto it = std::find_if(m_entries.begin(), m_entries.end(), [&d](auto& e) {
|
||||||
|
return e.app_id == d.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it != m_entries.end()) {
|
||||||
|
LoadResultIntoEntry(*it, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
||||||
// const auto& [x, y, w, h] = v;
|
// const auto& [x, y, w, h] = v;
|
||||||
auto& e = m_entries[pos];
|
auto& e = m_entries[pos];
|
||||||
|
|
||||||
if (e.status == NacpLoadStatus::None) {
|
if (e.status == NacpLoadStatus::None) {
|
||||||
LoadControlEntry(e);
|
m_thread_data.Push(e.app_id);
|
||||||
|
e.status = NacpLoadStatus::Progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lazy load image
|
// lazy load image
|
||||||
@@ -377,20 +583,7 @@ void Menu::ScanHomebrew() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
s64 size{};
|
m_entries.emplace_back(e.application_id);
|
||||||
// code for sorting by size, it's too slow however...
|
|
||||||
#if 0
|
|
||||||
ApplicationOccupiedSize occupied_size;
|
|
||||||
if (R_SUCCEEDED(nsCalculateApplicationOccupiedSize(e.application_id, (NsApplicationOccupiedSize*)&occupied_size))) {
|
|
||||||
for (auto& s : occupied_size.entry) {
|
|
||||||
size += s.sizeApplication;
|
|
||||||
size += s.sizePatch;
|
|
||||||
size += s.sizeAddOnContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_entries.emplace_back(e.application_id, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += record_count;
|
offset += record_count;
|
||||||
@@ -401,6 +594,8 @@ void Menu::ScanHomebrew() {
|
|||||||
log_write("games found: %zu time_taken: %.2f seconds %zu ms %zu ns\n", m_entries.size(), ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
|
log_write("games found: %zu time_taken: %.2f seconds %zu ms %zu ns\n", m_entries.size(), ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
|
||||||
this->Sort();
|
this->Sort();
|
||||||
SetIndex(0);
|
SetIndex(0);
|
||||||
|
|
||||||
|
// m_thread_data.Push(m_entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::Sort() {
|
void Menu::Sort() {
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ Result Menu::GcMount() {
|
|||||||
std::vector<u8> extended_header;
|
std::vector<u8> extended_header;
|
||||||
std::vector<NcmPackagedContentInfo> infos;
|
std::vector<NcmPackagedContentInfo> infos;
|
||||||
const auto path = BuildGcPath(e.name, &m_handle);
|
const auto path = BuildGcPath(e.name, &m_handle);
|
||||||
R_TRY(yati::ParseCnmtNca(path, 0, header, extended_header, infos));
|
R_TRY(nca::ParseCnmt(path, 0, header, extended_header, infos));
|
||||||
|
|
||||||
u8 key_gen;
|
u8 key_gen;
|
||||||
FsRightsId rights_id;
|
FsRightsId rights_id;
|
||||||
@@ -511,7 +511,10 @@ void Menu::OnChangeIndex(s64 new_index) {
|
|||||||
fsGetProgramId(&program_id, path, FsContentAttributes_All);
|
fsGetProgramId(&program_id, path, FsContentAttributes_All);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (R_SUCCEEDED(yati::ParseControlNca(path, program_id, &nacp, sizeof(nacp), &icon))) {
|
TimeStamp ts;
|
||||||
|
if (R_SUCCEEDED(nca::ParseControl(path, program_id, &nacp, sizeof(nacp), &icon))) {
|
||||||
|
log_write("\t\tnca::ParseControl(): %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
|
||||||
log_write("managed to parse control nca %s\n", path.s);
|
log_write("managed to parse control nca %s\n", path.s);
|
||||||
NacpLanguageEntry* lang_entry{};
|
NacpLanguageEntry* lang_entry{};
|
||||||
nacpGetLanguageEntry(&nacp, &lang_entry);
|
nacpGetLanguageEntry(&nacp, &lang_entry);
|
||||||
|
|||||||
@@ -151,6 +151,74 @@ Result VerifyFixedKey(const Header& header) {
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos) {
|
||||||
|
FsFileSystem fs;
|
||||||
|
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All));
|
||||||
|
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 ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out) {
|
||||||
|
FsFileSystem fs;
|
||||||
|
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_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) {
|
||||||
|
// todo: use matching icon based on the language version.
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
auto GetKeyGenStr(u8 key_gen) -> const char* {
|
auto GetKeyGenStr(u8 key_gen) -> const char* {
|
||||||
switch (key_gen) {
|
switch (key_gen) {
|
||||||
case KeyGenerationOld_100: return "1.0.0";
|
case KeyGenerationOld_100: return "1.0.0";
|
||||||
|
|||||||
@@ -929,7 +929,7 @@ Result Yati::InstallNca(std::span<TikCollection> tickets, NcaCollection& nca) {
|
|||||||
} else if (nca.header.content_type == nca::ContentType_Control) {
|
} else if (nca.header.content_type == nca::ContentType_Control) {
|
||||||
NacpLanguageEntry entry;
|
NacpLanguageEntry entry;
|
||||||
std::vector<u8> icon;
|
std::vector<u8> icon;
|
||||||
R_TRY(yati::ParseControlNca(path, nca.header.program_id, &entry, sizeof(entry), &icon));
|
R_TRY(nca::ParseControl(path, nca.header.program_id, &entry, sizeof(entry), &icon));
|
||||||
pbox->SetTitle(entry.name).SetImageData(icon);
|
pbox->SetTitle(entry.name).SetImageData(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,7 +949,7 @@ Result Yati::InstallCnmtNca(std::span<TikCollection> tickets, CnmtCollection& cn
|
|||||||
|
|
||||||
ncm::PackagedContentMeta header;
|
ncm::PackagedContentMeta header;
|
||||||
std::vector<NcmPackagedContentInfo> infos;
|
std::vector<NcmPackagedContentInfo> infos;
|
||||||
R_TRY(ParseCnmtNca(path, cnmt.header.program_id, header, cnmt.extended_header, infos));
|
R_TRY(nca::ParseCnmt(path, cnmt.header.program_id, header, cnmt.extended_header, infos));
|
||||||
|
|
||||||
for (const auto& packed_info : infos) {
|
for (const auto& packed_info : infos) {
|
||||||
const auto& info = packed_info.info;
|
const auto& info = packed_info.info;
|
||||||
@@ -1418,71 +1418,4 @@ Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Bas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ParseCnmtNca(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos) {
|
|
||||||
FsFileSystem fs;
|
|
||||||
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentMeta, path, FsContentAttributes_All));
|
|
||||||
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 program_id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out) {
|
|
||||||
FsFileSystem fs;
|
|
||||||
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_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
|
} // namespace sphaira::yati
|
||||||
|
|||||||
Reference in New Issue
Block a user