5 Commits

Author SHA1 Message Date
ITotalJustice
4d27bf5492 bump version for new release 0.13.2 -> 0.13.3 2025-06-19 11:40:42 +01:00
ITotalJustice
6b85d2cef1 fix account save listing where the profile uid differs from the account uid.
i am unsure how this happens, as i thought the profile uid was the same as the account uid, but apparently this can differ.
on the same switch in sysmmc, the uid's match, so everything works.
again on the same switch, created in emummc, same account, the uid differs...

i performed the same test but on another 2 switches, and the uid's all match, so i am not sure what causes them to change.

in any case, using the uid from the account is the intended behaviour anyway, so this commit fixes that.
2025-06-19 11:37:08 +01:00
xHR
aae9930f5e Updated UK translation (#180)
* fixed long strings, translate untranslated

* Updated UK translation
2025-06-19 11:36:42 +01:00
ITotalJustice
eca19aa4bf fix hdd createDirWithPath, improve flashcart detection in gc menu, bump version for new release 0.13.1 -> 0.13.2 2025-06-18 23:17:58 +01:00
ITotalJustice
8e02538405 fix nvjpg crashing when trying to load a non-jpg image, fix building with nvjpg disable, optimise invalid nro asset loading, bump version for new release 0.13.0 -> 0.13.1 2025-06-18 21:10:15 +01:00
8 changed files with 74 additions and 36 deletions

View File

@@ -72,7 +72,7 @@
"Append folder with .xci": "Додати до теки .xci",
"Trim XCI": "Обрізати XCI",
"Label trimmed XCI": "Позначити обрізаний XCI",
"Multi-threaded USB transfer": "Многопоточ. передача USB",
"Multi-threaded USB transfer": "Багатопотокова передача USB",
"Dump All Bins": "Дамп всіх BIN-файлів",
"Dump XCI": "Дамп XCI",
"Dump Card ID Set": "Дамп набору ID картриджа",
@@ -116,8 +116,8 @@
"SSID:": "SSID:",
"Passphrase:": "Кодова фраза:",
"Failed to install, press B to exit...": "Не вдалося встановити через FTP, натисніть B для виходу...",
"Install success!": "Встановлення через FTP успішно завершено.",
"Install failed!": "Встановлення через FTP не вдалося.",
"Install success!": "Успішно встановлено",
"Install failed!": "Встановлення не вдалося.",
"USB Install": "Встановлення через USB",
"USB": "USB",
"Connected, waiting for file list...": "Підключено, очікування списку файлів...",
@@ -206,8 +206,8 @@
"Slow": "Повільно",
"Normal": "Середнє",
"Fast": "Швидко",
"Set left-side menu": "Ліве меню",
"Set right-side menu": "Праве меню",
"Set left-side menu": "Ліве меню (L)",
"Set right-side menu": "Праве меню (R)",
"Install options": "Опції встановлення",
"Install Options": "Опції встановлення",
"Enable sysmmc": "Дозволити в sysMMC",

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.13)
set(sphaira_VERSION 0.13.0)
set(sphaira_VERSION 0.13.3)
project(sphaira
VERSION ${sphaira_VERSION}
@@ -208,8 +208,8 @@ FetchContent_Declare(libnxtc
)
FetchContent_Declare(nvjpg
GIT_REPOSITORY https://github.com/averne/oss-nvjpg.git
GIT_TAG 16c10a3
GIT_REPOSITORY https://github.com/ITotalJustice/oss-nvjpg.git
GIT_TAG 45680e7
)
set(USE_NEW_ZSTD ON)

View File

@@ -227,6 +227,10 @@ public:
AccountProfileBase base;
if (R_SUCCEEDED(accountProfileGet(&profile, nullptr, &base))) {
// sometimes the uid for the acc can differ to the base.
base.uid = uids[i];
log_write("[ACC] found uid: 0x%016lX%016lX\n", uids[i].uid[0], uids[i].uid[1]);
log_write("[ACC] base uid: 0x%016lX%016lX\n", base.uid.uid[0], base.uid.uid[1]);
out.emplace_back(base);
}
}

View File

@@ -185,7 +185,6 @@ Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path,
FsPath new_path{};
std::snprintf(new_path, sizeof(new_path), "%.*s", (int)(last_slash - _path.s), _path.s);
R_TRY(CreateDirectoryRecursively(fs, new_path, ignore_read_only));
fsFsCommit(fs);
R_SUCCEED();
}

View File

