/*
* 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 .
*
* 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 "util.hpp"
static Result consoleDisplayError(const std::string& message, Result res)
{
gfxInitDefault();
ATEXIT(gfxExit);
consoleInit(GFX_TOP, nullptr);
printf("\x1b[2;13HCheckpoint v%d.%d.%d-%s", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO, GIT_REV);
printf("\x1b[5;1HError during startup: \x1b[31m0x%08lX\x1b[0m", res);
printf("\x1b[8;1HDescription: \x1b[33m%s\x1b[0m", message.c_str());
printf("\x1b[29;16HPress START to exit.");
gfxFlushBuffers();
gfxSwapBuffers();
gspWaitForVBlank();
while (aptMainLoop() && !(hidKeysDown() & KEY_START)) {
hidScanInput();
}
return res;
}
Result servicesInit(void)
{
sdmcInit();
ATEXIT(sdmcExit);
Logger::getInstance().info("Checkpoint loading started...");
Result res = 0;
Handle hbldrHandle;
if (R_FAILED(res = svcConnectToPort(&hbldrHandle, "hb:ldr"))) {
Logger::getInstance().error("Error during startup with result %llX. Rosalina not found on this system", res);
return consoleDisplayError("Rosalina not found on this system.\nAn updated CFW is required to launch Checkpoint.", res);
}
romfsInit();
ATEXIT(romfsExit);
srvInit();
ATEXIT(srvExit);
amInit();
ATEXIT(amExit);
pxiDevInit();
ATEXIT(pxiDevExit);
if (R_FAILED(res = Archive::init())) {
Logger::getInstance().error("Archive::init failed with result %llX", res);
return consoleDisplayError("Archive::init failed.", res);
}
ATEXIT(Archive::exit);
mkdir("sdmc:/3ds", 777);
mkdir("sdmc:/3ds/Checkpoint", 777);
mkdir("sdmc:/3ds/Checkpoint/saves", 777);
mkdir("sdmc:/3ds/Checkpoint/extdata", 777);
mkdir("sdmc:/3ds/Checkpoint/cheats", 777);
mkdir("sdmc:/cheats", 777);
Gui::init();
ATEXIT(Gui::exit);
// consoleDebugInit(debugDevice_SVC);
// while (aptMainLoop() && !(hidKeysDown() & KEY_START)) { hidScanInput(); }
Configuration::getInstance();
Logger::getInstance().info("Checkpoint loading finished!");
return 0;
}
void calculateTitleDBHash(u8* hash)
{
u32 titleCount, nandCount, titlesRead, nandTitlesRead;
AM_GetTitleCount(MEDIATYPE_SD, &titleCount);
if (Configuration::getInstance().nandSaves()) {
AM_GetTitleCount(MEDIATYPE_NAND, &nandCount);
std::vector ordered;
ordered.reserve(titleCount + nandCount);
AM_GetTitleList(&titlesRead, MEDIATYPE_SD, titleCount, ordered.data());
AM_GetTitleList(&nandTitlesRead, MEDIATYPE_NAND, nandCount, ordered.data() + titlesRead * sizeof(u64));
sort(ordered.begin(), ordered.end());
sha256(hash, (u8*)ordered.data(), (titleCount + nandCount) * sizeof(u64));
}
else {
std::vector ordered;
ordered.reserve(titleCount);
AM_GetTitleList(&titlesRead, MEDIATYPE_SD, titleCount, ordered.data());
sort(ordered.begin(), ordered.end());
sha256(hash, (u8*)ordered.data(), titleCount * sizeof(u64));
}
}
std::u16string StringUtils::UTF8toUTF16(const char* src)
{
char16_t tmp[256] = {0};
utf8_to_utf16((uint16_t*)tmp, (uint8_t*)src, 256);
return std::u16string(tmp);
}
std::u16string StringUtils::removeForbiddenCharacters(std::u16string src)
{
static const std::u16string illegalChars = StringUtils::UTF8toUTF16(".,!\\/:?*\"<>|");
for (size_t i = 0; i < src.length(); i++) {
if (illegalChars.find(src[i]) != std::string::npos) {
src[i] = ' ';
}
}
size_t i;
for (i = src.length() - 1; i > 0 && src[i] == L' '; i--)
;
src.erase(i + 1, src.length() - i);
return src;
}
static std::map widthCache;
static std::queue widthCacheOrder;
std::string StringUtils::splitWord(const std::string& text, float scaleX, float maxWidth)
{
std::string word = text;
if (StringUtils::textWidth(word, scaleX) > maxWidth) {
float currentWidth = 0.0f;
for (size_t i = 0; i < word.size(); i++) {
u16 codepoint = 0xFFFF;
int iMod = 0;
if (word[i] & 0x80 && word[i] & 0x40 && word[i] & 0x20 && !(word[i] & 0x10) && i + 2 < word.size()) {
codepoint = word[i] & 0x0F;
codepoint = codepoint << 6 | (word[i + 1] & 0x3F);
codepoint = codepoint << 6 | (word[i + 2] & 0x3F);
iMod = 2;
}
else if (word[i] & 0x80 && word[i] & 0x40 && !(word[i] & 0x20) && i + 1 < word.size()) {
codepoint = word[i] & 0x1F;
codepoint = codepoint << 6 | (word[i + 1] & 0x3F);
iMod = 1;
}
else if (!(word[i] & 0x80)) {
codepoint = word[i];
}
float charWidth;
auto width = widthCache.find(codepoint);
if (width != widthCache.end()) {
charWidth = width->second->charWidth * scaleX;
}
else {
widthCache.insert_or_assign(codepoint, fontGetCharWidthInfo(NULL, fontGlyphIndexFromCodePoint(NULL, codepoint)));
widthCacheOrder.push(codepoint);
if (widthCache.size() > 512) {
widthCache.erase(widthCacheOrder.front());
widthCacheOrder.pop();
}
charWidth = widthCache[codepoint]->charWidth * scaleX;
}
currentWidth += charWidth;
if (currentWidth > maxWidth) {
word.insert(i, 1, '\n');
currentWidth = charWidth;
}
i += iMod; // Yay, variable width encodings
}
}
return word;
}
float StringUtils::textWidth(const std::string& text, float scaleX)
{
float ret = 0.0f;
float largestRet = 0.0f;
for (size_t i = 0; i < text.size(); i++) {
if (text[i] == '\n') {
largestRet = std::max(largestRet, ret);
ret = 0.0f;
continue;
}
u16 codepoint = 0xFFFF;
if (text[i] & 0x80 && text[i] & 0x40 && text[i] & 0x20 && !(text[i] & 0x10) && i + 2 < text.size()) {
codepoint = text[i] & 0x0F;
codepoint = codepoint << 6 | (text[i + 1] & 0x3F);
codepoint = codepoint << 6 | (text[i + 2] & 0x3F);
i += 2;
}
else if (text[i] & 0x80 && text[i] & 0x40 && !(text[i] & 0x20) && i + 1 < text.size()) {
codepoint = text[i] & 0x1F;
codepoint = codepoint << 6 | (text[i + 1] & 0x3F);
i += 1;
}
else if (!(text[i] & 0x80)) {
codepoint = text[i];
}
float charWidth;
auto width = widthCache.find(codepoint);
if (width != widthCache.end()) {
charWidth = width->second->charWidth * scaleX;
}
else {
widthCache.insert_or_assign(codepoint, fontGetCharWidthInfo(NULL, fontGlyphIndexFromCodePoint(NULL, codepoint)));
widthCacheOrder.push(codepoint);
if (widthCache.size() > 1000) {
widthCache.erase(widthCacheOrder.front());
widthCacheOrder.pop();
}
charWidth = widthCache[codepoint]->charWidth * scaleX;
}
ret += charWidth;
}
return std::max(largestRet, ret);
}
float StringUtils::textWidth(const C2D_Text& text, float scaleX)
{
return ceilf(text.width * scaleX);
}
std::string StringUtils::wrap(const std::string& text, float scaleX, float maxWidth)
{
if (textWidth(text, scaleX) <= maxWidth) {
return text;
}
std::string dst, line, word;
dst = line = word = "";
for (std::string::const_iterator it = text.begin(); it != text.end(); ++it) {
word += *it;
if (*it == ' ') {
// split single words that are bigger than maxWidth
if (StringUtils::textWidth(line + word, scaleX) <= maxWidth) {
line += word;
}
else {
if (StringUtils::textWidth(word, scaleX) > maxWidth) {
line += word;
line = StringUtils::splitWord(line, scaleX, maxWidth);
word = line.substr(line.find('\n') + 1, std::string::npos);
line = line.substr(0, line.find('\n')); // Split line on first newLine; assign second part to word and first to line
}
if (line[line.size() - 1] == ' ') {
dst += line.substr(0, line.size() - 1) + '\n';
}
else {
dst += line + '\n';
}
line = word;
}
word = "";
}
}
// "Another iteration" of the loop b/c it probably won't end with a space
// If it does, no harm done
// word = StringUtils::splitWord(word, scaleX, maxWidth);
if (StringUtils::textWidth(line + word, scaleX) <= maxWidth) {
dst += line + word;
}
else {
if (StringUtils::textWidth(word, scaleX) > maxWidth) {
line += word;
line = StringUtils::splitWord(line, scaleX, maxWidth);
word = line.substr(line.find('\n') + 1, std::string::npos);
line = line.substr(0, line.find('\n'));
}
if (line[line.size() - 1] == ' ') {
dst += line.substr(0, line.size() - 1) + '\n' + word;
}
else {
dst += line + '\n' + word;
}
}
return dst;
}
float StringUtils::textHeight(const std::string& text, float scaleY)
{
size_t n = std::count(text.begin(), text.end(), '\n') + 1;
return ceilf(scaleY * fontGetInfo(NULL)->lineFeed * n);
}