mirror of
https://gitlab.com/Theopse/fbi-i18n-zh.git
synced 2025-04-06 03:58:02 +08:00
361 lines
13 KiB
C
361 lines
13 KiB
C
#include <sys/syslimits.h>
|
|
#include <malloc.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <3ds.h>
|
|
#include <3ds/services/am.h>
|
|
|
|
#include "../../list.h"
|
|
#include "../../error.h"
|
|
#include "../../../util.h"
|
|
#include "../../../screen.h"
|
|
#include "task.h"
|
|
|
|
typedef struct {
|
|
list_item* items;
|
|
u32* count;
|
|
u32 max;
|
|
|
|
Handle cancelEvent;
|
|
} populate_titles_data;
|
|
|
|
static Result task_populate_titles_add_ctr(populate_titles_data* data, FS_MediaType mediaType, u64 titleId) {
|
|
Result res = 0;
|
|
|
|
AM_TitleEntry entry;
|
|
if(R_SUCCEEDED(res = AM_GetTitleInfo(mediaType, 1, &titleId, &entry))) {
|
|
title_info* titleInfo = (title_info*) calloc(1, sizeof(title_info));
|
|
if(titleInfo != NULL) {
|
|
titleInfo->mediaType = mediaType;
|
|
titleInfo->titleId = titleId;
|
|
AM_GetTitleProductCode(mediaType, titleId, titleInfo->productCode);
|
|
titleInfo->version = entry.version;
|
|
titleInfo->installedSize = entry.size;
|
|
titleInfo->twl = false;
|
|
titleInfo->hasMeta = false;
|
|
|
|
list_item* item = &data->items[*data->count];
|
|
|
|
static const u32 filePathData[] = {0x00000000, 0x00000000, 0x00000002, 0x6E6F6369, 0x00000000};
|
|
static const FS_Path filePath = (FS_Path) {PATH_BINARY, 0x14, (u8*) filePathData};
|
|
u32 archivePath[] = {(u32) (titleId & 0xFFFFFFFF), (u32) ((titleId >> 32) & 0xFFFFFFFF), mediaType, 0x00000000};
|
|
FS_Archive archive = {ARCHIVE_SAVEDATA_AND_CONTENT, (FS_Path) {PATH_BINARY, 0x10, (u8*) archivePath}};
|
|
|
|
Handle fileHandle;
|
|
if(R_SUCCEEDED(FSUSER_OpenFileDirectly(&fileHandle, archive, filePath, FS_OPEN_READ, 0))) {
|
|
SMDH* smdh = (SMDH*) calloc(1, sizeof(SMDH));
|
|
if(smdh != NULL) {
|
|
u32 bytesRead;
|
|
if(R_SUCCEEDED(FSFILE_Read(fileHandle, &bytesRead, 0, smdh, sizeof(SMDH))) && bytesRead == sizeof(SMDH)) {
|
|
if(smdh->magic[0] == 'S' && smdh->magic[1] == 'M' && smdh->magic[2] == 'D' && smdh->magic[3] == 'H') {
|
|
titleInfo->hasMeta = true;
|
|
|
|
u8 systemLanguage = CFG_LANGUAGE_EN;
|
|
CFGU_GetSystemLanguage(&systemLanguage);
|
|
|
|
utf16_to_utf8((uint8_t*) item->name, smdh->titles[systemLanguage].shortDescription, NAME_MAX - 1);
|
|
|
|
utf16_to_utf8((uint8_t*) titleInfo->meta.shortDescription, smdh->titles[systemLanguage].shortDescription, sizeof(titleInfo->meta.shortDescription) - 1);
|
|
utf16_to_utf8((uint8_t*) titleInfo->meta.longDescription, smdh->titles[systemLanguage].longDescription, sizeof(titleInfo->meta.longDescription) - 1);
|
|
utf16_to_utf8((uint8_t*) titleInfo->meta.publisher, smdh->titles[systemLanguage].publisher, sizeof(titleInfo->meta.publisher) - 1);
|
|
titleInfo->meta.texture = screen_load_texture_tiled_auto(smdh->largeIcon, sizeof(smdh->largeIcon), 48, 48, GPU_RGB565, false);
|
|
}
|
|
}
|
|
|
|
free(smdh);
|
|
}
|
|
|
|
FSFILE_Close(fileHandle);
|
|
}
|
|
|
|
bool empty = strlen(item->name) == 0;
|
|
if(!empty) {
|
|
empty = true;
|
|
|
|
char* curr = item->name;
|
|
while(*curr) {
|
|
if(*curr != ' ') {
|
|
empty = false;
|
|
break;
|
|
}
|
|
|
|
curr++;
|
|
}
|
|
}
|
|
|
|
if(empty) {
|
|
snprintf(item->name, NAME_MAX, "%016llX", titleId);
|
|
}
|
|
|
|
if(mediaType == MEDIATYPE_NAND) {
|
|
item->rgba = COLOR_NAND;
|
|
} else if(mediaType == MEDIATYPE_SD) {
|
|
item->rgba = COLOR_SD;
|
|
} else if(mediaType == MEDIATYPE_GAME_CARD) {
|
|
item->rgba = COLOR_GAME_CARD;
|
|
}
|
|
|
|
item->data = titleInfo;
|
|
|
|
(*data->count)++;
|
|
} else {
|
|
res = R_FBI_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static Result task_populate_titles_add_twl(populate_titles_data* data, FS_MediaType mediaType, u64 titleId) {
|
|
Result res = 0;
|
|
|
|
u64 realTitleId = 0;
|
|
char productCode[12] = {'\0'};
|
|
u16 version = 0;
|
|
u64 installedSize = 0;
|
|
|
|
AM_TitleEntry entry;
|
|
if(R_SUCCEEDED(res = AM_GetTitleInfo(mediaType, 1, &titleId, &entry))) {
|
|
realTitleId = titleId;
|
|
AM_GetTitleProductCode(mediaType, titleId, productCode);
|
|
version = entry.version;
|
|
installedSize = entry.size;
|
|
} else {
|
|
u8* header = (u8*) calloc(1, 0x3B4);
|
|
if(header != NULL) {
|
|
if(R_SUCCEEDED(res = FSUSER_GetLegacyRomHeader(mediaType, titleId, header))) {
|
|
realTitleId = titleId != 0 ? titleId : *(u64*) &header[0x230];
|
|
memcpy(productCode, header, 0x00C);
|
|
version = header[0x01E];
|
|
installedSize = (header[0x012] & 0x2) != 0 ? *(u32*) &header[0x210] : *(u32*) &header[0x080];
|
|
}
|
|
|
|
free(header);
|
|
} else {
|
|
res = R_FBI_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
if(R_SUCCEEDED(res)) {
|
|
title_info* titleInfo = (title_info*) calloc(1, sizeof(title_info));
|
|
if(titleInfo != NULL) {
|
|
titleInfo->mediaType = mediaType;
|
|
titleInfo->titleId = realTitleId;
|
|
strncpy(titleInfo->productCode, productCode, 12);
|
|
titleInfo->version = version;
|
|
titleInfo->installedSize = installedSize;
|
|
titleInfo->twl = true;
|
|
titleInfo->hasMeta = false;
|
|
|
|
list_item* item = &data->items[*data->count];
|
|
|
|
BNR* bnr = (BNR*) calloc(1, sizeof(BNR));
|
|
if(bnr != NULL) {
|
|
if(R_SUCCEEDED(FSUSER_GetLegacyBannerData(mediaType, titleId, (u8*) bnr))) {
|
|
titleInfo->hasMeta = true;
|
|
|
|
u8 systemLanguage = CFG_LANGUAGE_EN;
|
|
CFGU_GetSystemLanguage(&systemLanguage);
|
|
|
|
char title[0x100] = {'\0'};
|
|
utf16_to_utf8((uint8_t*) title, bnr->titles[systemLanguage], sizeof(title) - 1);
|
|
|
|
if(strchr(title, '\n') == NULL) {
|
|
size_t len = strlen(title);
|
|
strncpy(item->name, title, len);
|
|
strncpy(titleInfo->meta.shortDescription, title, len);
|
|
} else {
|
|
char* destinations[] = {titleInfo->meta.shortDescription, titleInfo->meta.longDescription, titleInfo->meta.publisher};
|
|
int currDest = 0;
|
|
|
|
char* last = title;
|
|
char* curr = NULL;
|
|
|
|
while(currDest < 3 && (curr = strchr(last, '\n')) != NULL) {
|
|
if(currDest == 0) {
|
|
strncpy(item->name, last, curr - last);
|
|
}
|
|
|
|
strncpy(destinations[currDest++], last, curr - last);
|
|
last = curr + 1;
|
|
}
|
|
|
|
strncpy(item->name, title, last - title);
|
|
if(currDest < 3) {
|
|
strncpy(destinations[currDest], last, strlen(title) - (last - title));
|
|
}
|
|
}
|
|
|
|
u8 icon[32 * 32 * 2];
|
|
for(u32 x = 0; x < 32; x++) {
|
|
for(u32 y = 0; y < 32; y++) {
|
|
u32 srcPos = (((y >> 3) * 4 + (x >> 3)) * 8 + (y & 7)) * 4 + ((x & 7) >> 1);
|
|
u32 srcShift = (x & 1) * 4;
|
|
u16 srcPx = bnr->mainIconPalette[(bnr->mainIconBitmap[srcPos] >> srcShift) & 0xF];
|
|
|
|
u8 r = (u8) (srcPx & 0x1F);
|
|
u8 g = (u8) ((srcPx >> 5) & 0x1F);
|
|
u8 b = (u8) ((srcPx >> 10) & 0x1F);
|
|
|
|
u16 reversedPx = (u16) ((r << 11) | (g << 6) | (b << 1) | 1);
|
|
|
|
u32 dstPos = (y * 32 + x) * 2;
|
|
icon[dstPos + 0] = (u8) (reversedPx & 0xFF);
|
|
icon[dstPos + 1] = (u8) ((reversedPx >> 8) & 0xFF);
|
|
}
|
|
}
|
|
|
|
titleInfo->meta.texture = screen_load_texture_auto(icon, sizeof(icon), 32, 32, GPU_RGBA5551, false);
|
|
}
|
|
|
|
free(bnr);
|
|
}
|
|
|
|
bool empty = strlen(item->name) == 0;
|
|
if(!empty) {
|
|
empty = true;
|
|
|
|
char* curr = item->name;
|
|
while(*curr) {
|
|
if(*curr != ' ') {
|
|
empty = false;
|
|
break;
|
|
}
|
|
|
|
curr++;
|
|
}
|
|
}
|
|
|
|
if(empty) {
|
|
snprintf(item->name, NAME_MAX, "%016llX", realTitleId);
|
|
}
|
|
|
|
item->rgba = COLOR_DS_TITLE;
|
|
item->data = titleInfo;
|
|
|
|
(*data->count)++;
|
|
} else {
|
|
res = R_FBI_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static Result task_populate_titles_from(populate_titles_data* data, FS_MediaType mediaType, bool useDSiWare) {
|
|
bool inserted;
|
|
FS_CardType type;
|
|
if(mediaType == MEDIATYPE_GAME_CARD && (R_FAILED(FSUSER_CardSlotIsInserted(&inserted)) || !inserted || R_FAILED(FSUSER_GetCardType(&type)))) {
|
|
return 0;
|
|
}
|
|
|
|
Result res = 0;
|
|
|
|
if(mediaType != MEDIATYPE_GAME_CARD || type == CARD_CTR) {
|
|
u32 titleCount = 0;
|
|
if(R_SUCCEEDED(res = AM_GetTitleCount(mediaType, &titleCount))) {
|
|
u64* titleIds = (u64*) calloc(titleCount, sizeof(u64));
|
|
if(titleIds != NULL) {
|
|
if(R_SUCCEEDED(res = AM_GetTitleList(&titleCount, mediaType, titleCount, titleIds))) {
|
|
qsort(titleIds, titleCount, sizeof(u64), util_compare_u64);
|
|
|
|
for(u32 i = 0; i < titleCount && *data->count < data->max && R_SUCCEEDED(res); i++) {
|
|
if(task_is_quit_all() || svcWaitSynchronization(data->cancelEvent, 0) == 0) {
|
|
break;
|
|
}
|
|
|
|
bool dsiWare = ((titleIds[i] >> 32) & 0x8000) != 0;
|
|
if(dsiWare != useDSiWare) {
|
|
continue;
|
|
}
|
|
|
|
res = dsiWare ? task_populate_titles_add_twl(data, mediaType, titleIds[i]) : task_populate_titles_add_ctr(data, mediaType, titleIds[i]);
|
|
}
|
|
}
|
|
|
|
free(titleIds);
|
|
} else {
|
|
res = R_FBI_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
} else {
|
|
res = task_populate_titles_add_twl(data, mediaType, 0);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static void task_populate_titles_thread(void* arg) {
|
|
populate_titles_data* data = (populate_titles_data*) arg;
|
|
|
|
Result res = 0;
|
|
if(R_FAILED(res = task_populate_titles_from(data, MEDIATYPE_GAME_CARD, false)) || R_FAILED(res = task_populate_titles_from(data, MEDIATYPE_SD, false)) || R_FAILED(res = task_populate_titles_from(data, MEDIATYPE_NAND, false)) || R_FAILED(res = task_populate_titles_from(data, MEDIATYPE_NAND, true))) {
|
|
error_display_res(NULL, NULL, NULL, res, "Failed to load title listing.");
|
|
}
|
|
|
|
svcCloseHandle(data->cancelEvent);
|
|
free(data);
|
|
}
|
|
|
|
void task_clear_titles(list_item* items, u32* count) {
|
|
if(items == NULL || count == NULL) {
|
|
return;
|
|
}
|
|
|
|
u32 prevCount = *count;
|
|
*count = 0;
|
|
|
|
for(u32 i = 0; i < prevCount; i++) {
|
|
if(items[i].data != NULL) {
|
|
title_info* titleInfo = (title_info*) items[i].data;
|
|
if(titleInfo->hasMeta) {
|
|
screen_unload_texture(titleInfo->meta.texture);
|
|
}
|
|
|
|
free(items[i].data);
|
|
items[i].data = NULL;
|
|
}
|
|
|
|
memset(items[i].name, '\0', NAME_MAX);
|
|
items[i].rgba = 0;
|
|
}
|
|
}
|
|
|
|
Handle task_populate_titles(list_item* items, u32* count, u32 max) {
|
|
if(items == NULL || count == NULL || max == 0) {
|
|
return 0;
|
|
}
|
|
|
|
task_clear_titles(items, count);
|
|
|
|
populate_titles_data* data = (populate_titles_data*) calloc(1, sizeof(populate_titles_data));
|
|
if(data == NULL) {
|
|
error_display(NULL, NULL, NULL, "Failed to allocate title list data.");
|
|
|
|
return 0;
|
|
}
|
|
|
|
data->items = items;
|
|
data->count = count;
|
|
data->max = max;
|
|
|
|
Result eventRes = svcCreateEvent(&data->cancelEvent, 1);
|
|
if(R_FAILED(eventRes)) {
|
|
error_display_res(NULL, NULL, NULL, eventRes, "Failed to create title list cancel event.");
|
|
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
if(threadCreate(task_populate_titles_thread, data, 0x4000, 0x18, 1, true) == NULL) {
|
|
error_display(NULL, NULL, NULL, "Failed to create title list thread.");
|
|
|
|
svcCloseHandle(data->cancelEvent);
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
return data->cancelEvent;
|
|
} |