2018-05-09 09:25:01 +00:00
|
|
|
/*
|
2019-05-06 20:51:09 +00:00
|
|
|
* 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.
|
|
|
|
*/
|
2018-05-09 09:25:01 +00:00
|
|
|
|
|
|
|
#include "title.hpp"
|
|
|
|
|
2018-06-10 09:11:01 +00:00
|
|
|
static std::unordered_map<u128, std::vector<Title>> titles;
|
2018-10-10 21:20:05 +00:00
|
|
|
static std::unordered_map<u64, SDL_Texture*> icons;
|
2018-05-09 09:25:01 +00:00
|
|
|
|
2018-06-12 19:53:05 +00:00
|
|
|
void freeIcons(void)
|
2018-05-09 09:25:01 +00:00
|
|
|
{
|
2019-05-06 20:51:09 +00:00
|
|
|
for (auto& i : icons) {
|
2018-10-10 21:20:05 +00:00
|
|
|
SDL_DestroyTexture(i.second);
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
2018-06-12 19:53:05 +00:00
|
|
|
}
|
2018-05-09 09:25:01 +00:00
|
|
|
|
2018-06-12 19:53:05 +00:00
|
|
|
static void loadIcon(u64 id, NsApplicationControlData* nsacd, size_t iconsize)
|
|
|
|
{
|
|
|
|
auto it = icons.find(id);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (it == icons.end()) {
|
2018-10-10 21:20:05 +00:00
|
|
|
SDL_Texture* texture;
|
|
|
|
SDLH_LoadImage(&texture, nsacd->icon, iconsize);
|
2018-12-14 19:52:01 +00:00
|
|
|
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE);
|
2018-10-10 21:20:05 +00:00
|
|
|
icons.insert({id, texture});
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-17 16:25:07 +00:00
|
|
|
void Title::init(u8 saveDataType, u64 id, u128 userID, const std::string& name, const std::string& author)
|
2018-05-09 09:25:01 +00:00
|
|
|
{
|
2019-05-06 20:51:09 +00:00
|
|
|
mId = id;
|
|
|
|
mUserId = userID;
|
2018-07-17 16:25:07 +00:00
|
|
|
mSaveDataType = saveDataType;
|
2019-05-06 20:51:09 +00:00
|
|
|
mUserName = Account::username(userID);
|
|
|
|
mAuthor = author;
|
|
|
|
mName = name;
|
|
|
|
mSafeName = StringUtils::containsInvalidChar(name) ? StringUtils::format("0x%016llX", mId) : StringUtils::removeForbiddenCharacters(name);
|
|
|
|
mPath = "sdmc:/switch/Checkpoint/saves/" + StringUtils::format("0x%016llX", mId) + " " + mSafeName;
|
|
|
|
|
2019-07-04 06:57:25 +00:00
|
|
|
std::string aname = StringUtils::removeAccents(mName);
|
|
|
|
size_t pos = aname.rfind(":");
|
|
|
|
mDisplayName = std::make_pair(name, "");
|
|
|
|
if (pos != std::string::npos) {
|
|
|
|
std::string name1 = aname.substr(0, pos);
|
|
|
|
std::string name2 = aname.substr(pos + 1);
|
|
|
|
StringUtils::trim(name1);
|
|
|
|
StringUtils::trim(name2);
|
|
|
|
mDisplayName.first = name1;
|
|
|
|
mDisplayName.second = name2;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// check for parenthesis
|
|
|
|
size_t pos1 = aname.find("(");
|
|
|
|
size_t pos2 = aname.rfind(")");
|
|
|
|
if (pos1 != std::string::npos && pos2 != std::string::npos) {
|
|
|
|
std::string name1 = aname.substr(0, pos1);
|
|
|
|
std::string name2 = aname.substr(pos1 + 1, pos2 - pos1);
|
|
|
|
StringUtils::trim(name1);
|
|
|
|
StringUtils::trim(name2);
|
|
|
|
mDisplayName.first = name1;
|
|
|
|
mDisplayName.second = name2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-06 20:51:09 +00:00
|
|
|
if (!io::directoryExists(mPath)) {
|
2018-05-09 09:25:01 +00:00
|
|
|
io::createDirectory(mPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshDirectories();
|
|
|
|
}
|
|
|
|
|
2018-07-17 16:25:07 +00:00
|
|
|
bool Title::systemSave(void)
|
|
|
|
{
|
|
|
|
return mSaveDataType != FsSaveDataType_SaveData;
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 Title::saveDataType(void)
|
|
|
|
{
|
|
|
|
return mSaveDataType;
|
|
|
|
}
|
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
u64 Title::id(void)
|
|
|
|
{
|
|
|
|
return mId;
|
|
|
|
}
|
|
|
|
|
2019-04-22 07:48:50 +00:00
|
|
|
u64 Title::saveId(void)
|
|
|
|
{
|
|
|
|
return mSaveId;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Title::saveId(u64 saveId)
|
|
|
|
{
|
|
|
|
mSaveId = saveId;
|
|
|
|
}
|
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
u128 Title::userId(void)
|
|
|
|
{
|
|
|
|
return mUserId;
|
|
|
|
}
|
|
|
|
|
2018-06-10 20:24:37 +00:00
|
|
|
std::string Title::userName(void)
|
|
|
|
{
|
|
|
|
return mUserName;
|
|
|
|
}
|
|
|
|
|
2018-06-11 20:41:37 +00:00
|
|
|
std::string Title::author(void)
|
|
|
|
{
|
|
|
|
return mAuthor;
|
|
|
|
}
|
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
std::string Title::name(void)
|
|
|
|
{
|
2019-04-12 21:39:55 +00:00
|
|
|
return mName;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<std::string, std::string> Title::displayName(void)
|
|
|
|
{
|
2019-07-04 06:57:25 +00:00
|
|
|
return mDisplayName;
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string Title::path(void)
|
|
|
|
{
|
|
|
|
return mPath;
|
|
|
|
}
|
|
|
|
|
2018-07-17 16:25:07 +00:00
|
|
|
std::string Title::fullPath(size_t index)
|
|
|
|
{
|
|
|
|
return mFullSavePaths.at(index);
|
|
|
|
}
|
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
std::vector<std::string> Title::saves()
|
|
|
|
{
|
|
|
|
return mSaves;
|
|
|
|
}
|
|
|
|
|
2018-10-10 21:20:05 +00:00
|
|
|
SDL_Texture* Title::icon(void)
|
2018-06-10 20:24:37 +00:00
|
|
|
{
|
2018-06-12 19:53:05 +00:00
|
|
|
auto it = icons.find(mId);
|
2018-10-10 21:20:05 +00:00
|
|
|
return it != icons.end() ? it->second : NULL;
|
2018-06-10 20:24:37 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
void Title::refreshDirectories(void)
|
|
|
|
{
|
|
|
|
mSaves.clear();
|
2018-07-17 16:25:07 +00:00
|
|
|
mFullSavePaths.clear();
|
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
Directory savelist(mPath);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (savelist.good()) {
|
|
|
|
for (size_t i = 0, sz = savelist.size(); i < sz; i++) {
|
|
|
|
if (savelist.folder(i)) {
|
2018-05-09 09:25:01 +00:00
|
|
|
mSaves.push_back(savelist.entry(i));
|
2018-07-17 16:25:07 +00:00
|
|
|
mFullSavePaths.push_back(mPath + "/" + savelist.entry(i));
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-06 20:51:09 +00:00
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
std::sort(mSaves.rbegin(), mSaves.rend());
|
2018-07-17 16:25:07 +00:00
|
|
|
std::sort(mFullSavePaths.rbegin(), mFullSavePaths.rend());
|
2018-05-09 09:25:01 +00:00
|
|
|
mSaves.insert(mSaves.begin(), "New...");
|
2018-07-17 16:25:07 +00:00
|
|
|
mFullSavePaths.insert(mFullSavePaths.begin(), "New...");
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
2019-05-06 20:51:09 +00:00
|
|
|
else {
|
2019-07-02 21:42:48 +00:00
|
|
|
// Gui::showError(savelist.error(), "Couldn't retrieve the directory list for the title " + name() + ".");
|
|
|
|
// TODO: log this
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
2018-07-17 16:25:07 +00:00
|
|
|
|
|
|
|
// save backups from configuration
|
|
|
|
std::vector<std::string> additionalFolders = Configuration::getInstance().additionalSaveFolders(mId);
|
2019-05-18 20:51:58 +00:00
|
|
|
for (std::vector<std::string>::const_iterator it = additionalFolders.begin(); it != additionalFolders.end(); ++it) {
|
2018-07-17 16:25:07 +00:00
|
|
|
// we have other folders to parse
|
|
|
|
Directory list(*it);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (list.good()) {
|
|
|
|
for (size_t i = 0, sz = list.size(); i < sz; i++) {
|
|
|
|
if (list.folder(i)) {
|
2018-07-17 16:25:07 +00:00
|
|
|
mSaves.push_back(list.entry(i));
|
|
|
|
mFullSavePaths.push_back(*it + "/" + list.entry(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void loadTitles(void)
|
|
|
|
{
|
|
|
|
titles.clear();
|
|
|
|
|
|
|
|
FsSaveDataIterator iterator;
|
|
|
|
FsSaveDataInfo info;
|
|
|
|
size_t total_entries = 0;
|
2019-05-06 20:51:09 +00:00
|
|
|
size_t outsize = 0;
|
2018-05-09 09:25:01 +00:00
|
|
|
|
2019-05-06 20:51:09 +00:00
|
|
|
NacpLanguageEntry* nle = NULL;
|
2018-05-09 09:25:01 +00:00
|
|
|
NsApplicationControlData* nsacd = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData));
|
2019-05-06 20:51:09 +00:00
|
|
|
if (nsacd == NULL) {
|
2018-05-09 09:25:01 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
memset(nsacd, 0, sizeof(NsApplicationControlData));
|
2019-05-06 20:51:09 +00:00
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
Result res = fsOpenSaveDataIterator(&iterator, FsSaveDataSpaceId_NandUser);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (R_FAILED(res)) {
|
2019-04-23 17:21:53 +00:00
|
|
|
free(nsacd);
|
2018-05-09 09:25:01 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-06 20:51:09 +00:00
|
|
|
while (1) {
|
2018-05-09 09:25:01 +00:00
|
|
|
res = fsSaveDataIteratorRead(&iterator, &info, 1, &total_entries);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (R_FAILED(res) || total_entries == 0) {
|
2018-05-09 09:25:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-05-06 20:51:09 +00:00
|
|
|
if (info.SaveDataType == FsSaveDataType_SaveData) {
|
|
|
|
u64 tid = info.titleID;
|
|
|
|
u64 sid = info.saveID;
|
2018-05-09 09:25:01 +00:00
|
|
|
u128 uid = info.userID;
|
2019-05-06 20:51:09 +00:00
|
|
|
if (!Configuration::getInstance().filter(tid)) {
|
2018-07-14 15:05:30 +00:00
|
|
|
res = nsGetApplicationControlData(1, tid, nsacd, sizeof(NsApplicationControlData), &outsize);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (R_SUCCEEDED(res) && !(outsize < sizeof(nsacd->nacp))) {
|
2018-07-14 15:05:30 +00:00
|
|
|
res = nacpGetLanguageEntry(&nsacd->nacp, &nle);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (R_SUCCEEDED(res) && nle != NULL) {
|
2018-07-14 15:05:30 +00:00
|
|
|
Title title;
|
2018-07-17 16:25:07 +00:00
|
|
|
title.init(info.SaveDataType, tid, uid, std::string(nle->name), std::string(nle->author));
|
2019-04-22 07:48:50 +00:00
|
|
|
title.saveId(sid);
|
2018-07-14 15:05:30 +00:00
|
|
|
loadIcon(tid, nsacd, outsize - sizeof(nsacd->nacp));
|
|
|
|
|
|
|
|
// check if the vector is already created
|
|
|
|
std::unordered_map<u128, std::vector<Title>>::iterator it = titles.find(uid);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (it != titles.end()) {
|
2018-07-14 15:05:30 +00:00
|
|
|
// found
|
|
|
|
it->second.push_back(title);
|
|
|
|
}
|
2019-05-06 20:51:09 +00:00
|
|
|
else {
|
2018-07-14 15:05:30 +00:00
|
|
|
// not found, insert into map
|
|
|
|
std::vector<Title> v;
|
|
|
|
v.push_back(title);
|
|
|
|
titles.emplace(uid, v);
|
|
|
|
}
|
2018-06-10 09:11:01 +00:00
|
|
|
}
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
2018-07-14 15:05:30 +00:00
|
|
|
nle = NULL;
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free(nsacd);
|
|
|
|
fsSaveDataIteratorClose(&iterator);
|
|
|
|
|
2019-05-06 20:51:09 +00:00
|
|
|
for (auto& vect : titles) {
|
2018-06-10 09:11:01 +00:00
|
|
|
std::sort(vect.second.begin(), vect.second.end(), [](Title& l, Title& r) {
|
2019-05-06 20:51:09 +00:00
|
|
|
return l.name() < r.name() && Configuration::getInstance().favorite(l.id()) > Configuration::getInstance().favorite(r.id());
|
2018-06-10 09:11:01 +00:00
|
|
|
});
|
|
|
|
}
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
|
2019-05-06 20:51:09 +00:00
|
|
|
void getTitle(Title& dst, u128 uid, size_t i)
|
2018-05-09 09:25:01 +00:00
|
|
|
{
|
2018-06-10 09:11:01 +00:00
|
|
|
std::unordered_map<u128, std::vector<Title>>::iterator it = titles.find(uid);
|
2019-05-06 20:51:09 +00:00
|
|
|
if (it != titles.end() && i < getTitleCount(uid)) {
|
2018-06-10 09:11:01 +00:00
|
|
|
dst = it->second.at(i);
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-10 09:11:01 +00:00
|
|
|
size_t getTitleCount(u128 uid)
|
2018-05-09 09:25:01 +00:00
|
|
|
{
|
2018-06-10 09:11:01 +00:00
|
|
|
std::unordered_map<u128, std::vector<Title>>::iterator it = titles.find(uid);
|
|
|
|
return it != titles.end() ? it->second.size() : 0;
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
|
2018-09-16 14:34:12 +00:00
|
|
|
bool favorite(u128 uid, int i)
|
|
|
|
{
|
|
|
|
std::unordered_map<u128, std::vector<Title>>::iterator it = titles.find(uid);
|
|
|
|
return it != titles.end() ? Configuration::getInstance().favorite(it->second.at(i).id()) : false;
|
|
|
|
}
|
|
|
|
|
2018-05-09 09:25:01 +00:00
|
|
|
void refreshDirectories(u64 id)
|
|
|
|
{
|
2019-05-06 20:51:09 +00:00
|
|
|
for (auto& pair : titles) {
|
|
|
|
for (size_t i = 0; i < pair.second.size(); i++) {
|
|
|
|
if (pair.second.at(i).id() == id) {
|
2018-06-10 09:11:01 +00:00
|
|
|
pair.second.at(i).refreshDirectories();
|
|
|
|
}
|
2018-05-09 09:25:01 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-10 20:24:37 +00:00
|
|
|
}
|
|
|
|
|
2018-10-10 21:20:05 +00:00
|
|
|
SDL_Texture* smallIcon(u128 uid, size_t i)
|
2018-06-10 20:24:37 +00:00
|
|
|
{
|
|
|
|
std::unordered_map<u128, std::vector<Title>>::iterator it = titles.find(uid);
|
2019-05-06 20:51:09 +00:00
|
|
|
return it != titles.end() ? it->second.at(i).icon() : NULL;
|
2019-04-12 21:39:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::unordered_map<std::string, std::string> getCompleteTitleList(void)
|
|
|
|
{
|
|
|
|
std::unordered_map<std::string, std::string> map;
|
2019-05-06 20:51:09 +00:00
|
|
|
for (const auto& pair : titles) {
|
|
|
|
for (auto value : pair.second) {
|
|
|
|
map.insert({StringUtils::format("0x%016llX", value.id()), value.name()});
|
2019-04-12 21:39:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return map;
|
2018-06-12 19:54:01 +00:00
|
|
|
}
|