Checkpoint/3ds/source/title.cpp
2019-07-16 00:02:12 +02:00

987 lines
32 KiB
C++

/*
* This file is part of Checkpoint
* Copyright (C) 2017-2019 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "title.hpp"
static bool validId(u64 id);
static C2D_Image loadTextureIcon(smdh_s* smdh);
static std::vector<Title> titleSaves;
static std::vector<Title> titleExtdatas;
static void exportTitleListCache(std::vector<Title>& list, const std::u16string& path);
static void importTitleListCache(void);
void Title::load(void)
{
mId = 0xFFFFFFFFFFFFFFFF;
mMedia = MEDIATYPE_SD;
mCard = CARD_CTR;
memset(productCode, 0, 16);
mShortDescription = StringUtils::UTF8toUTF16("");
mLongDescription = StringUtils::UTF8toUTF16("");
mSavePath = StringUtils::UTF8toUTF16("");
mExtdataPath = StringUtils::UTF8toUTF16("");
mIcon = Gui::noIcon();
mAccessibleSave = false;
mAccessibleExtdata = false;
mSaves.clear();
mExtdata.clear();
}
void Title::load(u64 id, u8* _productCode, bool accessibleSave, bool accessibleExtdata, std::u16string shortDescription,
std::u16string longDescription, std::u16string savePath, std::u16string extdataPath, FS_MediaType media, FS_CardType cardType, CardType card)
{
mId = id;
mAccessibleSave = accessibleSave;
mAccessibleExtdata = accessibleExtdata;
mShortDescription = shortDescription;
mLongDescription = longDescription;
mSavePath = savePath;
mExtdataPath = extdataPath;
mMedia = media;
mCard = cardType;
mCardType = card;
mIcon = Gui::noIcon();
memcpy(productCode, _productCode, 16);
}
bool Title::load(u64 _id, FS_MediaType _media, FS_CardType _card)
{
bool loadTitle = false;
mId = _id;
mMedia = _media;
mCard = _card;
if (mCard == CARD_CTR) {
smdh_s* smdh;
if (mId == TID_PKSM) {
smdh = loadSMDH("romfs:/PKSM.smdh");
}
else {
smdh = loadSMDH(lowId(), highId(), mMedia);
}
if (smdh == NULL) {
return false;
}
char unique[12] = {0};
sprintf(unique, "0x%05X ", (unsigned int)uniqueId());
mShortDescription = StringUtils::removeForbiddenCharacters((char16_t*)smdh->applicationTitles[1].shortDescription);
mLongDescription = (char16_t*)smdh->applicationTitles[1].longDescription;
mSavePath = StringUtils::UTF8toUTF16("/3ds/Checkpoint/saves/") + StringUtils::UTF8toUTF16(unique) + mShortDescription;
mExtdataPath = StringUtils::UTF8toUTF16("/3ds/Checkpoint/extdata/") + StringUtils::UTF8toUTF16(unique) + mShortDescription;
AM_GetTitleProductCode(mMedia, mId, productCode);
mAccessibleSave = Archive::accessible(mediaType(), lowId(), highId());
mAccessibleExtdata = mMedia == MEDIATYPE_NAND ? false : Archive::accessible(extdataId());
if (mAccessibleSave) {
loadTitle = true;
if (!io::directoryExists(Archive::sdmc(), mSavePath)) {
Result res = io::createDirectory(Archive::sdmc(), mSavePath);
if (R_FAILED(res)) {
loadTitle = false;
// TODO: log this
// Gui::showError(res, "Failed to create backup directory.");
}
}
}
if (mAccessibleExtdata) {
loadTitle = true;
if (!io::directoryExists(Archive::sdmc(), mExtdataPath)) {
Result res = io::createDirectory(Archive::sdmc(), mExtdataPath);
if (R_FAILED(res)) {
loadTitle = false;
// TODO: log this
// Gui::showError(res, "Failed to create backup directory.");
}
}
}
if (loadTitle) {
mIcon = loadTextureIcon(smdh);
}
delete smdh;
}
else {
u8* headerData = new u8[0x3B4];
Result res = FSUSER_GetLegacyRomHeader(mMedia, 0LL, headerData);
if (R_FAILED(res)) {
delete[] headerData;
return false;
}
char _cardTitle[14] = {0};
char _gameCode[6] = {0};
std::copy(headerData, headerData + 12, _cardTitle);
std::copy(headerData + 12, headerData + 16, _gameCode);
_cardTitle[13] = '\0';
_gameCode[5] = '\0';
res = SPIGetCardType(&mCardType, (_gameCode[0] == 'I') ? 1 : 0);
if (R_FAILED(res)) {
delete[] headerData;
return false;
}
delete[] headerData;
mShortDescription = StringUtils::removeForbiddenCharacters(StringUtils::UTF8toUTF16(_cardTitle));
mLongDescription = mShortDescription;
mSavePath = StringUtils::UTF8toUTF16("/3ds/Checkpoint/saves/") + StringUtils::UTF8toUTF16(_gameCode) + StringUtils::UTF8toUTF16(" ") +
mShortDescription;
mExtdataPath = mSavePath;
memset(productCode, 0, 16);
mAccessibleSave = true;
mAccessibleExtdata = false;
loadTitle = true;
if (!io::directoryExists(Archive::sdmc(), mSavePath)) {
res = io::createDirectory(Archive::sdmc(), mSavePath);
if (R_FAILED(res)) {
loadTitle = false;
// TODO: log this
// Gui::showError(res, "Failed to create backup directory.");
}
}
mIcon = Gui::TWLIcon();
}
refreshDirectories();
return loadTitle;
}
bool Title::accessibleSave(void)
{
return mAccessibleSave;
}
bool Title::accessibleExtdata(void)
{
return mAccessibleExtdata;
}
std::string Title::mediaTypeString(void)
{
switch (mMedia) {
case MEDIATYPE_SD:
return "SD Card";
case MEDIATYPE_GAME_CARD:
return "Cartridge";
case MEDIATYPE_NAND:
return "NAND";
default:
return " ";
}
return " ";
}
std::string Title::shortDescription(void)
{
return StringUtils::UTF16toUTF8(mShortDescription);
}
std::u16string Title::getShortDescription(void)
{
return mShortDescription;
}
std::string Title::longDescription(void)
{
return StringUtils::UTF16toUTF8(mLongDescription);
}
std::u16string Title::getLongDescription(void)
{
return mLongDescription;
}
std::u16string Title::savePath(void)
{
return mSavePath;
}
std::u16string Title::extdataPath(void)
{
return mExtdataPath;
}
std::u16string Title::fullSavePath(size_t index)
{
return mFullSavePaths.at(index);
}
std::u16string Title::fullExtdataPath(size_t index)
{
return mFullExtdataPaths.at(index);
}
std::vector<std::u16string> Title::saves(void)
{
return mSaves;
}
std::vector<std::u16string> Title::extdata(void)
{
return mExtdata;
}
void Title::refreshDirectories(void)
{
mSaves.clear();
mExtdata.clear();
mFullSavePaths.clear();
mFullExtdataPaths.clear();
if (accessibleSave()) {
// standard save backups
Directory savelist(Archive::sdmc(), mSavePath);
if (savelist.good()) {
for (size_t i = 0, sz = savelist.size(); i < sz; i++) {
if (savelist.folder(i)) {
mSaves.push_back(savelist.entry(i));
mFullSavePaths.push_back(mSavePath + StringUtils::UTF8toUTF16("/") + savelist.entry(i));
}
}
std::sort(mSaves.rbegin(), mSaves.rend());
std::sort(mFullSavePaths.rbegin(), mFullSavePaths.rend());
mSaves.insert(mSaves.begin(), StringUtils::UTF8toUTF16("New..."));
mFullSavePaths.insert(mFullSavePaths.begin(), StringUtils::UTF8toUTF16("New..."));
}
else {
// fprintf(stderr, "Title 0x%016llX, %s\n", id(), shortDescription().c_str());
// LOG THIS Gui::showError(savelist.error(), "Couldn't retrieve the directory list\nfor the title " + shortDescription());
}
// save backups from configuration
std::vector<std::u16string> additionalFolders = Configuration::getInstance().additionalSaveFolders(mId);
for (std::vector<std::u16string>::const_iterator it = additionalFolders.begin(); it != additionalFolders.end(); ++it) {
// we have other folders to parse
Directory list(Archive::sdmc(), *it);
if (list.good()) {
for (size_t i = 0, sz = list.size(); i < sz; i++) {
if (list.folder(i)) {
mSaves.push_back(list.entry(i));
mFullSavePaths.push_back(*it + StringUtils::UTF8toUTF16("/") + list.entry(i));
}
}
}
}
}
if (accessibleExtdata()) {
// extdata backups
Directory extlist(Archive::sdmc(), mExtdataPath);
if (extlist.good()) {
for (size_t i = 0, sz = extlist.size(); i < sz; i++) {
if (extlist.folder(i)) {
mExtdata.push_back(extlist.entry(i));
mFullExtdataPaths.push_back(mExtdataPath + StringUtils::UTF8toUTF16("/") + extlist.entry(i));
}
}
std::sort(mExtdata.begin(), mExtdata.end());
std::sort(mFullExtdataPaths.begin(), mFullExtdataPaths.end());
mExtdata.insert(mExtdata.begin(), StringUtils::UTF8toUTF16("New..."));
mFullExtdataPaths.insert(mFullExtdataPaths.begin(), StringUtils::UTF8toUTF16("New..."));
}
else {
// fprintf(stderr, "Title 0x%016llX, %s\n", id(), shortDescription().c_str());
// LOG THIS Gui::showError(extlist.error(), "Couldn't retrieve the extdata list\nfor the title " + shortDescription());
}
// extdata backups from configuration
std::vector<std::u16string> additionalFolders = Configuration::getInstance().additionalExtdataFolders(mId);
for (std::vector<std::u16string>::const_iterator it = additionalFolders.begin(); it != additionalFolders.end(); ++it) {
// we have other folders to parse
Directory list(Archive::sdmc(), *it);
if (list.good()) {
for (size_t i = 0, sz = list.size(); i < sz; i++) {
if (list.folder(i)) {
mExtdata.push_back(list.entry(i));
mFullExtdataPaths.push_back(*it + StringUtils::UTF8toUTF16("/") + list.entry(i));
}
}
}
}
}
}
u32 Title::highId(void)
{
return (u32)(mId >> 32);
}
u32 Title::lowId(void)
{
return (u32)mId;
}
u32 Title::uniqueId(void)
{
return (lowId() >> 8);
}
u64 Title::id(void)
{
return mId;
}
u32 Title::extdataId(void)
{
u32 low = lowId();
switch (low) {
case 0x00055E00:
return 0x055D; // Pokémon Y
case 0x0011C400:
return 0x11C5; // Pokémon Omega Ruby
case 0x00175E00:
return 0x1648; // Pokémon Moon
case 0x00179600:
case 0x00179800:
return 0x1794; // Fire Emblem Conquest SE NA
case 0x00179700:
case 0x0017A800:
return 0x1795; // Fire Emblem Conquest SE EU
case 0x0012DD00:
case 0x0012DE00:
return 0x12DC; // Fire Emblem If JP
case 0x001B5100:
return 0x1B50; // Pokémon Ultramoon
// TODO: need confirmation for this
// case 0x001C5100:
// case 0x001C5300:
// return 0x0BD3; // Etrian Odyssey V: Beyond the Myth
}
return low >> 8;
}
FS_MediaType Title::mediaType(void)
{
return mMedia;
}
FS_CardType Title::cardType(void)
{
return mCard;
}
CardType Title::SPICardType(void)
{
return mCardType;
}
C2D_Image Title::icon(void)
{
return mIcon;
}
static bool validId(u64 id)
{
// check for invalid titles
switch ((u32)id) {
// Instruction Manual
case 0x00008602:
case 0x00009202:
case 0x00009B02:
case 0x0000A402:
case 0x0000AC02:
case 0x0000B402:
// Internet Browser
case 0x00008802:
case 0x00009402:
case 0x00009D02:
case 0x0000A602:
case 0x0000AE02:
case 0x0000B602:
case 0x20008802:
case 0x20009402:
case 0x20009D02:
case 0x2000AE02:
// Garbage
case 0x00021A00:
return false;
}
// check for updates
u32 high = id >> 32;
if (high == 0x0004000E) {
return false;
}
return !Configuration::getInstance().filter(id);
}
void loadTitles(bool forceRefresh)
{
std::u16string savecachePath = StringUtils::UTF8toUTF16("/3ds/Checkpoint/fullsavecache");
std::u16string extdatacachePath = StringUtils::UTF8toUTF16("/3ds/Checkpoint/fullextdatacache");
// on refreshing
titleSaves.clear();
titleExtdatas.clear();
bool optimizedLoad = false;
u8 hash[SHA256_BLOCK_SIZE];
calculateTitleDBHash(hash);
std::u16string titlesHashPath = StringUtils::UTF8toUTF16("/3ds/Checkpoint/titles.sha");
if (!io::fileExists(Archive::sdmc(), titlesHashPath) || !io::fileExists(Archive::sdmc(), savecachePath) ||
!io::fileExists(Archive::sdmc(), extdatacachePath)) {
// create title list sha256 hash file if it doesn't exist in the working directory
FSStream output(Archive::sdmc(), titlesHashPath, FS_OPEN_WRITE, SHA256_BLOCK_SIZE);
output.write(hash, SHA256_BLOCK_SIZE);
output.close();
}
else {
// compare current hash with the previous hash
FSStream input(Archive::sdmc(), titlesHashPath, FS_OPEN_READ);
if (input.good() && input.size() == SHA256_BLOCK_SIZE) {
u8* buf = new u8[input.size()];
input.read(buf, input.size());
input.close();
if (memcmp(hash, buf, SHA256_BLOCK_SIZE) == 0) {
// hash matches
optimizedLoad = true;
}
else {
FSUSER_DeleteFile(Archive::sdmc(), fsMakePath(PATH_UTF16, titlesHashPath.data()));
FSStream output(Archive::sdmc(), titlesHashPath, FS_OPEN_WRITE, SHA256_BLOCK_SIZE);
output.write(hash, SHA256_BLOCK_SIZE);
output.close();
}
delete[] buf;
}
}
if (optimizedLoad && !forceRefresh) {
// deserialize data
importTitleListCache();
for (auto& title : titleSaves) {
title.refreshDirectories();
}
for (auto& title : titleExtdatas) {
title.refreshDirectories();
}
}
else {
u32 count = 0;
if (Configuration::getInstance().nandSaves()) {
AM_GetTitleCount(MEDIATYPE_NAND, &count);
u64 ids_nand[count];
AM_GetTitleList(NULL, MEDIATYPE_NAND, count, ids_nand);
for (u32 i = 0; i < count; i++) {
if (validId(ids_nand[i])) {
Title title;
if (title.load(ids_nand[i], MEDIATYPE_NAND, CARD_CTR)) {
if (title.accessibleSave()) {
titleSaves.push_back(title);
}
// TODO: extdata?
}
}
}
}
count = 0;
AM_GetTitleCount(MEDIATYPE_SD, &count);
u64 ids[count];
AM_GetTitleList(NULL, MEDIATYPE_SD, count, ids);
for (u32 i = 0; i < count; i++) {
if (validId(ids[i])) {
Title title;
if (title.load(ids[i], MEDIATYPE_SD, CARD_CTR)) {
if (title.accessibleSave()) {
titleSaves.push_back(title);
}
if (title.accessibleExtdata()) {
titleExtdatas.push_back(title);
}
}
}
}
// always check for PKSM's extdata archive
bool isPKSMIdAlreadyHere = false;
for (u32 i = 0; i < count; i++) {
if (ids[i] == TID_PKSM) {
isPKSMIdAlreadyHere = true;
break;
}
}
if (!isPKSMIdAlreadyHere) {
Title title;
if (title.load(TID_PKSM, MEDIATYPE_SD, CARD_CTR)) {
if (title.accessibleExtdata()) {
titleExtdatas.push_back(title);
}
}
}
}
std::sort(titleSaves.begin(), titleSaves.end(), [](Title& l, Title& r) {
if (Configuration::getInstance().favorite(l.id()) != Configuration::getInstance().favorite(r.id())) {
return Configuration::getInstance().favorite(l.id());
}
else {
return l.shortDescription() < r.shortDescription();
}
});
std::sort(titleExtdatas.begin(), titleExtdatas.end(), [](Title& l, Title& r) {
if (Configuration::getInstance().favorite(l.id()) != Configuration::getInstance().favorite(r.id())) {
return Configuration::getInstance().favorite(l.id());
}
else {
return l.shortDescription() < r.shortDescription();
}
});
// serialize data
exportTitleListCache(titleSaves, savecachePath);
exportTitleListCache(titleExtdatas, extdatacachePath);
FS_CardType cardType;
Result res = FSUSER_GetCardType(&cardType);
if (R_SUCCEEDED(res)) {
if (cardType == CARD_CTR) {
u32 count = 0;
AM_GetTitleCount(MEDIATYPE_GAME_CARD, &count);
if (count > 0) {
u64 ids[count];
AM_GetTitleList(NULL, MEDIATYPE_GAME_CARD, count, ids);
if (validId(ids[0])) {
Title title;
if (title.load(ids[0], MEDIATYPE_GAME_CARD, cardType)) {
if (title.accessibleSave()) {
titleSaves.insert(titleSaves.begin(), title);
}
if (title.accessibleExtdata()) {
titleExtdatas.insert(titleExtdatas.begin(), title);
}
}
}
}
}
else {
Title title;
if (title.load(0, MEDIATYPE_GAME_CARD, cardType)) {
titleSaves.insert(titleSaves.begin(), title);
}
}
}
}
void getTitle(Title& dst, int i)
{
const Mode_t mode = Archive::mode();
if (i < getTitleCount()) {
dst = mode == MODE_SAVE ? titleSaves.at(i) : titleExtdatas.at(i);
}
}
int getTitleCount(void)
{
const Mode_t mode = Archive::mode();
return mode == MODE_SAVE ? titleSaves.size() : titleExtdatas.size();
}
void Title::setIcon(C2D_Image icon)
{
mIcon = icon;
}
C2D_Image icon(int i)
{
const Mode_t mode = Archive::mode();
return mode == MODE_SAVE ? titleSaves.at(i).icon() : titleExtdatas.at(i).icon();
}
bool favorite(int i)
{
const Mode_t mode = Archive::mode();
u64 id = mode == MODE_SAVE ? titleSaves.at(i).id() : titleExtdatas.at(i).id();
return Configuration::getInstance().favorite(id);
}
static C2D_Image loadTextureFromBytes(u16* bigIconData)
{
C3D_Tex* tex = (C3D_Tex*)malloc(sizeof(C3D_Tex));
static const Tex3DS_SubTexture subt3x = {48, 48, 0.0f, 48 / 64.0f, 48 / 64.0f, 0.0f};
C2D_Image image = (C2D_Image){tex, &subt3x};
C3D_TexInit(image.tex, 64, 64, GPU_RGB565);
u16* dest = (u16*)image.tex->data + (64 - 48) * 64;
u16* src = bigIconData;
for (int j = 0; j < 48; j += 8) {
memcpy(dest, src, 48 * 8 * sizeof(u16));
src += 48 * 8;
dest += 64 * 8;
}
return image;
}
static C2D_Image loadTextureIcon(smdh_s* smdh)
{
return loadTextureFromBytes(smdh->bigIconData);
}
void refreshDirectories(u64 id)
{
const Mode_t mode = Archive::mode();
if (mode == MODE_SAVE) {
for (size_t i = 0; i < titleSaves.size(); i++) {
if (titleSaves.at(i).id() == id) {
titleSaves.at(i).refreshDirectories();
}
}
}
else {
for (size_t i = 0; i < titleExtdatas.size(); i++) {
if (titleExtdatas.at(i).id() == id) {
titleExtdatas.at(i).refreshDirectories();
}
}
}
}
static const size_t ENTRYSIZE = 5341;
/**
* CACHE STRUCTURE
* start, len
* id (0, 8)
* productCode (8, 16)
* accessibleSave (24, 1)
* accessibleExtdata (25, 1)
* shortDescription (26, 64)
* longDescription (90, 128)
* savePath (218, 256)
* extdataPath (474, 256)
* mediaType (730, 1)
* fs cardtype (731, 1)
* cardtype (732, 1)
* bigIconData (733, 4608)
*/
static void exportTitleListCache(std::vector<Title>& list, const std::u16string& path)
{
u8* cache = new u8[list.size() * ENTRYSIZE]();
for (size_t i = 0; i < list.size(); i++) {
u64 id = list.at(i).id();
bool accessibleSave = list.at(i).accessibleSave();
bool accessibleExtdata = list.at(i).accessibleExtdata();
std::string shortDescription = StringUtils::UTF16toUTF8(list.at(i).getShortDescription());
std::string longDescription = StringUtils::UTF16toUTF8(list.at(i).getLongDescription());
std::string savePath = StringUtils::UTF16toUTF8(list.at(i).savePath());
std::string extdataPath = StringUtils::UTF16toUTF8(list.at(i).extdataPath());
FS_MediaType media = list.at(i).mediaType();
FS_CardType cardType = list.at(i).cardType();
CardType card = list.at(i).SPICardType();
if (cardType == CARD_CTR) {
smdh_s* smdh = loadSMDH(list.at(i).lowId(), list.at(i).highId(), media);
if (smdh != NULL) {
memcpy(cache + i * ENTRYSIZE + 733, smdh->bigIconData, 0x900 * 2);
}
delete smdh;
}
memcpy(cache + i * ENTRYSIZE + 0, &id, sizeof(u64));
memcpy(cache + i * ENTRYSIZE + 8, list.at(i).productCode, 16);
memcpy(cache + i * ENTRYSIZE + 24, &accessibleSave, sizeof(u8));
memcpy(cache + i * ENTRYSIZE + 25, &accessibleExtdata, sizeof(u8));
memcpy(cache + i * ENTRYSIZE + 26, shortDescription.c_str(), shortDescription.length());
memcpy(cache + i * ENTRYSIZE + 90, longDescription.c_str(), longDescription.length());
memcpy(cache + i * ENTRYSIZE + 218, savePath.c_str(), savePath.length());
memcpy(cache + i * ENTRYSIZE + 474, extdataPath.c_str(), extdataPath.length());
memcpy(cache + i * ENTRYSIZE + 730, &media, sizeof(u8));
memcpy(cache + i * ENTRYSIZE + 731, &cardType, sizeof(u8));
memcpy(cache + i * ENTRYSIZE + 732, &card, sizeof(u8));
}
FSUSER_DeleteFile(Archive::sdmc(), fsMakePath(PATH_UTF16, path.data()));
FSStream output(Archive::sdmc(), path, FS_OPEN_WRITE, list.size() * ENTRYSIZE);
output.write(cache, list.size() * ENTRYSIZE);
output.close();
delete[] cache;
}
static void importTitleListCache(void)
{
FSStream inputsaves(Archive::sdmc(), StringUtils::UTF8toUTF16("/3ds/Checkpoint/fullsavecache"), FS_OPEN_READ);
u32 sizesaves = inputsaves.size() / ENTRYSIZE;
u8* cachesaves = new u8[inputsaves.size()];
inputsaves.read(cachesaves, inputsaves.size());
inputsaves.close();
FSStream inputextdatas(Archive::sdmc(), StringUtils::UTF8toUTF16("/3ds/Checkpoint/fullextdatacache"), FS_OPEN_READ);
u32 sizeextdatas = inputextdatas.size() / ENTRYSIZE;
u8* cacheextdatas = new u8[inputextdatas.size()];
inputextdatas.read(cacheextdatas, inputextdatas.size());
inputextdatas.close();
// fill the lists with blank titles firsts
for (size_t i = 0, sz = std::max(sizesaves, sizeextdatas); i < sz; i++) {
Title title;
title.load();
if (i < sizesaves) {
titleSaves.push_back(title);
}
if (i < sizeextdatas) {
titleExtdatas.push_back(title);
}
}
// store already loaded ids
std::vector<u64> alreadystored;
for (size_t i = 0; i < sizesaves; i++) {
u64 id;
u8 productCode[16];
bool accessibleSave;
bool accessibleExtdata;
char shortDescription[0x40];
char longDescription[0x80];
char savePath[256];
char extdataPath[256];
FS_MediaType media;
FS_CardType cardType;
CardType card;
memcpy(&id, cachesaves + i * ENTRYSIZE, sizeof(u64));
memcpy(productCode, cachesaves + i * ENTRYSIZE + 8, 16);
memcpy(&accessibleSave, cachesaves + i * ENTRYSIZE + 24, sizeof(u8));
memcpy(&accessibleExtdata, cachesaves + i * ENTRYSIZE + 25, sizeof(u8));
memcpy(shortDescription, cachesaves + i * ENTRYSIZE + 26, 0x40);
memcpy(longDescription, cachesaves + i * ENTRYSIZE + 90, 0x80);
memcpy(savePath, cachesaves + i * ENTRYSIZE + 218, 256);
memcpy(extdataPath, cachesaves + i * ENTRYSIZE + 474, 256);
memcpy(&media, cachesaves + i * ENTRYSIZE + 730, sizeof(u8));
memcpy(&cardType, cachesaves + i * ENTRYSIZE + 731, sizeof(u8));
memcpy(&card, cachesaves + i * ENTRYSIZE + 732, sizeof(u8));
Title title;
title.load(id, productCode, accessibleSave, accessibleExtdata, StringUtils::UTF8toUTF16(shortDescription),
StringUtils::UTF8toUTF16(longDescription), StringUtils::UTF8toUTF16(savePath), StringUtils::UTF8toUTF16(extdataPath), media, cardType,
card);
if (cardType == CARD_CTR) {
u16 bigIconData[0x900];
memcpy(bigIconData, cachesaves + i * ENTRYSIZE + 733, 0x900 * 2);
C2D_Image smallIcon = loadTextureFromBytes(bigIconData);
title.setIcon(smallIcon);
}
else {
title.setIcon(Gui::TWLIcon());
}
titleSaves.at(i) = title;
alreadystored.push_back(id);
}
for (size_t i = 0; i < sizeextdatas; i++) {
u64 id;
memcpy(&id, cacheextdatas + i * ENTRYSIZE, sizeof(u64));
std::vector<u64>::iterator it = find(alreadystored.begin(), alreadystored.end(), id);
if (it == alreadystored.end()) {
u8 productCode[16];
bool accessibleSave;
bool accessibleExtdata;
char shortDescription[0x40];
char longDescription[0x80];
char savePath[256];
char extdataPath[256];
FS_MediaType media;
FS_CardType cardType;
CardType card;
memcpy(productCode, cacheextdatas + i * ENTRYSIZE + 8, 16);
memcpy(&accessibleSave, cacheextdatas + i * ENTRYSIZE + 24, sizeof(u8));
memcpy(&accessibleExtdata, cacheextdatas + i * ENTRYSIZE + 25, sizeof(u8));
memcpy(shortDescription, cacheextdatas + i * ENTRYSIZE + 26, 0x40);
memcpy(longDescription, cacheextdatas + i * ENTRYSIZE + 90, 0x80);
memcpy(savePath, cacheextdatas + i * ENTRYSIZE + 218, 256);
memcpy(extdataPath, cacheextdatas + i * ENTRYSIZE + 474, 256);
memcpy(&media, cacheextdatas + i * ENTRYSIZE + 730, sizeof(u8));
memcpy(&cardType, cacheextdatas + i * ENTRYSIZE + 731, sizeof(u8));
memcpy(&card, cacheextdatas + i * ENTRYSIZE + 732, sizeof(u8));
Title title;
title.load(id, productCode, accessibleSave, accessibleExtdata, StringUtils::UTF8toUTF16(shortDescription),
StringUtils::UTF8toUTF16(longDescription), StringUtils::UTF8toUTF16(savePath), StringUtils::UTF8toUTF16(extdataPath), media, cardType,
card);
if (cardType == CARD_CTR) {
u16 bigIconData[0x900];
memcpy(bigIconData, cacheextdatas + i * ENTRYSIZE + 733, 0x900 * 2);
C2D_Image smallIcon = loadTextureFromBytes(bigIconData);
title.setIcon(smallIcon);
}
else {
title.setIcon(Gui::TWLIcon());
}
titleExtdatas.at(i) = title;
}
else {
auto pos = it - alreadystored.begin();
// avoid to copy a cartridge title into the extdata list twice
if (i != 0 && pos == 0) {
auto newpos = find(alreadystored.rbegin(), alreadystored.rend(), id);
titleExtdatas.at(i) = titleSaves.at(alreadystored.rend() - newpos);
}
else {
titleExtdatas.at(i) = titleSaves.at(pos);
}
}
}
delete[] cachesaves;
delete[] cacheextdatas;
}
static bool scanCard(void)
{
static bool isScanning = false;
if (isScanning) {
return false;
}
else {
isScanning = true;
}
bool ret = false;
Result res = 0;
u32 count = 0;
FS_CardType cardType;
res = FSUSER_GetCardType(&cardType);
if (R_SUCCEEDED(res)) {
if (cardType == CARD_CTR) {
res = AM_GetTitleCount(MEDIATYPE_GAME_CARD, &count);
if (R_SUCCEEDED(res) && count > 0) {
u64 id;
res = AM_GetTitleList(NULL, MEDIATYPE_GAME_CARD, count, &id);
if (validId(id)) {
Title title;
if (title.load(id, MEDIATYPE_GAME_CARD, cardType)) {
ret = true;
if (title.accessibleSave()) {
if (titleSaves.at(0).mediaType() != MEDIATYPE_GAME_CARD) {
titleSaves.insert(titleSaves.begin(), title);
}
}
if (title.accessibleExtdata()) {
if (titleExtdatas.at(0).mediaType() != MEDIATYPE_GAME_CARD) {
titleExtdatas.insert(titleExtdatas.begin(), title);
}
}
}
}
}
}
else {
Title title;
if (title.load(0, MEDIATYPE_GAME_CARD, cardType)) {
ret = true;
if (titleSaves.at(0).mediaType() != MEDIATYPE_GAME_CARD) {
titleSaves.insert(titleSaves.begin(), title);
}
}
}
}
isScanning = false;
return ret;
}
void updateCard(void)
{
static bool first = true;
static bool oldCardIn = false;
if (first) {
FSUSER_CardSlotIsInserted(&oldCardIn);
first = false;
return;
}
bool cardIn = false;
FSUSER_CardSlotIsInserted(&cardIn);
if (cardIn != oldCardIn) {
if (cardIn) {
bool power;
FSUSER_CardSlotPowerOn(&power);
while (!power) {
FSUSER_CardSlotGetCardIFPowerStatus(&power);
}
oldCardIn = scanCard();
}
else {
if (titleSaves.at(0).mediaType() == MEDIATYPE_GAME_CARD) {
titleSaves.erase(titleSaves.begin());
}
if (titleExtdatas.at(0).mediaType() == MEDIATYPE_GAME_CARD) {
titleExtdatas.erase(titleExtdatas.begin());
}
oldCardIn = false;
}
}
}
bool Title::isActivityLog(void)
{
bool activityId = false;
switch (lowId()) {
case 0x00020200:
case 0x00021200:
case 0x00022200:
case 0x00026200:
case 0x00027200:
case 0x00028200:
activityId = true;
}
return mMedia == MEDIATYPE_NAND && activityId;
}