draft transfer implementation
This commit is contained in:
BIN
source/.DS_Store
vendored
BIN
source/.DS_Store
vendored
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
#include <MainApplication.hpp>
|
||||
#include <data.h>
|
||||
#include <fs.h>
|
||||
#include "util.hpp"
|
||||
#include "main.hpp"
|
||||
|
||||
static int nxlink_sock = -1;
|
||||
|
||||
@@ -35,7 +35,14 @@ extern "C" void userAppExit() {
|
||||
|
||||
// Main entrypoint
|
||||
int main() {
|
||||
printf("main\n");
|
||||
Result res = servicesInit();
|
||||
if (R_FAILED(res)) {
|
||||
servicesExit();
|
||||
exit(res);
|
||||
}
|
||||
|
||||
loadTitles();
|
||||
|
||||
// First create our renderer, where one can customize SDL or other stuff's
|
||||
// initialization.
|
||||
auto renderer_opts = pu::ui::render::RendererInitOptions(
|
||||
@@ -46,8 +53,6 @@ int main() {
|
||||
|
||||
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
|
||||
|
||||
data::init();
|
||||
fs::init();
|
||||
// Create our main application from the renderer
|
||||
auto main = ui::MainApplication::New(renderer);
|
||||
|
||||
|
||||
@@ -1,43 +1,21 @@
|
||||
#include <MainApplication.hpp>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <data.h>
|
||||
#include <stdio.h>
|
||||
#include <main.hpp>
|
||||
#include <const.h>
|
||||
#include <client.hpp>
|
||||
|
||||
static std::vector<uint64_t> accSids, devSids, bcatSids, cacheSids;
|
||||
|
||||
//Sort save create tids alphabetically by title from data
|
||||
static struct
|
||||
{
|
||||
bool operator()(const uint64_t& tid1, const uint64_t& tid2)
|
||||
{
|
||||
std::string tid1Title = data::getTitleNameByTID(tid1);
|
||||
std::string tid2Title = data::getTitleNameByTID(tid2);
|
||||
|
||||
uint32_t pointA = 0, pointB = 0;
|
||||
for(unsigned i = 0, j = 0; i < tid1Title.length(); )
|
||||
{
|
||||
ssize_t tid1Cnt = decode_utf8(&pointA, (const uint8_t *)&tid1Title.c_str()[i]);
|
||||
ssize_t tid2Cnt = decode_utf8(&pointB, (const uint8_t *)&tid2Title.c_str()[j]);
|
||||
|
||||
pointA = tolower(pointA), pointB = tolower(pointB);
|
||||
if(pointA != pointB)
|
||||
return pointA < pointB;
|
||||
|
||||
i += tid1Cnt, j += tid2Cnt;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} sortCreateTIDs;
|
||||
|
||||
namespace ui {
|
||||
extern MainApplication *mainApp;
|
||||
void TitlesLayout::InitTitles() {
|
||||
this->titlesMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7);
|
||||
const data::user *u = data::getCurrentUser();
|
||||
for(const data::userTitleInfo& t : u->titleInfo) {
|
||||
auto titleItem = pu::ui::elm::MenuItem::New(data::getTitleNameByTID(t.tid).c_str());
|
||||
for (size_t i = 0; i < getTitleCount(g_currentUId); i++) {
|
||||
Title title;
|
||||
getTitle(title, g_currentUId, i);
|
||||
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
|
||||
titleItem->SetColor(COLOR("#FFFFFFFF"));
|
||||
titlesMenu->AddItem(titleItem);
|
||||
this->titlesMenu->AddItem(titleItem);
|
||||
}
|
||||
|
||||
this->Add(this->titlesMenu);
|
||||
@@ -52,13 +30,32 @@ namespace ui {
|
||||
}
|
||||
|
||||
if (Down & HidNpadButton_A) {
|
||||
printf("current game index is %i\n", this->titlesMenu->GetSelectedIndex());
|
||||
data::setTitleIndex(this->titlesMenu->GetSelectedIndex());
|
||||
int opt = mainApp->CreateShowDialog("", "What do you want?", { "Transfer", "Receive" }, true);
|
||||
printf("opt is %d\n", opt);
|
||||
auto index = this->titlesMenu->GetSelectedIndex();
|
||||
Title title;
|
||||
getTitle(title, g_currentUId, index);
|
||||
printf("userid is 0x%lX%lX\n", title.userId().uid[1], title.userId().uid[0]);
|
||||
printf("current game index is %i\n", index);
|
||||
int opt = mainApp->CreateShowDialog(title.name().c_str(), "What do you want?", { "Transfer", "Receive" }, true);
|
||||
switch (opt) {
|
||||
case 0: {
|
||||
// Transfert selected
|
||||
printf("path is %s\n", title.fullPath(0).c_str());
|
||||
// Transfer selected
|
||||
auto result = io::backup(index, g_currentUId);
|
||||
if (std::get<0>(result)) {
|
||||
printf("path is %s\n", std::get<2>(result).c_str());
|
||||
std::string path = std::get<2>(result);
|
||||
std::vector<std::string> files;
|
||||
std::vector<char*> cstrings{};
|
||||
auto directory = std::filesystem::path(path);
|
||||
for (const auto & entry : std::filesystem::directory_iterator(path)) {
|
||||
std::cout << entry.path() << std::endl;
|
||||
files.push_back(entry.path().string());
|
||||
}
|
||||
for (auto& file : files) {
|
||||
cstrings.push_back(&file.front());
|
||||
}
|
||||
transfer_files(directory, cstrings.data(), cstrings.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
#include <cstdio>
|
||||
#include <data.h>
|
||||
#include <MainApplication.hpp>
|
||||
#include "main.hpp"
|
||||
|
||||
namespace ui {
|
||||
extern MainApplication *mainApp;
|
||||
|
||||
UsersLayout::UsersLayout() : Layout::Layout() {
|
||||
this->usersMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94,
|
||||
7);
|
||||
this->usersMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7);
|
||||
this->usersMenu->SetScrollbarColor(COLOR("#170909FF"));
|
||||
|
||||
for (data::user &u: data::users) {
|
||||
auto username = pu::ui::elm::MenuItem::New(u.getUsername() + ": " + std::to_string(u.titleInfo.size()));
|
||||
for (AccountUid const& uid : Account::ids()) {
|
||||
auto username = pu::ui::elm::MenuItem::New(Account::username(uid) + ": " + std::to_string(getTitleCount(uid)));
|
||||
username->SetColor(COLOR("#FFFFFFFF"));
|
||||
this->usersMenu->AddItem(username);
|
||||
}
|
||||
@@ -31,7 +30,7 @@ namespace ui {
|
||||
|
||||
if (Down & HidNpadButton_A) {
|
||||
printf("current index is %i\n", this->usersMenu->GetSelectedIndex());
|
||||
data::setUserIndex(this->usersMenu->GetSelectedIndex());
|
||||
g_currentUId = Account::ids().at(this->usersMenu->GetSelectedIndex());
|
||||
mainApp->titlesLayout->InitTitles();
|
||||
mainApp->LoadLayout(mainApp->titlesLayout);
|
||||
}
|
||||
|
||||
95
source/account.cpp
Normal file
95
source/account.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* This file is part of Checkpoint
|
||||
* Copyright (C) 2017-2021 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 "account.hpp"
|
||||
#include <main.hpp>
|
||||
|
||||
static std::map<AccountUid, User> mUsers;
|
||||
|
||||
Result Account::init(void)
|
||||
{
|
||||
return accountInitialize(AccountServiceType_Application);
|
||||
}
|
||||
|
||||
void Account::exit(void)
|
||||
{
|
||||
accountExit();
|
||||
}
|
||||
|
||||
std::vector<AccountUid> Account::ids(void)
|
||||
{
|
||||
std::vector<AccountUid> v;
|
||||
for (auto& value : mUsers) {
|
||||
v.push_back(value.second.id);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static User getUser(AccountUid id)
|
||||
{
|
||||
User user{id, ""};
|
||||
AccountProfile profile;
|
||||
AccountProfileBase profilebase;
|
||||
memset(&profilebase, 0, sizeof(profilebase));
|
||||
|
||||
if (R_SUCCEEDED(accountGetProfile(&profile, id)) && R_SUCCEEDED(accountProfileGet(&profile, NULL, &profilebase))) {
|
||||
user.name = std::string(profilebase.nickname, 0x20);
|
||||
}
|
||||
|
||||
accountProfileClose(&profile);
|
||||
return user;
|
||||
}
|
||||
|
||||
std::string Account::username(AccountUid id)
|
||||
{
|
||||
std::map<AccountUid, User>::const_iterator got = mUsers.find(id);
|
||||
if (got == mUsers.end()) {
|
||||
User user = getUser(id);
|
||||
mUsers.insert({id, user});
|
||||
return user.name;
|
||||
}
|
||||
|
||||
return got->second.name;
|
||||
}
|
||||
|
||||
AccountUid Account::selectAccount(void)
|
||||
{
|
||||
LibAppletArgs args;
|
||||
libappletArgsCreate(&args, 0x10000);
|
||||
u8 st_in[0xA0] = {0};
|
||||
u8 st_out[0x18] = {0};
|
||||
size_t repsz;
|
||||
|
||||
Result res = libappletLaunch(AppletId_LibraryAppletPlayerSelect, &args, st_in, 0xA0, st_out, 0x18, &repsz);
|
||||
if (R_SUCCEEDED(res)) {
|
||||
u64 lres = *(u64*)st_out;
|
||||
AccountUid uid = *(AccountUid*)&st_out[8];
|
||||
if (lres == 0)
|
||||
return uid;
|
||||
}
|
||||
|
||||
return g_currentUId;
|
||||
}
|
||||
258
source/client.cpp
Normal file
258
source/client.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <logger.hpp>
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#include <client.hpp>
|
||||
namespace fs = std::filesystem;
|
||||
#else
|
||||
namespace fs = std::__fs::filesystem;
|
||||
#endif
|
||||
#define PORT 8080
|
||||
#define BUFFER_SIZE 1024
|
||||
#define MULTICAST_PORT 8081
|
||||
#define MULTICAST_GROUP "239.0.0.1" // Multicast group IP
|
||||
|
||||
struct ThreadArgs
|
||||
{
|
||||
char **filenames;
|
||||
int file_count;
|
||||
int sock;
|
||||
fs::path directory;
|
||||
};
|
||||
|
||||
bool receiveAck(int sock)
|
||||
{
|
||||
char ack[4] = {0};
|
||||
int bytes_received = read(sock, ack, 3);
|
||||
return bytes_received > 0 && std::string(ack) == "ACK";
|
||||
}
|
||||
|
||||
// Отправка строки с учетом её длины
|
||||
void send_string(int sock, const std::string &str)
|
||||
{
|
||||
size_t length = str.size();
|
||||
send(sock, &length, sizeof(length), 0);
|
||||
send(sock, str.c_str(), length, 0);
|
||||
}
|
||||
|
||||
void *send_files_thread(void *args)
|
||||
{
|
||||
ThreadArgs *thread_args = static_cast<ThreadArgs *>(args);
|
||||
char **filenames = thread_args->filenames;
|
||||
int file_count = thread_args->file_count;
|
||||
int sock = thread_args->sock;
|
||||
fs::path cwd = thread_args->directory;
|
||||
delete thread_args;
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
|
||||
// Send the directory length
|
||||
|
||||
std::cout << Logger::INFO << "cwd.filename is: " << cwd.filename().c_str() << std::endl; // Get the parent directory
|
||||
std::string dirname = cwd.parent_path().filename();
|
||||
uint32_t directory_len = dirname.size();
|
||||
send(sock, &directory_len, sizeof(directory_len), 0);
|
||||
|
||||
// Send the dirname
|
||||
send(sock, dirname.c_str(), directory_len, 0);
|
||||
|
||||
for (int i = 0; i < file_count; ++i)
|
||||
{
|
||||
std::string path = filenames[i];
|
||||
std::size_t found = path.find_last_of("/\\");
|
||||
std::string filename = path.substr(found+1);
|
||||
std::ifstream infile(path, std::ios::binary | std::ios::ate);
|
||||
if (!infile.is_open())
|
||||
{
|
||||
std::cout << Logger::ERROR << "File not found: " << filename.c_str() << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the size of the file
|
||||
std::streamsize file_size = infile.tellg();
|
||||
infile.seekg(0, std::ios::beg);
|
||||
|
||||
// Send the filename length
|
||||
uint32_t filename_len = filename.size();
|
||||
std::cout << Logger::INFO << "Send filename length: " << filename_len << std::endl;
|
||||
send(sock, &filename_len, sizeof(filename_len), 0);
|
||||
|
||||
// Send the filename
|
||||
std::cout << Logger::INFO << "Send file name: " << filename.c_str() << std::endl;
|
||||
send(sock, filename.c_str(), filename_len, 0);
|
||||
|
||||
// Send the file size
|
||||
std::cout << Logger::INFO << "Send file size: " << file_size << std::endl;
|
||||
send(sock, &file_size, sizeof(file_size), 0);
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
// Send the file data
|
||||
while (file_size > 0)
|
||||
{
|
||||
infile.read(buffer, BUFFER_SIZE);
|
||||
send(sock, buffer, infile.gcount(), 0);
|
||||
file_size -= infile.gcount();
|
||||
std::cout << Logger::INFO << "wait for ACK" << std::endl;
|
||||
// Wait for acknowledgment after each chunk
|
||||
if (!receiveAck(sock))
|
||||
{
|
||||
std::cout << Logger::ERROR << "Failed to receive acknowledgment" << std::endl;;
|
||||
}
|
||||
}
|
||||
|
||||
send(sock, 0, 0, 0);
|
||||
|
||||
std::cout << Logger::INFO << "File sent successfully: " << filename.c_str() << std::endl;
|
||||
|
||||
infile.close();
|
||||
}
|
||||
|
||||
close(sock);
|
||||
pthread_exit(nullptr);
|
||||
}
|
||||
|
||||
int find_server(char *server_ip)
|
||||
{
|
||||
std::cout << Logger::INFO << "Init find_server" << std::endl;;
|
||||
int sockfd;
|
||||
struct sockaddr_in multicast_addr;
|
||||
|
||||
std::cout << Logger::INFO << "Create socket" << std::endl;
|
||||
|
||||
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "Socket creation error" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&multicast_addr, 0, sizeof(multicast_addr));
|
||||
|
||||
multicast_addr.sin_family = AF_INET;
|
||||
multicast_addr.sin_port = htons(MULTICAST_PORT);
|
||||
multicast_addr.sin_addr.s_addr = inet_addr(MULTICAST_GROUP);
|
||||
|
||||
std::cout << Logger::INFO << "Send DISCOVER_SERVER" << std::endl;
|
||||
|
||||
const char *multicast_message = "DISCOVER_SERVER";
|
||||
if (sendto(sockfd, multicast_message, strlen(multicast_message), 0, (struct sockaddr *)&multicast_addr, sizeof(multicast_addr)) < 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "sendto failed" << std::endl;
|
||||
close(sockfd);
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << Logger::INFO << "send multicast message success" << std::endl;
|
||||
}
|
||||
|
||||
struct sockaddr_in cliaddr;
|
||||
socklen_t len = sizeof(cliaddr);
|
||||
char buffer[BUFFER_SIZE];
|
||||
|
||||
ssize_t n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&cliaddr, &len);
|
||||
std::cout << Logger::INFO << "recvfrom n: %i" << n << std::endl;
|
||||
if (n < 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "recvfrom failed" << std::endl;
|
||||
close(sockfd);
|
||||
return -1;
|
||||
}
|
||||
std::cout << Logger::INFO << "buffer: " << buffer << std::endl;
|
||||
buffer[n] = '\0';
|
||||
if (strcmp(buffer, "SERVER_HERE") == 0)
|
||||
{
|
||||
std::cout << Logger::INFO << "Server found" << std::endl;
|
||||
inet_ntop(AF_INET, &cliaddr.sin_addr, server_ip, INET_ADDRSTRLEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << Logger::ERROR << "Unable to find server, close socket" << std::endl;;
|
||||
close(sockfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(sockfd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int transfer_files(fs::path directory, char **filenames, int file_count) {
|
||||
std::cout << Logger::INFO << "Init transfer_files" << std::endl;
|
||||
char server_ip[INET_ADDRSTRLEN];
|
||||
if (find_server(server_ip) != 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "Failed to find server" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sock = 0;
|
||||
struct sockaddr_in serv_addr;
|
||||
|
||||
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "Socket creation error" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_port = htons(PORT);
|
||||
|
||||
if (inet_pton(AF_INET, server_ip, &serv_addr.sin_addr) <= 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "Invalid address / Address not supported" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "Connection failed" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_t file_thread;
|
||||
ThreadArgs *thread_args = new ThreadArgs{filenames, file_count, sock, directory};
|
||||
if (pthread_create(&file_thread, nullptr, send_files_thread,
|
||||
(void *)thread_args) < 0)
|
||||
{
|
||||
std::cout << Logger::ERROR << "Thread creation failed" << std::endl;
|
||||
close(sock);
|
||||
delete thread_args;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << Logger::INFO << "Wait for file_thread" << std::endl;
|
||||
pthread_join(file_thread, nullptr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef __SWITCH__ // for desktop
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0] << " <filename1> <filename2> ..." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
char **filenames = const_cast<char **>(&argv[1]);
|
||||
int file_count = argc - 1;
|
||||
|
||||
transfer_files(filenames, file_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
124
source/common.cpp
Normal file
124
source/common.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* This file is part of Checkpoint
|
||||
* Copyright (C) 2017-2021 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 "common.hpp"
|
||||
|
||||
std::string DateTime::timeStr(void)
|
||||
{
|
||||
time_t unixTime;
|
||||
struct tm timeStruct;
|
||||
time(&unixTime);
|
||||
localtime_r(&unixTime, &timeStruct);
|
||||
return StringUtils::format("%02i:%02i:%02i", timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec);
|
||||
}
|
||||
|
||||
std::string DateTime::dateTimeStr(void)
|
||||
{
|
||||
time_t unixTime;
|
||||
struct tm timeStruct;
|
||||
time(&unixTime);
|
||||
localtime_r(&unixTime, &timeStruct);
|
||||
return StringUtils::format("%04i%02i%02i-%02i%02i%02i", timeStruct.tm_year + 1900, timeStruct.tm_mon + 1, timeStruct.tm_mday, timeStruct.tm_hour,
|
||||
timeStruct.tm_min, timeStruct.tm_sec);
|
||||
}
|
||||
|
||||
std::string DateTime::logDateTime(void)
|
||||
{
|
||||
time_t unixTime;
|
||||
struct tm timeStruct;
|
||||
time(&unixTime);
|
||||
localtime_r(&unixTime, &timeStruct);
|
||||
return StringUtils::format("%04i-%02i-%02i %02i:%02i:%02i", timeStruct.tm_year + 1900, timeStruct.tm_mon + 1, timeStruct.tm_mday,
|
||||
timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec);
|
||||
}
|
||||
|
||||
std::string StringUtils::UTF16toUTF8(const std::u16string& src)
|
||||
{
|
||||
static std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
|
||||
std::string dst = convert.to_bytes(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
std::string StringUtils::removeForbiddenCharacters(std::string src)
|
||||
{
|
||||
static const std::string illegalChars = ".,!\\/:?*\"<>|";
|
||||
for (size_t i = 0, sz = src.length(); i < sz; i++) {
|
||||
if (illegalChars.find(src[i]) != std::string::npos) {
|
||||
src[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = src.length() - 1; i > 0 && src[i] == ' '; i--)
|
||||
;
|
||||
src.erase(i + 1, src.length() - i);
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
std::string StringUtils::format(const std::string fmt_str, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char* fp = NULL;
|
||||
va_start(ap, fmt_str);
|
||||
vasprintf(&fp, fmt_str.c_str(), ap);
|
||||
va_end(ap);
|
||||
std::unique_ptr<char[]> formatted(fp);
|
||||
return std::string(formatted.get());
|
||||
}
|
||||
|
||||
bool StringUtils::containsInvalidChar(const std::string& str)
|
||||
{
|
||||
for (size_t i = 0, sz = str.length(); i < sz; i++) {
|
||||
if (!isascii(str[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void StringUtils::ltrim(std::string& s)
|
||||
{
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); }));
|
||||
}
|
||||
|
||||
void StringUtils::rtrim(std::string& s)
|
||||
{
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end());
|
||||
}
|
||||
|
||||
void StringUtils::trim(std::string& s)
|
||||
{
|
||||
ltrim(s);
|
||||
rtrim(s);
|
||||
}
|
||||
|
||||
char* getConsoleIP(void)
|
||||
{
|
||||
struct in_addr in;
|
||||
in.s_addr = gethostid();
|
||||
return inet_ntoa(in);
|
||||
}
|
||||
352
source/data.cpp
352
source/data.cpp
@@ -1,352 +0,0 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <switch.h>
|
||||
|
||||
#include "data.h"
|
||||
#include "fs/file.h"
|
||||
#include "util.h"
|
||||
#include "type.h"
|
||||
|
||||
//FsSaveDataSpaceId_All doesn't work for SD
|
||||
static const unsigned saveOrder[] = {0, 1, 2, 3, 4, 100, 101};
|
||||
|
||||
int selUser = 0, selData = 0;
|
||||
|
||||
//User vector
|
||||
std::vector <data::user> data::users;
|
||||
|
||||
//For other save types
|
||||
static bool sysBCATPushed = false, tempPushed = false;
|
||||
std::unordered_map <uint64_t, data::titleInfo> data::titles;
|
||||
|
||||
//Sorts titles by sortType
|
||||
static struct {
|
||||
bool operator()(const data::userTitleInfo &a, const data::userTitleInfo &b) {
|
||||
std::string titleA = data::getTitleNameByTID(a.tid);
|
||||
std::string titleB = data::getTitleNameByTID(b.tid);
|
||||
uint32_t pointA, pointB;
|
||||
for (unsigned i = 0, j = 0; i < titleA.length();) {
|
||||
ssize_t aCnt = decode_utf8(&pointA, (const uint8_t *) &titleA.data()[i]);
|
||||
ssize_t bCnt = decode_utf8(&pointB, (const uint8_t *) &titleB.data()[j]);
|
||||
pointA = tolower(pointA), pointB = tolower(pointB);
|
||||
if (pointA != pointB)
|
||||
return pointA < pointB;
|
||||
|
||||
i += aCnt;
|
||||
j += bCnt;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} sortTitles;
|
||||
|
||||
//Returns -1 for new
|
||||
static int getUserIndex(const AccountUid &id) {
|
||||
u128 nId = util::accountUIDToU128(id);
|
||||
for (unsigned i = 0; i < data::users.size(); i++)
|
||||
if (data::users[i].getUID128() == nId) return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline bool accountSystemSaveCheck(const FsSaveDataInfo &_inf) {
|
||||
if (_inf.save_data_type == FsSaveDataType_System && util::accountUIDToU128(_inf.uid) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//Minimal init/test to avoid loading and creating things I don't need
|
||||
static bool testMount(const FsSaveDataInfo &_inf) {
|
||||
bool ret = false;
|
||||
|
||||
if ((ret = fs::mountSave(_inf)))
|
||||
fs::unmountSave();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void addTitleToList(const uint64_t &tid) {
|
||||
uint64_t outSize = 0;
|
||||
NsApplicationControlData *ctrlData = new NsApplicationControlData;
|
||||
NacpLanguageEntry *ent;
|
||||
Result ctrlRes = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, ctrlData,
|
||||
sizeof(NsApplicationControlData), &outSize);
|
||||
Result nacpRes = nacpGetLanguageEntry(&ctrlData->nacp, &ent);
|
||||
size_t iconSize = outSize - sizeof(ctrlData->nacp);
|
||||
|
||||
if (R_SUCCEEDED(ctrlRes) && !(outSize < sizeof(ctrlData->nacp)) && R_SUCCEEDED(nacpRes) && iconSize > 0) {
|
||||
//Copy nacp
|
||||
memcpy(&data::titles[tid].nacp, &ctrlData->nacp, sizeof(NacpStruct));
|
||||
|
||||
//Setup 'shortcuts' to strings
|
||||
NacpLanguageEntry *ent;
|
||||
nacpGetLanguageEntry(&data::titles[tid].nacp, &ent);
|
||||
if (strlen(ent->name) == 0)
|
||||
data::titles[tid].title = ctrlData->nacp.lang[SetLanguage_ENUS].name;
|
||||
else
|
||||
data::titles[tid].title = ent->name;
|
||||
|
||||
data::titles[tid].author = ent->author;
|
||||
|
||||
if ((data::titles[tid].safeTitle = util::safeString(ent->name)) == "")
|
||||
data::titles[tid].safeTitle = util::getIDStr(tid);
|
||||
} else {
|
||||
memset(&data::titles[tid].nacp, 0, sizeof(NacpStruct));
|
||||
data::titles[tid].title = util::getIDStr(tid);
|
||||
data::titles[tid].author = "Someone?";
|
||||
data::titles[tid].safeTitle = util::getIDStr(tid);
|
||||
}
|
||||
delete ctrlData;
|
||||
}
|
||||
|
||||
static inline bool titleIsLoaded(const uint64_t &tid) {
|
||||
auto findTid = data::titles.find(tid);
|
||||
|
||||
return findTid == data::titles.end() ? false : true;
|
||||
}
|
||||
|
||||
static void loadUserAccounts() {
|
||||
s32 total = 0;
|
||||
AccountUid *uids = new AccountUid[8];
|
||||
if (R_SUCCEEDED(accountListAllUsers(uids, 8, &total))) {
|
||||
for (int i = 0; i < total; i++)
|
||||
data::users.emplace_back(uids[i], "", "");
|
||||
}
|
||||
delete[] uids;
|
||||
}
|
||||
|
||||
//This can load titles installed without having save data
|
||||
static void loadTitlesFromRecords() {
|
||||
NsApplicationRecord nsRecord;
|
||||
int32_t entryCount = 0, recordOffset = 0;
|
||||
while (R_SUCCEEDED(nsListApplicationRecord(&nsRecord, 1, recordOffset++, &entryCount)) && entryCount > 0) {
|
||||
if (!titleIsLoaded(nsRecord.application_id))
|
||||
addTitleToList(nsRecord.application_id);
|
||||
}
|
||||
}
|
||||
|
||||
bool data::loadUsersTitles(bool clearUsers) {
|
||||
static unsigned systemUserCount = 4;
|
||||
FsSaveDataInfoReader it;
|
||||
FsSaveDataInfo info;
|
||||
s64 total = 0;
|
||||
|
||||
//Clear titles
|
||||
for (data::user &u: data::users)
|
||||
u.titleInfo.clear();
|
||||
if (clearUsers) {
|
||||
systemUserCount = 4;
|
||||
data::users.clear();
|
||||
|
||||
loadUserAccounts();
|
||||
sysBCATPushed = false;
|
||||
tempPushed = false;
|
||||
|
||||
users.emplace_back(util::u128ToAccountUID(3), "Device", "Device");
|
||||
users.emplace_back(util::u128ToAccountUID(2), "BCAT", "BCAT");
|
||||
users.emplace_back(util::u128ToAccountUID(5), "Cache", "Cache");
|
||||
users.emplace_back(util::u128ToAccountUID(0), "System", "System");
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 7; i++) {
|
||||
if (R_FAILED(fsOpenSaveDataInfoReader(&it, (FsSaveDataSpaceId) saveOrder[i])))
|
||||
continue;
|
||||
|
||||
while (R_SUCCEEDED(fsSaveDataInfoReaderRead(&it, &info, 1, &total)) && total != 0) {
|
||||
uint64_t tid = 0;
|
||||
if (info.save_data_type == FsSaveDataType_System || info.save_data_type == FsSaveDataType_SystemBcat)
|
||||
tid = info.system_save_data_id;
|
||||
else
|
||||
tid = info.application_id;
|
||||
|
||||
if (!titleIsLoaded(tid))
|
||||
addTitleToList(tid);
|
||||
|
||||
//Don't bother with this stuff
|
||||
if (!accountSystemSaveCheck(info) || !testMount(info))
|
||||
continue;
|
||||
|
||||
switch (info.save_data_type) {
|
||||
case FsSaveDataType_Bcat:
|
||||
info.uid = util::u128ToAccountUID(2);
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
info.uid = util::u128ToAccountUID(3);
|
||||
break;
|
||||
|
||||
case FsSaveDataType_SystemBcat:
|
||||
info.uid = util::u128ToAccountUID(4);
|
||||
if (!sysBCATPushed) {
|
||||
++systemUserCount;
|
||||
sysBCATPushed = true;
|
||||
users.emplace_back(util::u128ToAccountUID(4), "System BCAT",
|
||||
"System BCAT");
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
info.uid = util::u128ToAccountUID(5);
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Temporary:
|
||||
info.uid = util::u128ToAccountUID(6);
|
||||
if (!tempPushed) {
|
||||
++systemUserCount;
|
||||
tempPushed = true;
|
||||
users.emplace_back(util::u128ToAccountUID(6), "Temporary",
|
||||
"Temporary");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int u = getUserIndex(info.uid);
|
||||
if (u == -1) {
|
||||
users.emplace(data::users.end() - systemUserCount, info.uid, "", "");
|
||||
u = getUserIndex(info.uid);
|
||||
}
|
||||
|
||||
PdmPlayStatistics playStats;
|
||||
if (info.save_data_type == FsSaveDataType_Account || info.save_data_type == FsSaveDataType_Device)
|
||||
pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(info.application_id, info.uid, false,
|
||||
&playStats);
|
||||
else
|
||||
memset(&playStats, 0, sizeof(PdmPlayStatistics));
|
||||
users[u].addUserTitleInfo(tid, &info, &playStats);
|
||||
}
|
||||
fsSaveDataInfoReaderClose(&it);
|
||||
}
|
||||
|
||||
//Get reference to device save user
|
||||
unsigned devPos = getUserIndex(util::u128ToAccountUID(3));
|
||||
data::user &dev = data::users[devPos];
|
||||
for (unsigned i = 0; i < devPos; i++) {
|
||||
//Not needed but makes this easier to read
|
||||
data::user &u = data::users[i];
|
||||
u.titleInfo.insert(u.titleInfo.end(), dev.titleInfo.begin(), dev.titleInfo.end());
|
||||
}
|
||||
|
||||
data::sortUserTitles();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void data::sortUserTitles() {
|
||||
|
||||
for (data::user &u: data::users)
|
||||
std::sort(u.titleInfo.begin(), u.titleInfo.end(), sortTitles);
|
||||
}
|
||||
|
||||
void data::init() {
|
||||
data::loadUsersTitles(true);
|
||||
}
|
||||
|
||||
void data::exit() {
|
||||
/*Still needed for planned future revisions*/
|
||||
}
|
||||
|
||||
void data::setUserIndex(unsigned _sUser) {
|
||||
selUser = _sUser;
|
||||
}
|
||||
|
||||
data::user *data::getCurrentUser() {
|
||||
return &users[selUser];
|
||||
}
|
||||
|
||||
unsigned data::getCurrentUserIndex() {
|
||||
return selUser;
|
||||
}
|
||||
|
||||
void data::setTitleIndex(unsigned _sTitle) {
|
||||
selData = _sTitle;
|
||||
}
|
||||
|
||||
data::userTitleInfo *data::getCurrentUserTitleInfo() {
|
||||
return &users[selUser].titleInfo[selData];
|
||||
}
|
||||
|
||||
unsigned data::getCurrentUserTitleInfoIndex() {
|
||||
return selData;
|
||||
}
|
||||
|
||||
data::titleInfo *data::getTitleInfoByTID(const uint64_t &tid) {
|
||||
if (titles.find(tid) != titles.end())
|
||||
return &titles[tid];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::string data::getTitleNameByTID(const uint64_t &tid) {
|
||||
return titles[tid].title;
|
||||
}
|
||||
|
||||
std::string data::getTitleSafeNameByTID(const uint64_t &tid) {
|
||||
return titles[tid].safeTitle;
|
||||
}
|
||||
|
||||
int data::getTitleIndexInUser(const data::user &u, const uint64_t &tid) {
|
||||
for (unsigned i = 0; i < u.titleInfo.size(); i++) {
|
||||
if (u.titleInfo[i].tid == tid)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
data::user::user(const AccountUid &_id, const std::string &_backupName, const std::string &_safeBackupName) {
|
||||
userID = _id;
|
||||
uID128 = util::accountUIDToU128(_id);
|
||||
|
||||
AccountProfile prof;
|
||||
AccountProfileBase base;
|
||||
|
||||
if (R_SUCCEEDED(accountGetProfile(&prof, userID)) && R_SUCCEEDED(accountProfileGet(&prof, NULL, &base))) {
|
||||
username = base.nickname;
|
||||
userSafe = util::safeString(username);
|
||||
if (userSafe.empty()) {
|
||||
char tmp[32];
|
||||
sprintf(tmp, "Acc%08X", (uint32_t) uID128);
|
||||
userSafe = tmp;
|
||||
}
|
||||
|
||||
uint32_t jpgSize = 0;
|
||||
accountProfileGetImageSize(&prof, &jpgSize);
|
||||
uint8_t *jpegData = new uint8_t[jpgSize];
|
||||
accountProfileLoadImage(&prof, jpegData, jpgSize, &jpgSize);
|
||||
delete[] jpegData;
|
||||
|
||||
accountProfileClose(&prof);
|
||||
} else {
|
||||
username = _backupName.empty() ? util::getIDStr((uint64_t) uID128) : _backupName;
|
||||
userSafe = _safeBackupName.empty() ? util::getIDStr((uint64_t) uID128) : _safeBackupName;
|
||||
}
|
||||
titles.reserve(64);
|
||||
}
|
||||
|
||||
void data::user::setUID(const AccountUid &_id) {
|
||||
userID = _id;
|
||||
uID128 = util::accountUIDToU128(_id);
|
||||
}
|
||||
|
||||
void data::user::addUserTitleInfo(const uint64_t &tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats) {
|
||||
data::userTitleInfo newInfo;
|
||||
newInfo.tid = tid;
|
||||
memcpy(&newInfo.saveInfo, _saveInfo, sizeof(FsSaveDataInfo));
|
||||
memcpy(&newInfo.playStats, _stats, sizeof(PdmPlayStatistics));
|
||||
titleInfo.push_back(newInfo);
|
||||
}
|
||||
|
||||
void data::dispStats() {
|
||||
data::user *cu = data::getCurrentUser();
|
||||
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
|
||||
|
||||
//Easiest/laziest way to do this
|
||||
std::string stats = std::to_string(users.size()) + "\n";
|
||||
for (data::user &u: data::users) {
|
||||
stats += u.getUsername() + ": " + std::to_string(u.titleInfo.size()) + "\n";
|
||||
}
|
||||
}
|
||||
77
source/directory.cpp
Normal file
77
source/directory.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* This file is part of Checkpoint
|
||||
* Copyright (C) 2017-2021 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 "directory.hpp"
|
||||
|
||||
Directory::Directory(const std::string& root)
|
||||
{
|
||||
mGood = false;
|
||||
mError = 0;
|
||||
mList.clear();
|
||||
|
||||
DIR* dir = opendir(root.c_str());
|
||||
struct dirent* ent;
|
||||
|
||||
if (dir == NULL) {
|
||||
mError = (Result)errno;
|
||||
}
|
||||
else {
|
||||
while ((ent = readdir(dir))) {
|
||||
std::string name = std::string(ent->d_name);
|
||||
bool directory = ent->d_type == DT_DIR;
|
||||
struct DirectoryEntry de = {name, directory};
|
||||
mList.push_back(de);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
mGood = true;
|
||||
}
|
||||
|
||||
Result Directory::error(void)
|
||||
{
|
||||
return mError;
|
||||
}
|
||||
|
||||
bool Directory::good(void)
|
||||
{
|
||||
return mGood;
|
||||
}
|
||||
|
||||
std::string Directory::entry(size_t index)
|
||||
{
|
||||
return index < mList.size() ? mList.at(index).name : "";
|
||||
}
|
||||
|
||||
bool Directory::folder(size_t index)
|
||||
{
|
||||
return index < mList.size() ? mList.at(index).directory : false;
|
||||
}
|
||||
|
||||
size_t Directory::size(void)
|
||||
{
|
||||
return mList.size();
|
||||
}
|
||||
42
source/filesystem.cpp
Normal file
42
source/filesystem.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of Checkpoint
|
||||
* Copyright (C) 2017-2021 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 "filesystem.hpp"
|
||||
|
||||
Result FileSystem::mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID)
|
||||
{
|
||||
return fsOpen_SaveData(fileSystem, titleID, userID);
|
||||
}
|
||||
|
||||
int FileSystem::mount(FsFileSystem fs)
|
||||
{
|
||||
return fsdevMountDevice("save", fs);
|
||||
}
|
||||
|
||||
void FileSystem::unmount(void)
|
||||
{
|
||||
fsdevUnmountDevice("save");
|
||||
}
|
||||
519
source/fs.cpp
519
source/fs.cpp
@@ -1,519 +0,0 @@
|
||||
#include <switch.h>
|
||||
|
||||
#include "fs.h"
|
||||
#include "threads.h"
|
||||
#include "util.h"
|
||||
|
||||
static std::string wd = "sdmc:/NXST/";
|
||||
|
||||
static FSFILE *debLog;
|
||||
|
||||
static FsFileSystem sv;
|
||||
|
||||
static std::vector <std::string> pathFilter;
|
||||
|
||||
void fs::init() {
|
||||
mkDirRec(wd);
|
||||
fs::logOpen();
|
||||
}
|
||||
|
||||
bool fs::mountSave(const FsSaveDataInfo &_m) {
|
||||
Result svOpen;
|
||||
FsSaveDataAttribute attr = {0};
|
||||
switch (_m.save_data_type) {
|
||||
case FsSaveDataType_System:
|
||||
case FsSaveDataType_SystemBcat: {
|
||||
attr.uid = _m.uid;
|
||||
attr.system_save_data_id = _m.system_save_data_id;
|
||||
attr.save_data_type = _m.save_data_type;
|
||||
svOpen = fsOpenSaveDataFileSystemBySystemSaveDataId(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr);
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Account: {
|
||||
attr.uid = _m.uid;
|
||||
attr.application_id = _m.application_id;
|
||||
attr.save_data_type = _m.save_data_type;
|
||||
attr.save_data_rank = _m.save_data_rank;
|
||||
attr.save_data_index = _m.save_data_index;
|
||||
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr);
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device: {
|
||||
attr.application_id = _m.application_id;
|
||||
attr.save_data_type = FsSaveDataType_Device;
|
||||
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr);
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat: {
|
||||
attr.application_id = _m.application_id;
|
||||
attr.save_data_type = FsSaveDataType_Bcat;
|
||||
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr);
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache: {
|
||||
attr.application_id = _m.application_id;
|
||||
attr.save_data_type = FsSaveDataType_Cache;
|
||||
attr.save_data_index = _m.save_data_index;
|
||||
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr);
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Temporary: {
|
||||
attr.application_id = _m.application_id;
|
||||
attr.save_data_type = _m.save_data_type;
|
||||
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
svOpen = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return R_SUCCEEDED(svOpen) && fsdevMountDevice("sv", sv) != -1;
|
||||
}
|
||||
|
||||
bool fs::commitToDevice(const std::string &dev) {
|
||||
bool ret = true;
|
||||
Result res = fsdevCommitDevice(dev.c_str());
|
||||
if (R_FAILED(res)) {
|
||||
fs::logWrite("Error committing file to device -> 0x%X\n", res);
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string fs::getWorkDir() { return wd; }
|
||||
|
||||
void fs::setWorkDir(const std::string &_w) { wd = _w; }
|
||||
|
||||
|
||||
void fs::loadPathFilters(const uint64_t &tid) {
|
||||
char path[256];
|
||||
// sprintf(path, "sdmc:/config/JKSV/0x%016lX_filter.txt", tid);
|
||||
if (fs::fileExists(path)) {
|
||||
fs::dataFile filter(path);
|
||||
while (filter.readNextLine(false))
|
||||
pathFilter.push_back(filter.getLine());
|
||||
}
|
||||
}
|
||||
|
||||
bool fs::pathIsFiltered(const std::string &_path) {
|
||||
if (pathFilter.empty())
|
||||
return false;
|
||||
|
||||
for (std::string &_p: pathFilter) {
|
||||
if (_path == _p)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void fs::freePathFilters() {
|
||||
pathFilter.clear();
|
||||
}
|
||||
|
||||
void fs::createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _uid, threadInfo *t) {
|
||||
data::titleInfo *tinfo = data::getTitleInfoByTID(_tid);
|
||||
if (t)
|
||||
t->status->setStatus("Creating Save Data for #%s#...", tinfo->title.c_str());
|
||||
|
||||
uint16_t cacheIndex = 0;
|
||||
std::string indexStr;
|
||||
if (_type == FsSaveDataType_Cache &&
|
||||
!(indexStr = util::getStringInput(SwkbdType_NumPad, "0", "Enter cache index", 2, 0, NULL)).empty())
|
||||
cacheIndex = strtoul(indexStr.c_str(), NULL, 10);
|
||||
else if (_type == FsSaveDataType_Cache && indexStr.empty()) {
|
||||
if (t)
|
||||
t->finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
FsSaveDataAttribute attr;
|
||||
memset(&attr, 0, sizeof(FsSaveDataAttribute));
|
||||
attr.application_id = _tid;
|
||||
attr.uid = _uid;
|
||||
attr.system_save_data_id = 0;
|
||||
attr.save_data_type = _type;
|
||||
attr.save_data_rank = 0;
|
||||
attr.save_data_index = cacheIndex;
|
||||
|
||||
FsSaveDataCreationInfo crt;
|
||||
memset(&crt, 0, sizeof(FsSaveDataCreationInfo));
|
||||
int64_t saveSize = 0, journalSize = 0;
|
||||
switch (_type) {
|
||||
case FsSaveDataType_Account:
|
||||
saveSize = tinfo->nacp.user_account_save_data_size;
|
||||
journalSize = tinfo->nacp.user_account_save_data_journal_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
saveSize = tinfo->nacp.device_save_data_size;
|
||||
journalSize = tinfo->nacp.device_save_data_journal_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat:
|
||||
saveSize = tinfo->nacp.bcat_delivery_cache_storage_size;
|
||||
journalSize = tinfo->nacp.bcat_delivery_cache_storage_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
saveSize = 32 * 1024 * 1024;
|
||||
if (tinfo->nacp.cache_storage_journal_size > tinfo->nacp.cache_storage_data_and_journal_size_max)
|
||||
journalSize = tinfo->nacp.cache_storage_journal_size;
|
||||
else
|
||||
journalSize = tinfo->nacp.cache_storage_data_and_journal_size_max;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (t)
|
||||
t->finished = true;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
crt.save_data_size = saveSize;
|
||||
crt.journal_size = journalSize;
|
||||
crt.available_size = 0x4000;
|
||||
crt.owner_id = _type == FsSaveDataType_Bcat ? 0x010000000000000C : tinfo->nacp.save_data_owner_id;
|
||||
crt.flags = 0;
|
||||
crt.save_data_space_id = FsSaveDataSpaceId_User;
|
||||
|
||||
FsSaveDataMetaInfo meta;
|
||||
memset(&meta, 0, sizeof(FsSaveDataMetaInfo));
|
||||
if (_type != FsSaveDataType_Bcat) {
|
||||
meta.size = 0x40060;
|
||||
meta.type = FsSaveDataMetaType_Thumbnail;
|
||||
}
|
||||
|
||||
Result res = 0;
|
||||
if (R_SUCCEEDED(res = fsCreateSaveDataFileSystem(&attr, &crt, &meta))) {
|
||||
util::createTitleDirectoryByTID(_tid);
|
||||
data::loadUsersTitles(false);
|
||||
} else {
|
||||
fs::logWrite("SaveCreate Failed -> %X\n", res);
|
||||
}
|
||||
}
|
||||
|
||||
static void createSaveData_t(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
fs::svCreateArgs *crt = (fs::svCreateArgs *) t->argPtr;
|
||||
fs::createSaveData(crt->type, crt->tid, crt->account, t);
|
||||
delete crt;
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::createSaveDataThreaded(FsSaveDataType _type, uint64_t _tid, AccountUid _uid) {
|
||||
fs::svCreateArgs *send = new fs::svCreateArgs;
|
||||
send->type = _type;
|
||||
send->tid = _tid;
|
||||
send->account = _uid;
|
||||
threads::newThread(createSaveData_t, send, NULL);
|
||||
}
|
||||
|
||||
bool fs::extendSaveData(const data::userTitleInfo *tinfo, uint64_t extSize, threadInfo *t) {
|
||||
if (t)
|
||||
t->status->setStatus("Extending Save Data for #%s#...", data::getTitleNameByTID(tinfo->tid).c_str());
|
||||
|
||||
uint64_t journal = fs::getJournalSizeMax(tinfo);
|
||||
uint64_t saveID = tinfo->saveInfo.save_data_id;
|
||||
FsSaveDataSpaceId space = (FsSaveDataSpaceId) tinfo->saveInfo.save_data_space_id;
|
||||
Result res = 0;
|
||||
if (R_FAILED((res = fsExtendSaveDataFileSystem(space, saveID, extSize, journal)))) {
|
||||
int64_t totalSize = 0;
|
||||
fs::mountSave(tinfo->saveInfo);
|
||||
fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &totalSize);
|
||||
fs::unmountSave();
|
||||
|
||||
fs::logWrite("Extend Failed: %uMB to %uMB -> %X\n", totalSize / 1024 / 1024, extSize / 1024 / 1024, res);
|
||||
// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataExtendFailed", 0));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void extendSaveData_t(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
fs::svExtendArgs *e = (fs::svExtendArgs *) t->argPtr;
|
||||
fs::extendSaveData(e->tinfo, e->extSize, t);
|
||||
delete e;
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::extendSaveDataThreaded(const data::userTitleInfo *tinfo, uint64_t extSize) {
|
||||
fs::svExtendArgs *send = new fs::svExtendArgs;
|
||||
send->tinfo = tinfo;
|
||||
send->extSize = extSize;
|
||||
threads::newThread(extendSaveData_t, send, NULL);
|
||||
}
|
||||
|
||||
uint64_t fs::getJournalSize(const data::userTitleInfo *tinfo) {
|
||||
uint64_t ret = 0;
|
||||
data::titleInfo *t = data::getTitleInfoByTID(tinfo->tid);
|
||||
switch (tinfo->saveInfo.save_data_type) {
|
||||
case FsSaveDataType_Account:
|
||||
ret = t->nacp.user_account_save_data_journal_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
ret = t->nacp.device_save_data_journal_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat:
|
||||
ret = t->nacp.bcat_delivery_cache_storage_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
if (t->nacp.cache_storage_journal_size > 0)
|
||||
ret = t->nacp.cache_storage_journal_size;
|
||||
else
|
||||
ret = t->nacp.cache_storage_data_and_journal_size_max;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = BUFF_SIZE;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t fs::getJournalSizeMax(const data::userTitleInfo *tinfo) {
|
||||
uint64_t ret = 0;
|
||||
data::titleInfo *extend = data::getTitleInfoByTID(tinfo->tid);
|
||||
switch (tinfo->saveInfo.save_data_type) {
|
||||
case FsSaveDataType_Account:
|
||||
if (extend->nacp.user_account_save_data_journal_size_max > extend->nacp.user_account_save_data_journal_size)
|
||||
ret = extend->nacp.user_account_save_data_journal_size_max;
|
||||
else
|
||||
ret = extend->nacp.user_account_save_data_journal_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat:
|
||||
ret = extend->nacp.bcat_delivery_cache_storage_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
if (extend->nacp.cache_storage_data_and_journal_size_max > extend->nacp.cache_storage_journal_size)
|
||||
ret = extend->nacp.cache_storage_data_and_journal_size_max;
|
||||
else
|
||||
ret = extend->nacp.cache_storage_journal_size;
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
if (extend->nacp.device_save_data_journal_size_max > extend->nacp.device_save_data_journal_size)
|
||||
ret = extend->nacp.device_save_data_journal_size_max;
|
||||
else
|
||||
ret = extend->nacp.device_save_data_journal_size;
|
||||
break;
|
||||
|
||||
default:
|
||||
//will just fail
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wipeSave_t(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
t->status->setStatus("Resetting save data...");
|
||||
fs::delDir("sv:/");
|
||||
fs::commitToDevice("sv");
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::wipeSave() {
|
||||
threads::newThread(wipeSave_t, NULL, NULL);
|
||||
}
|
||||
|
||||
void fs::createNewBackup(void *a) {
|
||||
if (!fs::dirNotEmpty("sv:/")) {
|
||||
// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popSaveIsEmpty", 0));
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t held = threads::padKeysHeld();
|
||||
|
||||
data::user *u = data::getCurrentUser();
|
||||
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
|
||||
data::titleInfo *t = data::getTitleInfoByTID(d->tid);
|
||||
|
||||
std::string out;
|
||||
|
||||
const std::string dict[] =
|
||||
{
|
||||
util::getDateTime(util::DATE_FMT_YMD),
|
||||
util::getDateTime(util::DATE_FMT_YDM),
|
||||
util::getDateTime(util::DATE_FMT_HOYSTE),
|
||||
util::getDateTime(util::DATE_FMT_JHK),
|
||||
util::getDateTime(util::DATE_FMT_ASC),
|
||||
u->getUsernameSafe(),
|
||||
t->safeTitle,
|
||||
util::generateAbbrev(d->tid),
|
||||
".zip"
|
||||
};
|
||||
std::string defaultText = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD);
|
||||
out = util::getStringInput(SwkbdType_QWERTY, defaultText, "Enter a new name", 64, 9, dict);
|
||||
out = util::safeString(out);
|
||||
|
||||
if (!out.empty()) {
|
||||
std::string ext = util::getExtensionFromString(out);
|
||||
std::string path = util::generatePathByTID(d->tid) + out;
|
||||
|
||||
fs::mkDir(path);
|
||||
path += "/";
|
||||
fs::copyDirToDirThreaded("sv:/", path);
|
||||
// ui::fldRefreshMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void fs::overwriteBackup(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
std::string *dst = (std::string *) t->argPtr;
|
||||
bool saveHasFiles = fs::dirNotEmpty("sv:/");
|
||||
if (fs::isDir(*dst) && saveHasFiles) {
|
||||
fs::delDir(*dst);
|
||||
fs::mkDir(*dst);
|
||||
dst->append("/");
|
||||
fs::copyDirToDirThreaded("sv:/", *dst);
|
||||
}
|
||||
delete dst;
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::restoreBackup(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
std::string *restore = (std::string *) t->argPtr;
|
||||
data::user *u = data::getCurrentUser();
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
if ((utinfo->saveInfo.save_data_type != FsSaveDataType_System)) {
|
||||
bool saveHasFiles = fs::dirNotEmpty("sv:/");
|
||||
if (saveHasFiles) {
|
||||
std::string autoFolder = util::generatePathByTID(utinfo->tid) + "/AUTO - " + u->getUsernameSafe() + " - " +
|
||||
util::getDateTime(util::DATE_FMT_YMD) + "/";
|
||||
fs::mkDir(autoFolder.substr(0, autoFolder.length() - 1));
|
||||
fs::copyDirToDirThreaded("sv:/", autoFolder);
|
||||
}
|
||||
|
||||
if (fs::isDir(*restore)) {
|
||||
restore->append("/");
|
||||
if (fs::dirNotEmpty(*restore)) {
|
||||
t->status->setStatus("Calculating save data size...");
|
||||
unsigned dirCount = 0, fileCount = 0;
|
||||
uint64_t saveSize = 0;
|
||||
int64_t availSize = 0;
|
||||
fs::getDirProps(*restore, dirCount, fileCount, saveSize);
|
||||
fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &availSize);
|
||||
if ((int) saveSize > availSize) {
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
fs::unmountSave();
|
||||
fs::extendSaveData(utinfo, saveSize + 0x500000, t);
|
||||
fs::mountSave(utinfo->saveInfo);
|
||||
}
|
||||
|
||||
fs::wipeSave();
|
||||
fs::copyDirToDirCommitThreaded(*restore, "sv:/", "sv");
|
||||
}
|
||||
// else
|
||||
// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popFolderIsEmpty", 0));
|
||||
} else {
|
||||
std::string dstPath = "sv:/" + util::getFilenameFromPath(*restore);
|
||||
fs::copyFileCommitThreaded(*restore, dstPath, "sv");
|
||||
}
|
||||
}
|
||||
|
||||
delete restore;
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::deleteBackup(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
std::string *deletePath = (std::string *) t->argPtr;
|
||||
std::string backupName = util::getFilenameFromPath(*deletePath);
|
||||
|
||||
t->status->setStatus("Deleting...");
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
|
||||
if (fs::isDir(*deletePath)) {
|
||||
*deletePath += "/";
|
||||
fs::delDir(*deletePath);
|
||||
// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str());
|
||||
} else {
|
||||
fs::delfile(*deletePath);
|
||||
// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str());
|
||||
}
|
||||
// ui::fldRefreshMenu();
|
||||
delete deletePath;
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::dumpAllUserSaves(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0);
|
||||
t->argPtr = c;
|
||||
data::user *u = data::getCurrentUser();
|
||||
|
||||
for (unsigned i = 0; i < u->titleInfo.size(); i++) {
|
||||
bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo);
|
||||
util::createTitleDirectoryByTID(u->titleInfo[i].tid);
|
||||
if (saveMounted && fs::dirNotEmpty("sv:/")) {
|
||||
fs::loadPathFilters(u->titleInfo[i].tid);
|
||||
std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " +
|
||||
util::getDateTime(util::DATE_FMT_YMD) + "/";
|
||||
fs::mkDir(dst.substr(0, dst.length() - 1));
|
||||
fs::copyDirToDir("sv:/", dst, t);
|
||||
fs::freePathFilters();
|
||||
}
|
||||
fs::unmountSave();
|
||||
}
|
||||
fs::copyArgsDestroy(c);
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::dumpAllUsersAllSaves(void *a) {
|
||||
threadInfo *t = (threadInfo *) a;
|
||||
fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0);
|
||||
t->argPtr = c;
|
||||
unsigned curUser = 0;
|
||||
while (data::users[curUser].getUID128() != 2) {
|
||||
data::user *u = &data::users[curUser++];
|
||||
for (unsigned i = 0; i < u->titleInfo.size(); i++) {
|
||||
bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo);
|
||||
util::createTitleDirectoryByTID(u->titleInfo[i].tid);
|
||||
if (saveMounted && fs::dirNotEmpty("sv:/")) {
|
||||
fs::loadPathFilters(u->titleInfo[i].tid);
|
||||
std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " +
|
||||
util::getDateTime(util::DATE_FMT_YMD) + "/";
|
||||
fs::mkDir(dst.substr(0, dst.length() - 1));
|
||||
fs::copyDirToDir("sv:/", dst, t);
|
||||
fs::freePathFilters();
|
||||
}
|
||||
fs::unmountSave();
|
||||
}
|
||||
}
|
||||
fs::copyArgsDestroy(c);
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::logOpen() {
|
||||
std::string logPath = wd + "log.txt";
|
||||
debLog = fsfopen(logPath.c_str(), FsOpenMode_Write);
|
||||
fsfclose(debLog);
|
||||
}
|
||||
|
||||
void fs::logWrite(const char *fmt, ...) {
|
||||
std::string logPath = wd + "log.txt";
|
||||
debLog = fsfopen(logPath.c_str(), FsOpenMode_Append | FsOpenMode_Write);
|
||||
char tmp[256];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsprintf(tmp, fmt, args);
|
||||
va_end(args);
|
||||
fsfwrite(tmp, 1, strlen(tmp), debLog);
|
||||
fsfclose(debLog);
|
||||
}
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
#include <switch.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "fs.h"
|
||||
#include "util.h"
|
||||
#include <threads.h>
|
||||
|
||||
static struct
|
||||
{
|
||||
bool operator()(const fs::dirItem& a, const fs::dirItem& b)
|
||||
{
|
||||
if(a.isDir() != b.isDir())
|
||||
return a.isDir();
|
||||
|
||||
for(unsigned i = 0; i < a.getItm().length(); i++)
|
||||
{
|
||||
char charA = tolower(a.getItm()[i]);
|
||||
char charB = tolower(b.getItm()[i]);
|
||||
if(charA != charB)
|
||||
return charA < charB;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} sortDirList;
|
||||
|
||||
void fs::mkDir(const std::string& _p)
|
||||
{
|
||||
mkdir(_p.c_str(), 777);
|
||||
}
|
||||
|
||||
void fs::mkDirRec(const std::string& _p)
|
||||
{
|
||||
//skip first slash
|
||||
size_t pos = _p.find('/', 0) + 1;
|
||||
while((pos = _p.find('/', pos)) != _p.npos)
|
||||
{
|
||||
fs::mkDir(_p.substr(0, pos).c_str());
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
void fs::delDir(const std::string& path)
|
||||
{
|
||||
dirList list(path);
|
||||
for(unsigned i = 0; i < list.getCount(); i++)
|
||||
{
|
||||
if(pathIsFiltered(path + list.getItem(i)))
|
||||
continue;
|
||||
|
||||
if(list.isDir(i))
|
||||
{
|
||||
std::string newPath = path + list.getItem(i) + "/";
|
||||
delDir(newPath);
|
||||
|
||||
std::string delPath = path + list.getItem(i);
|
||||
// rmdir(delPath.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string delPath = path + list.getItem(i);
|
||||
std::remove(delPath.c_str());
|
||||
}
|
||||
}
|
||||
// rmdir(path.c_str());
|
||||
}
|
||||
|
||||
bool fs::dirNotEmpty(const std::string& _dir)
|
||||
{
|
||||
fs::dirList tmp(_dir);
|
||||
return tmp.getCount() > 0;
|
||||
}
|
||||
|
||||
bool fs::isDir(const std::string& _path)
|
||||
{
|
||||
struct stat s;
|
||||
return stat(_path.c_str(), &s) == 0 && S_ISDIR(s.st_mode);
|
||||
}
|
||||
|
||||
void fs::copyDirToDir(const std::string& src, const std::string& dst, threadInfo *t)
|
||||
{
|
||||
if(t)
|
||||
t->status->setStatus("Opening '#%s#'...");
|
||||
|
||||
fs::dirList *list = new fs::dirList(src);
|
||||
for(unsigned i = 0; i < list->getCount(); i++)
|
||||
{
|
||||
if(pathIsFiltered(src + list->getItem(i)))
|
||||
continue;
|
||||
|
||||
if(list->isDir(i))
|
||||
{
|
||||
std::string newSrc = src + list->getItem(i) + "/";
|
||||
std::string newDst = dst + list->getItem(i) + "/";
|
||||
fs::mkDir(newDst.substr(0, newDst.length() - 1));
|
||||
fs::copyDirToDir(newSrc, newDst, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string fullSrc = src + list->getItem(i);
|
||||
std::string fullDst = dst + list->getItem(i);
|
||||
|
||||
if(t)
|
||||
t->status->setStatus("Copying '#%s#'...");
|
||||
|
||||
fs::copyFile(fullSrc, fullDst, t);
|
||||
}
|
||||
}
|
||||
delete list;
|
||||
}
|
||||
|
||||
static void copyDirToDir_t(void *a)
|
||||
{
|
||||
threadInfo *t = (threadInfo *)a;
|
||||
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
|
||||
fs::copyDirToDir(in->src, in->dst, t);
|
||||
if(in->cleanup)
|
||||
fs::copyArgsDestroy(in);
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::copyDirToDirThreaded(const std::string& src, const std::string& dst)
|
||||
{
|
||||
fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0);
|
||||
threads::newThread(copyDirToDir_t, send, fs::fileDrawFunc);
|
||||
}
|
||||
|
||||
void fs::copyDirToDirCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t)
|
||||
{
|
||||
if(t)
|
||||
t->status->setStatus("Opening '#%s#'...");
|
||||
|
||||
fs::dirList *list = new fs::dirList(src);
|
||||
for(unsigned i = 0; i < list->getCount(); i++)
|
||||
{
|
||||
if(pathIsFiltered(src + list->getItem(i)))
|
||||
continue;
|
||||
|
||||
if(list->isDir(i))
|
||||
{
|
||||
std::string newSrc = src + list->getItem(i) + "/";
|
||||
std::string newDst = dst + list->getItem(i) + "/";
|
||||
fs::mkDir(newDst.substr(0, newDst.length() - 1));
|
||||
fs::copyDirToDirCommit(newSrc, newDst, dev, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string fullSrc = src + list->getItem(i);
|
||||
std::string fullDst = dst + list->getItem(i);
|
||||
|
||||
if(t)
|
||||
t->status->setStatus("Copying '#%s#'...");
|
||||
|
||||
fs::copyFileCommit(fullSrc, fullDst, dev, t);
|
||||
}
|
||||
}
|
||||
delete list;
|
||||
}
|
||||
|
||||
static void copyDirToDirCommit_t(void *a)
|
||||
{
|
||||
threadInfo *t = (threadInfo *)a;
|
||||
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
|
||||
fs::copyDirToDirCommit(in->src, in->dst, in->dev, t);
|
||||
if(in->cleanup)
|
||||
fs::copyArgsDestroy(in);
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::copyDirToDirCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev)
|
||||
{
|
||||
fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0);
|
||||
threads::newThread(copyDirToDirCommit_t, send, fs::fileDrawFunc);
|
||||
}
|
||||
|
||||
void fs::getDirProps(const std::string& path, unsigned& dirCount, unsigned& fileCount, uint64_t& totalSize)
|
||||
{
|
||||
fs::dirList *d = new fs::dirList(path);
|
||||
for(unsigned i = 0; i < d->getCount(); i++)
|
||||
{
|
||||
if(d->isDir(i))
|
||||
{
|
||||
++dirCount;
|
||||
std::string newPath = path + d->getItem(i) + "/";
|
||||
fs::getDirProps(newPath, dirCount, fileCount, totalSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
++fileCount;
|
||||
std::string filePath = path + d->getItem(i);
|
||||
totalSize += fs::fsize(filePath);
|
||||
}
|
||||
}
|
||||
delete d;
|
||||
}
|
||||
|
||||
fs::dirItem::dirItem(const std::string& pathTo, const std::string& sItem)
|
||||
{
|
||||
itm = sItem;
|
||||
|
||||
std::string fullPath = pathTo + sItem;
|
||||
struct stat s;
|
||||
if(stat(fullPath.c_str(), &s) == 0 && S_ISDIR(s.st_mode))
|
||||
dir = true;
|
||||
}
|
||||
|
||||
std::string fs::dirItem::getName() const
|
||||
{
|
||||
size_t extPos = itm.find_last_of('.'), slPos = itm.find_last_of('/');
|
||||
if(extPos == itm.npos)
|
||||
return "";
|
||||
|
||||
return itm.substr(slPos + 1, extPos);
|
||||
}
|
||||
|
||||
std::string fs::dirItem::getExt() const
|
||||
{
|
||||
return util::getExtensionFromString(itm);
|
||||
}
|
||||
|
||||
fs::dirList::dirList(const std::string& _path)
|
||||
{
|
||||
DIR *d;
|
||||
struct dirent *ent;
|
||||
path = _path;
|
||||
d = opendir(path.c_str());
|
||||
|
||||
while((ent = readdir(d)))
|
||||
item.emplace_back(path, ent->d_name);
|
||||
|
||||
closedir(d);
|
||||
|
||||
std::sort(item.begin(), item.end(), sortDirList);
|
||||
}
|
||||
|
||||
void fs::dirList::reassign(const std::string& _path)
|
||||
{
|
||||
DIR *d;
|
||||
struct dirent *ent;
|
||||
path = _path;
|
||||
|
||||
d = opendir(path.c_str());
|
||||
|
||||
item.clear();
|
||||
|
||||
while((ent = readdir(d)))
|
||||
item.emplace_back(path, ent->d_name);
|
||||
|
||||
closedir(d);
|
||||
|
||||
std::sort(item.begin(), item.end(), sortDirList);
|
||||
}
|
||||
|
||||
void fs::dirList::rescan()
|
||||
{
|
||||
item.clear();
|
||||
DIR *d;
|
||||
struct dirent *ent;
|
||||
d = opendir(path.c_str());
|
||||
|
||||
while((ent = readdir(d)))
|
||||
item.emplace_back(path, ent->d_name);
|
||||
|
||||
closedir(d);
|
||||
|
||||
std::sort(item.begin(), item.end(), sortDirList);
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <switch.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdarg>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <sys/stat.h>
|
||||
#include "fs.h"
|
||||
#include "util.h"
|
||||
#include "data.h"
|
||||
#include <threads.h>
|
||||
|
||||
static std::string wd = "sdmc:/JKSV/";
|
||||
|
||||
typedef struct
|
||||
{
|
||||
std::mutex bufferLock;
|
||||
std::condition_variable cond;
|
||||
std::vector<uint8_t> sharedBuffer;
|
||||
std::string dst, dev;
|
||||
bool bufferIsFull = false;
|
||||
unsigned int filesize = 0, writeLimit = 0;
|
||||
} fileCpyThreadArgs;
|
||||
|
||||
static void writeFile_t(void *a)
|
||||
{
|
||||
fileCpyThreadArgs *in = (fileCpyThreadArgs *)a;
|
||||
size_t written = 0;
|
||||
std::vector<uint8_t> localBuffer;
|
||||
std::FILE *out = std::fopen(in->dst.c_str(), "wb");
|
||||
|
||||
while(written < in->filesize)
|
||||
{
|
||||
std::unique_lock<std::mutex> buffLock(in->bufferLock);
|
||||
in->cond.wait(buffLock, [in]{ return in->bufferIsFull;});
|
||||
localBuffer.clear();
|
||||
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
|
||||
in->sharedBuffer.clear();
|
||||
in->bufferIsFull = false;
|
||||
buffLock.unlock();
|
||||
in->cond.notify_one();
|
||||
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
|
||||
}
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
static void writeFileCommit_t(void *a)
|
||||
{
|
||||
fileCpyThreadArgs *in = (fileCpyThreadArgs *)a;
|
||||
size_t written = 0, journalCount = 0;
|
||||
std::vector<uint8_t> localBuffer;
|
||||
FILE *out = fopen(in->dst.c_str(), "wb");
|
||||
|
||||
while(written < in->filesize)
|
||||
{`
|
||||
std::unique_lock<std::mutex> buffLock(in->bufferLock);
|
||||
in->cond.wait(buffLock, [in]{ return in->bufferIsFull; });
|
||||
localBuffer.clear();
|
||||
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
|
||||
in->sharedBuffer.clear();
|
||||
in->bufferIsFull = false;
|
||||
buffLock.unlock();
|
||||
in->cond.notify_one();
|
||||
|
||||
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
|
||||
|
||||
journalCount += written;
|
||||
if(journalCount >= in->writeLimit)
|
||||
{
|
||||
journalCount = 0;
|
||||
fclose(out);
|
||||
fs::commitToDevice(in->dev.c_str());
|
||||
out = fopen(in->dst.c_str(), "ab");
|
||||
}
|
||||
}
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
fs::copyArgs *fs::copyArgsCreate(const std::string& src, const std::string& dst, const std::string& dev, zipFile z, unzFile unz, bool _cleanup, bool _trimZipPath, uint8_t _trimPlaces)
|
||||
{
|
||||
copyArgs *ret = new copyArgs;
|
||||
ret->src = src;
|
||||
ret->dst = dst;
|
||||
ret->dev = dev;
|
||||
ret->z = z;
|
||||
ret->unz = unz;
|
||||
ret->cleanup = _cleanup;
|
||||
ret->offset = 0;
|
||||
ret->trimZipPath = _trimZipPath;
|
||||
ret->trimZipPlaces = _trimPlaces;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fs::copyArgsDestroy(copyArgs *c)
|
||||
{
|
||||
// delete c->prog;
|
||||
delete c;
|
||||
c = NULL;
|
||||
}
|
||||
|
||||
fs::dataFile::dataFile(const std::string& _path)
|
||||
{
|
||||
f = fopen(_path.c_str(), "r");
|
||||
if(f != NULL)
|
||||
opened = true;
|
||||
}
|
||||
|
||||
fs::dataFile::~dataFile()
|
||||
{
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
bool fs::dataFile::readNextLine(bool proc)
|
||||
{
|
||||
bool ret = false;
|
||||
char tmp[1024];
|
||||
while(fgets(tmp, 1024, f))
|
||||
{
|
||||
if(tmp[0] != '#' && tmp[0] != '\n' && tmp[0] != '\r')
|
||||
{
|
||||
line = tmp;
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
util::stripChar('\n', line);
|
||||
util::stripChar('\r', line);
|
||||
if(proc)
|
||||
procLine();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fs::dataFile::procLine()
|
||||
{
|
||||
size_t pPos = line.find_first_of("(=,");
|
||||
if(pPos != line.npos)
|
||||
{
|
||||
lPos = pPos;
|
||||
name.assign(line.begin(), line.begin() + lPos);
|
||||
}
|
||||
else
|
||||
name = line;
|
||||
|
||||
util::stripChar(' ', name);
|
||||
++lPos;
|
||||
}
|
||||
|
||||
std::string fs::dataFile::getNextValueStr()
|
||||
{
|
||||
std::string ret = "";
|
||||
//Skip all spaces until we hit actual text
|
||||
size_t pos1 = line.find_first_not_of(", ", lPos);
|
||||
//If reading from quotes
|
||||
if(line[pos1] == '"')
|
||||
lPos = line.find_first_of('"', ++pos1);
|
||||
else
|
||||
lPos = line.find_first_of(",;\n", pos1);//Set lPos to end of string we want. This should just set lPos to the end of the line if it fails, which is ok
|
||||
|
||||
ret = line.substr(pos1, lPos++ - pos1);
|
||||
|
||||
util::replaceStr(ret, "\\n", "\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fs::dataFile::getNextValueInt()
|
||||
{
|
||||
int ret = 0;
|
||||
std::string no = getNextValueStr();
|
||||
if(no[0] == '0' && tolower(no[1]) == 'x')
|
||||
ret = strtoul(no.c_str(), NULL, 16);
|
||||
else
|
||||
ret = strtoul(no.c_str(), NULL, 10);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fs::copyFile(const std::string& src, const std::string& dst, threadInfo *t)
|
||||
{
|
||||
fs::copyArgs *c = NULL;
|
||||
size_t filesize = fs::fsize(src);
|
||||
if(t)
|
||||
{
|
||||
c = (fs::copyArgs *)t->argPtr;
|
||||
c->offset = 0;
|
||||
}
|
||||
|
||||
FILE *fsrc = fopen(src.c_str(), "rb");
|
||||
if(!fsrc)
|
||||
{
|
||||
fclose(fsrc);
|
||||
return;
|
||||
}
|
||||
|
||||
fileCpyThreadArgs thrdArgs;
|
||||
thrdArgs.dst = dst;
|
||||
thrdArgs.filesize = filesize;
|
||||
|
||||
uint8_t *buff = new uint8_t[BUFF_SIZE];
|
||||
std::vector<uint8_t> transferBuffer;
|
||||
Thread writeThread;
|
||||
threadCreate(&writeThread, writeFile_t, &thrdArgs, NULL, 0x40000, 0x2E, 2);
|
||||
threadStart(&writeThread);
|
||||
size_t readIn = 0;
|
||||
uint64_t readCount = 0;
|
||||
while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0)
|
||||
{
|
||||
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
|
||||
readCount += readIn;
|
||||
|
||||
if(c)
|
||||
c->offset = readCount;
|
||||
|
||||
if(transferBuffer.size() >= TRANSFER_BUFFER_LIMIT || readCount == filesize)
|
||||
{
|
||||
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
|
||||
thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; });
|
||||
thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
|
||||
transferBuffer.clear();
|
||||
thrdArgs.bufferIsFull = true;
|
||||
buffLock.unlock();
|
||||
thrdArgs.cond.notify_one();
|
||||
}
|
||||
}
|
||||
threadWaitForExit(&writeThread);
|
||||
threadClose(&writeThread);
|
||||
fclose(fsrc);
|
||||
delete[] buff;
|
||||
}
|
||||
|
||||
|
||||
static void copyFileThreaded_t(void *a)
|
||||
{
|
||||
threadInfo *t = (threadInfo *)a;
|
||||
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
|
||||
|
||||
t->status->setStatus("Copy file", in->src.c_str());
|
||||
|
||||
fs::copyFile(in->src, in->dst, t);
|
||||
if(in->cleanup)
|
||||
fs::copyArgsDestroy(in);
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::copyFileThreaded(const std::string& src, const std::string& dst)
|
||||
{
|
||||
fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0);
|
||||
threads::newThread(copyFileThreaded_t, send, fs::fileDrawFunc);
|
||||
}
|
||||
|
||||
void fs::copyFileCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t)
|
||||
{
|
||||
fs::copyArgs *c = NULL;
|
||||
size_t filesize = fs::fsize(src);
|
||||
if(t)
|
||||
{
|
||||
c = (fs::copyArgs *)t->argPtr;
|
||||
c->offset = 0;
|
||||
// c->prog->setMax(filesize);
|
||||
// c->prog->update(0);
|
||||
}
|
||||
|
||||
FILE *fsrc = fopen(src.c_str(), "rb");
|
||||
if(!fsrc)
|
||||
{
|
||||
fclose(fsrc);
|
||||
return;
|
||||
}
|
||||
|
||||
fileCpyThreadArgs thrdArgs;
|
||||
thrdArgs.dst = dst;
|
||||
thrdArgs.dev = dev;
|
||||
thrdArgs.filesize = filesize;
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
uint64_t journalSpace = fs::getJournalSize(utinfo);
|
||||
thrdArgs.writeLimit = (journalSpace - 0x100000) < TRANSFER_BUFFER_LIMIT ? journalSpace - 0x100000 : TRANSFER_BUFFER_LIMIT;
|
||||
|
||||
Thread writeThread;
|
||||
threadCreate(&writeThread, writeFileCommit_t, &thrdArgs, NULL, 0x040000, 0x2E, 2);
|
||||
|
||||
uint8_t *buff = new uint8_t[BUFF_SIZE];
|
||||
size_t readIn = 0;
|
||||
uint64_t readCount = 0;
|
||||
std::vector<uint8_t> transferBuffer;
|
||||
threadStart(&writeThread);
|
||||
while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0)
|
||||
{
|
||||
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
|
||||
readCount += readIn;
|
||||
if(c)
|
||||
c->offset = readCount;
|
||||
|
||||
if(transferBuffer.size() >= thrdArgs.writeLimit || readCount == filesize)
|
||||
{
|
||||
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
|
||||
thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; });
|
||||
thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
|
||||
transferBuffer.clear();
|
||||
thrdArgs.bufferIsFull = true;
|
||||
buffLock.unlock();
|
||||
thrdArgs.cond.notify_one();
|
||||
}
|
||||
}
|
||||
threadWaitForExit(&writeThread);
|
||||
threadClose(&writeThread);
|
||||
|
||||
fclose(fsrc);
|
||||
fs::commitToDevice(dev);
|
||||
delete[] buff;
|
||||
}
|
||||
|
||||
static void copyFileCommit_t(void *a)
|
||||
{
|
||||
threadInfo *t = (threadInfo *)a;
|
||||
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
|
||||
|
||||
t->status->setStatus("Copy file", in->src.c_str());
|
||||
|
||||
fs::copyFileCommit(in->src, in->dst, in->dev, t);
|
||||
if(in->cleanup)
|
||||
fs::copyArgsDestroy(in);
|
||||
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::copyFileCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev)
|
||||
{
|
||||
fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0);
|
||||
threads::newThread(copyFileCommit_t, send, fs::fileDrawFunc);
|
||||
}
|
||||
|
||||
void fs::fileDrawFunc(void *a)
|
||||
{
|
||||
threadInfo *t = (threadInfo *)a;
|
||||
if(!t->finished && t->argPtr)
|
||||
{
|
||||
copyArgs *c = (copyArgs *)t->argPtr;
|
||||
std::string tmp;
|
||||
t->status->getStatus(tmp);
|
||||
c->argLock();
|
||||
// c->prog->update(c->offset);
|
||||
// c->prog->draw(tmp);
|
||||
c->argUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
void fs::delfile(const std::string& path)
|
||||
{
|
||||
remove(path.c_str());
|
||||
}
|
||||
|
||||
void fs::getShowFileProps(const std::string& _path)
|
||||
{
|
||||
size_t size = fs::fsize(_path);
|
||||
// ui::showMessage(ui::getUICString("fileModeFileProperties", 0), _path.c_str(), util::getSizeString(size).c_str());
|
||||
}
|
||||
|
||||
bool fs::fileExists(const std::string& path)
|
||||
{
|
||||
bool ret = false;
|
||||
FILE *test = fopen(path.c_str(), "rb");
|
||||
if(test != NULL)
|
||||
ret = true;
|
||||
fclose(test);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t fs::fsize(const std::string& _f)
|
||||
{
|
||||
size_t ret = 0;
|
||||
FILE *get = fopen(_f.c_str(), "rb");
|
||||
if(get != NULL)
|
||||
{
|
||||
fseek(get, 0, SEEK_END);
|
||||
ret = ftell(get);
|
||||
}
|
||||
fclose(get);
|
||||
return ret;
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#include "fs/fsfile.h"
|
||||
|
||||
char *getDeviceFromPath(char *dev, size_t _max, const char *path)
|
||||
{
|
||||
memset(dev, 0, _max);
|
||||
char *c = strchr(path, ':');
|
||||
if(c - path > _max)
|
||||
return NULL;
|
||||
|
||||
//probably not good? idk
|
||||
memcpy(dev, path, c - path);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
char *getFilePath(char *pathOut, size_t _max, const char *path)
|
||||
{
|
||||
memset(pathOut, 0, _max);
|
||||
char *c = strchr(path, '/');
|
||||
size_t pLength = strlen(c);
|
||||
if(pLength > _max)
|
||||
return NULL;
|
||||
|
||||
memcpy(pathOut, c, pLength);
|
||||
|
||||
return pathOut;
|
||||
}
|
||||
|
||||
bool fsMkDir(const char *_p)
|
||||
{
|
||||
char devStr[16];
|
||||
char path[FS_MAX_PATH];
|
||||
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p))
|
||||
return false;
|
||||
|
||||
Result res = fsFsCreateDirectory(fsdevGetDeviceFileSystem(devStr), path);
|
||||
return res == 0;
|
||||
}
|
||||
|
||||
int fsremove(const char *_p)
|
||||
{
|
||||
char devStr[16];
|
||||
char path[FS_MAX_PATH];
|
||||
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p))
|
||||
return -1;
|
||||
|
||||
Result res = fsFsDeleteFile(fsdevGetDeviceFileSystem(devStr), path);
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fsDelDirRec(const char *_p)
|
||||
{
|
||||
char devStr[16];
|
||||
char path[FS_MAX_PATH];
|
||||
if(!getDeviceFromPath(devStr, 16, _p) || ! getFilePath(path, FS_MAX_PATH, _p))
|
||||
return 1;
|
||||
|
||||
return fsFsDeleteDirectoryRecursively(fsdevGetDeviceFileSystem(devStr), path);
|
||||
}
|
||||
|
||||
bool fsfcreate(const char *_p, int64_t crSize)
|
||||
{
|
||||
char devStr[16];
|
||||
char filePath[FS_MAX_PATH];
|
||||
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p))
|
||||
return false;
|
||||
|
||||
FsFileSystem *s = fsdevGetDeviceFileSystem(devStr);
|
||||
if(s == NULL)
|
||||
return false;
|
||||
|
||||
Result res = fsFsCreateFile(s, filePath, crSize, 0);
|
||||
if(R_SUCCEEDED(res))
|
||||
res = fsdevCommitDevice(devStr);
|
||||
|
||||
return R_SUCCEEDED(res) ? true : false;
|
||||
}
|
||||
|
||||
FSFILE *fsfopen(const char *_p, uint32_t mode)
|
||||
{
|
||||
char devStr[16];
|
||||
char filePath[FS_MAX_PATH];
|
||||
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p))
|
||||
return NULL;
|
||||
|
||||
FsFileSystem *s = fsdevGetDeviceFileSystem(devStr);
|
||||
if(s == NULL)
|
||||
return NULL;
|
||||
|
||||
if(mode == FsOpenMode_Write)
|
||||
{
|
||||
fsFsDeleteFile(s, filePath);
|
||||
fsFsCreateFile(s, filePath, 0, 0);
|
||||
}
|
||||
|
||||
FSFILE *ret = malloc(sizeof(FSFILE));
|
||||
ret->error = fsFsOpenFile(s, filePath, mode, &ret->_f);
|
||||
if(R_FAILED(ret->error))
|
||||
{
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
||||
fsFileGetSize(&ret->_f, &ret->fsize);
|
||||
ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FSFILE *fsfopenWithSystem(FsFileSystem *_s, const char *_p, uint32_t mode)
|
||||
{
|
||||
if(mode & FsOpenMode_Write)
|
||||
{
|
||||
fsFsDeleteFile(_s, _p);
|
||||
fsFsCreateFile(_s, _p, 0, 0);
|
||||
}
|
||||
else if(mode & FsOpenMode_Append)
|
||||
fsFsCreateFile(_s, _p, 0, 0);
|
||||
|
||||
FSFILE *ret = malloc(sizeof(FSFILE));
|
||||
ret->error = fsFsOpenFile(_s, _p, mode, &ret->_f);
|
||||
if(R_FAILED(ret->error))
|
||||
{
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
||||
fsFileGetSize(&ret->_f, &ret->fsize);
|
||||
ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t fsfwrite(const void *buf, size_t sz, size_t count, FSFILE *_f)
|
||||
{
|
||||
size_t fullSize = sz * count;
|
||||
if(_f->offset + fullSize > _f->fsize)
|
||||
{
|
||||
s64 newSize = (_f->fsize + fullSize) - (_f->fsize - _f->offset);
|
||||
fsFileSetSize(&_f->_f, newSize);
|
||||
_f->fsize = newSize;
|
||||
}
|
||||
_f->error = fsFileWrite(&_f->_f, _f->offset, buf, fullSize, FsWriteOption_Flush);
|
||||
_f->offset += fullSize;
|
||||
|
||||
return fullSize;
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
#include <switch.h>
|
||||
#include <time.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "fs.h"
|
||||
#include "util.h"
|
||||
#include "threads.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
std::mutex buffLock;
|
||||
std::condition_variable cond;
|
||||
std::vector<uint8_t> sharedBuffer;
|
||||
std::string dst, dev;
|
||||
bool bufferIsFull = false;
|
||||
unzFile unz;
|
||||
unsigned int fileSize, writeLimit = 0;
|
||||
} unzThrdArgs;
|
||||
|
||||
static void writeFileFromZip_t(void *a)
|
||||
{
|
||||
unzThrdArgs *in = (unzThrdArgs *)a;
|
||||
std::vector<uint8_t> localBuffer;
|
||||
unsigned int written = 0, journalCount = 0;
|
||||
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
uint64_t journalSpace = fs::getJournalSize(utinfo);
|
||||
|
||||
FILE *out = fopen(in->dst.c_str(), "wb");
|
||||
while(written < in->fileSize)
|
||||
{
|
||||
std::unique_lock<std::mutex> buffLock(in->buffLock);
|
||||
in->cond.wait(buffLock, [in]{ return in->bufferIsFull; });
|
||||
localBuffer.clear();
|
||||
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
|
||||
in->sharedBuffer.clear();
|
||||
in->bufferIsFull = false;
|
||||
buffLock.unlock();
|
||||
in->cond.notify_one();
|
||||
|
||||
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
|
||||
journalCount += written;
|
||||
if(journalCount >= in->writeLimit)
|
||||
{
|
||||
journalCount = 0;
|
||||
fclose(out);
|
||||
fs::commitToDevice(in->dev);
|
||||
out = fopen(in->dst.c_str(), "ab");
|
||||
}
|
||||
}
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
void fs::copyDirToZip(const std::string& src, zipFile dst, bool trimPath, int trimPlaces, threadInfo *t)
|
||||
{
|
||||
fs::copyArgs *c = NULL;
|
||||
if(t)
|
||||
{
|
||||
t->status->setStatus("threadStatusOpeningFolder");
|
||||
c = (fs::copyArgs *)t->argPtr;
|
||||
}
|
||||
|
||||
fs::dirList *list = new fs::dirList(src);
|
||||
for(unsigned i = 0; i < list->getCount(); i++)
|
||||
{
|
||||
std::string itm = list->getItem(i);
|
||||
if(fs::pathIsFiltered(src + itm))
|
||||
continue;
|
||||
|
||||
if(list->isDir(i))
|
||||
{
|
||||
std::string newSrc = src + itm + "/";
|
||||
fs::copyDirToZip(newSrc, dst, trimPath, trimPlaces, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
time_t raw;
|
||||
time(&raw);
|
||||
tm *locTime = localtime(&raw);
|
||||
zip_fileinfo inf = { (unsigned)locTime->tm_sec, (unsigned)locTime->tm_min, (unsigned)locTime->tm_hour,
|
||||
(unsigned)locTime->tm_mday, (unsigned)locTime->tm_mon, (unsigned)(1900 + locTime->tm_year), 0, 0, 0 };
|
||||
|
||||
std::string filename = src + itm;
|
||||
size_t zipNameStart = 0;
|
||||
if(trimPath)
|
||||
util::trimPath(filename, trimPlaces);
|
||||
else
|
||||
zipNameStart = filename.find_first_of('/') + 1;
|
||||
|
||||
if(t)
|
||||
t->status->setStatus("threadStatusAddingFileToZip");
|
||||
|
||||
int zipOpenFile = zipOpenNewFileInZip64(dst, filename.substr(zipNameStart, filename.npos).c_str(), &inf, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0);
|
||||
if(zipOpenFile == ZIP_OK)
|
||||
{
|
||||
std::string fullSrc = src + itm;
|
||||
if(c)
|
||||
{
|
||||
c->offset = 0;
|
||||
// c->prog->setMax(fs::fsize(fullSrc));
|
||||
// c->prog->update(0);
|
||||
}
|
||||
|
||||
FILE *fsrc = fopen(fullSrc.c_str(), "rb");
|
||||
size_t readIn = 0;
|
||||
uint8_t *buff = new uint8_t[ZIP_BUFF_SIZE];
|
||||
while((readIn = fread(buff, 1, ZIP_BUFF_SIZE, fsrc)) > 0)
|
||||
{
|
||||
zipWriteInFileInZip(dst, buff, readIn);
|
||||
if(c)
|
||||
c->offset += readIn;
|
||||
}
|
||||
delete[] buff;
|
||||
fclose(fsrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void copyDirToZip_t(void *a)
|
||||
{
|
||||
threadInfo *t = (threadInfo *)a;
|
||||
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
|
||||
|
||||
fs::copyDirToZip(c->src, c->z, c->trimZipPath, c->trimZipPlaces, t);
|
||||
|
||||
if(c->cleanup)
|
||||
{
|
||||
zipClose(c->z, NULL);
|
||||
delete c;
|
||||
}
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::copyDirToZipThreaded(const std::string& src, zipFile dst, bool trimPath, int trimPlaces)
|
||||
{
|
||||
fs::copyArgs *send = fs::copyArgsCreate(src, "", "", dst, NULL, true, false, 0);
|
||||
threads::newThread(copyDirToZip_t, send, fs::fileDrawFunc);
|
||||
}
|
||||
|
||||
void fs::copyZipToDir(unzFile src, const std::string& dst, const std::string& dev, threadInfo *t)
|
||||
{
|
||||
fs::copyArgs *c = NULL;
|
||||
if(t)
|
||||
c = (fs::copyArgs *)t->argPtr;
|
||||
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
uint64_t journalSize = getJournalSize(utinfo), writeCount = 0;
|
||||
char filename[FS_MAX_PATH];
|
||||
uint8_t *buff = new uint8_t[BUFF_SIZE];
|
||||
int readIn = 0;
|
||||
unz_file_info64 info;
|
||||
do
|
||||
{
|
||||
unzGetCurrentFileInfo64(src, &info, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
|
||||
if(unzOpenCurrentFile(src) == UNZ_OK)
|
||||
{
|
||||
if(t)
|
||||
t->status->setStatus("threadStatusDecompressingFile");
|
||||
|
||||
if(c)
|
||||
{
|
||||
// c->prog->setMax(info.uncompressed_size);
|
||||
// c->prog->update(0);
|
||||
c->offset = 0;
|
||||
}
|
||||
|
||||
std::string fullDst = dst + filename;
|
||||
fs::mkDirRec(fullDst.substr(0, fullDst.find_last_of('/') + 1));
|
||||
|
||||
unzThrdArgs unzThrd;
|
||||
unzThrd.dst = fullDst;
|
||||
unzThrd.fileSize = info.uncompressed_size;
|
||||
unzThrd.dev = dev;
|
||||
unzThrd.writeLimit = (journalSize - 0x100000) < TRANSFER_BUFFER_LIMIT ? (journalSize - 0x100000) : TRANSFER_BUFFER_LIMIT;
|
||||
|
||||
Thread writeThread;
|
||||
threadCreate(&writeThread, writeFileFromZip_t, &unzThrd, NULL, 0x8000, 0x2B, 2);
|
||||
threadStart(&writeThread);
|
||||
|
||||
std::vector<uint8_t> transferBuffer;
|
||||
uint64_t readCount = 0;
|
||||
while((readIn = unzReadCurrentFile(src, buff, BUFF_SIZE)) > 0)
|
||||
{
|
||||
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
|
||||
readCount += readIn;
|
||||
|
||||
if(c)
|
||||
c->offset += readIn;
|
||||
|
||||
if(transferBuffer.size() >= unzThrd.writeLimit || readCount == info.uncompressed_size)
|
||||
{
|
||||
std::unique_lock<std::mutex> buffLock(unzThrd.buffLock);
|
||||
unzThrd.cond.wait(buffLock, [&unzThrd]{ return unzThrd.bufferIsFull == false; });
|
||||
unzThrd.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
|
||||
transferBuffer.clear();
|
||||
unzThrd.bufferIsFull = true;
|
||||
unzThrd.cond.notify_one();
|
||||
}
|
||||
}
|
||||
threadWaitForExit(&writeThread);
|
||||
threadClose(&writeThread);
|
||||
fs::commitToDevice(dev);
|
||||
}
|
||||
}
|
||||
while(unzGoToNextFile(src) != UNZ_END_OF_LIST_OF_FILE);
|
||||
delete[] buff;
|
||||
}
|
||||
|
||||
static void copyZipToDir_t(void *a)
|
||||
{
|
||||
threadInfo *t = (threadInfo *)a;
|
||||
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
|
||||
fs::copyZipToDir(c->unz, c->dst, c->dev, t);
|
||||
if(c->cleanup)
|
||||
{
|
||||
unzClose(c->unz);
|
||||
delete c;
|
||||
}
|
||||
t->finished = true;
|
||||
}
|
||||
|
||||
void fs::copyZipToDirThreaded(unzFile src, const std::string& dst, const std::string& dev)
|
||||
{
|
||||
fs::copyArgs *send = fs::copyArgsCreate("", dst, dev, NULL, src, true, false, 0);
|
||||
threads::newThread(copyZipToDir_t, send, fs::fileDrawFunc);
|
||||
}
|
||||
|
||||
uint64_t fs::getZipTotalSize(unzFile unz)
|
||||
{
|
||||
uint64_t ret = 0;
|
||||
if(unzGoToFirstFile(unz) == UNZ_OK)
|
||||
{
|
||||
unz_file_info64 finfo;
|
||||
char filename[FS_MAX_PATH];
|
||||
do
|
||||
{
|
||||
unzGetCurrentFileInfo64(unz, &finfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
|
||||
ret += finfo.uncompressed_size;
|
||||
} while(unzGoToNextFile(unz) != UNZ_END_OF_LIST_OF_FILE);
|
||||
unzGoToFirstFile(unz);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool fs::zipNotEmpty(unzFile unz)
|
||||
{
|
||||
return unzGoToFirstFile(unz) == UNZ_OK;
|
||||
}
|
||||
269
source/io.cpp
Normal file
269
source/io.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* This file is part of Checkpoint
|
||||
* Copyright (C) 2017-2021 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 "io.hpp"
|
||||
#include "main.hpp"
|
||||
#include <logger.hpp>
|
||||
|
||||
bool io::fileExists(const std::string& path)
|
||||
{
|
||||
struct stat buffer;
|
||||
return (stat(path.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
void io::copyFile(const std::string& srcPath, const std::string& dstPath)
|
||||
{
|
||||
g_isTransferringFile = true;
|
||||
|
||||
FILE* src = fopen(srcPath.c_str(), "rb");
|
||||
if (src == NULL) {
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to open source file " + srcPath + " during copy with errno %d. Skipping...", errno);
|
||||
return;
|
||||
}
|
||||
FILE* dst = fopen(dstPath.c_str(), "wb");
|
||||
if (dst == NULL) {
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to open destination file " + dstPath + " during copy with errno %d. Skipping...", errno);
|
||||
fclose(src);
|
||||
return;
|
||||
}
|
||||
|
||||
fseek(src, 0, SEEK_END);
|
||||
u64 sz = ftell(src);
|
||||
rewind(src);
|
||||
|
||||
u8* buf = new u8[BUFFER_SIZE];
|
||||
u64 offset = 0;
|
||||
|
||||
size_t slashpos = srcPath.rfind("/");
|
||||
g_currentFile = srcPath.substr(slashpos + 1, srcPath.length() - slashpos - 1);
|
||||
|
||||
while (offset < sz) {
|
||||
u32 count = fread((char*)buf, 1, BUFFER_SIZE, src);
|
||||
fwrite((char*)buf, 1, count, dst);
|
||||
offset += count;
|
||||
}
|
||||
|
||||
delete[] buf;
|
||||
fclose(src);
|
||||
fclose(dst);
|
||||
|
||||
// commit each file to the save
|
||||
if (dstPath.rfind("save:/", 0) == 0) {
|
||||
Logger::getInstance().log(Logger::ERROR, "Committing file " + dstPath + " to the save archive.");
|
||||
fsdevCommitDevice("save");
|
||||
}
|
||||
|
||||
g_isTransferringFile = false;
|
||||
}
|
||||
|
||||
Result io::copyDirectory(const std::string& srcPath, const std::string& dstPath)
|
||||
{
|
||||
Result res = 0;
|
||||
bool quit = false;
|
||||
Directory items(srcPath);
|
||||
|
||||
if (!items.good()) {
|
||||
return items.error();
|
||||
}
|
||||
|
||||
for (size_t i = 0, sz = items.size(); i < sz && !quit; i++) {
|
||||
std::string newsrc = srcPath + items.entry(i);
|
||||
std::string newdst = dstPath + items.entry(i);
|
||||
|
||||
if (items.folder(i)) {
|
||||
res = io::createDirectory(newdst);
|
||||
if (R_SUCCEEDED(res)) {
|
||||
newsrc += "/";
|
||||
newdst += "/";
|
||||
res = io::copyDirectory(newsrc, newdst);
|
||||
}
|
||||
else {
|
||||
quit = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
io::copyFile(newsrc, newdst);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result io::createDirectory(const std::string& path)
|
||||
{
|
||||
mkdir(path.c_str(), 777);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool io::directoryExists(const std::string& path)
|
||||
{
|
||||
struct stat sb;
|
||||
return (stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode));
|
||||
}
|
||||
|
||||
Result io::deleteFolderRecursively(const std::string& path)
|
||||
{
|
||||
Directory dir(path);
|
||||
if (!dir.good()) {
|
||||
return dir.error();
|
||||
}
|
||||
|
||||
for (size_t i = 0, sz = dir.size(); i < sz; i++) {
|
||||
if (dir.folder(i)) {
|
||||
std::string newpath = path + "/" + dir.entry(i) + "/";
|
||||
deleteFolderRecursively(newpath);
|
||||
newpath = path + dir.entry(i);
|
||||
rmdir(newpath.c_str());
|
||||
}
|
||||
else {
|
||||
std::string newpath = path + dir.entry(i);
|
||||
std::remove(newpath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
rmdir(path.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::tuple<bool, Result, std::string> io::backup(size_t index, AccountUid uid)
|
||||
{
|
||||
Result res = 0;
|
||||
std::tuple<bool, Result, std::string> ret = std::make_tuple(false, -1, "");
|
||||
Title title;
|
||||
getTitle(title, uid, index);
|
||||
|
||||
Logger::getInstance().log(Logger::INFO, "Started backup of %s. Title id: 0x%016lX; User id: 0x%lX%lX.", title.name().c_str(), title.id(),
|
||||
title.userId().uid[1], title.userId().uid[0]);
|
||||
|
||||
FsFileSystem fileSystem;
|
||||
res = FileSystem::mount(&fileSystem, title.id(), title.userId());
|
||||
if (R_SUCCEEDED(res)) {
|
||||
int rc = FileSystem::mount(fileSystem);
|
||||
if (rc == -1) {
|
||||
FileSystem::unmount();
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to mount filesystem during backup. Title id: 0x%016lX; User id: 0x%lX%lX.", title.id(),
|
||||
title.userId().uid[1], title.userId().uid[0]);
|
||||
return std::make_tuple(false, -2, "Failed to mount save.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
Logger::getInstance().log(Logger::ERROR,
|
||||
"Failed to mount filesystem during backup with result 0x%08lX. Title id: 0x%016lX; User id: 0x%lX%lX.", res, title.id(),
|
||||
title.userId().uid[1], title.userId().uid[0]);
|
||||
return std::make_tuple(false, res, "Failed to mount save.");
|
||||
}
|
||||
|
||||
std::string suggestion = StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(title.userId())));
|
||||
|
||||
std::string dstPath = title.path() + "/" + suggestion;
|
||||
|
||||
if (io::directoryExists(dstPath)) {
|
||||
int rc = io::deleteFolderRecursively((dstPath + "/").c_str());
|
||||
if (rc != 0) {
|
||||
FileSystem::unmount();
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to recursively delete directory " + dstPath + " with result %d.", rc);
|
||||
return std::make_tuple(false, (Result)rc, "Failed to delete the existing backup\ndirectory recursively.");
|
||||
}
|
||||
}
|
||||
|
||||
io::createDirectory(dstPath);
|
||||
res = io::copyDirectory("save:/", dstPath + "/");
|
||||
if (R_FAILED(res)) {
|
||||
FileSystem::unmount();
|
||||
io::deleteFolderRecursively((dstPath + "/").c_str());
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to copy directory " + dstPath + " with result 0x%08lX. Skipping...", res);
|
||||
return std::make_tuple(false, res, "Failed to backup save.");
|
||||
}
|
||||
|
||||
refreshDirectories(title.id());
|
||||
|
||||
FileSystem::unmount();
|
||||
|
||||
ret = std::make_tuple(true, 0, dstPath);
|
||||
Logger::getInstance().log(Logger::INFO, "Backup succeeded.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::tuple<bool, Result, std::string> io::restore(size_t index, AccountUid uid, size_t cellIndex, const std::string& nameFromCell)
|
||||
{
|
||||
Result res = 0;
|
||||
std::tuple<bool, Result, std::string> ret = std::make_tuple(false, -1, "");
|
||||
Title title;
|
||||
getTitle(title, uid, index);
|
||||
|
||||
Logger::getInstance().log(Logger::INFO, "Started restore of %s. Title id: 0x%016lX; User id: 0x%lX%lX.", title.name().c_str(), title.id(),
|
||||
title.userId().uid[1], title.userId().uid[0]);
|
||||
|
||||
FsFileSystem fileSystem;
|
||||
res = FileSystem::mount(&fileSystem, title.id(), title.userId());
|
||||
if (R_SUCCEEDED(res)) {
|
||||
int rc = FileSystem::mount(fileSystem);
|
||||
if (rc == -1) {
|
||||
FileSystem::unmount();
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to mount filesystem during restore. Title id: 0x%016lX; User id: 0x%lX%lX.", title.id(),
|
||||
title.userId().uid[1], title.userId().uid[0]);
|
||||
return std::make_tuple(false, -2, "Failed to mount save.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
Logger::getInstance().log(Logger::ERROR,
|
||||
"Failed to mount filesystem during restore with result 0x%08lX. Title id: 0x%016lX; User id: 0x%lX%lX.", res, title.id(),
|
||||
title.userId().uid[1], title.userId().uid[0]);
|
||||
return std::make_tuple(false, res, "Failed to mount save.");
|
||||
}
|
||||
|
||||
std::string srcPath = title.fullPath(cellIndex) + "/";
|
||||
std::string dstPath = "save:/";
|
||||
|
||||
res = io::deleteFolderRecursively(dstPath.c_str());
|
||||
if (R_FAILED(res)) {
|
||||
FileSystem::unmount();
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to recursively delete directory " + dstPath + " with result 0x%08lX.", res);
|
||||
return std::make_tuple(false, res, "Failed to delete save.");
|
||||
}
|
||||
|
||||
res = io::copyDirectory(srcPath, dstPath);
|
||||
if (R_FAILED(res)) {
|
||||
FileSystem::unmount();
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to copy directory " + srcPath + " to " + dstPath + " with result 0x%08lX. Skipping...", res);
|
||||
return std::make_tuple(false, res, "Failed to restore save.");
|
||||
}
|
||||
|
||||
res = fsdevCommitDevice("save");
|
||||
if (R_FAILED(res)) {
|
||||
Logger::getInstance().log(Logger::ERROR, "Failed to commit save with result 0x%08lX.", res);
|
||||
return std::make_tuple(false, res, "Failed to commit to save device.");
|
||||
}
|
||||
else {
|
||||
blinkLed(4);
|
||||
ret = std::make_tuple(true, 0, nameFromCell + "\nhas been restored successfully.");
|
||||
}
|
||||
|
||||
FileSystem::unmount();
|
||||
|
||||
Logger::getInstance().log(Logger::INFO, "Restore succeeded.");
|
||||
return ret;
|
||||
}
|
||||
543
source/ldn.cpp
543
source/ldn.cpp
@@ -1,543 +0,0 @@
|
||||
#include "ldn.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "util.h"
|
||||
|
||||
static const u8 sec_data[0x10] = {0x04, 0xb9, 0x9d, 0x4d, 0x58, 0xbc,
|
||||
0x65, 0xe1, 0x77, 0x13, 0xc2, 0xb8,
|
||||
0xd1, 0xb8, 0xec, 0xf6};
|
||||
|
||||
Result create_network(const LdnSecurityConfig *sec_config,
|
||||
const LdnUserConfig *user_config,
|
||||
const LdnNetworkConfig *netconfig, const void *advert,
|
||||
size_t advert_size) {
|
||||
Result rc = 0;
|
||||
|
||||
rc = ldnOpenAccessPoint();
|
||||
if (R_FAILED(rc)) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ldnCreateNetwork(sec_config, user_config, netconfig);
|
||||
if (R_FAILED(rc)) {
|
||||
goto error_close;
|
||||
}
|
||||
|
||||
rc = ldnSetAdvertiseData(advert, advert_size);
|
||||
if (R_FAILED(rc))
|
||||
goto error_close;
|
||||
|
||||
return rc;
|
||||
error_close:
|
||||
ldnCloseAccessPoint();
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result connect_network(const LdnScanFilter *filter,
|
||||
const LdnSecurityConfig *sec_config,
|
||||
const LdnUserConfig *user_config, const void *advert,
|
||||
size_t advert_size) {
|
||||
Result rc = 0;
|
||||
s32 total_out = 0;
|
||||
LdnNetworkInfo netinfo_list[0x18];
|
||||
|
||||
rc = ldnOpenStation();
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = ldnScan(0, filter, netinfo_list, 0x18, &total_out);
|
||||
}
|
||||
|
||||
// In an actual app you'd display the output netinfo_list and let the user
|
||||
// select which one to connect to, however in this example we'll just
|
||||
// connect to the first one.
|
||||
|
||||
if (R_SUCCEEDED(rc) && !total_out) {
|
||||
rc = MAKERESULT(Module_Libnx, LibnxError_NotFound);
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) { // Handle this / parse it with any method you want.
|
||||
if (netinfo_list[0].advertise_data_size != advert_size ||
|
||||
memcmp(netinfo_list[0].advertise_data, advert, advert_size) != 0) {
|
||||
rc = MAKERESULT(Module_Libnx, LibnxError_NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = ldnConnect(sec_config, user_config, 0, 0, &netinfo_list[0]);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc))
|
||||
ldnCloseStation();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void leave_network(void) {
|
||||
Result rc = 0;
|
||||
LdnState state;
|
||||
|
||||
rc = ldnGetState(&state);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (state == LdnState_AccessPointOpened ||
|
||||
state == LdnState_AccessPointCreated) {
|
||||
if (state == LdnState_AccessPointCreated) {
|
||||
rc = ldnDestroyNetwork();
|
||||
}
|
||||
rc = ldnCloseAccessPoint();
|
||||
}
|
||||
|
||||
if (state == LdnState_StationOpened || state == LdnState_StationConnected) {
|
||||
if (state == LdnState_StationConnected) {
|
||||
rc = ldnDisconnect();
|
||||
}
|
||||
rc = ldnCloseStation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LDN::destroyLDN() {
|
||||
leave_network();
|
||||
ldnExit();
|
||||
}
|
||||
|
||||
LDN::LDNCommunicate* LDN::createCommunicate(void)
|
||||
{
|
||||
LDN::LDNCommunicate* comm = new LDN::LDNCommunicate;
|
||||
comm->serverFD = -1;
|
||||
comm->commFD = -1;
|
||||
return comm;
|
||||
}
|
||||
|
||||
void LDN::destroyCommunicate(LDNCommunicate *comm) {
|
||||
if (comm->commFD >= 0)
|
||||
close(comm->commFD);
|
||||
if (comm->serverFD >= 0)
|
||||
close(comm->serverFD);
|
||||
delete comm;
|
||||
}
|
||||
|
||||
Result LDN::createLDNServer(LDNCommunicate *comm) {
|
||||
Result rc;
|
||||
LdnSecurityConfig sec_config = {0};
|
||||
LdnUserConfig user_config = {0};
|
||||
LdnNetworkConfig netconfig = {0};
|
||||
LdnState state;
|
||||
data::user *user = data::getCurrentUser();
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
/*
|
||||
* Use Title ID to the advert, so if advert not match
|
||||
* LDN connect between client and server will bind fail
|
||||
*/
|
||||
std::string advertStr = std::to_string(utinfo->tid);
|
||||
int serverSocket;
|
||||
|
||||
// send nickname to make sure
|
||||
strncpy(user_config.nickname, user->getUsername().c_str(), 0x20 - 1);
|
||||
|
||||
netconfig.local_communication_id = -1;
|
||||
netconfig.participant_max = 2; // Adjust as needed.
|
||||
|
||||
sec_config.type = 1;
|
||||
sec_config.data_size = sizeof(sec_data);
|
||||
memcpy(sec_config.data, sec_data, sizeof(sec_data));
|
||||
|
||||
rc = ldnInitialize(LdnServiceType_User);
|
||||
if (R_FAILED(rc)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = ldnGetState(&state);
|
||||
if (!R_SUCCEEDED(rc) || state != LdnState_Initialized) {
|
||||
goto ldn_out;
|
||||
}
|
||||
|
||||
rc = create_network(&sec_config, &user_config, &netconfig, advertStr.c_str(),
|
||||
advertStr.length());
|
||||
if (R_FAILED(rc)) {
|
||||
goto ldn_out;
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
||||
ldn_out:
|
||||
ldnExit();
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result LDN::createLDNClient(LDNCommunicate *comm) {
|
||||
Result rc;
|
||||
LdnUserConfig user_config = {0};
|
||||
LdnSecurityConfig sec_config = {0};
|
||||
LdnNetworkConfig netconfig = {0};
|
||||
LdnScanFilter filter = {0};
|
||||
LdnState state;
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
/*
|
||||
* Use Title ID to the advert, so if advert not match
|
||||
* LDN connect between client and server will bind fail
|
||||
*/
|
||||
std::string advertStr = std::to_string(utinfo->tid);
|
||||
data::user *user = data::getCurrentUser();
|
||||
|
||||
strncpy(user_config.nickname, user->getUsername().c_str(), 0x20 - 1);
|
||||
|
||||
rc = ldnInitialize(LdnServiceType_User);
|
||||
if (R_FAILED(rc)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
netconfig.local_communication_id = -1;
|
||||
netconfig.participant_max = 2; // Adjust as needed.
|
||||
|
||||
sec_config.type = 1;
|
||||
sec_config.data_size = sizeof(sec_data);
|
||||
memcpy(sec_config.data, sec_data, sizeof(sec_data));
|
||||
|
||||
filter.local_communication_id = -1;
|
||||
filter.userdata_filter = netconfig.userdata_filter;
|
||||
filter.flags =
|
||||
LdnScanFilterFlags_LocalCommunicationId | LdnScanFilterFlags_UserData;
|
||||
|
||||
rc = ldnGetState(&state);
|
||||
if (!R_SUCCEEDED(rc) || state != LdnState_Initialized) {
|
||||
goto ldnOut;
|
||||
}
|
||||
|
||||
rc = connect_network(&filter, &sec_config, &user_config, advertStr.c_str(),
|
||||
advertStr.length());
|
||||
|
||||
out:
|
||||
return rc;
|
||||
ldnOut:
|
||||
ldnExit();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int LDN::bindClient(int serverFD) {
|
||||
struct sockaddr_in clientAddr;
|
||||
socklen_t length = sizeof(clientAddr);
|
||||
int cSocket =
|
||||
accept(serverFD, (struct sockaddr *)&clientAddr, &length);
|
||||
if (cSocket < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cSocket;
|
||||
}
|
||||
|
||||
int LDN::bindServer(struct sockaddr_in *serverAddr) {
|
||||
/// sockfd
|
||||
int sock_cli = socket(AF_INET, SOCK_STREAM, 0);
|
||||
int ret;
|
||||
if ((ret = connect(sock_cli, (struct sockaddr *)serverAddr,
|
||||
sizeof(*serverAddr))) < 0) {
|
||||
close(sock_cli);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sock_cli;
|
||||
}
|
||||
|
||||
static void reciveBuf(char *buf, ssize_t size, int socketfd) {
|
||||
ssize_t offset = 0, ssize;
|
||||
while (offset < size) {
|
||||
ssize =
|
||||
size - offset < SOCKET_BUFFER_SIZE ? size - offset : SOCKET_BUFFER_SIZE;
|
||||
ssize_t done = recv(socketfd, buf + offset, ssize, 0);
|
||||
if (done == -1) {
|
||||
break;
|
||||
}
|
||||
offset += done;
|
||||
}
|
||||
}
|
||||
|
||||
static void sendBuf(const char *buf, ssize_t size, int socketfd) {
|
||||
ssize_t offset = 0, ssize;
|
||||
while (offset < size) {
|
||||
ssize =
|
||||
size - offset < SOCKET_BUFFER_SIZE ? size - offset : SOCKET_BUFFER_SIZE;
|
||||
ssize_t done = send(socketfd, buf + offset, ssize, 0);
|
||||
if (done == -1) {
|
||||
break;
|
||||
}
|
||||
offset += done;
|
||||
}
|
||||
}
|
||||
|
||||
bool LDN::waitForOK(int socketfd) {
|
||||
commMeta meta;
|
||||
reciveBuf((char *)&meta, sizeof(meta), socketfd);
|
||||
if (meta.type == UPDATE_OK)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LDN::waitForDONE(int socketfd) {
|
||||
commMeta meta;
|
||||
reciveBuf((char *)&meta, sizeof(meta), socketfd);
|
||||
if (meta.type == UPDATE_DONE)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void LDN::sendOK(int socket_fd) {
|
||||
commMeta meta;
|
||||
meta.type = UPDATE_OK;
|
||||
|
||||
sendBuf((char *)&meta, sizeof(meta), socket_fd);
|
||||
}
|
||||
|
||||
void LDN::sendDONE(int socket_fd) {
|
||||
commMeta meta;
|
||||
meta.type = UPDATE_DONE;
|
||||
|
||||
sendBuf((char *)&meta, sizeof(meta), socket_fd);
|
||||
}
|
||||
|
||||
void LDN::sendAbort(int socket_fd) {
|
||||
commMeta meta;
|
||||
meta.type = UPDATE_ABORT;
|
||||
|
||||
sendBuf((char *)&meta, sizeof(meta), socket_fd);
|
||||
}
|
||||
|
||||
void LDN::reciveMeta(commMeta *meta, int socketfd) {
|
||||
bzero(meta, sizeof(commMeta));
|
||||
reciveBuf((char *)meta, sizeof(commMeta), socketfd);
|
||||
sendOK(socketfd);
|
||||
}
|
||||
|
||||
void LDN::sendMeta(commMeta *meta, int socketfd) {
|
||||
sendBuf((char *)meta, sizeof(commMeta), socketfd);
|
||||
waitForOK(socketfd);
|
||||
}
|
||||
|
||||
static void copySaveFileToRemote_t(void *a) {
|
||||
LDN::LDNfcopyArgs *in = (LDN::LDNfcopyArgs *)a;
|
||||
LDN::LDNCommunicate *comm = in->comm;
|
||||
size_t written = 0;
|
||||
std::vector<uint8_t> localBuffer;
|
||||
|
||||
int fSocket = LDN::bindClient(comm->serverFD);
|
||||
if (fSocket < 0)
|
||||
return;
|
||||
|
||||
while (written < in->filesize) {
|
||||
std::unique_lock<std::mutex> buffLock(in->bufferLock);
|
||||
in->cond.wait(buffLock, [in] { return in->bufferIsFull; });
|
||||
localBuffer.clear();
|
||||
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
|
||||
in->sharedBuffer.clear();
|
||||
in->bufferIsFull = false;
|
||||
buffLock.unlock();
|
||||
in->cond.notify_one();
|
||||
|
||||
written += send(fSocket, localBuffer.data(), localBuffer.size(), 0);
|
||||
}
|
||||
|
||||
close(fSocket);
|
||||
}
|
||||
|
||||
void LDN::copySaveFileToRemote(const std::string &local, threadInfo *t) {
|
||||
fs::copyArgs *c = NULL;
|
||||
c = (fs::copyArgs *)t->argPtr;
|
||||
LDN::LDNCommunicate *comm = c->comm;
|
||||
commMeta cm;
|
||||
std::string zipPath = fs::getWorkDir() + "tempSave.zip";
|
||||
|
||||
//Trim this to unzip direct in direct sv:/
|
||||
int zipTrim = util::getTotalPlacesInPath("sv:/");
|
||||
|
||||
t->status->setStatus("LDNStatus");
|
||||
|
||||
|
||||
zipFile zip = zipOpen64(zipPath.c_str(), 0);
|
||||
fs::copyDirToZip(local, zip, true, zipTrim, t);
|
||||
zipClose(zip, NULL);
|
||||
|
||||
|
||||
size_t filesize = fs::fsize(zipPath);
|
||||
t->status->setStatus("LDNStatus");
|
||||
c->offset = 0;
|
||||
|
||||
FILE *fsrc = fopen(zipPath.c_str(), "rb");
|
||||
if(!fsrc)
|
||||
{
|
||||
sendAbort(comm->commFD);
|
||||
fclose(fsrc);
|
||||
return;
|
||||
}
|
||||
|
||||
cm.type = UPDATE_FILE;
|
||||
cm.fsz = filesize;
|
||||
sendMeta(&cm, comm->commFD);
|
||||
|
||||
LDNfcopyArgs thrdArgs;
|
||||
thrdArgs.comm = comm;
|
||||
thrdArgs.filesize = filesize;
|
||||
|
||||
uint8_t *buff = new uint8_t[BUFF_SIZE];
|
||||
std::vector<uint8_t> transferBuffer;
|
||||
Thread writeThread;
|
||||
threadCreate(&writeThread, copySaveFileToRemote_t, &thrdArgs, NULL,
|
||||
0x40000, 0x2E, 2);
|
||||
threadStart(&writeThread);
|
||||
size_t readIn = 0;
|
||||
uint64_t readCount = 0;
|
||||
|
||||
while ((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0) {
|
||||
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
|
||||
readCount += readIn;
|
||||
|
||||
if (c)
|
||||
c->offset = readCount;
|
||||
|
||||
if (transferBuffer.size() >= TRANSFER_BUFFER_LIMIT ||
|
||||
readCount == filesize) {
|
||||
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
|
||||
thrdArgs.cond.wait(
|
||||
buffLock, [&thrdArgs] { return thrdArgs.bufferIsFull == false; });
|
||||
thrdArgs.sharedBuffer.assign(transferBuffer.begin(),
|
||||
transferBuffer.end());
|
||||
transferBuffer.clear();
|
||||
thrdArgs.bufferIsFull = true;
|
||||
buffLock.unlock();
|
||||
thrdArgs.cond.notify_one();
|
||||
}
|
||||
}
|
||||
threadWaitForExit(&writeThread);
|
||||
threadClose(&writeThread);
|
||||
fclose(fsrc);
|
||||
fs::delfile(zipPath);
|
||||
delete[] buff;
|
||||
|
||||
t->status->setStatus("LDNStatus");
|
||||
// wait for client recived sure
|
||||
// otherwise, may lost package
|
||||
LDN::sendDONE(comm->commFD);
|
||||
LDN::waitForDONE(comm->commFD);
|
||||
}
|
||||
|
||||
static void copyRemoteFileWrite_t(void *a) {
|
||||
LDN::LDNfcopyArgs *in = (LDN::LDNfcopyArgs *)a;
|
||||
size_t written = 0;
|
||||
std::vector<uint8_t> localBuffer;
|
||||
FILE *out = fopen(in->dst.c_str(), "wb");
|
||||
|
||||
while (written < in->filesize) {
|
||||
std::unique_lock<std::mutex> buffLock(in->bufferLock);
|
||||
in->cond.wait(buffLock, [in] { return in->bufferIsFull; });
|
||||
localBuffer.clear();
|
||||
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
|
||||
in->sharedBuffer.clear();
|
||||
in->bufferIsFull = false;
|
||||
buffLock.unlock();
|
||||
in->cond.notify_one();
|
||||
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
|
||||
}
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
static void copyRemoteSaveFileCommit(LDN::commMeta *meta, threadInfo *t) {
|
||||
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
|
||||
LDN::LDNCommunicate *comm = c->comm;
|
||||
|
||||
std::string fullPath = fs::getWorkDir() + "tempSave.zip";
|
||||
size_t filesize = meta->fsz;
|
||||
|
||||
c->offset = 0;
|
||||
|
||||
int fSocket = LDN::bindServer(&comm->serverAddr);
|
||||
if (fSocket < 0)
|
||||
return;
|
||||
|
||||
t->status->setStatus("LDNStatus");
|
||||
LDN::LDNfcopyArgs thrdArgs;
|
||||
thrdArgs.dst = fullPath;
|
||||
thrdArgs.filesize = filesize;
|
||||
|
||||
uint8_t *buff = new uint8_t[BUFF_SIZE];
|
||||
std::vector<uint8_t> transferBuffer;
|
||||
Thread writeThread;
|
||||
threadCreate(&writeThread, copyRemoteFileWrite_t, &thrdArgs, NULL, 0x40000,
|
||||
0x2E, 2);
|
||||
threadStart(&writeThread);
|
||||
size_t readIn = 0;
|
||||
uint64_t readCount = 0;
|
||||
while ((readIn = recv(fSocket, buff, BUFF_SIZE, 0)) > 0) {
|
||||
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
|
||||
readCount += readIn;
|
||||
|
||||
if (c)
|
||||
c->offset = readCount;
|
||||
|
||||
if (transferBuffer.size() >= TRANSFER_BUFFER_LIMIT ||
|
||||
readCount == filesize) {
|
||||
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
|
||||
thrdArgs.cond.wait(
|
||||
buffLock, [&thrdArgs] { return thrdArgs.bufferIsFull == false; });
|
||||
thrdArgs.sharedBuffer.assign(transferBuffer.begin(),
|
||||
transferBuffer.end());
|
||||
transferBuffer.clear();
|
||||
thrdArgs.bufferIsFull = true;
|
||||
buffLock.unlock();
|
||||
thrdArgs.cond.notify_one();
|
||||
}
|
||||
}
|
||||
threadWaitForExit(&writeThread);
|
||||
threadClose(&writeThread);
|
||||
|
||||
t->status->setStatus("LDNStatus");
|
||||
|
||||
unzFile unz = unzOpen64(fullPath.c_str());
|
||||
if(unz && fs::zipNotEmpty(unz)) {
|
||||
t->status->setStatus("threadStatusCalculatingSaveSize");
|
||||
uint64_t saveSize = fs::getZipTotalSize(unz);
|
||||
int64_t availSize = 0;
|
||||
fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &availSize);
|
||||
if ((int)saveSize > availSize) {
|
||||
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
|
||||
fs::unmountSave();
|
||||
fs::extendSaveData(utinfo, saveSize + 0x500000, t);
|
||||
fs::mountSave(utinfo->saveInfo);
|
||||
}
|
||||
|
||||
fs::delDir("sv:/");
|
||||
fs::commitToDevice("sv");
|
||||
// fs::copyZipToDir(unz, "sv:/", "sv", t);
|
||||
}
|
||||
|
||||
// if (unz)
|
||||
// unzClose(unz);
|
||||
close(fSocket);
|
||||
|
||||
delete[] buff;
|
||||
fs::delfile(fullPath);
|
||||
|
||||
LDN::waitForDONE(comm->commFD);
|
||||
LDN::sendDONE(comm->commFD);
|
||||
}
|
||||
|
||||
void LDN::copyRemoteSaveFile(threadInfo *t) {
|
||||
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
|
||||
LDN::LDNCommunicate *comm = (LDN::LDNCommunicate *)c->comm;
|
||||
LDN::commMeta meta;
|
||||
|
||||
t->status->setStatus("LDNStatus");
|
||||
reciveMeta(&meta, comm->commFD);
|
||||
if (meta.type == UPDATE_ABORT)
|
||||
return;
|
||||
|
||||
if (meta.type == UPDATE_FILE) {
|
||||
copyRemoteSaveFileCommit(&meta, t);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
#include <switch.h>
|
||||
#include "threads.h"
|
||||
#include "util.h"
|
||||
|
||||
static threads::threadProcMngr *threadMngr;
|
||||
|
||||
threads::threadProcMngr::~threadProcMngr()
|
||||
{
|
||||
for(threadInfo *t : threads)
|
||||
{
|
||||
threadWaitForExit(&t->thrd);
|
||||
threadClose(&t->thrd);
|
||||
delete t->status;
|
||||
delete t;
|
||||
}
|
||||
}
|
||||
|
||||
threadInfo *threads::threadProcMngr::newThread(ThreadFunc func, void *args, funcPtr _drawfunc)
|
||||
{
|
||||
threadInfo *t = new threadInfo;
|
||||
t->status = new threadStatus;
|
||||
t->running = false;
|
||||
t->finished = false;
|
||||
t->thrdFunc = func;
|
||||
t->drawFunc = _drawfunc;
|
||||
t->argPtr = args;
|
||||
|
||||
mutexLock(&threadLock);
|
||||
threads.push_back(t);
|
||||
mutexUnlock(&threadLock);
|
||||
return threads[threads.size() - 1];
|
||||
}
|
||||
|
||||
void threads::threadProcMngr::update()
|
||||
{
|
||||
if(!threads.empty())
|
||||
{
|
||||
Result res = 0;
|
||||
threadInfo *t = threads[0];
|
||||
if(!t->running && R_SUCCEEDED((res = threadCreate(&t->thrd, t->thrdFunc, t, NULL, 0x80000, 0x2B, 1))))
|
||||
{
|
||||
threadStart(&t->thrd);
|
||||
t->running = true;
|
||||
}
|
||||
else if(!t->running && R_FAILED(res))//Should kill the thread that failed.
|
||||
t->finished = true;
|
||||
else if(t->finished)
|
||||
{
|
||||
threadWaitForExit(&t->thrd);
|
||||
threadClose(&t->thrd);
|
||||
delete t->status;
|
||||
delete t;
|
||||
mutexLock(&threadLock);
|
||||
threads.erase(threads.begin());
|
||||
mutexUnlock(&threadLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
threadInfo *threads::newThread(ThreadFunc func, void *args, funcPtr _drawFunc)
|
||||
{
|
||||
return threadMngr->newThread(func, args, _drawFunc);
|
||||
}
|
||||
333
source/title.cpp
Normal file
333
source/title.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* This file is part of Checkpoint
|
||||
* Copyright (C) 2017-2021 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"
|
||||
#include "main.hpp"
|
||||
|
||||
static std::unordered_map<AccountUid, std::vector<Title>> titles;
|
||||
|
||||
void Title::init(u8 saveDataType, u64 id, AccountUid userID, const std::string& name, const std::string& author)
|
||||
{
|
||||
mId = id;
|
||||
mUserId = userID;
|
||||
mSaveDataType = saveDataType;
|
||||
mUserName = Account::username(userID);
|
||||
mAuthor = author;
|
||||
mName = name;
|
||||
mSafeName = StringUtils::containsInvalidChar(name) ? StringUtils::format("0x%016llX", mId) : StringUtils::removeForbiddenCharacters(name);
|
||||
mPath = "sdmc:/switch/NXST/saves/" + StringUtils::format("0x%016llX", mId) + " " + mSafeName;
|
||||
|
||||
std::string aname = StringUtils::removeAccents(mName);
|
||||
size_t pos = aname.rfind(":");
|
||||
mDisplayName = std::make_pair(aname, "");
|
||||
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.rfind("(");
|
||||
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 - 1 - pos1);
|
||||
StringUtils::trim(name1);
|
||||
StringUtils::trim(name2);
|
||||
mDisplayName.first = name1;
|
||||
mDisplayName.second = name2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!io::directoryExists(mPath)) {
|
||||
io::createDirectory(mPath);
|
||||
}
|
||||
|
||||
refreshDirectories();
|
||||
}
|
||||
|
||||
u8 Title::saveDataType(void)
|
||||
{
|
||||
return mSaveDataType;
|
||||
}
|
||||
|
||||
u64 Title::id(void)
|
||||
{
|
||||
return mId;
|
||||
}
|
||||
|
||||
u64 Title::saveId(void)
|
||||
{
|
||||
return mSaveId;
|
||||
}
|
||||
|
||||
void Title::saveId(u64 saveId)
|
||||
{
|
||||
mSaveId = saveId;
|
||||
}
|
||||
|
||||
AccountUid Title::userId(void)
|
||||
{
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
std::string Title::userName(void)
|
||||
{
|
||||
return mUserName;
|
||||
}
|
||||
|
||||
std::string Title::author(void)
|
||||
{
|
||||
return mAuthor;
|
||||
}
|
||||
|
||||
std::string Title::name(void)
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> Title::displayName(void)
|
||||
{
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
std::string Title::path(void)
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
std::string Title::fullPath(size_t index)
|
||||
{
|
||||
return mFullSavePaths.at(index);
|
||||
}
|
||||
|
||||
std::vector<std::string> Title::saves()
|
||||
{
|
||||
return mSaves;
|
||||
}
|
||||
|
||||
u64 Title::playTimeNanoseconds(void)
|
||||
{
|
||||
return mPlayTimeNanoseconds;
|
||||
}
|
||||
|
||||
std::string Title::playTime(void)
|
||||
{
|
||||
const u64 playTimeMinutes = mPlayTimeNanoseconds / 60000000000;
|
||||
return StringUtils::format("%d", playTimeMinutes / 60) + ":" + StringUtils::format("%02d", playTimeMinutes % 60) + " hours";
|
||||
}
|
||||
|
||||
void Title::playTimeNanoseconds(u64 playTimeNanoseconds)
|
||||
{
|
||||
mPlayTimeNanoseconds = playTimeNanoseconds;
|
||||
}
|
||||
|
||||
u32 Title::lastPlayedTimestamp(void)
|
||||
{
|
||||
return mLastPlayedTimestamp;
|
||||
}
|
||||
|
||||
void Title::lastPlayedTimestamp(u32 lastPlayedTimestamp)
|
||||
{
|
||||
mLastPlayedTimestamp = lastPlayedTimestamp;
|
||||
}
|
||||
|
||||
void Title::refreshDirectories(void)
|
||||
{
|
||||
mSaves.clear();
|
||||
mFullSavePaths.clear();
|
||||
|
||||
Directory savelist(mPath);
|
||||
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(mPath + "/" + savelist.entry(i));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(mSaves.rbegin(), mSaves.rend());
|
||||
std::sort(mFullSavePaths.rbegin(), mFullSavePaths.rend());
|
||||
mSaves.insert(mSaves.begin(), g_emptySave);
|
||||
mFullSavePaths.insert(mFullSavePaths.begin(), g_emptySave);
|
||||
}
|
||||
else {
|
||||
Logger::getInstance().log(Logger::ERROR, "Couldn't retrieve the extdata directory list for the title " + name());
|
||||
}
|
||||
|
||||
// save backups from configuration
|
||||
// std::vector<std::string> additionalFolders = Configuration::getInstance().additionalSaveFolders(mId);
|
||||
// for (std::vector<std::string>::const_iterator it = additionalFolders.begin(); it != additionalFolders.end(); ++it) {
|
||||
// we have other folders to parse
|
||||
// Directory list(*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 + "/" + list.entry(i));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void loadTitles(void)
|
||||
{
|
||||
titles.clear();
|
||||
|
||||
FsSaveDataInfoReader reader;
|
||||
FsSaveDataInfo info;
|
||||
s64 total_entries = 0;
|
||||
size_t outsize = 0;
|
||||
|
||||
NacpLanguageEntry* nle = NULL;
|
||||
NsApplicationControlData* nsacd = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData));
|
||||
if (nsacd == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(nsacd, 0, sizeof(NsApplicationControlData));
|
||||
|
||||
Result res = fsOpenSaveDataInfoReader(&reader, FsSaveDataSpaceId_User);
|
||||
if (R_FAILED(res)) {
|
||||
free(nsacd);
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
res = fsSaveDataInfoReaderRead(&reader, &info, 1, &total_entries);
|
||||
if (R_FAILED(res) || total_entries == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (info.save_data_type == FsSaveDataType_Account) {
|
||||
u64 tid = info.application_id;
|
||||
u64 sid = info.save_data_id;
|
||||
AccountUid uid = info.uid;
|
||||
// if (mFilterIds.find(tid) == mFilterIds.end()) {
|
||||
res = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, nsacd, sizeof(NsApplicationControlData), &outsize);
|
||||
if (R_SUCCEEDED(res) && !(outsize < sizeof(nsacd->nacp))) {
|
||||
res = nacpGetLanguageEntry(&nsacd->nacp, &nle);
|
||||
if (R_SUCCEEDED(res) && nle != NULL) {
|
||||
Title title;
|
||||
title.init(info.save_data_type, tid, uid, std::string(nle->name), std::string(nle->author));
|
||||
title.saveId(sid);
|
||||
|
||||
// load play statistics
|
||||
PdmPlayStatistics stats;
|
||||
res = pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(tid, uid, false, &stats);
|
||||
if (R_SUCCEEDED(res)) {
|
||||
title.playTimeNanoseconds(stats.playtime);
|
||||
title.lastPlayedTimestamp(stats.last_timestamp_user);
|
||||
}
|
||||
|
||||
// loadIcon(tid, nsacd, outsize - sizeof(nsacd->nacp));
|
||||
|
||||
// check if the vector is already created
|
||||
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
|
||||
if (it != titles.end()) {
|
||||
// found
|
||||
it->second.push_back(title);
|
||||
}
|
||||
else {
|
||||
// not found, insert into map
|
||||
std::vector<Title> v;
|
||||
v.push_back(title);
|
||||
titles.emplace(uid, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
nle = NULL;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
free(nsacd);
|
||||
fsSaveDataInfoReaderClose(&reader);
|
||||
|
||||
sortTitles();
|
||||
}
|
||||
|
||||
void sortTitles(void)
|
||||
{
|
||||
for (auto& vect : titles) {
|
||||
std::sort(vect.second.begin(), vect.second.end(), [](Title& l, Title& r) {
|
||||
switch (g_sortMode) {
|
||||
case SORT_LAST_PLAYED:
|
||||
return l.lastPlayedTimestamp() > r.lastPlayedTimestamp();
|
||||
case SORT_PLAY_TIME:
|
||||
return l.playTimeNanoseconds() > r.playTimeNanoseconds();
|
||||
case SORT_ALPHA:
|
||||
default:
|
||||
return l.name() < r.name();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void rotateSortMode(void)
|
||||
{
|
||||
g_sortMode = static_cast<sort_t>((g_sortMode + 1) % SORT_MODES_COUNT);
|
||||
sortTitles();
|
||||
}
|
||||
|
||||
void getTitle(Title& dst, AccountUid uid, size_t i)
|
||||
{
|
||||
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
|
||||
if (it != titles.end() && i < getTitleCount(uid)) {
|
||||
dst = it->second.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
size_t getTitleCount(AccountUid uid)
|
||||
{
|
||||
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
|
||||
return it != titles.end() ? it->second.size() : 0;
|
||||
}
|
||||
|
||||
void refreshDirectories(u64 id)
|
||||
{
|
||||
for (auto& pair : titles) {
|
||||
for (size_t i = 0; i < pair.second.size(); i++) {
|
||||
if (pair.second.at(i).id() == id) {
|
||||
pair.second.at(i).refreshDirectories();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> getCompleteTitleList(void)
|
||||
{
|
||||
std::unordered_map<std::string, std::string> map;
|
||||
for (const auto& pair : titles) {
|
||||
for (auto value : pair.second) {
|
||||
map.insert({StringUtils::format("0x%016llX", value.id()), value.name()});
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
444
source/util.cpp
444
source/util.cpp
@@ -1,351 +1,169 @@
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <sys/stat.h>
|
||||
#include <json-c/json.h>
|
||||
/*
|
||||
* This file is part of Checkpoint
|
||||
* Copyright (C) 2017-2021 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 "fs/file.h"
|
||||
#include "data.h"
|
||||
#include "util.h"
|
||||
#include "type.h"
|
||||
#include "util.hpp"
|
||||
#include <logger.hpp>
|
||||
#include <MainApplication.hpp>
|
||||
#include "main.hpp"
|
||||
|
||||
static const uint32_t verboten[] = { L',', L'/', L'\\', L'<', L'>', L':', L'"', L'|', L'?', L'*', L'™', L'©', L'®'};
|
||||
|
||||
static bool isVerboten(const uint32_t& t)
|
||||
void servicesExit(void)
|
||||
{
|
||||
for(unsigned i = 0; i < 13; i++)
|
||||
{
|
||||
if(t == verboten[i])
|
||||
return true;
|
||||
Logger::getInstance().flush();
|
||||
pdmqryExit();
|
||||
socketExit();
|
||||
Account::exit();
|
||||
nsExit();
|
||||
plExit();
|
||||
romfsExit();
|
||||
}
|
||||
|
||||
Result servicesInit(void)
|
||||
{
|
||||
io::createDirectory("sdmc:/switch");
|
||||
io::createDirectory("sdmc:/switch/NXST");
|
||||
io::createDirectory("sdmc:/switch/NXST/saves");
|
||||
|
||||
Logger::getInstance().log(Logger::INFO, "Starting Checkpoint loading...");
|
||||
|
||||
if (appletGetAppletType() != AppletType_Application) {
|
||||
Logger::getInstance().log(Logger::WARN, "Please do not run Checkpoint in applet mode.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// Result socinit = 0;
|
||||
// if ((socinit = socketInitializeDefault()) == 0) {
|
||||
// nxlinkStdio();
|
||||
// }
|
||||
// else {
|
||||
// Logger::getInstance().log(Logger::INFO, "Unable to initialize socket. Result code 0x%08lX.", socinit);
|
||||
// }
|
||||
|
||||
void util::replaceStr(std::string& _str, const std::string& _find, const std::string& _rep)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while((pos = _str.find(_find)) != _str.npos)
|
||||
_str.replace(pos, _find.length(), _rep);
|
||||
}
|
||||
// g_shouldExitNetworkLoop = R_FAILED(socinit);
|
||||
|
||||
//Used to split version tag git
|
||||
static void getVersionFromTag(const std::string& tag, unsigned& _year, unsigned& _month, unsigned& _day)
|
||||
{
|
||||
_month = strtoul(tag.substr(0, 2).c_str(), NULL, 10);
|
||||
_day = strtoul(tag.substr(3, 5).c_str(), NULL, 10);
|
||||
_year = strtoul(tag.substr(6, 10).c_str(), NULL, 10);
|
||||
}
|
||||
Result res = 0;
|
||||
|
||||
//Missing swkbd config funcs for now
|
||||
typedef enum
|
||||
{
|
||||
SwkbdPosStart = 0,
|
||||
SwkbdPosEnd = 1
|
||||
} SwkbdInitPos;
|
||||
romfsInit();
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t read[0x32 / sizeof(uint16_t)];
|
||||
uint16_t word[0x32 / sizeof(uint16_t)];
|
||||
} dictWord;
|
||||
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
||||
hidInitializeTouchScreen();
|
||||
|
||||
void swkbdDictWordCreate(dictWord *w, const char *read, const char *word)
|
||||
{
|
||||
memset(w, 0, sizeof(*w));
|
||||
|
||||
utf8_to_utf16(w->read, (uint8_t *)read, (sizeof(w->read) / sizeof(uint16_t)) - 1);
|
||||
utf8_to_utf16(w->word, (uint8_t *)word, (sizeof(w->word) / sizeof(uint16_t)) - 1);
|
||||
}
|
||||
|
||||
uint32_t replaceChar(uint32_t c)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case L'é':
|
||||
return 'e';
|
||||
break;
|
||||
if (R_FAILED(res = plInitialize(PlServiceType_User))) {
|
||||
Logger::getInstance().log(Logger::ERROR, "plInitialize failed. Result code 0x%08lX.", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline void replaceCharCStr(char *_s, char _find, char _rep)
|
||||
{
|
||||
size_t strLength = strlen(_s);
|
||||
for(unsigned i = 0; i < strLength; i++)
|
||||
{
|
||||
if(_s[i] == _find)
|
||||
_s[i] = _rep;
|
||||
}
|
||||
}
|
||||
|
||||
std::string util::getDateTime(int fmt)
|
||||
{
|
||||
char ret[128];
|
||||
|
||||
time_t raw;
|
||||
time(&raw);
|
||||
tm *Time = localtime(&raw);
|
||||
|
||||
switch(fmt)
|
||||
{
|
||||
case DATE_FMT_YMD:
|
||||
sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min, Time->tm_sec);
|
||||
break;
|
||||
|
||||
case DATE_FMT_YDM:
|
||||
sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mday, Time->tm_mon + 1, Time->tm_hour, Time->tm_min, Time->tm_sec);
|
||||
break;
|
||||
|
||||
case DATE_FMT_HOYSTE:
|
||||
sprintf(ret, "%02d.%02d.%04d", Time->tm_mday, Time->tm_mon + 1, Time->tm_year + 1900);
|
||||
break;
|
||||
|
||||
case DATE_FMT_JHK:
|
||||
sprintf(ret, "%04d%02d%02d_%02d%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min);
|
||||
break;
|
||||
|
||||
case DATE_FMT_ASC:
|
||||
strcpy(ret, asctime(Time));
|
||||
replaceCharCStr(ret, ':', '_');
|
||||
replaceCharCStr(ret, '\n', 0x00);
|
||||
break;
|
||||
if (R_FAILED(res = Account::init())) {
|
||||
Logger::getInstance().log(Logger::ERROR, "Account::init failed. Result code 0x%08lX.", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
return std::string(ret);
|
||||
}
|
||||
|
||||
//void util::copyDirListToMenu(const fs::dirList& d, ui::menu& m)
|
||||
//{
|
||||
// m.reset();
|
||||
// m.addOpt(NULL, ".");
|
||||
// m.addOpt(NULL, "..");
|
||||
// for(unsigned i = 0; i < d.getCount(); i++)
|
||||
// {
|
||||
// if(d.isDir(i))
|
||||
// m.addOpt(NULL, "D " + d.getItem(i));
|
||||
// else
|
||||
// m.addOpt(NULL, "F " + d.getItem(i));
|
||||
// }
|
||||
//}
|
||||
|
||||
void util::removeLastFolderFromString(std::string& _path)
|
||||
{
|
||||
unsigned last = _path.find_last_of('/', _path.length() - 2);
|
||||
_path.erase(last + 1, _path.length());
|
||||
}
|
||||
|
||||
size_t util::getTotalPlacesInPath(const std::string& _path)
|
||||
{
|
||||
//Skip device
|
||||
size_t pos = _path.find_first_of('/'), ret = 0;
|
||||
while((pos = _path.find_first_of('/', ++pos)) != _path.npos)
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void util::trimPath(std::string& _path, uint8_t _places)
|
||||
{
|
||||
size_t pos = _path.find_first_of('/');
|
||||
for(int i = 0; i < _places; i++)
|
||||
pos = _path.find_first_of('/', ++pos);
|
||||
_path = _path.substr(++pos, _path.npos);
|
||||
}
|
||||
|
||||
std::string util::safeString(const std::string& s)
|
||||
{
|
||||
std::string ret = "";
|
||||
for(unsigned i = 0; i < s.length(); )
|
||||
{
|
||||
uint32_t tmpChr = 0;
|
||||
ssize_t untCnt = decode_utf8(&tmpChr, (uint8_t *)&s.data()[i]);
|
||||
|
||||
i += untCnt;
|
||||
|
||||
tmpChr = replaceChar(tmpChr);
|
||||
|
||||
if(isVerboten(tmpChr))
|
||||
ret += ' ';
|
||||
else if(!isASCII(tmpChr))
|
||||
return ""; //return empty string so titledata::init defaults to titleID
|
||||
else
|
||||
ret += (char)tmpChr;
|
||||
if (R_FAILED(res = nsInitialize())) {
|
||||
Logger::getInstance().log(Logger::ERROR, "nsInitialize failed. Result code 0x%08lX.", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
//Check for spaces at end
|
||||
while(ret[ret.length() - 1] == ' ' || ret[ret.length() - 1] == '.')
|
||||
ret.erase(ret.length() - 1, 1);
|
||||
// Configuration::getInstance();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline std::string getTimeString(const uint32_t& _h, const uint32_t& _m)
|
||||
{
|
||||
char tmp[32];
|
||||
sprintf(tmp, "%02d:%02d", _h, _m);
|
||||
return std::string(tmp);
|
||||
}
|
||||
|
||||
std::string util::getStringInput(SwkbdType _type, const std::string& def, const std::string& head, size_t maxLength, unsigned dictCnt, const std::string dictWords[])
|
||||
{
|
||||
SwkbdConfig swkbd;
|
||||
swkbdCreate(&swkbd, dictCnt);
|
||||
swkbdConfigSetBlurBackground(&swkbd, true);
|
||||
swkbdConfigSetInitialText(&swkbd, def.c_str());
|
||||
swkbdConfigSetHeaderText(&swkbd, head.c_str());
|
||||
swkbdConfigSetGuideText(&swkbd, head.c_str());
|
||||
swkbdConfigSetInitialCursorPos(&swkbd, SwkbdPosEnd);
|
||||
swkbdConfigSetType(&swkbd, _type);
|
||||
swkbdConfigSetStringLenMax(&swkbd, maxLength);
|
||||
swkbdConfigSetKeySetDisableBitmask(&swkbd, SwkbdKeyDisableBitmask_Backslash | SwkbdKeyDisableBitmask_Percent);
|
||||
swkbdConfigSetDicFlag(&swkbd, 1);
|
||||
|
||||
if(dictCnt > 0)
|
||||
{
|
||||
dictWord words[dictCnt];
|
||||
for(unsigned i = 0; i < dictCnt; i++)
|
||||
swkbdDictWordCreate(&words[i], dictWords[i].c_str(), dictWords[i].c_str());
|
||||
|
||||
swkbdConfigSetDictionary(&swkbd, (SwkbdDictWord *)words, dictCnt);
|
||||
if (R_SUCCEEDED(res = pdmqryInitialize())) {}
|
||||
else {
|
||||
Logger::getInstance().log(Logger::WARN, "pdmqryInitialize failed with result 0x%08lX.", res);
|
||||
}
|
||||
|
||||
char out[maxLength + 1];
|
||||
memset(out, 0, maxLength + 1);
|
||||
swkbdShow(&swkbd, out, maxLength + 1);
|
||||
swkbdClose(&swkbd);
|
||||
Logger::getInstance().log(Logger::INFO, "NXST loading completed!");
|
||||
|
||||
return std::string(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string util::getExtensionFromString(const std::string& get)
|
||||
std::u16string StringUtils::UTF8toUTF16(const char* src)
|
||||
{
|
||||
size_t ext = get.find_last_of('.');
|
||||
if(ext != get.npos)
|
||||
return get.substr(ext + 1, get.npos);
|
||||
else
|
||||
return "";
|
||||
char16_t tmp[256] = {0};
|
||||
utf8_to_utf16((uint16_t*)tmp, (uint8_t*)src, 256);
|
||||
return std::u16string(tmp);
|
||||
}
|
||||
|
||||
std::string util::getFilenameFromPath(const std::string& get)
|
||||
// https://stackoverflow.com/questions/14094621/change-all-accented-letters-to-normal-letters-in-c
|
||||
std::string StringUtils::removeAccents(std::string str)
|
||||
{
|
||||
size_t nameStart = get.find_last_of('/');
|
||||
if(nameStart != get.npos)
|
||||
return get.substr(nameStart + 1, get.npos);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
std::u16string src = UTF8toUTF16(str.c_str());
|
||||
const std::u16string illegal = UTF8toUTF16("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüūýþÿ");
|
||||
const std::u16string fixed = UTF8toUTF16("AAAAAAECEEEEIIIIDNOOOOOx0UUUUYPsaaaaaaeceeeeiiiiOnooooo/0uuuuuypy");
|
||||
|
||||
std::string util::generateAbbrev(const uint64_t& tid)
|
||||
{
|
||||
data::titleInfo *tmp = data::getTitleInfoByTID(tid);
|
||||
size_t titleLength = tmp->safeTitle.length();
|
||||
|
||||
char temp[titleLength + 1];
|
||||
memset(temp, 0, titleLength + 1);
|
||||
memcpy(temp, tmp->safeTitle.c_str(), titleLength);
|
||||
|
||||
std::string ret;
|
||||
char *tok = strtok(temp, " ");
|
||||
while(tok)
|
||||
{
|
||||
if(isASCII(tok[0]))
|
||||
ret += tok[0];
|
||||
tok = strtok(NULL, " ");
|
||||
for (size_t i = 0, sz = src.length(); i < sz; i++) {
|
||||
size_t index = illegal.find(src[i]);
|
||||
if (index != std::string::npos) {
|
||||
src[i] = fixed[index];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
return UTF16toUTF8(src);
|
||||
}
|
||||
|
||||
void util::stripChar(char _c, std::string& _s)
|
||||
std::string StringUtils::removeNotAscii(std::string str)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while((pos = _s.find(_c)) != _s.npos)
|
||||
_s.erase(pos, 1);
|
||||
for (size_t i = 0, sz = str.length(); i < sz; i++) {
|
||||
if (!isascii(str[i])) {
|
||||
str[i] = ' ';
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void util::replaceButtonsInString(std::string& rep)
|
||||
HidsysNotificationLedPattern blinkLedPattern(u8 times)
|
||||
{
|
||||
replaceStr(rep, "[A]", "\ue0e0");
|
||||
replaceStr(rep, "[B]", "\ue0e1");
|
||||
replaceStr(rep, "[X]", "\ue0e2");
|
||||
replaceStr(rep, "[Y]", "\ue0e3");
|
||||
replaceStr(rep, "[L]", "\ue0e4");
|
||||
replaceStr(rep, "[R]", "\ue0e5");
|
||||
replaceStr(rep, "[ZL]", "\ue0e6");
|
||||
replaceStr(rep, "[ZR]", "\ue0e7");
|
||||
replaceStr(rep, "[SL]", "\ue0e8");
|
||||
replaceStr(rep, "[SR]", "\ue0e9");
|
||||
replaceStr(rep, "[DPAD]", "\ue0ea");
|
||||
replaceStr(rep, "[DUP]", "\ue0eb");
|
||||
replaceStr(rep, "[DDOWN]", "\ue0ec");
|
||||
replaceStr(rep, "[DLEFT]", "\ue0ed");
|
||||
replaceStr(rep, "[DRIGHT]", "\ue0ee");
|
||||
replaceStr(rep, "[+]", "\ue0ef");
|
||||
replaceStr(rep, "[-]", "\ue0f0");
|
||||
HidsysNotificationLedPattern pattern;
|
||||
memset(&pattern, 0, sizeof(pattern));
|
||||
|
||||
pattern.baseMiniCycleDuration = 0x1; // 12.5ms.
|
||||
pattern.totalMiniCycles = 0x2; // 2 mini cycles.
|
||||
pattern.totalFullCycles = times; // Repeat n times.
|
||||
pattern.startIntensity = 0x0; // 0%.
|
||||
|
||||
pattern.miniCycles[0].ledIntensity = 0xF; // 100%.
|
||||
pattern.miniCycles[0].transitionSteps = 0xF; // 15 steps. Total 187.5ms.
|
||||
pattern.miniCycles[0].finalStepDuration = 0x0; // Forced 12.5ms.
|
||||
pattern.miniCycles[1].ledIntensity = 0x0; // 0%.
|
||||
pattern.miniCycles[1].transitionSteps = 0xF; // 15 steps. Total 187.5ms.
|
||||
pattern.miniCycles[1].finalStepDuration = 0x0; // Forced 12.5ms.
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
void util::sysBoost()
|
||||
void blinkLed(u8 times)
|
||||
{
|
||||
if(R_FAILED(clkrstInitialize()))
|
||||
return;
|
||||
|
||||
ClkrstSession cpu, gpu, ram;
|
||||
clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3);
|
||||
clkrstOpenSession(&gpu, PcvModuleId_GPU, 3);
|
||||
clkrstOpenSession(&ram, PcvModuleId_EMC, 3);
|
||||
|
||||
clkrstSetClockRate(&cpu, util::CPU_SPEED_1785MHz);
|
||||
clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz);
|
||||
clkrstSetClockRate(&ram, util::RAM_SPEED_1600MHz);
|
||||
|
||||
clkrstCloseSession(&cpu);
|
||||
clkrstCloseSession(&gpu);
|
||||
clkrstCloseSession(&ram);
|
||||
clkrstExit();
|
||||
}
|
||||
|
||||
void util::sysNormal()
|
||||
{
|
||||
if(R_FAILED(clkrstInitialize()))
|
||||
return;
|
||||
|
||||
ClkrstSession cpu, gpu, ram;
|
||||
clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3);
|
||||
clkrstOpenSession(&gpu, PcvModuleId_GPU, 3);
|
||||
clkrstOpenSession(&ram, PcvModuleId_EMC, 3);
|
||||
|
||||
clkrstSetClockRate(&cpu, util::CPU_SPEED_1020MHz);
|
||||
clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz);
|
||||
clkrstSetClockRate(&ram, util::RAM_SPEED_1331MHz);
|
||||
|
||||
clkrstCloseSession(&cpu);
|
||||
clkrstCloseSession(&gpu);
|
||||
clkrstCloseSession(&ram);
|
||||
clkrstExit();
|
||||
|
||||
}
|
||||
|
||||
std::string util::getSizeString(const uint64_t& _size)
|
||||
{
|
||||
char sizeStr[32];
|
||||
if(_size >= 0x40000000)
|
||||
sprintf(sizeStr, "%.2fGB", (float)_size / 1024.0f / 1024.0f / 1024.0f);
|
||||
else if(_size >= 0x100000)
|
||||
sprintf(sizeStr, "%.2fMB", (float)_size / 1024.0f / 1024.0f);
|
||||
else if(_size >= 0x400)
|
||||
sprintf(sizeStr, "%.2fKB", (float)_size / 1024.0f);
|
||||
else
|
||||
sprintf(sizeStr, "%lu Bytes", _size);
|
||||
return std::string(sizeStr);
|
||||
}
|
||||
|
||||
Result util::accountDeleteUser(AccountUid *uid)
|
||||
{
|
||||
Service *account = accountGetServiceSession();
|
||||
struct
|
||||
{
|
||||
AccountUid uid;
|
||||
} in = {*uid};
|
||||
|
||||
return serviceDispatchIn(account, 203, in);
|
||||
if (g_notificationLedAvailable) {
|
||||
PadState pad;
|
||||
padInitializeDefault(&pad);
|
||||
s32 n;
|
||||
HidsysUniquePadId uniquePadIds[2] = {0};
|
||||
HidsysNotificationLedPattern pattern = blinkLedPattern(times);
|
||||
memset(uniquePadIds, 0, sizeof(uniquePadIds));
|
||||
Result res = hidsysGetUniquePadsFromNpad(padIsHandheld(&pad) ? HidNpadIdType_Handheld : HidNpadIdType_No1, uniquePadIds, 2, &n);
|
||||
if (R_SUCCEEDED(res)) {
|
||||
for (s32 i = 0; i < n; i++) {
|
||||
hidsysSetNotificationLedPattern(&pattern, uniquePadIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user