@@ -2,6 +2,7 @@
#include "app.hpp"
#include "threaded_file_transfer.hpp"
#include <mbedtls/md5.h>
#include <utility>
namespace sphaira::hash {
namespace {
@@ -16,11 +17,11 @@ struct FileSource final : BaseSource {
m_is_file_based_emummc = App::IsFileBaseEmummc();
}
Result Size(s64* out) {
Result Size(s64* out) override {
return m_file.GetSize(out);
}
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) {
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override {
const auto rc = m_file.Read(off, buf, size, 0, bytes_read);
if (m_fs->IsNative() && m_is_file_based_emummc) {
svcSleepThread(2e+6); // 2ms
@@ -38,12 +39,12 @@ private:
struct MemSource final : BaseSource {
MemSource(std::span<const u8> data) : m_data{data} { }
Result Size(s64* out) {
Result Size(s64* out) override {
*out = m_data.size();
R_SUCCEED();
}
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) {
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override {
size = std::min<s64>(size, m_data.size() - off);
std::memcpy(buf, m_data.data() + off, size);
*bytes_read = size;
@@ -61,11 +62,11 @@ struct HashSource {
};
struct HashCrc32 final : HashSource {
void Update(const void* buf, s64 size) {
void Update(const void* buf, s64 size) override {
m_seed = crc32CalculateWithSeed(m_seed, buf, size);
}
void Get(std::string& out) {
void Get(std::string& out) override {
char str[CalculateHashStrLen(sizeof(m_seed))];
std::snprintf(str, sizeof(str), "%08x", m_seed);
out = str;
@@ -85,11 +86,11 @@ struct HashMd5 final : HashSource {
mbedtls_md5_free(&m_ctx);
}
void Update(const void* buf, s64 size) {
void Update(const void* buf, s64 size) override {
mbedtls_md5_update_ret(&m_ctx, (const u8*)buf, size);
}
void Get(std::string& out) {
void Get(std::string& out) override {
u8 hash[16];
mbedtls_md5_finish_ret(&m_ctx, hash);
@@ -110,11 +111,11 @@ struct HashSha1 final : HashSource {
sha1ContextCreate(&m_ctx);
}
void Update(const void* buf, s64 size) {
void Update(const void* buf, s64 size) override {
sha1ContextUpdate(&m_ctx, buf, size);
}
void Get(std::string& out) {
void Get(std::string& out) override {
u8 hash[SHA1_HASH_SIZE];
sha1ContextGetHash(&m_ctx, hash);
@@ -135,11 +136,11 @@ struct HashSha256 final : HashSource {
sha256ContextCreate(&m_ctx);
}
void Update(const void* buf, s64 size) {
void Update(const void* buf, s64 size) override {
sha256ContextUpdate(&m_ctx, buf, size);
}
void Get(std::string& out) {
void Get(std::string& out) override {
u8 hash[SHA256_HASH_SIZE];
sha256ContextGetHash(&m_ctx, hash);

View File

@@ -59,18 +59,20 @@ auto nro_parse_internal(fs::Fs* fs, const fs::FsPath& path, NroEntry& entry) ->
std::strncpy(nacp.lang.name, file_name, file_name_len - 4);
std::strcpy(nacp.lang.author, "Unknown");
std::strcpy(nacp.display_version, "Unknown");
entry.icon_offset = entry.icon_size = 0;
entry.is_nacp_valid = false;
} else {
entry.size += sizeof(asset) + asset.icon.size + asset.nacp.size + asset.romfs.size;
R_TRY(f.Read(data.header.size + asset.nacp.offset, &nacp.lang, sizeof(nacp.lang), FsReadOption_None, &bytes_read));
R_TRY(f.Read(data.header.size + asset.nacp.offset + offsetof(NacpStruct, display_version), nacp.display_version, sizeof(nacp.display_version), FsReadOption_None, &bytes_read));
// lazy load the icons
entry.icon_size = asset.icon.size;
entry.icon_offset = data.header.size + asset.icon.offset;
entry.is_nacp_valid = true;
}
// lazy load the icons
entry.icon_size = asset.icon.size;
entry.icon_offset = data.header.size + asset.icon.offset;
R_SUCCEED();
}

View File

@@ -1045,25 +1045,35 @@ Result Menu::DumpGames(u32 flags) {
bool is_trimmed = false;
Result trim_rc = 0;
if ((flags & DumpFileFlag_XCI) && m_storage_trimmed_size < m_storage_total_size) {
u8 temp{};
if (R_FAILED(trim_rc = GcStorageRead(&temp, m_storage_trimmed_size, sizeof(temp)))) {
log_write("[GC] WARNING! GameCard is already trimmed: 0x%X FlashError: %u\n", trim_rc, trim_rc == 0x13D002);
const auto start_offset = std::min<s64>(0, m_storage_trimmed_size - 0x4000);
// works on fw 1.2.0 and below.
std::vector<u8> temp(1024*1024*1);
if (R_FAILED(trim_rc = GcStorageRead(temp.data(), m_storage_trimmed_size, std::min<s64>(temp.size(), m_storage_total_size - start_offset)))) {
log_write("[GC] WARNING1! GameCard is already trimmed: 0x%X FlashError: %u\n", trim_rc, trim_rc == 0x13D002);
is_trimmed = true;
}
if (!is_trimmed) {
// works on fw 1.2.0 and below.
if (R_FAILED(trim_rc = GcStorageRead(temp.data(), m_storage_total_size - temp.size(), temp.size()))) {
log_write("[GC] WARNING2! GameCard is already trimmed: 0x%X FlashError: %u\n", trim_rc, trim_rc == 0x13D002);
is_trimmed = true;
}
}
}
// if trimmed and the user wants to dump the full xci, error.
if ((flags & DumpFileFlag_XCI) && is_trimmed && App::GetApp()->m_dump_trim_xci.Get()) {
App::PushErrorBox(trim_rc, "GameCard is already trimmed!"_i18n);
} else if ((flags & DumpFileFlag_XCI) && is_trimmed) {
App::Push(std::make_shared<ui::OptionBox>(
"WARNING: GameCard is already trimmed!"_i18n,
"Back"_i18n, "Continue"_i18n, 0, [&](auto op_index){
if (op_index && *op_index) {
do_dump(flags);
}
}
}, m_icon
));
} else if ((flags & DumpFileFlag_XCI) && is_trimmed) {
App::PushErrorBox(trim_rc, "GameCard is trimmed, full dump is not possible!"_i18n);
} else {
do_dump(flags);
}

View File

@@ -815,6 +815,9 @@ Menu::Menu(u32 flags) : grid::Menu{"Saves"_i18n, flags} {
if (it != m_accounts.end()) {
m_account_index = std::distance(m_accounts.begin(), it);
log_write("[SAVE] found account uid at: %zu\n", m_account_index);
} else {
log_write("[SAVE] account uid is not found: 0x%016lX%016lX\n", uid.uid[0], uid.uid[1]);
}
for (auto& e : ncm_entries) {
@@ -936,10 +939,26 @@ void Menu::SetIndex(s64 index) {
m_list->SetYoff(0);
}
if (m_accounts.empty()) {
return;
}
u64 id{};
if (!m_entries.empty()) {
if (m_data_type == FsSaveDataType_System || m_data_type == FsSaveDataType_SystemBcat) {
id = m_entries[m_index].system_save_data_id;
} else {
id = m_entries[m_index].application_id;
}
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries.size()));
} else {
this->SetSubHeading("0 / 0");
}
char title[0x40];
std::snprintf(title, sizeof(title), "%s | %016lX", m_accounts[m_account_index].nickname, m_entries[m_index].application_id);
std::snprintf(title, sizeof(title), "%s | %016lX", m_accounts[m_account_index].nickname, id);
SetTitleSubHeading(title);
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries.size()));
}
void Menu::ScanHomebrew() {
@@ -947,7 +966,10 @@ void Menu::ScanHomebrew() {
TimeStamp ts;
FreeEntries();
ClearSelection();
m_entries.reserve(ENTRY_CHUNK_COUNT);
m_is_reversed = false;
m_dirty = false;
if (m_accounts.empty()) {
return;
@@ -958,7 +980,9 @@ void Menu::ScanHomebrew() {
GetFsSaveAttr(m_accounts[m_account_index], m_data_type, space_id, filter);
FsSaveDataInfoReader reader;
fsOpenSaveDataInfoReaderWithFilter(&reader, space_id, &filter);
if (R_FAILED(fsOpenSaveDataInfoReaderWithFilter(&reader, space_id, &filter))) {
log_write("[SAVE] failed to open reader\n");
}
ON_SCOPE_EXIT(fsSaveDataInfoReaderClose(&reader));
std::vector<FsSaveDataInfo> info_list(ENTRY_CHUNK_COUNT);
@@ -979,8 +1003,6 @@ void Menu::ScanHomebrew() {
}
}
m_is_reversed = false;
m_dirty = false;
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();
SetIndex(0);