add save creation. add loading sys-tweek entries. add title cache delete.
This commit is contained in:
@@ -1,9 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// #include <optional>
|
|
||||||
// #include <variant>
|
|
||||||
// #include <list>
|
|
||||||
// #include <string>
|
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
#include <span>
|
||||||
@@ -47,8 +43,7 @@ enum class NacpLoadStatus {
|
|||||||
|
|
||||||
struct ThreadResultData {
|
struct ThreadResultData {
|
||||||
u64 id{};
|
u64 id{};
|
||||||
std::shared_ptr<NsApplicationControlData> control{};
|
std::vector<u8> icon;
|
||||||
u64 jpeg_size{};
|
|
||||||
NacpLanguageEntry lang{};
|
NacpLanguageEntry lang{};
|
||||||
NacpLoadStatus status{NacpLoadStatus::None};
|
NacpLoadStatus status{NacpLoadStatus::None};
|
||||||
};
|
};
|
||||||
@@ -60,23 +55,11 @@ Result Init();
|
|||||||
void Exit();
|
void Exit();
|
||||||
|
|
||||||
// adds new entry to queue.
|
// adds new entry to queue.
|
||||||
void Push(u64 app_id);
|
void PushAsync(u64 app_id);
|
||||||
// adds array of entries to queue.
|
|
||||||
void Push(std::span<const u64> app_ids);
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// removes entry from the queue into out.
|
|
||||||
void Pop(u64 app_id, std::vector<ThreadResultData>& out);
|
|
||||||
// removes array of entries from the queue into out.
|
|
||||||
void Pop(std::span<const u64> app_ids, std::vector<ThreadResultData>& out);
|
|
||||||
// removes all entries from the queue into out.
|
|
||||||
void Pop(std::vector<ThreadResultData>& out);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// gets entry without removing it from the queue.
|
// gets entry without removing it from the queue.
|
||||||
auto Get(u64 app_id) -> std::optional<ThreadResultData>;
|
auto GetAsync(u64 app_id) -> std::shared_ptr<ThreadResultData>;
|
||||||
// gets array of entries without removing it from the queue.
|
// single threaded title info fetch.
|
||||||
void Get(std::span<const u64> app_ids, std::vector<ThreadResultData>& out);
|
auto Get(u64 app_id, bool* cached = nullptr) -> std::shared_ptr<ThreadResultData>;
|
||||||
|
|
||||||
auto GetNcmCs(u8 storage_id) -> NcmContentStorage&;
|
auto GetNcmCs(u8 storage_id) -> NcmContentStorage&;
|
||||||
auto GetNcmDb(u8 storage_id) -> NcmContentMetaDatabase&;
|
auto GetNcmDb(u8 storage_id) -> NcmContentMetaDatabase&;
|
||||||
@@ -87,10 +70,10 @@ Result GetMetaEntries(u64 id, MetaEntries& out, u32 flags = ContentFlag_All);
|
|||||||
// returns the nca path of a control nca.
|
// returns the nca path of a control nca.
|
||||||
Result GetControlPathFromStatus(const NsApplicationContentMetaStatus& status, u64* out_program_id, fs::FsPath* out_path);
|
Result GetControlPathFromStatus(const NsApplicationContentMetaStatus& status, u64* out_program_id, fs::FsPath* out_path);
|
||||||
|
|
||||||
// single threaded title info fetch.
|
|
||||||
auto LoadControlEntry(u64 id, bool* cached = nullptr) -> ThreadResultData;
|
|
||||||
|
|
||||||
// taken from nxdumptool.
|
// taken from nxdumptool.
|
||||||
void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
|
void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
|
||||||
|
|
||||||
|
// /atmosphere/contents/xxx
|
||||||
|
auto GetContentsPath(u64 app_id) -> fs::FsPath;
|
||||||
|
|
||||||
} // namespace sphaira::title
|
} // namespace sphaira::title
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ struct Entry {
|
|||||||
int image{};
|
int image{};
|
||||||
bool selected{};
|
bool selected{};
|
||||||
|
|
||||||
std::shared_ptr<NsApplicationControlData> control{};
|
std::shared_ptr<title::ThreadResultData> info{};
|
||||||
u64 jpeg_size{};
|
|
||||||
title::NacpLoadStatus status{title::NacpLoadStatus::None};
|
title::NacpLoadStatus status{title::NacpLoadStatus::None};
|
||||||
|
|
||||||
auto GetName() const -> const char* {
|
auto GetName() const -> const char* {
|
||||||
@@ -83,6 +82,7 @@ private:
|
|||||||
|
|
||||||
void DeleteGames();
|
void DeleteGames();
|
||||||
void DumpGames(u32 flags);
|
void DumpGames(u32 flags);
|
||||||
|
void CreateSaves(AccountUid uid);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr inline const char* INI_SECTION = "games";
|
static constexpr inline const char* INI_SECTION = "games";
|
||||||
|
|||||||
@@ -151,8 +151,7 @@ struct ApplicationEntry {
|
|||||||
u64 app_id{};
|
u64 app_id{};
|
||||||
u32 version{};
|
u32 version{};
|
||||||
u8 key_gen{};
|
u8 key_gen{};
|
||||||
std::shared_ptr<NsApplicationControlData> control{};
|
std::vector<u8> icon;
|
||||||
u64 jpeg_size{};
|
|
||||||
NacpLanguageEntry lang_entry{};
|
NacpLanguageEntry lang_entry{};
|
||||||
|
|
||||||
std::vector<GcCollections> application{};
|
std::vector<GcCollections> application{};
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ struct Entry final : FsSaveDataInfo {
|
|||||||
int image{};
|
int image{};
|
||||||
bool selected{};
|
bool selected{};
|
||||||
|
|
||||||
std::shared_ptr<NsApplicationControlData> control{};
|
std::shared_ptr<title::ThreadResultData> info{};
|
||||||
u64 jpeg_size{};
|
|
||||||
title::NacpLoadStatus status{title::NacpLoadStatus::None};
|
title::NacpLoadStatus status{title::NacpLoadStatus::None};
|
||||||
|
|
||||||
auto GetName() const -> const char* {
|
auto GetName() const -> const char* {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include <nxtc.h>
|
#include <nxtc.h>
|
||||||
|
#include <minIni.h>
|
||||||
|
|
||||||
namespace sphaira::title {
|
namespace sphaira::title {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -25,17 +26,9 @@ struct ThreadData {
|
|||||||
void Run();
|
void Run();
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
void Push(u64 id);
|
void PushAsync(u64 id);
|
||||||
void Push(std::span<const u64> app_ids);
|
auto GetAsync(u64 app_id) -> std::shared_ptr<ThreadResultData>;
|
||||||
|
auto Get(u64 app_id, bool* cached = nullptr) -> std::shared_ptr<ThreadResultData>;
|
||||||
#if 0
|
|
||||||
auto Pop(u64 app_id) -> std::optional<ThreadResultData>;
|
|
||||||
void Pop(std::span<const u64> app_ids, std::vector<ThreadResultData>& out);
|
|
||||||
void PopAll(std::vector<ThreadResultData>& out);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto Get(u64 app_id) -> std::optional<ThreadResultData>;
|
|
||||||
void Get(std::span<const u64> app_ids, std::vector<ThreadResultData>& out);
|
|
||||||
|
|
||||||
auto IsRunning() const -> bool {
|
auto IsRunning() const -> bool {
|
||||||
return m_running;
|
return m_running;
|
||||||
@@ -46,6 +39,7 @@ struct ThreadData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
fs::FsNativeSd m_fs{};
|
||||||
UEvent m_uevent{};
|
UEvent m_uevent{};
|
||||||
Mutex m_mutex_id{};
|
Mutex m_mutex_id{};
|
||||||
Mutex m_mutex_result{};
|
Mutex m_mutex_result{};
|
||||||
@@ -54,7 +48,7 @@ private:
|
|||||||
// app_ids pushed to the queue, signal uevent when pushed.
|
// app_ids pushed to the queue, signal uevent when pushed.
|
||||||
std::vector<u64> m_ids{};
|
std::vector<u64> m_ids{};
|
||||||
// control data pushed to the queue.
|
// control data pushed to the queue.
|
||||||
std::vector<ThreadResultData> m_result{};
|
std::vector<std::shared_ptr<ThreadResultData>> m_result{};
|
||||||
|
|
||||||
std::atomic_bool m_running{};
|
std::atomic_bool m_running{};
|
||||||
};
|
};
|
||||||
@@ -113,44 +107,15 @@ auto& GetNcmEntry(u8 storage_id) {
|
|||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
// taken from nxtc
|
|
||||||
constexpr u8 g_nacpLangTable[SetLanguage_Total] = {
|
|
||||||
[SetLanguage_JA] = 2,
|
|
||||||
[SetLanguage_ENUS] = 0,
|
|
||||||
[SetLanguage_FR] = 3,
|
|
||||||
[SetLanguage_DE] = 4,
|
|
||||||
[SetLanguage_IT] = 7,
|
|
||||||
[SetLanguage_ES] = 6,
|
|
||||||
[SetLanguage_ZHCN] = 14,
|
|
||||||
[SetLanguage_KO] = 12,
|
|
||||||
[SetLanguage_NL] = 8,
|
|
||||||
[SetLanguage_PT] = 10,
|
|
||||||
[SetLanguage_RU] = 11,
|
|
||||||
[SetLanguage_ZHTW] = 13,
|
|
||||||
[SetLanguage_ENGB] = 1,
|
|
||||||
[SetLanguage_FRCA] = 9,
|
|
||||||
[SetLanguage_ES419] = 5,
|
|
||||||
[SetLanguage_ZHHANS] = 14,
|
|
||||||
[SetLanguage_ZHHANT] = 13,
|
|
||||||
[SetLanguage_PTBR] = 15
|
|
||||||
};
|
|
||||||
|
|
||||||
auto GetNacpLangEntryIndex() -> u8 {
|
|
||||||
SetLanguage lang{SetLanguage_ENUS};
|
|
||||||
nxtcGetCacheLanguage(&lang);
|
|
||||||
return g_nacpLangTable[lang];
|
|
||||||
}
|
|
||||||
|
|
||||||
// also sets the status to error.
|
// also sets the status to error.
|
||||||
void FakeNacpEntry(ThreadResultData& e) {
|
void FakeNacpEntry(std::shared_ptr<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");
|
||||||
std::strcpy(e.lang.author, "Corrupted");
|
std::strcpy(e->lang.author, "Corrupted");
|
||||||
e.control.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result LoadControlManual(u64 id, ThreadResultData& data) {
|
Result LoadControlManual(u64 id, NacpStruct& nacp, std::shared_ptr<ThreadResultData>& data) {
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
|
|
||||||
MetaEntries entries;
|
MetaEntries entries;
|
||||||
@@ -160,14 +125,9 @@ Result LoadControlManual(u64 id, ThreadResultData& data) {
|
|||||||
u64 program_id;
|
u64 program_id;
|
||||||
fs::FsPath path;
|
fs::FsPath path;
|
||||||
R_TRY(GetControlPathFromStatus(entries.back(), &program_id, &path));
|
R_TRY(GetControlPathFromStatus(entries.back(), &program_id, &path));
|
||||||
|
R_TRY(nca::ParseControl(path, program_id, &nacp, sizeof(nacp), &data->icon));
|
||||||
|
|
||||||
std::vector<u8> icon;
|
|
||||||
R_TRY(nca::ParseControl(path, program_id, &data.control->nacp.lang[GetNacpLangEntryIndex()], sizeof(NacpLanguageEntry), &icon));
|
|
||||||
std::memcpy(data.control->icon, icon.data(), icon.size());
|
|
||||||
|
|
||||||
data.jpeg_size = icon.size();
|
|
||||||
log_write("\t\t[manual control] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
log_write("\t\t[manual control] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +139,10 @@ ThreadData::ThreadData(bool title_cache) : m_title_cache{title_cache} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ThreadData::Run() {
|
void ThreadData::Run() {
|
||||||
|
TimeStamp ts{};
|
||||||
|
bool cached{true};
|
||||||
const auto waiter = waiterForUEvent(&m_uevent);
|
const auto waiter = waiterForUEvent(&m_uevent);
|
||||||
|
|
||||||
while (IsRunning()) {
|
while (IsRunning()) {
|
||||||
const auto rc = waitSingle(waiter, 3e+9);
|
const auto rc = waitSingle(waiter, 3e+9);
|
||||||
|
|
||||||
@@ -204,16 +167,15 @@ void ThreadData::Run() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cached{};
|
// sleep after every other entry loaded.
|
||||||
const auto result = LoadControlEntry(ids[i], &cached);
|
const auto elapsed = (s64)2e+6 - (s64)ts.GetNs();
|
||||||
|
if (!cached && elapsed > 0) {
|
||||||
if (!cached) {
|
svcSleepThread(elapsed);
|
||||||
// sleep after every other entry loaded.
|
|
||||||
svcSleepThread(2e+6); // 2ms
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SCOPED_MUTEX(&m_mutex_result);
|
// loads new entry into cache.
|
||||||
m_result.emplace_back(result);
|
std::ignore = Get(ids[i], &cached);
|
||||||
|
ts.Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,13 +185,13 @@ void ThreadData::Close() {
|
|||||||
ueventSignal(&m_uevent);
|
ueventSignal(&m_uevent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadData::Push(u64 id) {
|
void ThreadData::PushAsync(u64 id) {
|
||||||
SCOPED_MUTEX(&m_mutex_id);
|
SCOPED_MUTEX(&m_mutex_id);
|
||||||
SCOPED_MUTEX(&m_mutex_result);
|
SCOPED_MUTEX(&m_mutex_result);
|
||||||
|
|
||||||
const auto it_id = std::ranges::find(m_ids, id);
|
const auto it_id = std::ranges::find(m_ids, id);
|
||||||
const auto it_result = std::ranges::find_if(m_result, [id](auto& e){
|
const auto it_result = std::ranges::find_if(m_result, [id](auto& e){
|
||||||
return id == e.id;
|
return id == e->id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (it_id == m_ids.end() && it_result == m_result.end()) {
|
if (it_id == m_ids.end() && it_result == m_result.end()) {
|
||||||
@@ -238,61 +200,150 @@ void ThreadData::Push(u64 id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadData::Push(std::span<const u64> app_ids) {
|
auto ThreadData::GetAsync(u64 app_id) -> std::shared_ptr<ThreadResultData> {
|
||||||
for (auto& e : app_ids) {
|
|
||||||
Push(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
auto ThreadData::Pop(u64 app_id) -> std::optional<ThreadResultData> {
|
|
||||||
SCOPED_MUTEX(&m_mutex_result);
|
SCOPED_MUTEX(&m_mutex_result);
|
||||||
|
|
||||||
for (s64 i = 0; i < std::size(m_result); i++) {
|
for (s64 i = 0; i < std::size(m_result); i++) {
|
||||||
if (app_id == m_result[i].id) {
|
if (app_id == m_result[i]->id) {
|
||||||
const auto result = m_result[i];
|
|
||||||
m_result.erase(m_result.begin() + i);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadData::Pop(std::span<const u64> app_ids, std::vector<ThreadResultData>& out) {
|
|
||||||
for (auto& e : app_ids) {
|
|
||||||
if (const auto result = Pop(e)) {
|
|
||||||
out.emplace_back(*result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadData::PopAll(std::vector<ThreadResultData>& out) {
|
|
||||||
SCOPED_MUTEX(&m_mutex_result);
|
|
||||||
|
|
||||||
std::swap(out, m_result);
|
|
||||||
m_result.clear();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto ThreadData::Get(u64 app_id) -> std::optional<ThreadResultData> {
|
|
||||||
SCOPED_MUTEX(&m_mutex_result);
|
|
||||||
|
|
||||||
for (s64 i = 0; i < std::size(m_result); i++) {
|
|
||||||
if (app_id == m_result[i].id) {
|
|
||||||
return m_result[i];
|
return m_result[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadData::Get(std::span<const u64> app_ids, std::vector<ThreadResultData>& out) {
|
auto ThreadData::Get(u64 app_id, bool* cached) -> std::shared_ptr<ThreadResultData> {
|
||||||
for (auto& e : app_ids) {
|
// try and fetch from results first, before manually loading.
|
||||||
if (const auto result = Get(e)) {
|
if (auto data = GetAsync(app_id)) {
|
||||||
out.emplace_back(*result);
|
if (cached) {
|
||||||
|
*cached = true;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeStamp ts;
|
||||||
|
auto result = std::make_shared<ThreadResultData>(app_id);
|
||||||
|
result->status = NacpLoadStatus::Error;
|
||||||
|
|
||||||
|
if (auto data = nxtcGetApplicationMetadataEntryById(app_id)) {
|
||||||
|
log_write("[NXTC] loaded from cache time taken: %.2fs %zums %zuns\n", ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
|
||||||
|
ON_SCOPE_EXIT(nxtcFreeApplicationMetadata(&data));
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
*cached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result->status = NacpLoadStatus::Loaded;
|
||||||
|
std::strcpy(result->lang.name, data->name);
|
||||||
|
std::strcpy(result->lang.author, data->publisher);
|
||||||
|
result->icon.resize(data->icon_size);
|
||||||
|
std::memcpy(result->icon.data(), data->icon_data, result->icon.size());
|
||||||
|
} else {
|
||||||
|
if (cached) {
|
||||||
|
*cached = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool manual_load = true;
|
||||||
|
u64 actual_size{};
|
||||||
|
auto control = std::make_unique<NsApplicationControlData>();
|
||||||
|
|
||||||
|
if (hosversionBefore(20,0,0)) {
|
||||||
|
TimeStamp ts;
|
||||||
|
if (R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_CacheOnly, app_id, control.get(), sizeof(NsApplicationControlData), &actual_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(app_id, control->nacp, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc{};
|
||||||
|
if (!manual_load) {
|
||||||
|
TimeStamp ts;
|
||||||
|
if (R_SUCCEEDED(rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, app_id, control.get(), sizeof(NsApplicationControlData), &actual_size))) {
|
||||||
|
log_write("\t\t[ns control storage] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
FakeNacpEntry(result);
|
||||||
|
} else {
|
||||||
|
bool valid = true;
|
||||||
|
NacpLanguageEntry* lang;
|
||||||
|
if (R_SUCCEEDED(nsGetApplicationDesiredLanguage(&control->nacp, &lang))) {
|
||||||
|
result->lang = *lang;
|
||||||
|
} else {
|
||||||
|
FakeNacpEntry(result);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manual_load) {
|
||||||
|
const auto jpeg_size = actual_size - sizeof(NacpStruct);
|
||||||
|
result->icon.resize(jpeg_size);
|
||||||
|
std::memcpy(result->icon.data(), control->icon, result->icon.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new entry to cache, if valid.
|
||||||
|
if (valid) {
|
||||||
|
nxtcAddEntry(app_id, &control->nacp, result->icon.size(), result->icon.data(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
result->status = NacpLoadStatus::Loaded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// load override from sys-tweek.
|
||||||
|
if (result->status == NacpLoadStatus::Loaded) {
|
||||||
|
const auto tweek_path = GetContentsPath(app_id);
|
||||||
|
if (m_fs.DirExists(tweek_path)) {
|
||||||
|
log_write("[TITLE] found contents path: %s\n", tweek_path.s);
|
||||||
|
|
||||||
|
std::vector<u8> icon;
|
||||||
|
m_fs.read_entire_file(fs::AppendPath(tweek_path, "icon.jpg"), icon);
|
||||||
|
|
||||||
|
struct Overrides {
|
||||||
|
std::string name;
|
||||||
|
std::string author;
|
||||||
|
} overrides;
|
||||||
|
|
||||||
|
static const auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||||
|
auto e = static_cast<Overrides*>(UserData);
|
||||||
|
|
||||||
|
if (!std::strcmp(Section, "override_nacp")) {
|
||||||
|
if (!std::strcmp(Key, "name")) {
|
||||||
|
e->name = Value;
|
||||||
|
} else if (!std::strcmp(Key, "author")) {
|
||||||
|
e->author = Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
ini_browse(cb, &overrides, fs::AppendPath(tweek_path, "config.ini"));
|
||||||
|
|
||||||
|
if (!icon.empty() && icon.size() < sizeof(NsApplicationControlData::icon)) {
|
||||||
|
log_write("[TITLE] overriding icon: %zu -> %zu\n", result->icon.size(), icon.size());
|
||||||
|
result->icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overrides.name.empty() && overrides.name.length() < sizeof(result->lang.name)) {
|
||||||
|
log_write("[TITLE] overriding name: %s -> %s\n", result->lang.name, overrides.name.c_str());
|
||||||
|
std::strcpy(result->lang.name, overrides.name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overrides.author.empty() && overrides.author.length() < sizeof(result->lang.author)) {
|
||||||
|
log_write("[TITLE] overriding author: %s -> %s\n", result->lang.author, overrides.author.c_str());
|
||||||
|
std::strcpy(result->lang.author, overrides.author.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SCOPED_MUTEX(&m_mutex_result);
|
||||||
|
m_result.emplace_back(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadFunc(void* user) {
|
void ThreadFunc(void* user) {
|
||||||
@@ -314,10 +365,6 @@ void ThreadFunc(void* user) {
|
|||||||
Result Init() {
|
Result Init() {
|
||||||
SCOPED_MUTEX(&g_mutex);
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
if (g_ref_count) {
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_ref_count) {
|
if (!g_ref_count) {
|
||||||
R_TRY(nsInitialize());
|
R_TRY(nsInitialize());
|
||||||
R_TRY(ncmInitialize());
|
R_TRY(ncmInitialize());
|
||||||
@@ -347,50 +394,40 @@ void Exit() {
|
|||||||
if (!g_ref_count) {
|
if (!g_ref_count) {
|
||||||
g_thread_data->Close();
|
g_thread_data->Close();
|
||||||
|
|
||||||
for (auto& e : ncm_entries) {
|
|
||||||
e.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
threadWaitForExit(&g_thread);
|
threadWaitForExit(&g_thread);
|
||||||
threadClose(&g_thread);
|
threadClose(&g_thread);
|
||||||
g_thread_data.reset();
|
g_thread_data.reset();
|
||||||
|
|
||||||
|
for (auto& e : ncm_entries) {
|
||||||
|
e.Close();
|
||||||
|
}
|
||||||
|
|
||||||
nsExit();
|
nsExit();
|
||||||
ncmExit();
|
ncmExit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds new entry to queue.
|
void PushAsync(u64 app_id) {
|
||||||
void Push(u64 app_id) {
|
|
||||||
SCOPED_MUTEX(&g_mutex);
|
SCOPED_MUTEX(&g_mutex);
|
||||||
if (g_thread_data) {
|
if (g_thread_data) {
|
||||||
g_thread_data->Push(app_id);
|
g_thread_data->PushAsync(app_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds array of entries to queue.
|
auto GetAsync(u64 app_id) -> std::shared_ptr<ThreadResultData> {
|
||||||
void Push(std::span<const u64> app_ids) {
|
|
||||||
SCOPED_MUTEX(&g_mutex);
|
SCOPED_MUTEX(&g_mutex);
|
||||||
if (g_thread_data) {
|
if (g_thread_data) {
|
||||||
g_thread_data->Push(app_ids);
|
return g_thread_data->GetAsync(app_id);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets entry without removing it from the queue.
|
|
||||||
auto Get(u64 app_id) -> std::optional<ThreadResultData> {
|
|
||||||
SCOPED_MUTEX(&g_mutex);
|
|
||||||
if (g_thread_data) {
|
|
||||||
return g_thread_data->Get(app_id);
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets array of entries without removing it from the queue.
|
auto Get(u64 app_id, bool* cached) -> std::shared_ptr<ThreadResultData> {
|
||||||
void Get(std::span<const u64> app_ids, std::vector<ThreadResultData>& out) {
|
|
||||||
SCOPED_MUTEX(&g_mutex);
|
SCOPED_MUTEX(&g_mutex);
|
||||||
if (g_thread_data) {
|
if (g_thread_data) {
|
||||||
g_thread_data->Get(app_ids, out);
|
return g_thread_data->Get(app_id, cached);
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto GetNcmCs(u8 storage_id) -> NcmContentStorage& {
|
auto GetNcmCs(u8 storage_id) -> NcmContentStorage& {
|
||||||
@@ -440,80 +477,6 @@ Result GetControlPathFromStatus(const NsApplicationContentMetaStatus& status, u6
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto LoadControlEntry(u64 id, bool* cached) -> ThreadResultData {
|
|
||||||
// try and fetch from results first, before manually loading.
|
|
||||||
if (auto data = Get(id)) {
|
|
||||||
return *data;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeStamp ts;
|
|
||||||
ThreadResultData result{id};
|
|
||||||
result.control = std::make_shared<NsApplicationControlData>();
|
|
||||||
result.status = NacpLoadStatus::Error;
|
|
||||||
|
|
||||||
if (auto data = nxtcGetApplicationMetadataEntryById(id)) {
|
|
||||||
log_write("[NXTC] loaded from cache time taken: %.2fs %zums %zuns\n", ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
|
|
||||||
ON_SCOPE_EXIT(nxtcFreeApplicationMetadata(&data));
|
|
||||||
|
|
||||||
if (cached) {
|
|
||||||
*cached = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.status = NacpLoadStatus::Loaded;
|
|
||||||
std::strcpy(result.lang.name, data->name);
|
|
||||||
std::strcpy(result.lang.author, data->publisher);
|
|
||||||
std::memcpy(result.control->icon, data->icon_data, data->icon_size);
|
|
||||||
result.jpeg_size = data->icon_size;
|
|
||||||
} else {
|
|
||||||
if (cached) {
|
|
||||||
*cached = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool manual_load = true;
|
|
||||||
if (hosversionBefore(20,0,0)) {
|
|
||||||
TimeStamp ts;
|
|
||||||
u64 actual_size;
|
|
||||||
if (R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_CacheOnly, id, result.control.get(), sizeof(NsApplicationControlData), &actual_size))) {
|
|
||||||
manual_load = false;
|
|
||||||
result.jpeg_size = actual_size - sizeof(NacpStruct);
|
|
||||||
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, result));
|
|
||||||
}
|
|
||||||
|
|
||||||
Result rc{};
|
|
||||||
if (!manual_load) {
|
|
||||||
TimeStamp ts;
|
|
||||||
u64 actual_size;
|
|
||||||
if (R_SUCCEEDED(rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, id, result.control.get(), sizeof(NsApplicationControlData), &actual_size))) {
|
|
||||||
result.jpeg_size = actual_size - sizeof(NacpStruct);
|
|
||||||
log_write("\t\t[ns control storage] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (R_FAILED(rc)) {
|
|
||||||
FakeNacpEntry(result);
|
|
||||||
} else {
|
|
||||||
if (!manual_load) {
|
|
||||||
NacpLanguageEntry* lang;
|
|
||||||
if (R_SUCCEEDED(nsGetApplicationDesiredLanguage(&result.control->nacp, &lang))) {
|
|
||||||
result.lang = *lang;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.lang = result.control->nacp.lang[GetNacpLangEntryIndex()];
|
|
||||||
}
|
|
||||||
|
|
||||||
nxtcAddEntry(id, &result.control->nacp, result.jpeg_size, result.control->icon, true);
|
|
||||||
result.status = NacpLoadStatus::Loaded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// taken from nxdumptool.
|
// taken from nxdumptool.
|
||||||
void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
|
void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
|
||||||
{
|
{
|
||||||
@@ -554,4 +517,10 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
|
|||||||
*ptr2 = '\0';
|
*ptr2 = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto GetContentsPath(u64 app_id) -> fs::FsPath {
|
||||||
|
fs::FsPath path;
|
||||||
|
std::snprintf(path, sizeof(path), "/atmosphere/contents/%016lX", app_id);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sphaira::title
|
} // namespace sphaira::title
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "swkbd.hpp"
|
#include "swkbd.hpp"
|
||||||
|
|
||||||
#include "ui/menus/game_menu.hpp"
|
#include "ui/menus/game_menu.hpp"
|
||||||
|
#include "ui/menus/save_menu.hpp"
|
||||||
#include "ui/sidebar.hpp"
|
#include "ui/sidebar.hpp"
|
||||||
#include "ui/error_box.hpp"
|
#include "ui/error_box.hpp"
|
||||||
#include "ui/option_box.hpp"
|
#include "ui/option_box.hpp"
|
||||||
@@ -186,11 +187,11 @@ Result GetMetaEntries(const Entry& e, title::MetaEntries& out, u32 flags = title
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool LoadControlImage(Entry& e) {
|
bool LoadControlImage(Entry& e) {
|
||||||
if (!e.image && e.control) {
|
if (!e.image && e.info && !e.info->icon.empty()) {
|
||||||
ON_SCOPE_EXIT(e.control.reset());
|
ON_SCOPE_EXIT(e.info.reset());
|
||||||
|
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
const auto image = ImageLoadFromMemory({e.control->icon, e.jpeg_size}, ImageFlag_JPEG);
|
const auto image = ImageLoadFromMemory(e.info->icon, ImageFlag_JPEG);
|
||||||
if (!image.data.empty()) {
|
if (!image.data.empty()) {
|
||||||
e.image = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
e.image = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
||||||
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
@@ -201,18 +202,16 @@ bool LoadControlImage(Entry& e) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadResultIntoEntry(Entry& e, const title::ThreadResultData& result) {
|
void LoadResultIntoEntry(Entry& e, const std::shared_ptr<title::ThreadResultData>& result) {
|
||||||
e.status = result.status;
|
e.info = result;
|
||||||
e.control = result.control;
|
e.status = result->status;
|
||||||
e.jpeg_size= result.jpeg_size;
|
e.lang = result->lang;
|
||||||
e.lang = result.lang;
|
e.status = result->status;
|
||||||
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 == title::NacpLoadStatus::None) {
|
if (e.status == title::NacpLoadStatus::None) {
|
||||||
const auto result = title::LoadControlEntry(e.app_id);
|
LoadResultIntoEntry(e, title::Get(e.app_id));
|
||||||
LoadResultIntoEntry(e, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force_image_load && e.status == title::NacpLoadStatus::Loaded) {
|
if (force_image_load && e.status == title::NacpLoadStatus::Loaded) {
|
||||||
@@ -411,6 +410,30 @@ void LaunchEntry(const Entry& e) {
|
|||||||
Notify(rc, "Failed to launch application");
|
Notify(rc, "Failed to launch application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result CreateSave(u64 app_id, AccountUid uid) {
|
||||||
|
u64 actual_size;
|
||||||
|
auto data = std::make_unique<NsApplicationControlData>();
|
||||||
|
R_TRY(nsGetApplicationControlData(NsApplicationControlSource_Storage, app_id, data.get(), sizeof(NsApplicationControlData), &actual_size));
|
||||||
|
|
||||||
|
FsSaveDataAttribute attr{};
|
||||||
|
attr.application_id = app_id;
|
||||||
|
attr.uid = uid;
|
||||||
|
attr.save_data_type = FsSaveDataType_Account;
|
||||||
|
|
||||||
|
FsSaveDataCreationInfo info{};
|
||||||
|
info.save_data_size = data->nacp.user_account_save_data_size;
|
||||||
|
info.journal_size = data->nacp.user_account_save_data_journal_size;
|
||||||
|
info.available_size = data->nacp.user_account_save_data_size; // todo: check what this should be.
|
||||||
|
info.owner_id = data->nacp.save_data_owner_id;
|
||||||
|
info.save_data_space_id = FsSaveDataSpaceId_User;
|
||||||
|
|
||||||
|
// what is this???
|
||||||
|
FsSaveDataMetaInfo meta{};
|
||||||
|
R_TRY(fsCreateSaveDataFileSystem(&attr, &info, &meta));
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Menu::Menu(u32 flags) : grid::Menu{"Games"_i18n, flags} {
|
Menu::Menu(u32 flags) : grid::Menu{"Games"_i18n, flags} {
|
||||||
@@ -586,36 +609,57 @@ Menu::Menu(u32 flags) : grid::Menu{"Games"_i18n, flags} {
|
|||||||
}, true));
|
}, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryBool>("Title cache"_i18n, m_title_cache.Get(), [this](bool& v_out){
|
options->Add(std::make_shared<SidebarEntryCallback>("Advanced options"_i18n, [this](){
|
||||||
m_title_cache.Set(v_out);
|
auto options = std::make_shared<Sidebar>("Advanced Options"_i18n, Sidebar::Side::RIGHT);
|
||||||
}));
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
|
|
||||||
// todo: impl this.
|
options->Add(std::make_shared<SidebarEntryCallback>("Refresh"_i18n, [this](){
|
||||||
#if 0
|
m_dirty = true;
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Create save"_i18n, [this](){
|
App::PopToMenu();
|
||||||
ui::PopupList::Items items{};
|
}));
|
||||||
const auto accounts = App::GetAccountList();
|
|
||||||
for (auto& p : accounts) {
|
|
||||||
items.emplace_back(p.nickname);
|
|
||||||
}
|
|
||||||
|
|
||||||
fsCreateSaveDataFileSystem;
|
options->Add(std::make_shared<SidebarEntryCallback>("Create contents folder"_i18n, [this](){
|
||||||
|
const auto rc = fs::FsNativeSd().CreateDirectory(title::GetContentsPath(m_entries[m_index].app_id));
|
||||||
|
App::PushErrorBox(rc, "Folder create failed!"_i18n);
|
||||||
|
|
||||||
App::Push(std::make_shared<ui::PopupList>(
|
if (R_SUCCEEDED(rc)) {
|
||||||
"Select user to create save for"_i18n, items, [accounts](auto op_index){
|
App::Notify("Folder created!"_i18n);
|
||||||
if (op_index) {
|
}
|
||||||
s64 out;
|
}));
|
||||||
if (R_SUCCEEDED(swkbd::ShowNumPad(out, "Enter the save size"_i18n.c_str()))) {
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Create save"_i18n, [this](){
|
||||||
|
ui::PopupList::Items items{};
|
||||||
|
const auto accounts = App::GetAccountList();
|
||||||
|
for (auto& p : accounts) {
|
||||||
|
items.emplace_back(p.nickname);
|
||||||
|
}
|
||||||
|
|
||||||
|
App::Push(std::make_shared<ui::PopupList>(
|
||||||
|
"Select user to create save for"_i18n, items, [this, accounts](auto op_index){
|
||||||
|
if (op_index) {
|
||||||
|
CreateSaves(accounts[*op_index].uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
));
|
||||||
));
|
}));
|
||||||
|
|
||||||
// 1. Select user to create save for.
|
options->Add(std::make_shared<SidebarEntryBool>("Title cache"_i18n, m_title_cache.Get(), [this](bool& v_out){
|
||||||
// 2. Enter the save size.
|
m_title_cache.Set(v_out);
|
||||||
// 3. Enter the journal size (0 for default).
|
}));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Delete title cache"_i18n, [this](){
|
||||||
|
App::Push(std::make_shared<OptionBox>(
|
||||||
|
"Are you sure you want to delete the title cache?"_i18n,
|
||||||
|
"Back"_i18n, "Delete"_i18n, 0, [this](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
m_dirty = true;
|
||||||
|
nxtcWipeCache();
|
||||||
|
App::PopToMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}));
|
||||||
}));
|
}));
|
||||||
#endif
|
|
||||||
}})
|
}})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -668,11 +712,11 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
auto& e = m_entries[pos];
|
auto& e = m_entries[pos];
|
||||||
|
|
||||||
if (e.status == title::NacpLoadStatus::None) {
|
if (e.status == title::NacpLoadStatus::None) {
|
||||||
title::Push(e.app_id);
|
title::PushAsync(e.app_id);
|
||||||
e.status = title::NacpLoadStatus::Progress;
|
e.status = title::NacpLoadStatus::Progress;
|
||||||
} else if (e.status == title::NacpLoadStatus::Progress) {
|
} else if (e.status == title::NacpLoadStatus::Progress) {
|
||||||
if (const auto data = title::Get(e.app_id)) {
|
if (const auto data = title::GetAsync(e.app_id)) {
|
||||||
LoadResultIntoEntry(e, *data);
|
LoadResultIntoEntry(e, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -880,4 +924,35 @@ void Menu::DumpGames(u32 flags) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::CreateSaves(AccountUid uid) {
|
||||||
|
App::Push(std::make_shared<ProgressBox>(0, "Creating"_i18n, "", [this, uid](auto pbox) -> Result {
|
||||||
|
auto targets = GetSelectedEntries();
|
||||||
|
|
||||||
|
for (s64 i = 0; i < std::size(targets); i++) {
|
||||||
|
auto& e = targets[i];
|
||||||
|
|
||||||
|
LoadControlEntry(e);
|
||||||
|
pbox->SetTitle(e.GetName());
|
||||||
|
pbox->UpdateTransfer(i + 1, std::size(targets));
|
||||||
|
const auto rc = CreateSave(e.app_id, uid);
|
||||||
|
|
||||||
|
// don't error if the save already exists.
|
||||||
|
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||||
|
R_THROW(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}, [this](Result rc){
|
||||||
|
App::PushErrorBox(rc, "Save create failed!"_i18n);
|
||||||
|
|
||||||
|
ClearSelection();
|
||||||
|
save::SignalChange();
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
App::Notify("Save create successfull!"_i18n);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::game
|
} // namespace sphaira::ui::menu::game
|
||||||
|
|||||||
@@ -863,12 +863,11 @@ void Menu::FreeImage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result Menu::LoadControlData(ApplicationEntry& e) {
|
Result Menu::LoadControlData(ApplicationEntry& e) {
|
||||||
const auto data = title::LoadControlEntry(e.app_id);
|
const auto data = title::Get(e.app_id);
|
||||||
R_UNLESS(data.status == title::NacpLoadStatus::Loaded, 0x1);
|
R_UNLESS(data->status == title::NacpLoadStatus::Loaded, 0x1);
|
||||||
|
|
||||||
e.control = data.control;
|
e.icon = data->icon;
|
||||||
e.jpeg_size = data.jpeg_size;
|
e.lang_entry = data->lang;
|
||||||
e.lang_entry = data.lang;
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,10 +882,9 @@ void Menu::OnChangeIndex(s64 new_index) {
|
|||||||
this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size()));
|
this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size()));
|
||||||
|
|
||||||
const auto& e = m_entries[m_entry_index];
|
const auto& e = m_entries[m_entry_index];
|
||||||
const auto jpeg_size = e.jpeg_size;
|
|
||||||
|
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
const auto image = ImageLoadFromMemory({e.control->icon, jpeg_size}, ImageFlag_JPEG);
|
const auto image = ImageLoadFromMemory(e.icon, ImageFlag_JPEG);
|
||||||
if (!image.data.empty()) {
|
if (!image.data.empty()) {
|
||||||
m_icon = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
m_icon = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
||||||
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
|||||||
@@ -216,15 +216,15 @@ void FakeNacpEntryForSystem(Entry& e) {
|
|||||||
// fake the nacp entry
|
// fake the nacp entry
|
||||||
std::snprintf(e.lang.name, sizeof(e.lang.name), "%s | %016lX", GetSystemSaveName(e.system_save_data_id), e.system_save_data_id);
|
std::snprintf(e.lang.name, sizeof(e.lang.name), "%s | %016lX", GetSystemSaveName(e.system_save_data_id), e.system_save_data_id);
|
||||||
std::strcpy(e.lang.author, "Nintendo");
|
std::strcpy(e.lang.author, "Nintendo");
|
||||||
e.control.reset();
|
e.info.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoadControlImage(Entry& e) {
|
bool LoadControlImage(Entry& e) {
|
||||||
if (!e.image && e.control) {
|
if (!e.image && e.info && !e.info->icon.empty()) {
|
||||||
ON_SCOPE_EXIT(e.control.reset());
|
ON_SCOPE_EXIT(e.info.reset());
|
||||||
|
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
const auto image = ImageLoadFromMemory({e.control->icon, e.jpeg_size}, ImageFlag_JPEG);
|
const auto image = ImageLoadFromMemory(e.info->icon, ImageFlag_JPEG);
|
||||||
if (!image.data.empty()) {
|
if (!image.data.empty()) {
|
||||||
e.image = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
e.image = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
|
||||||
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
@@ -235,12 +235,11 @@ bool LoadControlImage(Entry& e) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadResultIntoEntry(Entry& e, const title::ThreadResultData& result) {
|
void LoadResultIntoEntry(Entry& e, const std::shared_ptr<title::ThreadResultData>& result) {
|
||||||
e.status = result.status;
|
e.info = result;
|
||||||
e.control = result.control;
|
e.status = result->status;
|
||||||
e.jpeg_size= result.jpeg_size;
|
e.lang = result->lang;
|
||||||
e.lang = result.lang;
|
e.status = result->status;
|
||||||
e.status = result.status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadControlEntry(Entry& e, bool force_image_load = false) {
|
void LoadControlEntry(Entry& e, bool force_image_load = false) {
|
||||||
@@ -248,8 +247,7 @@ void LoadControlEntry(Entry& e, bool force_image_load = false) {
|
|||||||
if (e.save_data_type == FsSaveDataType_System || e.save_data_type == FsSaveDataType_SystemBcat) {
|
if (e.save_data_type == FsSaveDataType_System || e.save_data_type == FsSaveDataType_SystemBcat) {
|
||||||
FakeNacpEntryForSystem(e);
|
FakeNacpEntryForSystem(e);
|
||||||
} else {
|
} else {
|
||||||
const auto result = title::LoadControlEntry(e.application_id);
|
LoadResultIntoEntry(e, title::Get(e.application_id));
|
||||||
LoadResultIntoEntry(e, result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,14 +500,14 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
|
|
||||||
if (e.status == title::NacpLoadStatus::None) {
|
if (e.status == title::NacpLoadStatus::None) {
|
||||||
if (m_data_type != FsSaveDataType_System && m_data_type != FsSaveDataType_SystemBcat) {
|
if (m_data_type != FsSaveDataType_System && m_data_type != FsSaveDataType_SystemBcat) {
|
||||||
title::Push(e.application_id);
|
title::PushAsync(e.application_id);
|
||||||
e.status = title::NacpLoadStatus::Progress;
|
e.status = title::NacpLoadStatus::Progress;
|
||||||
} else {
|
} else {
|
||||||
FakeNacpEntryForSystem(e);
|
FakeNacpEntryForSystem(e);
|
||||||
}
|
}
|
||||||
} else if (e.status == title::NacpLoadStatus::Progress) {
|
} else if (e.status == title::NacpLoadStatus::Progress) {
|
||||||
if (const auto data = title::Get(e.application_id)) {
|
if (const auto data = title::GetAsync(e.application_id)) {
|
||||||
LoadResultIntoEntry(e, *data);
|
LoadResultIntoEntry(e, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,6 +575,7 @@ void Menu::ScanHomebrew() {
|
|||||||
|
|
||||||
FreeEntries();
|
FreeEntries();
|
||||||
ClearSelection();
|
ClearSelection();
|
||||||
|
ueventClear(&g_change_uevent);
|
||||||
m_entries.reserve(ENTRY_CHUNK_COUNT);
|
m_entries.reserve(ENTRY_CHUNK_COUNT);
|
||||||
m_is_reversed = false;
|
m_is_reversed = false;
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
@@ -808,8 +807,8 @@ Result Menu::RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::Fs
|
|||||||
pbox->SetTitle(e.GetName());
|
pbox->SetTitle(e.GetName());
|
||||||
if (e.image) {
|
if (e.image) {
|
||||||
pbox->SetImage(e.image);
|
pbox->SetImage(e.image);
|
||||||
} else if (e.control && e.jpeg_size) {
|
} else if (e.info && !e.info->icon.empty()) {
|
||||||
pbox->SetImageDataConst({e.control->icon, e.jpeg_size});
|
pbox->SetImageDataConst(e.info->icon);
|
||||||
} else {
|
} else {
|
||||||
pbox->SetImage(0);
|
pbox->SetImage(0);
|
||||||
}
|
}
|
||||||
@@ -935,8 +934,8 @@ Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& loc
|
|||||||
pbox->SetTitle(e.GetName());
|
pbox->SetTitle(e.GetName());
|
||||||
if (e.image) {
|
if (e.image) {
|
||||||
pbox->SetImage(e.image);
|
pbox->SetImage(e.image);
|
||||||
} else if (e.control && e.jpeg_size) {
|
} else if (e.info && !e.info->icon.empty()) {
|
||||||
pbox->SetImageDataConst({e.control->icon, e.jpeg_size});
|
pbox->SetImageDataConst(e.info->icon);
|
||||||
} else {
|
} else {
|
||||||
pbox->SetImage(0);
|
pbox->SetImage(0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user