save: support for mounting save fs, add LRU cache for fatfs. add mounting nro romfs.
This commit is contained in:
@@ -90,6 +90,10 @@ add_executable(sphaira
|
|||||||
source/minizip_helper.cpp
|
source/minizip_helper.cpp
|
||||||
source/fatfs.cpp
|
source/fatfs.cpp
|
||||||
|
|
||||||
|
source/utils/devoptab_save.cpp
|
||||||
|
# todo:
|
||||||
|
# source/utils/devoptab_zip.cpp
|
||||||
|
|
||||||
source/usb/base.cpp
|
source/usb/base.cpp
|
||||||
source/usb/usbds.cpp
|
source/usb/usbds.cpp
|
||||||
source/usb/usbhs.cpp
|
source/usb/usbhs.cpp
|
||||||
@@ -108,7 +112,9 @@ add_executable(sphaira
|
|||||||
source/yati/nx/nca.cpp
|
source/yati/nx/nca.cpp
|
||||||
source/yati/nx/ncm.cpp
|
source/yati/nx/ncm.cpp
|
||||||
source/yati/nx/ns.cpp
|
source/yati/nx/ns.cpp
|
||||||
|
|
||||||
source/yati/nx/nxdumptool_rsa.c
|
source/yati/nx/nxdumptool_rsa.c
|
||||||
|
source/yati/nx/nxdumptool/save.c
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(sphaira PRIVATE
|
target_compile_definitions(sphaira PRIVATE
|
||||||
@@ -397,6 +403,7 @@ target_include_directories(sphaira PRIVATE
|
|||||||
include
|
include
|
||||||
${minizip_inc}
|
${minizip_inc}
|
||||||
${mbedtls_inc}
|
${mbedtls_inc}
|
||||||
|
include/yati/nx/nxdumptool
|
||||||
)
|
)
|
||||||
|
|
||||||
# copy the romfs
|
# copy the romfs
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ struct NroEntry {
|
|||||||
u64 icon_size{};
|
u64 icon_size{};
|
||||||
u64 icon_offset{};
|
u64 icon_offset{};
|
||||||
|
|
||||||
|
u64 romfs_size{};
|
||||||
|
u64 romfs_offset{};
|
||||||
|
|
||||||
FsTimeStampRaw timestamp{};
|
FsTimeStampRaw timestamp{};
|
||||||
Hbini hbini{};
|
Hbini hbini{};
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ private:
|
|||||||
return m_sort.Get() >= SortType_UpdatedStar;
|
return m_sort.Get() >= SortType_UpdatedStar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result MountRomfsFs();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||||
|
|
||||||
|
|||||||
24
sphaira/include/utils/devoptab.hpp
Normal file
24
sphaira/include/utils/devoptab.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include "fs.hpp"
|
||||||
|
|
||||||
|
namespace sphaira::devoptab {
|
||||||
|
|
||||||
|
// mounts to "lower_case_hex_id:/"
|
||||||
|
Result MountFromSavePath(u64 id, fs::FsPath& out_path);
|
||||||
|
void UnmountSave(u64 id);
|
||||||
|
|
||||||
|
// todo:
|
||||||
|
void MountZip(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
||||||
|
void UmountZip(const fs::FsPath& mount);
|
||||||
|
|
||||||
|
// todo:
|
||||||
|
void MountNsp(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
||||||
|
void UmountNsp(const fs::FsPath& mount);
|
||||||
|
|
||||||
|
// todo:
|
||||||
|
void MountXci(fs::Fs* fs, const fs::FsPath& mount, fs::FsPath& out_path);
|
||||||
|
void UmountXci(const fs::FsPath& mount);
|
||||||
|
|
||||||
|
} // namespace sphaira::devoptab
|
||||||
63
sphaira/include/yati/nx/nxdumptool/core/nxdt_includes.h
Normal file
63
sphaira/include/yati/nx/nxdumptool/core/nxdt_includes.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* nxdt_includes.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||||
|
*
|
||||||
|
* nxdumptool 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.
|
||||||
|
*
|
||||||
|
* nxdumptool 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __NXDT_INCLUDES_H__
|
||||||
|
#define __NXDT_INCLUDES_H__
|
||||||
|
|
||||||
|
/* C headers. */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#else
|
||||||
|
#include <atomic>
|
||||||
|
#define _Atomic(X) std::atomic< X >
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* libnx header. */
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
/* Global defines. */
|
||||||
|
#include "../defines.h"
|
||||||
|
|
||||||
|
/* File/socket based logger. */
|
||||||
|
#include "nxdt_log.h"
|
||||||
|
|
||||||
|
#endif /* __NXDT_INCLUDES_H__ */
|
||||||
160
sphaira/include/yati/nx/nxdumptool/core/nxdt_log.h
Normal file
160
sphaira/include/yati/nx/nxdumptool/core/nxdt_log.h
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* nxdt_log.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||||
|
*
|
||||||
|
* nxdumptool 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.
|
||||||
|
*
|
||||||
|
* nxdumptool 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __NXDT_LOG_H__
|
||||||
|
#define __NXDT_LOG_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Used to control logfile verbosity.
|
||||||
|
#define LOG_LEVEL_DEBUG 0
|
||||||
|
#define LOG_LEVEL_INFO 1
|
||||||
|
#define LOG_LEVEL_WARNING 2
|
||||||
|
#define LOG_LEVEL_ERROR 3
|
||||||
|
#define LOG_LEVEL_NONE 4
|
||||||
|
|
||||||
|
/// Defines the log level used throughout the application.
|
||||||
|
/// Log messages with a log value lower than this one won't be compiled into the binary.
|
||||||
|
/// If a value lower than LOG_LEVEL_DEBUG or equal to/greater than LOG_LEVEL_NONE is used, logfile output will be entirely disabled.
|
||||||
|
#define LOG_LEVEL LOG_LEVEL_NONE /* TODO: change before release (warning?). */
|
||||||
|
|
||||||
|
#if (LOG_LEVEL >= LOG_LEVEL_DEBUG) && (LOG_LEVEL < LOG_LEVEL_NONE)
|
||||||
|
|
||||||
|
/// Helper macros.
|
||||||
|
|
||||||
|
#define LOG_MSG_GENERIC(level, fmt, ...) logWriteFormattedStringToLogFile(level, __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_MSG_BUF_GENERIC(dst, dst_size, level, fmt, ...) logWriteFormattedStringToBuffer(dst, dst_size, level, __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_DATA_GENERIC(data, data_size, level, fmt, ...) logWriteBinaryDataToLogFile(data, data_size, level, __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#if LOG_LEVEL == LOG_LEVEL_DEBUG
|
||||||
|
#define LOG_MSG_DEBUG(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_MSG_BUF_DEBUG(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_DATA_DEBUG(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_MSG_DEBUG(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_DEBUG(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_DEBUG(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
#endif /* LOG_LEVEL == LOG_LEVEL_DEBUG */
|
||||||
|
|
||||||
|
#if LOG_LEVEL <= LOG_LEVEL_INFO
|
||||||
|
#define LOG_MSG_INFO(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_MSG_BUF_INFO(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_DATA_INFO(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_MSG_INFO(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_INFO(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_INFO(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
#endif /* LOG_LEVEL <= LOG_LEVEL_INFO */
|
||||||
|
|
||||||
|
#if LOG_LEVEL <= LOG_LEVEL_WARNING
|
||||||
|
#define LOG_MSG_WARNING(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_MSG_BUF_WARNING(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_DATA_WARNING(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_MSG_WARNING(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_WARNING(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_WARNING(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
#endif /* LOG_LEVEL <= LOG_LEVEL_WARNING */
|
||||||
|
|
||||||
|
#if LOG_LEVEL <= LOG_LEVEL_ERROR
|
||||||
|
#define LOG_MSG_ERROR(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_MSG_BUF_ERROR(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_DATA_ERROR(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOG_MSG_ERROR(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_ERROR(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_ERROR(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
#endif /* LOG_LEVEL <= LOG_LEVEL_ERROR */
|
||||||
|
|
||||||
|
/// Writes the provided string to the logfile.
|
||||||
|
/// If the logfile hasn't been created and/or opened, this function takes care of it.
|
||||||
|
void logWriteStringToLogFile(const char *src);
|
||||||
|
|
||||||
|
/// Writes a formatted log string to the logfile.
|
||||||
|
/// If the logfile hasn't been created and/or opened, this function takes care of it.
|
||||||
|
__attribute__((format(printf, 5, 6))) void logWriteFormattedStringToLogFile(u8 level, const char *file_name, int line, const char *func_name, const char *fmt, ...);
|
||||||
|
|
||||||
|
/// Writes a formatted log string to the provided buffer.
|
||||||
|
/// If the buffer isn't big enough to hold both its current contents and the new formatted string, it will be resized.
|
||||||
|
__attribute__((format(printf, 7, 8))) void logWriteFormattedStringToBuffer(char **dst, size_t *dst_size, u8 level, const char *file_name, int line, const char *func_name, const char *fmt, ...);
|
||||||
|
|
||||||
|
/// Writes a formatted log string + a hex string representation of the provided binary data to the logfile.
|
||||||
|
/// If the logfile hasn't been created and/or opened, this function takes care of it.
|
||||||
|
__attribute__((format(printf, 7, 8))) void logWriteBinaryDataToLogFile(const void *data, size_t data_size, u8 level, const char *file_name, int line, const char *func_name, const char *fmt, ...);
|
||||||
|
|
||||||
|
/// Forces a flush operation on the logfile.
|
||||||
|
void logFlushLogFile(void);
|
||||||
|
|
||||||
|
/// Write any pending data to the logfile, flushes it and then closes it.
|
||||||
|
void logCloseLogFile(void);
|
||||||
|
|
||||||
|
/// Returns a pointer to a dynamically allocated buffer that holds the last error message string, or NULL if there's none.
|
||||||
|
/// The allocated buffer must be freed by the caller using free().
|
||||||
|
char *logGetLastMessage(void);
|
||||||
|
|
||||||
|
/// (Un)locks the log mutex. Can be used to block other threads and prevent them from writing data to the logfile.
|
||||||
|
/// Use with caution.
|
||||||
|
void logControlMutex(bool lock);
|
||||||
|
|
||||||
|
#else /* (LOG_LEVEL >= LOG_LEVEL_DEBUG) && (LOG_LEVEL < LOG_LEVEL_NONE) */
|
||||||
|
|
||||||
|
/// Helper macros.
|
||||||
|
|
||||||
|
#define LOG_MSG_GENERIC(level, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_GENERIC(dst, dst_size, level, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_GENERIC(data, data_size, level, fmt, ...) do {} while(0)
|
||||||
|
|
||||||
|
#define LOG_MSG_DEBUG(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_DEBUG(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_DEBUG(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
|
||||||
|
#define LOG_MSG_INFO(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_INFO(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_INFO(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
|
||||||
|
#define LOG_MSG_WARNING(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_WARNING(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_WARNING(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
|
||||||
|
#define LOG_MSG_ERROR(fmt, ...) do {} while(0)
|
||||||
|
#define LOG_MSG_BUF_ERROR(dst, dst_size, fmt, ...) do {} while(0)
|
||||||
|
#define LOG_DATA_ERROR(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
|
||||||
|
#define logWriteStringToLogFile(...) do {} while(0)
|
||||||
|
#define logWriteFormattedStringToLogFile(...) do {} while(0)
|
||||||
|
#define logWriteFormattedStringToBuffer(...) do {} while(0)
|
||||||
|
#define logWriteBinaryDataToLogFile(...) do {} while(0)
|
||||||
|
#define logFlushLogFile(...) do {} while(0)
|
||||||
|
#define logCloseLogFile(...) do {} while(0)
|
||||||
|
#define logGetLastMessage(...) NULL
|
||||||
|
#define logControlMutex(...) do {} while(0)
|
||||||
|
|
||||||
|
#endif /* (LOG_LEVEL >= LOG_LEVEL_DEBUG) && (LOG_LEVEL < LOG_LEVEL_NONE) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __NXDT_LOG_H__ */
|
||||||
560
sphaira/include/yati/nx/nxdumptool/core/save.h
Normal file
560
sphaira/include/yati/nx/nxdumptool/core/save.h
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
/*
|
||||||
|
* save.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2020, shchmue.
|
||||||
|
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||||
|
*
|
||||||
|
* nxdumptool 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.
|
||||||
|
*
|
||||||
|
* nxdumptool 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __SAVE_H__
|
||||||
|
#define __SAVE_H__
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define IVFC_MAX_LEVEL 6
|
||||||
|
|
||||||
|
#define SAVE_HEADER_SIZE 0x4000
|
||||||
|
#define SAVE_FAT_ENTRY_SIZE 8
|
||||||
|
#define SAVE_FS_LIST_MAX_NAME_LENGTH 0x40
|
||||||
|
#define SAVE_FS_LIST_ENTRY_SIZE 0x60
|
||||||
|
|
||||||
|
#define MAGIC_DISF 0x46534944
|
||||||
|
#define MAGIC_DPFS 0x53465044
|
||||||
|
#define MAGIC_JNGL 0x4C474E4A
|
||||||
|
#define MAGIC_SAVE 0x45564153
|
||||||
|
#define MAGIC_RMAP 0x50414D52
|
||||||
|
#define MAGIC_IVFC 0x43465649
|
||||||
|
|
||||||
|
#define ACTION_VERIFY (1 << 2)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
VALIDITY_UNCHECKED = 0,
|
||||||
|
VALIDITY_INVALID,
|
||||||
|
VALIDITY_VALID
|
||||||
|
} validity_t;
|
||||||
|
|
||||||
|
typedef struct save_ctx_t save_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic; /* "DISF". */
|
||||||
|
u32 version;
|
||||||
|
u8 hash[0x20];
|
||||||
|
u64 file_map_entry_offset;
|
||||||
|
u64 file_map_entry_size;
|
||||||
|
u64 meta_map_entry_offset;
|
||||||
|
u64 meta_map_entry_size;
|
||||||
|
u64 file_map_data_offset;
|
||||||
|
u64 file_map_data_size;
|
||||||
|
u64 duplex_l1_offset_a;
|
||||||
|
u64 duplex_l1_offset_b;
|
||||||
|
u64 duplex_l1_size;
|
||||||
|
u64 duplex_data_offset_a;
|
||||||
|
u64 duplex_data_offset_b;
|
||||||
|
u64 duplex_data_size;
|
||||||
|
u64 journal_data_offset;
|
||||||
|
u64 journal_data_size_a;
|
||||||
|
u64 journal_data_size_b;
|
||||||
|
u64 journal_size;
|
||||||
|
u64 duplex_master_offset_a;
|
||||||
|
u64 duplex_master_offset_b;
|
||||||
|
u64 duplex_master_size;
|
||||||
|
u64 ivfc_master_hash_offset_a;
|
||||||
|
u64 ivfc_master_hash_offset_b;
|
||||||
|
u64 ivfc_master_hash_size;
|
||||||
|
u64 journal_map_table_offset;
|
||||||
|
u64 journal_map_table_size;
|
||||||
|
u64 journal_physical_bitmap_offset;
|
||||||
|
u64 journal_physical_bitmap_size;
|
||||||
|
u64 journal_virtual_bitmap_offset;
|
||||||
|
u64 journal_virtual_bitmap_size;
|
||||||
|
u64 journal_free_bitmap_offset;
|
||||||
|
u64 journal_free_bitmap_size;
|
||||||
|
u64 ivfc_l1_offset;
|
||||||
|
u64 ivfc_l1_size;
|
||||||
|
u64 ivfc_l2_offset;
|
||||||
|
u64 ivfc_l2_size;
|
||||||
|
u64 ivfc_l3_offset;
|
||||||
|
u64 ivfc_l3_size;
|
||||||
|
u64 fat_offset;
|
||||||
|
u64 fat_size;
|
||||||
|
u64 duplex_index;
|
||||||
|
u64 fat_ivfc_master_hash_a;
|
||||||
|
u64 fat_ivfc_master_hash_b;
|
||||||
|
u64 fat_ivfc_l1_offset;
|
||||||
|
u64 fat_ivfc_l1_size;
|
||||||
|
u64 fat_ivfc_l2_offset;
|
||||||
|
u64 fat_ivfc_l2_size;
|
||||||
|
u8 _0x190[0x70];
|
||||||
|
} fs_layout_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(fs_layout_t, 0x200);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
u64 offset;
|
||||||
|
u64 length;
|
||||||
|
u32 block_size_power;
|
||||||
|
} duplex_info_t;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
NXDT_ASSERT(duplex_info_t, 0x14);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic; /* "DPFS". */
|
||||||
|
u32 version;
|
||||||
|
duplex_info_t layers[3];
|
||||||
|
} duplex_header_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(duplex_header_t, 0x44);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 version;
|
||||||
|
u32 main_data_block_count;
|
||||||
|
u32 journal_block_count;
|
||||||
|
u32 _0x0C;
|
||||||
|
} journal_map_header_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(journal_map_header_t, 0x10);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic; /* "JNGL". */
|
||||||
|
u32 version;
|
||||||
|
u64 total_size;
|
||||||
|
u64 journal_size;
|
||||||
|
u64 block_size;
|
||||||
|
} journal_header_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(journal_header_t, 0x20);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic; /* "SAVE". */
|
||||||
|
u32 version;
|
||||||
|
u64 block_count;
|
||||||
|
u64 block_size;
|
||||||
|
} save_fs_header_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(save_fs_header_t, 0x18);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 block_size;
|
||||||
|
u64 allocation_table_offset;
|
||||||
|
u32 allocation_table_block_count;
|
||||||
|
u32 _0x14;
|
||||||
|
u64 data_offset;
|
||||||
|
u32 data_block_count;
|
||||||
|
u32 _0x24;
|
||||||
|
u32 directory_table_block;
|
||||||
|
u32 file_table_block;
|
||||||
|
} fat_header_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(fat_header_t, 0x30);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic; /* "RMAP". */
|
||||||
|
u32 version;
|
||||||
|
u32 map_entry_count;
|
||||||
|
u32 map_segment_count;
|
||||||
|
u32 segment_bits;
|
||||||
|
u8 _0x14[0x2C];
|
||||||
|
} remap_header_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(remap_header_t, 0x40);
|
||||||
|
|
||||||
|
typedef struct remap_segment_ctx_t remap_segment_ctx_t;
|
||||||
|
typedef struct remap_entry_ctx_t remap_entry_ctx_t;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct remap_entry_ctx_t {
|
||||||
|
u64 virtual_offset;
|
||||||
|
u64 physical_offset;
|
||||||
|
u64 size;
|
||||||
|
u32 alignment;
|
||||||
|
u32 _0x1C;
|
||||||
|
u64 virtual_offset_end;
|
||||||
|
u64 physical_offset_end;
|
||||||
|
remap_segment_ctx_t *segment;
|
||||||
|
remap_entry_ctx_t *next;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
struct remap_segment_ctx_t{
|
||||||
|
u64 offset;
|
||||||
|
u64 length;
|
||||||
|
remap_entry_ctx_t **entries;
|
||||||
|
u64 entry_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 *data;
|
||||||
|
u8 *bitmap;
|
||||||
|
} duplex_bitmap_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 block_size;
|
||||||
|
u8 *bitmap_storage;
|
||||||
|
u8 *data_a;
|
||||||
|
u8 *data_b;
|
||||||
|
duplex_bitmap_t bitmap;
|
||||||
|
u64 _length;
|
||||||
|
} duplex_storage_ctx_t;
|
||||||
|
|
||||||
|
enum base_storage_type {
|
||||||
|
STORAGE_BYTES = 0,
|
||||||
|
STORAGE_DUPLEX = 1,
|
||||||
|
STORAGE_REMAP = 2,
|
||||||
|
STORAGE_JOURNAL = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
remap_header_t *header;
|
||||||
|
remap_entry_ctx_t *map_entries;
|
||||||
|
remap_segment_ctx_t *segments;
|
||||||
|
enum base_storage_type type;
|
||||||
|
u64 base_storage_offset;
|
||||||
|
duplex_storage_ctx_t *duplex;
|
||||||
|
FILE *file;
|
||||||
|
} remap_storage_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 title_id;
|
||||||
|
u8 user_id[0x10];
|
||||||
|
u64 save_id;
|
||||||
|
u8 save_data_type;
|
||||||
|
u8 _0x21[0x1F];
|
||||||
|
u64 save_owner_id;
|
||||||
|
u64 timestamp;
|
||||||
|
u64 _0x50;
|
||||||
|
u64 data_size;
|
||||||
|
u64 journal_size;
|
||||||
|
u64 commit_id;
|
||||||
|
} extra_data_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(extra_data_t, 0x70);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 logical_offset;
|
||||||
|
u64 hash_data_size;
|
||||||
|
u32 block_size;
|
||||||
|
u32 reserved;
|
||||||
|
} ivfc_level_hdr_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(ivfc_level_hdr_t, 0x18);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic;
|
||||||
|
u32 id;
|
||||||
|
u32 master_hash_size;
|
||||||
|
u32 num_levels;
|
||||||
|
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
|
||||||
|
u8 salt_source[0x20];
|
||||||
|
} ivfc_save_hdr_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(ivfc_save_hdr_t, 0xC0);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
u8 cmac[0x10];
|
||||||
|
u8 _0x10[0xF0];
|
||||||
|
fs_layout_t layout;
|
||||||
|
duplex_header_t duplex_header;
|
||||||
|
ivfc_save_hdr_t data_ivfc_header;
|
||||||
|
u32 _0x404;
|
||||||
|
journal_header_t journal_header;
|
||||||
|
journal_map_header_t map_header;
|
||||||
|
u8 _0x438[0x1D0];
|
||||||
|
save_fs_header_t save_header;
|
||||||
|
fat_header_t fat_header;
|
||||||
|
remap_header_t main_remap_header, meta_remap_header;
|
||||||
|
u64 _0x6D0;
|
||||||
|
extra_data_t extra_data;
|
||||||
|
u8 _0x748[0x390];
|
||||||
|
ivfc_save_hdr_t fat_ivfc_header;
|
||||||
|
u8 _0xB98[0x3468];
|
||||||
|
} save_header_t;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
NXDT_ASSERT(save_header_t, 0x4000);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
duplex_storage_ctx_t layers[2];
|
||||||
|
duplex_storage_ctx_t data_layer;
|
||||||
|
u64 _length;
|
||||||
|
} hierarchical_duplex_storage_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 *data_a;
|
||||||
|
u8 *data_b;
|
||||||
|
duplex_info_t info;
|
||||||
|
} duplex_fs_layer_info_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 *map_storage;
|
||||||
|
u8 *physical_block_bitmap;
|
||||||
|
u8 *virtual_block_bitmap;
|
||||||
|
u8 *free_block_bitmap;
|
||||||
|
} journal_map_params_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 physical_index;
|
||||||
|
u32 virtual_index;
|
||||||
|
} journal_map_entry_t;
|
||||||
|
|
||||||
|
NXDT_ASSERT(journal_map_entry_t, 0x8);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
journal_map_header_t *header;
|
||||||
|
journal_map_entry_t *entries;
|
||||||
|
u8 *map_storage;
|
||||||
|
} journal_map_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
journal_map_ctx_t map;
|
||||||
|
journal_header_t *header;
|
||||||
|
u32 block_size;
|
||||||
|
u64 journal_data_offset;
|
||||||
|
u64 _length;
|
||||||
|
FILE *file;
|
||||||
|
} journal_storage_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 data_offset;
|
||||||
|
u64 data_size;
|
||||||
|
u64 hash_offset;
|
||||||
|
u32 hash_block_size;
|
||||||
|
validity_t hash_validity;
|
||||||
|
enum base_storage_type type;
|
||||||
|
save_ctx_t *save_ctx;
|
||||||
|
} ivfc_level_save_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ivfc_level_save_ctx_t *data;
|
||||||
|
u32 block_size;
|
||||||
|
u8 salt[0x20];
|
||||||
|
} integrity_verification_info_ctx_t;
|
||||||
|
|
||||||
|
typedef struct integrity_verification_storage_ctx_t integrity_verification_storage_ctx_t;
|
||||||
|
|
||||||
|
struct integrity_verification_storage_ctx_t {
|
||||||
|
ivfc_level_save_ctx_t *hash_storage;
|
||||||
|
ivfc_level_save_ctx_t *base_storage;
|
||||||
|
validity_t *block_validities;
|
||||||
|
u8 salt[0x20];
|
||||||
|
u32 sector_size;
|
||||||
|
u32 sector_count;
|
||||||
|
u64 _length;
|
||||||
|
integrity_verification_storage_ctx_t *next_level;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ivfc_level_save_ctx_t levels[5];
|
||||||
|
ivfc_level_save_ctx_t *data_level;
|
||||||
|
validity_t **level_validities;
|
||||||
|
u64 _length;
|
||||||
|
integrity_verification_storage_ctx_t integrity_storages[4];
|
||||||
|
} hierarchical_integrity_verification_storage_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 prev;
|
||||||
|
u32 next;
|
||||||
|
} allocation_table_entry_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 free_list_entry_index;
|
||||||
|
void *base_storage;
|
||||||
|
fat_header_t *header;
|
||||||
|
} allocation_table_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
hierarchical_integrity_verification_storage_ctx_t *base_storage;
|
||||||
|
u32 block_size;
|
||||||
|
u32 initial_block;
|
||||||
|
allocation_table_ctx_t *fat;
|
||||||
|
u64 _length;
|
||||||
|
} allocation_table_storage_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
allocation_table_ctx_t *fat;
|
||||||
|
u32 virtual_block;
|
||||||
|
u32 physical_block;
|
||||||
|
u32 current_segment_size;
|
||||||
|
u32 next_block;
|
||||||
|
u32 prev_block;
|
||||||
|
} allocation_table_iterator_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
|
||||||
|
u32 parent;
|
||||||
|
} save_entry_key_t;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
u32 start_block;
|
||||||
|
u64 length;
|
||||||
|
u32 _0xC[2];
|
||||||
|
} save_file_info_t;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
NXDT_ASSERT(save_file_info_t, 0x14);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
u32 next_directory;
|
||||||
|
u32 next_file;
|
||||||
|
u32 _0x8[3];
|
||||||
|
} save_find_position_t;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
NXDT_ASSERT(save_find_position_t, 0x14);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
u32 next_sibling;
|
||||||
|
union { /* Save table entry type. Size = 0x14. */
|
||||||
|
save_file_info_t save_file_info;
|
||||||
|
save_find_position_t save_find_position;
|
||||||
|
};
|
||||||
|
} save_table_entry_t;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
NXDT_ASSERT(save_table_entry_t, 0x18);
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
u32 parent;
|
||||||
|
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
|
||||||
|
save_table_entry_t value;
|
||||||
|
u32 next;
|
||||||
|
} save_fs_list_entry_t;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
NXDT_ASSERT(save_fs_list_entry_t, 0x60);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 free_list_head_index;
|
||||||
|
u32 used_list_head_index;
|
||||||
|
allocation_table_storage_ctx_t storage;
|
||||||
|
u32 capacity;
|
||||||
|
} save_filesystem_list_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
save_filesystem_list_ctx_t file_table;
|
||||||
|
save_filesystem_list_ctx_t directory_table;
|
||||||
|
} hierarchical_save_file_table_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
hierarchical_integrity_verification_storage_ctx_t *base_storage;
|
||||||
|
allocation_table_ctx_t allocation_table;
|
||||||
|
save_fs_header_t *header;
|
||||||
|
hierarchical_save_file_table_ctx_t file_table;
|
||||||
|
} save_filesystem_ctx_t;
|
||||||
|
|
||||||
|
struct save_ctx_t {
|
||||||
|
save_header_t header;
|
||||||
|
FILE *file;
|
||||||
|
struct {
|
||||||
|
FILE *file;
|
||||||
|
u32 action;
|
||||||
|
} tool_ctx;
|
||||||
|
validity_t header_cmac_validity;
|
||||||
|
validity_t header_hash_validity;
|
||||||
|
u8 *data_ivfc_master;
|
||||||
|
u8 *fat_ivfc_master;
|
||||||
|
remap_storage_ctx_t data_remap_storage;
|
||||||
|
remap_storage_ctx_t meta_remap_storage;
|
||||||
|
duplex_fs_layer_info_t duplex_layers[3];
|
||||||
|
hierarchical_duplex_storage_ctx_t duplex_storage;
|
||||||
|
journal_storage_ctx_t journal_storage;
|
||||||
|
journal_map_params_t journal_map_info;
|
||||||
|
hierarchical_integrity_verification_storage_ctx_t core_data_ivfc_storage;
|
||||||
|
hierarchical_integrity_verification_storage_ctx_t fat_ivfc_storage;
|
||||||
|
u8 *fat_storage;
|
||||||
|
save_filesystem_ctx_t save_filesystem_core;
|
||||||
|
u8 save_mac_key[0x10];
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline u32 allocation_table_entry_index_to_block(u32 entry_index)
|
||||||
|
{
|
||||||
|
return (entry_index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u32 allocation_table_block_to_entry_index(u32 block_index)
|
||||||
|
{
|
||||||
|
return (block_index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int allocation_table_is_list_end(allocation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
return ((entry->next & 0x7FFFFFFF) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int allocation_table_is_list_start(allocation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
return (entry->prev == 0x80000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int allocation_table_get_next(allocation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
return (entry->next & 0x7FFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int allocation_table_get_prev(allocation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
return (entry->prev & 0x7FFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline allocation_table_entry_t *save_allocation_table_read_entry(allocation_table_ctx_t *ctx, u32 entry_index)
|
||||||
|
{
|
||||||
|
return ((allocation_table_entry_t*)((u8*)ctx->base_storage + (entry_index * SAVE_FAT_ENTRY_SIZE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u32 save_allocation_table_get_free_list_entry_index(allocation_table_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
return allocation_table_get_next(save_allocation_table_read_entry(ctx, ctx->free_list_entry_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u32 save_allocation_table_get_free_list_block_index(allocation_table_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
return allocation_table_entry_index_to_block(save_allocation_table_get_free_list_entry_index(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_process(save_ctx_t *ctx);
|
||||||
|
bool save_process_header(save_ctx_t *ctx);
|
||||||
|
void save_free_contexts(save_ctx_t *ctx);
|
||||||
|
|
||||||
|
bool save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, u32 block_index);
|
||||||
|
u32 save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, u64 offset, size_t count);
|
||||||
|
bool save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, u32 index, save_fs_list_entry_t *value);
|
||||||
|
u32 save_fs_list_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, u32 *prev_index);
|
||||||
|
bool save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, save_entry_key_t *key, const char *path);
|
||||||
|
bool save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry);
|
||||||
|
bool save_hierarchical_directory_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry);
|
||||||
|
|
||||||
|
save_ctx_t *save_open_savefile(const char *path, u32 action);
|
||||||
|
void save_close_savefile(save_ctx_t **ctx);
|
||||||
|
bool save_get_fat_storage_from_file_entry_by_path(save_ctx_t *ctx, const char *path, allocation_table_storage_ctx_t *out_fat_storage, u64 *out_file_entry_size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __SAVE_H__ */
|
||||||
61
sphaira/include/yati/nx/nxdumptool/defines.h
Normal file
61
sphaira/include/yati/nx/nxdumptool/defines.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* defines.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||||
|
*
|
||||||
|
* nxdumptool 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.
|
||||||
|
*
|
||||||
|
* nxdumptool 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __DEFINES_H__
|
||||||
|
#define __DEFINES_H__
|
||||||
|
|
||||||
|
/* Broadly useful language defines. */
|
||||||
|
|
||||||
|
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
|
||||||
|
|
||||||
|
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0])))
|
||||||
|
|
||||||
|
#define ALIGN_UP(x, y) (((x) + ((y) - 1)) & ~((y) - 1))
|
||||||
|
#define ALIGN_DOWN(x, y) ((x) & ~((y) - 1))
|
||||||
|
#define IS_ALIGNED(x, y) (((x) & ((y) - 1)) == 0)
|
||||||
|
|
||||||
|
#define IS_POWER_OF_TWO(x) ((x) > 0 && ((x) & ((x) - 1)) == 0)
|
||||||
|
|
||||||
|
#define DIVIDE_UP(x, y) (((x) + ((y) - 1)) / (y))
|
||||||
|
|
||||||
|
#define CONCATENATE_IMPL(s1, s2) s1##s2
|
||||||
|
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
|
||||||
|
|
||||||
|
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
|
||||||
|
|
||||||
|
#define NON_COPYABLE(cls) \
|
||||||
|
cls(const cls&) = delete; \
|
||||||
|
cls& operator=(const cls&) = delete
|
||||||
|
|
||||||
|
#define NON_MOVEABLE(cls) \
|
||||||
|
cls(cls&&) = delete; \
|
||||||
|
cls& operator=(cls&&) = delete
|
||||||
|
|
||||||
|
#define ALWAYS_INLINE inline __attribute__((always_inline))
|
||||||
|
#define ALWAYS_INLINE_LAMBDA __attribute__((always_inline))
|
||||||
|
|
||||||
|
#define CLEANUP(func) __attribute__((__cleanup__(func)))
|
||||||
|
|
||||||
|
#define NXDT_ASSERT(name, size) static_assert(sizeof(name) == (size), "Bad size for " #name "! Expected " #size ".")
|
||||||
|
|
||||||
|
#endif /* __DEFINES_H__ */
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -14,14 +15,99 @@
|
|||||||
namespace sphaira::fatfs {
|
namespace sphaira::fatfs {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// 256-512 are the best values, anything more has serious slow down
|
// todo: replace with off+size and have the data be in another struct
|
||||||
// due to non-seq reads.
|
// in order to be more lcache efficient.
|
||||||
struct BufferedFileData {
|
struct BufferedFileData {
|
||||||
u8 data[1024 * 256];
|
u8* data{};
|
||||||
s64 off;
|
u64 off{};
|
||||||
s64 size;
|
u64 size{};
|
||||||
|
|
||||||
|
~BufferedFileData() {
|
||||||
|
if (data) {
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Allocate(u64 new_size) {
|
||||||
|
data = (u8*)realloc(data, new_size * sizeof(*data));
|
||||||
|
off = 0;
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct LinkedList {
|
||||||
|
T* data;
|
||||||
|
LinkedList* next;
|
||||||
|
LinkedList* prev;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr u64 CACHE_LARGE_ALLOC_SIZE = 1024 * 512;
|
||||||
|
constexpr u64 CACHE_LARGE_SIZE = 1024 * 16;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct Lru {
|
||||||
|
using ListEntry = LinkedList<T>;
|
||||||
|
|
||||||
|
// pass span of the data.
|
||||||
|
void Init(std::span<T> data) {
|
||||||
|
list_flat_array.clear();
|
||||||
|
list_flat_array.resize(data.size());
|
||||||
|
|
||||||
|
auto list_entry = list_head = list_flat_array.data();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < data.size(); i++) {
|
||||||
|
list_entry = list_flat_array.data() + i;
|
||||||
|
list_entry->data = data.data() + i;
|
||||||
|
|
||||||
|
if (i + 1 < data.size()) {
|
||||||
|
list_entry->next = &list_flat_array[i + 1];
|
||||||
|
}
|
||||||
|
if (i) {
|
||||||
|
list_entry->prev = &list_flat_array[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list_tail = list_entry->prev->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// moves entry to the front of the list.
|
||||||
|
void Update(ListEntry* entry) {
|
||||||
|
// only update position if we are not the head.
|
||||||
|
if (list_head != entry) {
|
||||||
|
entry->prev->next = entry->next;
|
||||||
|
if (entry->next) {
|
||||||
|
entry->next->prev = entry->prev;
|
||||||
|
} else {
|
||||||
|
list_tail = entry->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update head.
|
||||||
|
auto head_temp = list_head;
|
||||||
|
list_head = entry;
|
||||||
|
list_head->prev = nullptr;
|
||||||
|
list_head->next = head_temp;
|
||||||
|
head_temp->prev = list_head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// moves last entry (tail) to the front of the list.
|
||||||
|
auto GetNextFree() {
|
||||||
|
Update(list_tail);
|
||||||
|
return list_head->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto begin() const { return list_head; }
|
||||||
|
auto end() const { return list_tail; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ListEntry* list_head{};
|
||||||
|
ListEntry* list_tail{};
|
||||||
|
std::vector<ListEntry> list_flat_array{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using LruBufferedData = Lru<BufferedFileData>;
|
||||||
|
|
||||||
enum BisMountType {
|
enum BisMountType {
|
||||||
BisMountType_PRODINFOF,
|
BisMountType_PRODINFOF,
|
||||||
BisMountType_SAFE,
|
BisMountType_SAFE,
|
||||||
@@ -31,7 +117,10 @@ enum BisMountType {
|
|||||||
|
|
||||||
struct FatStorageEntry {
|
struct FatStorageEntry {
|
||||||
FsStorage storage;
|
FsStorage storage;
|
||||||
BufferedFileData buffered;
|
s64 storage_size;
|
||||||
|
LruBufferedData lru_cache[2];
|
||||||
|
BufferedFileData buffered_small[1024]; // 1MiB (usually).
|
||||||
|
BufferedFileData buffered_large[2]; // 1MiB
|
||||||
FATFS fs;
|
FATFS fs;
|
||||||
devoptab_t devoptab;
|
devoptab_t devoptab;
|
||||||
};
|
};
|
||||||
@@ -52,35 +141,57 @@ static_assert(std::size(BIS_MOUNT_ENTRIES) == FF_VOLUMES);
|
|||||||
|
|
||||||
FatStorageEntry g_fat_storage[FF_VOLUMES];
|
FatStorageEntry g_fat_storage[FF_VOLUMES];
|
||||||
|
|
||||||
// crappy generic buffered io i wrote a while ago.
|
Result ReadStorage(FsStorage* storage, std::span<LruBufferedData> lru_cache, void *_buffer, u64 file_off, u64 read_size, u64 capacity) {
|
||||||
// this allows for 3-4x speed increase reading from storage.
|
// log_write("[FATFS] read offset: %zu size: %zu\n", file_off, read_size);
|
||||||
// as it avoids reading very small chunks at a time.
|
|
||||||
// note: this works best when the file is not fragmented.
|
|
||||||
Result ReadFile(FsStorage* storage, BufferedFileData& m_buffered, void *_buffer, size_t file_off, size_t read_size) {
|
|
||||||
auto dst = static_cast<u8*>(_buffer);
|
auto dst = static_cast<u8*>(_buffer);
|
||||||
size_t amount = 0;
|
size_t amount = 0;
|
||||||
|
|
||||||
// check if we already have this data buffered.
|
R_UNLESS(file_off < capacity, FsError_UnsupportedOperateRangeForFileStorage);
|
||||||
if (m_buffered.size) {
|
read_size = std::min(read_size, capacity - file_off);
|
||||||
// check if we can read this data into the beginning of dst.
|
|
||||||
if (file_off < m_buffered.off + m_buffered.size && file_off >= m_buffered.off) {
|
|
||||||
const auto off = file_off - m_buffered.off;
|
|
||||||
const auto size = std::min<s64>(read_size, m_buffered.size - off);
|
|
||||||
std::memcpy(dst, m_buffered.data + off, size);
|
|
||||||
|
|
||||||
read_size -= size;
|
// fatfs reads in max 16k chunks.
|
||||||
file_off += size;
|
// knowing this, it's possible to detect large file reads by simply checking if
|
||||||
amount += size;
|
// the read size is 16k (or more, maybe in the furter).
|
||||||
dst += size;
|
// however this would destroy random access performance, such as fetching 512 bytes.
|
||||||
|
// the fix was to have 2 LRU caches, one for large data and the other for small (anything below 16k).
|
||||||
|
// the results in file reads 32MB -> 184MB and directory listing is instant.
|
||||||
|
const auto large_read = read_size >= 1024 * 16;
|
||||||
|
auto& lru = large_read ? lru_cache[1] : lru_cache[0];
|
||||||
|
|
||||||
|
for (auto list = lru.begin(); list; list = list->next) {
|
||||||
|
const auto& m_buffered = list->data;
|
||||||
|
if (m_buffered->size) {
|
||||||
|
// check if we can read this data into the beginning of dst.
|
||||||
|
if (file_off < m_buffered->off + m_buffered->size && file_off >= m_buffered->off) {
|
||||||
|
const auto off = file_off - m_buffered->off;
|
||||||
|
const auto size = std::min<s64>(read_size, m_buffered->size - off);
|
||||||
|
if (size) {
|
||||||
|
// log_write("[FAT] cache HIT at: %zu\n", file_off);
|
||||||
|
std::memcpy(dst, m_buffered->data + off, size);
|
||||||
|
|
||||||
|
read_size -= size;
|
||||||
|
file_off += size;
|
||||||
|
amount += size;
|
||||||
|
dst += size;
|
||||||
|
|
||||||
|
lru.Update(list);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (read_size) {
|
if (read_size) {
|
||||||
m_buffered.off = 0;
|
// log_write("[FAT] cache miss at: %zu %zu\n", file_off, read_size);
|
||||||
m_buffered.size = 0;
|
|
||||||
|
|
||||||
// if the dst dst is big enough, read data in place.
|
auto alloc_size = large_read ? CACHE_LARGE_ALLOC_SIZE : std::max<u64>(read_size, 512);
|
||||||
if (read_size >= sizeof(m_buffered.data)) {
|
alloc_size = std::min(alloc_size, capacity - file_off);
|
||||||
|
|
||||||
|
auto m_buffered = lru.GetNextFree();
|
||||||
|
m_buffered->Allocate(alloc_size);
|
||||||
|
|
||||||
|
// if the dst is big enough, read data in place.
|
||||||
|
if (read_size > alloc_size) {
|
||||||
if (R_SUCCEEDED(fsStorageRead(storage, file_off, dst, read_size))) {
|
if (R_SUCCEEDED(fsStorageRead(storage, file_off, dst, read_size))) {
|
||||||
const auto bytes_read = read_size;
|
const auto bytes_read = read_size;
|
||||||
read_size -= bytes_read;
|
read_size -= bytes_read;
|
||||||
@@ -89,18 +200,18 @@ Result ReadFile(FsStorage* storage, BufferedFileData& m_buffered, void *_buffer,
|
|||||||
dst += bytes_read;
|
dst += bytes_read;
|
||||||
|
|
||||||
// save the last chunk of data to the m_buffered io.
|
// save the last chunk of data to the m_buffered io.
|
||||||
const auto max_advance = std::min(amount, sizeof(m_buffered.data));
|
const auto max_advance = std::min<u64>(amount, alloc_size);
|
||||||
m_buffered.off = file_off - max_advance;
|
m_buffered->off = file_off - max_advance;
|
||||||
m_buffered.size = max_advance;
|
m_buffered->size = max_advance;
|
||||||
std::memcpy(m_buffered.data, dst - max_advance, max_advance);
|
std::memcpy(m_buffered->data, dst - max_advance, max_advance);
|
||||||
}
|
}
|
||||||
} else if (R_SUCCEEDED(fsStorageRead(storage, file_off, m_buffered.data, sizeof(m_buffered.data)))) {
|
} else if (R_SUCCEEDED(fsStorageRead(storage, file_off, m_buffered->data, alloc_size))) {
|
||||||
const auto bytes_read = sizeof(m_buffered.data);
|
const auto bytes_read = alloc_size;
|
||||||
const auto max_advance = std::min(read_size, bytes_read);
|
const auto max_advance = std::min<u64>(read_size, bytes_read);
|
||||||
std::memcpy(dst, m_buffered.data, max_advance);
|
std::memcpy(dst, m_buffered->data, max_advance);
|
||||||
|
|
||||||
m_buffered.off = file_off;
|
m_buffered->off = file_off;
|
||||||
m_buffered.size = bytes_read;
|
m_buffered->size = bytes_read;
|
||||||
|
|
||||||
read_size -= max_advance;
|
read_size -= max_advance;
|
||||||
file_off += max_advance;
|
file_off += max_advance;
|
||||||
@@ -109,7 +220,7 @@ Result ReadFile(FsStorage* storage, BufferedFileData& m_buffered, void *_buffer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
void fill_stat(const FILINFO* fno, struct stat *st) {
|
void fill_stat(const FILINFO* fno, struct stat *st) {
|
||||||
@@ -199,7 +310,6 @@ DIR_ITER* fat_diropen(struct _reent *r, DIR_ITER *dirState, const char *path) {
|
|||||||
|
|
||||||
int fat_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
int fat_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||||
if (FR_OK != f_rewinddir((FDIR*)dirState->dirStruct)) {
|
if (FR_OK != f_rewinddir((FDIR*)dirState->dirStruct)) {
|
||||||
log_write("[FAT] fat_dirreset failed\n");
|
|
||||||
return set_errno(r, ENOENT);
|
return set_errno(r, ENOENT);
|
||||||
}
|
}
|
||||||
return r->_errno = 0;
|
return r->_errno = 0;
|
||||||
@@ -277,11 +387,15 @@ Result MountAll() {
|
|||||||
|
|
||||||
log_write("[FAT] %s\n", bis.volume_name);
|
log_write("[FAT] %s\n", bis.volume_name);
|
||||||
|
|
||||||
|
fat.lru_cache[0].Init(fat.buffered_small);
|
||||||
|
fat.lru_cache[1].Init(fat.buffered_large);
|
||||||
|
|
||||||
fat.devoptab = DEVOPTAB;
|
fat.devoptab = DEVOPTAB;
|
||||||
fat.devoptab.name = bis.volume_name;
|
fat.devoptab.name = bis.volume_name;
|
||||||
fat.devoptab.deviceData = &fat;
|
fat.devoptab.deviceData = &fat;
|
||||||
|
|
||||||
R_TRY(fsOpenBisStorage(&fat.storage, bis.id));
|
R_TRY(fsOpenBisStorage(&fat.storage, bis.id));
|
||||||
|
R_TRY(fsStorageGetSize(&fat.storage, &fat.storage_size));
|
||||||
log_write("[FAT] BIS SUCCESS %s\n", bis.volume_name);
|
log_write("[FAT] BIS SUCCESS %s\n", bis.volume_name);
|
||||||
|
|
||||||
R_UNLESS(FR_OK == f_mount(&fat.fs, bis.mount_name, 1), 0x1);
|
R_UNLESS(FR_OK == f_mount(&fat.fs, bis.mount_name, 1), 0x1);
|
||||||
@@ -319,7 +433,7 @@ const char* VolumeStr[] {
|
|||||||
Result fatfs_read(u8 num, void* dst, u64 offset, u64 size) {
|
Result fatfs_read(u8 num, void* dst, u64 offset, u64 size) {
|
||||||
// log_write("[FAT] num: %u\n", num);
|
// log_write("[FAT] num: %u\n", num);
|
||||||
auto& fat = sphaira::fatfs::g_fat_storage[num];
|
auto& fat = sphaira::fatfs::g_fat_storage[num];
|
||||||
return sphaira::fatfs::ReadFile(&fat.storage, fat.buffered, dst, offset, size);
|
return sphaira::fatfs::ReadStorage(&fat.storage, fat.lru_cache, dst, offset, size, fat.storage_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ auto nro_parse_internal(fs::Fs* fs, const fs::FsPath& path, NroEntry& entry) ->
|
|||||||
std::strcpy(nacp.lang.author, "Unknown");
|
std::strcpy(nacp.lang.author, "Unknown");
|
||||||
std::strcpy(nacp.display_version, "Unknown");
|
std::strcpy(nacp.display_version, "Unknown");
|
||||||
|
|
||||||
entry.icon_offset = entry.icon_size = 0;
|
entry.romfs_offset = entry.romfs_size = entry.icon_offset = entry.icon_size = 0;
|
||||||
entry.is_nacp_valid = false;
|
entry.is_nacp_valid = false;
|
||||||
} else {
|
} else {
|
||||||
entry.size += sizeof(asset) + asset.icon.size + asset.nacp.size + asset.romfs.size;
|
entry.size += sizeof(asset) + asset.icon.size + asset.nacp.size + asset.romfs.size;
|
||||||
@@ -70,6 +70,8 @@ auto nro_parse_internal(fs::Fs* fs, const fs::FsPath& path, NroEntry& entry) ->
|
|||||||
// lazy load the icons
|
// lazy load the icons
|
||||||
entry.icon_size = asset.icon.size;
|
entry.icon_size = asset.icon.size;
|
||||||
entry.icon_offset = data.header.size + asset.icon.offset;
|
entry.icon_offset = data.header.size + asset.icon.offset;
|
||||||
|
entry.romfs_offset = data.header.size + asset.romfs.offset;
|
||||||
|
entry.romfs_size = asset.romfs.size;
|
||||||
entry.is_nacp_valid = true;
|
entry.is_nacp_valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,9 @@ Menu::Menu(Entry& entry, const meta::MetaEntry& meta_entry)
|
|||||||
}}),
|
}}),
|
||||||
std::make_pair(Button::A, Action{"Mount Fs"_i18n, [this](){
|
std::make_pair(Button::A, Action{"Mount Fs"_i18n, [this](){
|
||||||
// todo: handle error here.
|
// todo: handle error here.
|
||||||
MountNcaFs();
|
if (!m_entries.empty() && !GetEntry().missing) {
|
||||||
|
MountNcaFs();
|
||||||
|
}
|
||||||
}}),
|
}}),
|
||||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||||
SetPop();
|
SetPop();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include "ui/menus/homebrew.hpp"
|
#include "ui/menus/homebrew.hpp"
|
||||||
|
#include "ui/menus/filebrowser.hpp"
|
||||||
#include "ui/sidebar.hpp"
|
#include "ui/sidebar.hpp"
|
||||||
#include "ui/error_box.hpp"
|
#include "ui/error_box.hpp"
|
||||||
#include "ui/option_box.hpp"
|
#include "ui/option_box.hpp"
|
||||||
@@ -469,7 +470,16 @@ void Menu::DisplayOptions() {
|
|||||||
ScanHomebrew();
|
ScanHomebrew();
|
||||||
App::PopToMenu();
|
App::PopToMenu();
|
||||||
}, "Hides the selected homebrew.\n\n"
|
}, "Hides the selected homebrew.\n\n"
|
||||||
"To Unhide homebrew, enable \"Show hidden\" in the sort options."_i18n);
|
"To unhide homebrew, enable \"Show hidden\" in the sort options."_i18n);
|
||||||
|
|
||||||
|
auto mount_option = options->Add<SidebarEntryCallback>("Mount RomFS"_i18n, [this](){
|
||||||
|
const auto rc = MountRomfsFs();
|
||||||
|
App::PushErrorBox(rc, "Failed to mount NRO RomFS"_i18n);
|
||||||
|
}, "Mounts the homebrew RomFS"_i18n);
|
||||||
|
|
||||||
|
mount_option->Depends([this](){
|
||||||
|
return GetEntry().romfs_offset && GetEntry().romfs_size;
|
||||||
|
}, "This homebrew does not have a RomFS"_i18n);
|
||||||
|
|
||||||
options->Add<SidebarEntryCallback>("Delete"_i18n, [this](){
|
options->Add<SidebarEntryCallback>("Delete"_i18n, [this](){
|
||||||
const auto buf = "Are you sure you want to delete "_i18n + GetEntry().path.toString() + "?";
|
const auto buf = "Are you sure you want to delete "_i18n + GetEntry().path.toString() + "?";
|
||||||
@@ -500,4 +510,47 @@ void Menu::DisplayOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NroRomFS final : fs::FsStdio {
|
||||||
|
NroRomFS(const fs::FsPath& name, const fs::FsPath& root) : FsStdio{true, root}, m_name{name} {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
~NroRomFS() {
|
||||||
|
romfsUnmount(m_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::FsPath m_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
Result Menu::MountRomfsFs() {
|
||||||
|
const char* name = "nro_romfs";
|
||||||
|
const char* root = "nro_romfs:/";
|
||||||
|
const auto& e = GetEntry();
|
||||||
|
|
||||||
|
// todo: add errors for when nro doesn't have romfs.
|
||||||
|
R_UNLESS(e.romfs_offset, 0x1);
|
||||||
|
R_UNLESS(e.romfs_size, 0x1);
|
||||||
|
|
||||||
|
FsFile file;
|
||||||
|
R_TRY(fsFsOpenFile(fsdevGetDeviceFileSystem("sdmc"), e.path, FsOpenMode_Read, &file));
|
||||||
|
|
||||||
|
const auto rc = romfsMountFromFile(file, e.romfs_offset, name);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
fsFileClose(&file);
|
||||||
|
R_THROW(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fs = std::make_shared<NroRomFS>(name, root);
|
||||||
|
|
||||||
|
const filebrowser::FsEntry fs_entry{
|
||||||
|
.name = e.GetName(),
|
||||||
|
.root = root,
|
||||||
|
.type = filebrowser::FsType::Custom,
|
||||||
|
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
App::Push<filebrowser::Menu>(fs, fs_entry, root);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::homebrew
|
} // namespace sphaira::ui::menu::homebrew
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "minizip_helper.hpp"
|
#include "minizip_helper.hpp"
|
||||||
#include "dumper.hpp"
|
#include "dumper.hpp"
|
||||||
|
|
||||||
|
#include "utils/devoptab.hpp"
|
||||||
|
|
||||||
#include "ui/menus/save_menu.hpp"
|
#include "ui/menus/save_menu.hpp"
|
||||||
#include "ui/menus/filebrowser.hpp"
|
#include "ui/menus/filebrowser.hpp"
|
||||||
|
|
||||||
@@ -40,6 +42,18 @@ constexpr const char* NX_SAVE_META_NAME = ".nx_save_meta.bin";
|
|||||||
|
|
||||||
constinit UEvent g_change_uevent;
|
constinit UEvent g_change_uevent;
|
||||||
|
|
||||||
|
struct SystemSaveFs final : fs::FsStdio {
|
||||||
|
SystemSaveFs(u64 id, const fs::FsPath& root) : FsStdio{true, root}, m_id{id} {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
~SystemSaveFs() {
|
||||||
|
devoptab::UnmountSave(m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const u64 m_id;
|
||||||
|
};
|
||||||
|
|
||||||
// https://github.com/J-D-K/JKSV/issues/264#issuecomment-2618962807
|
// https://github.com/J-D-K/JKSV/issues/264#issuecomment-2618962807
|
||||||
struct NXSaveMeta {
|
struct NXSaveMeta {
|
||||||
u32 magic{}; // NX_SAVE_META_MAGIC
|
u32 magic{}; // NX_SAVE_META_MAGIC
|
||||||
@@ -325,6 +339,12 @@ Menu::Menu(u32 flags) : grid::Menu{"Saves"_i18n, flags} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}),
|
}}),
|
||||||
|
std::make_pair(Button::A, Action{"Mount Fs"_i18n, [this](){
|
||||||
|
if (!m_entries.empty()) {
|
||||||
|
const auto rc = MountSaveFs();
|
||||||
|
App::PushErrorBox(rc, "Failed to mount save filesystem"_i18n);
|
||||||
|
}
|
||||||
|
}}),
|
||||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||||
SetPop();
|
SetPop();
|
||||||
}}),
|
}}),
|
||||||
@@ -666,11 +686,6 @@ void Menu::DisplayOptions() {
|
|||||||
RestoreSave();
|
RestoreSave();
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
options->Add<SidebarEntryCallback>("Mount Fs"_i18n, [this](){
|
|
||||||
const auto rc = MountSaveFs();
|
|
||||||
App::PushErrorBox(rc, "Failed to mount save filesystem"_i18n);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options->Add<SidebarEntryCallback>("Advanced"_i18n, [this](){
|
options->Add<SidebarEntryCallback>("Advanced"_i18n, [this](){
|
||||||
@@ -1103,27 +1118,45 @@ Result Menu::BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& loc
|
|||||||
|
|
||||||
Result Menu::MountSaveFs() {
|
Result Menu::MountSaveFs() {
|
||||||
const auto& e = m_entries[m_index];
|
const auto& e = m_entries[m_index];
|
||||||
const auto save_data_space_id = (FsSaveDataSpaceId)e.save_data_space_id;
|
fs::FsPath root;
|
||||||
|
|
||||||
FsSaveDataAttribute attr{};
|
if (e.system_save_data_id) {
|
||||||
attr.application_id = e.application_id;
|
R_TRY(devoptab::MountFromSavePath(e.system_save_data_id, root));
|
||||||
attr.uid = e.uid;
|
|
||||||
attr.system_save_data_id = e.system_save_data_id;
|
|
||||||
attr.save_data_type = e.save_data_type;
|
|
||||||
attr.save_data_rank = e.save_data_rank;
|
|
||||||
attr.save_data_index = e.save_data_index;
|
|
||||||
|
|
||||||
auto fs = std::make_shared<fs::FsNativeSave>((FsSaveDataType)e.save_data_type, save_data_space_id, &attr, true);
|
auto fs = std::make_shared<SystemSaveFs>(e.system_save_data_id, root);
|
||||||
R_TRY(fs->GetFsOpenResult());
|
|
||||||
|
|
||||||
const filebrowser::FsEntry fs_entry{
|
const filebrowser::FsEntry fs_entry{
|
||||||
.name = e.GetName(),
|
.name = e.GetName(),
|
||||||
.root = "/",
|
.root = root,
|
||||||
.type = filebrowser::FsType::Custom,
|
.type = filebrowser::FsType::Custom,
|
||||||
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
App::Push<filebrowser::Menu>(fs, fs_entry, root);
|
||||||
|
} else {
|
||||||
|
const auto save_data_space_id = (FsSaveDataSpaceId)e.save_data_space_id;
|
||||||
|
|
||||||
|
FsSaveDataAttribute attr{};
|
||||||
|
attr.application_id = e.application_id;
|
||||||
|
attr.uid = e.uid;
|
||||||
|
attr.system_save_data_id = e.system_save_data_id;
|
||||||
|
attr.save_data_type = e.save_data_type;
|
||||||
|
attr.save_data_rank = e.save_data_rank;
|
||||||
|
attr.save_data_index = e.save_data_index;
|
||||||
|
|
||||||
|
auto fs = std::make_shared<fs::FsNativeSave>((FsSaveDataType)e.save_data_type, save_data_space_id, &attr, true);
|
||||||
|
R_TRY(fs->GetFsOpenResult());
|
||||||
|
|
||||||
|
const filebrowser::FsEntry fs_entry{
|
||||||
|
.name = e.GetName(),
|
||||||
|
.root = "/",
|
||||||
|
.type = filebrowser::FsType::Custom,
|
||||||
|
.flags = filebrowser::FsEntryFlag_ReadOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
App::Push<filebrowser::Menu>(fs, fs_entry, "/");
|
||||||
|
}
|
||||||
|
|
||||||
App::Push<filebrowser::Menu>(fs, fs_entry, "/");
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
375
sphaira/source/utils/devoptab_save.cpp
Normal file
375
sphaira/source/utils/devoptab_save.cpp
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
|
||||||
|
#include "utils/devoptab.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
|
||||||
|
#include "yati/nx/nxdumptool/defines.h"
|
||||||
|
#include "yati/nx/nxdumptool/core/save.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sys/iosupport.h>
|
||||||
|
|
||||||
|
namespace sphaira::devoptab {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
save_ctx_t* ctx;
|
||||||
|
hierarchical_save_file_table_ctx_t* file_table;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
Device* device;
|
||||||
|
save_fs_list_entry_t entry;
|
||||||
|
allocation_table_storage_ctx_t storage;
|
||||||
|
size_t off;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DirNext {
|
||||||
|
u32 next_directory;
|
||||||
|
u32 next_file;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Dir {
|
||||||
|
Device* device;
|
||||||
|
save_fs_list_entry_t entry;
|
||||||
|
u32 next_directory;
|
||||||
|
u32 next_file;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool fix_path(const char* str, char* out) {
|
||||||
|
// log_write("[SAVE] got path: %s\n", str);
|
||||||
|
|
||||||
|
str = std::strrchr(str, ':');
|
||||||
|
if (!str) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip over ':'
|
||||||
|
str++;
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; str[i]; i++) {
|
||||||
|
// skip multiple slashes.
|
||||||
|
if (i && str[i] == '/' && str[i - 1] == '/') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add leading slash.
|
||||||
|
if (!i && str[i] != '/') {
|
||||||
|
out[len++] = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// save single char.
|
||||||
|
out[len++] = str[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// root path uses ""
|
||||||
|
if (len == 1 && out[0] == '/') {
|
||||||
|
// out[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// null the end.
|
||||||
|
out[len] = '\0';
|
||||||
|
|
||||||
|
// log_write("[SAVE] end path: %s\n", out);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int set_errno(struct _reent *r, int err) {
|
||||||
|
r->_errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
|
||||||
|
auto device = (Device*)r->deviceData;
|
||||||
|
auto file = static_cast<File*>(fileStruct);
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save_hierarchical_file_table_get_file_entry_by_path(device->file_table, path, &file->entry)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save_open_fat_storage(&device->ctx->save_filesystem_core, &file->storage, file->entry.value.save_file_info.start_block)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
file->device = device;
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_close(struct _reent *r, void *fd) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
|
||||||
|
// todo: maybe eof here?
|
||||||
|
const auto bytes_read = save_allocation_table_storage_read(&file->storage, ptr, file->off, len);
|
||||||
|
if (!bytes_read) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
file->off += bytes_read;
|
||||||
|
return bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
|
||||||
|
if (dir == SEEK_CUR) {
|
||||||
|
pos += file->off;
|
||||||
|
} else if (dir == SEEK_END) {
|
||||||
|
pos = file->storage._length;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->_errno = 0;
|
||||||
|
return file->off = std::clamp<u64>(pos, 0, file->storage._length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
|
||||||
|
log_write("[\t\tDEV] fstat\n");
|
||||||
|
std::memset(st, 0, sizeof(*st));
|
||||||
|
st->st_nlink = 1;
|
||||||
|
st->st_size = file->storage._length;
|
||||||
|
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) {
|
||||||
|
auto device = (Device*)r->deviceData;
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::strcmp(path, "/")) {
|
||||||
|
save_entry_key_t key{};
|
||||||
|
const auto idx = save_fs_list_get_index_from_key(&device->file_table->directory_table, &key, NULL);
|
||||||
|
if (idx == 0xFFFFFFFF) {
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save_fs_list_get_value(&device->file_table->directory_table, idx, &dir->entry)) {
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else if (!save_hierarchical_directory_table_get_file_entry_by_path(device->file_table, path, &dir->entry)) {
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->device = device;
|
||||||
|
dir->next_file = dir->entry.value.save_find_position.next_file;
|
||||||
|
dir->next_directory = dir->entry.value.save_find_position.next_directory;
|
||||||
|
|
||||||
|
r->_errno = 0;
|
||||||
|
return dirState;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
|
||||||
|
dir->next_file = dir->entry.value.save_find_position.next_file;
|
||||||
|
dir->next_directory = dir->entry.value.save_find_position.next_directory;
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
|
||||||
|
std::memset(filestat, 0, sizeof(*filestat));
|
||||||
|
save_fs_list_entry_t entry{};
|
||||||
|
|
||||||
|
if (dir->next_directory) {
|
||||||
|
// todo: use save_allocation_table_storage_read for faster reads
|
||||||
|
if (!save_fs_list_get_value(&dir->device->file_table->directory_table, dir->next_directory, &entry)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
dir->next_directory = entry.value.next_sibling;
|
||||||
|
}
|
||||||
|
else if (dir->next_file) {
|
||||||
|
// todo: use save_allocation_table_storage_read for faster reads
|
||||||
|
if (!save_fs_list_get_value(&dir->device->file_table->file_table, dir->next_file, &entry)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
// todo: confirm this.
|
||||||
|
filestat->st_size = entry.value.save_file_info.length;
|
||||||
|
// filestat->st_size = file->storage.block_size;
|
||||||
|
dir->next_file = entry.value.next_sibling;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
filestat->st_nlink = 1;
|
||||||
|
strcpy(filename, entry.name);
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||||
|
auto device = (Device*)r->deviceData;
|
||||||
|
|
||||||
|
log_write("[\t\tDEV] lstat\n");
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(st, 0, sizeof(*st));
|
||||||
|
save_fs_list_entry_t entry{};
|
||||||
|
|
||||||
|
// NOTE: this is very slow.
|
||||||
|
if (save_hierarchical_file_table_get_file_entry_by_path(device->file_table, path, &entry)) {
|
||||||
|
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
st->st_size = entry.value.save_file_info.length;
|
||||||
|
} else if (save_hierarchical_directory_table_get_file_entry_by_path(device->file_table, path, &entry)) {
|
||||||
|
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
} else {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
st->st_nlink = 1;
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr devoptab_t DEVOPTAB = {
|
||||||
|
.structSize = sizeof(File),
|
||||||
|
.open_r = devoptab_open,
|
||||||
|
.close_r = devoptab_close,
|
||||||
|
.read_r = devoptab_read,
|
||||||
|
.seek_r = devoptab_seek,
|
||||||
|
.fstat_r = devoptab_fstat,
|
||||||
|
.stat_r = devoptab_lstat,
|
||||||
|
.dirStateSize = sizeof(Dir),
|
||||||
|
.diropen_r = devoptab_diropen,
|
||||||
|
.dirreset_r = devoptab_dirreset,
|
||||||
|
.dirnext_r = devoptab_dirnext,
|
||||||
|
.dirclose_r = devoptab_dirclose,
|
||||||
|
.lstat_r = devoptab_lstat,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
u64 id;
|
||||||
|
Device device;
|
||||||
|
devoptab_t devoptab;
|
||||||
|
char name[32];
|
||||||
|
s32 ref_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
Mutex g_mutex;
|
||||||
|
std::vector<Entry> g_entries;
|
||||||
|
|
||||||
|
void MakeMountPath(u64 id, fs::FsPath& out_path) {
|
||||||
|
std::snprintf(out_path, sizeof(out_path), "%016lx:/", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Result MountFromSavePath(u64 id, fs::FsPath& out_path) {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
// check if we already have the save mounted.
|
||||||
|
for (auto& e : g_entries) {
|
||||||
|
if (e.id == id) {
|
||||||
|
e.ref_count++;
|
||||||
|
MakeMountPath(id, out_path);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[256];
|
||||||
|
std::snprintf(path, sizeof(path), "SYSTEM:/save/%016lx", id);
|
||||||
|
|
||||||
|
auto ctx = save_open_savefile(path, 0);
|
||||||
|
if (!ctx) {
|
||||||
|
R_THROW(0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_write("[SAVE] OPEN SUCCESS %s\n", path);
|
||||||
|
|
||||||
|
// create new entry.
|
||||||
|
auto& entry = g_entries.emplace_back();
|
||||||
|
std::snprintf(entry.name, sizeof(entry.name), "%016lx", id);
|
||||||
|
|
||||||
|
entry.id = id;
|
||||||
|
entry.device.ctx = ctx;
|
||||||
|
entry.device.file_table = &ctx->save_filesystem_core.file_table;
|
||||||
|
entry.devoptab = DEVOPTAB;
|
||||||
|
entry.devoptab.name = entry.name;
|
||||||
|
entry.devoptab.deviceData = &entry.device;
|
||||||
|
|
||||||
|
R_UNLESS(AddDevice(&entry.devoptab) >= 0, 0x1);
|
||||||
|
log_write("[SAVE] DEVICE SUCCESS %s %s\n", path, entry.name);
|
||||||
|
|
||||||
|
MakeMountPath(id, out_path);
|
||||||
|
|
||||||
|
entry.ref_count++;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnmountSave(u64 id) {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
auto itr = std::ranges::find_if(g_entries, [id](auto& e){
|
||||||
|
return id == e.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itr == g_entries.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itr->ref_count) {
|
||||||
|
itr->ref_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itr->ref_count) {
|
||||||
|
fs::FsPath path;
|
||||||
|
MakeMountPath(id, path);
|
||||||
|
|
||||||
|
// todo: verify this actually works.
|
||||||
|
RemoveDevice(path);
|
||||||
|
|
||||||
|
if (itr->device.ctx) {
|
||||||
|
save_close_savefile(&itr->device.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_entries.erase(itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::devoptab
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
#include "ui/types.hpp"
|
#include "ui/types.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
|
|
||||||
|
#include "yati/nx/nxdumptool/defines.h"
|
||||||
|
#include "yati/nx/nxdumptool/core/save.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
@@ -13,6 +16,101 @@
|
|||||||
namespace sphaira::es {
|
namespace sphaira::es {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
class CachedSave {
|
||||||
|
public:
|
||||||
|
constexpr CachedSave(const char* _path) : path{_path} {}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
if (ctx) {
|
||||||
|
save_close_savefile(&ctx);
|
||||||
|
ctx = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
auto Open() {
|
||||||
|
if (ctx) {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
return ctx = save_open_savefile(path, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* path;
|
||||||
|
save_ctx_t* ctx{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CachedCommonSave : public CachedSave {
|
||||||
|
public:
|
||||||
|
using CachedSave::CachedSave;
|
||||||
|
|
||||||
|
bool GetTicketBin(allocation_table_storage_ctx_t& storage, u64& size) {
|
||||||
|
return GetTicketBin(Open(), has_ticket_bin, ticket_bin_storage, ticket_bin_size, storage, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetTicketListBin(allocation_table_storage_ctx_t& storage, u64& size) {
|
||||||
|
return GetTicketBin(Open(), has_ticket_list_bin, ticket_list_bin_storage, ticket_list_bin_size, storage, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool GetTicketBin(save_ctx_t* ctx, bool& m_has, allocation_table_storage_ctx_t& m_storage, u64& m_size, allocation_table_storage_ctx_t& out_storage, u64& out_size) {
|
||||||
|
if (!ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_has) {
|
||||||
|
if (!save_get_fat_storage_from_file_entry_by_path(ctx, "/ticket.bin", &m_storage, &m_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out_storage = m_storage;
|
||||||
|
out_size = m_size;
|
||||||
|
return m_has = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
u64 ticket_bin_size{};
|
||||||
|
allocation_table_storage_ctx_t ticket_bin_storage{};
|
||||||
|
bool has_ticket_bin{};
|
||||||
|
|
||||||
|
u64 ticket_list_bin_size{};
|
||||||
|
allocation_table_storage_ctx_t ticket_list_bin_storage{};
|
||||||
|
bool has_ticket_list_bin{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CachedCertSave {
|
||||||
|
public:
|
||||||
|
constexpr CachedCertSave(const char* _path) : path{_path} {}
|
||||||
|
|
||||||
|
auto Get() {
|
||||||
|
if (ctx) {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
return ctx = save_open_savefile(path, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
if (ctx) {
|
||||||
|
save_close_savefile(&ctx);
|
||||||
|
ctx = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* path;
|
||||||
|
save_ctx_t* ctx{};
|
||||||
|
u64 ticket_bin_size{};
|
||||||
|
allocation_table_storage_ctx_t ticket_bin_storage{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// kept alive whilst es is init, closed after,
|
||||||
|
// so only the first time opening is slow (40ms).
|
||||||
|
// todo: set global dirty flag when a ticket has been installed.
|
||||||
|
// todo: check if its needed to cache now that ive added lru cache to fatfs
|
||||||
|
CachedCommonSave g_common_save{"SYSTEM:/save/80000000000000e1"};
|
||||||
|
CachedCommonSave g_personalised_save{"SYSTEM:/save/80000000000000e2"};
|
||||||
|
|
||||||
Service g_esSrv;
|
Service g_esSrv;
|
||||||
|
|
||||||
NX_GENERATE_SERVICE_GUARD(es);
|
NX_GENERATE_SERVICE_GUARD(es);
|
||||||
@@ -22,6 +120,9 @@ Result _esInitialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _esCleanup() {
|
void _esCleanup() {
|
||||||
|
// todo: add cert here when added.
|
||||||
|
g_common_save.Close();
|
||||||
|
g_personalised_save.Close();
|
||||||
serviceClose(&g_esSrv);
|
serviceClose(&g_esSrv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,34 +496,45 @@ Result GetCommonTicketAndCertificate(const FsRightsId& rights_id, std::vector<u8
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result GetPersonalisedTicketAndCertificate(const FsRightsId& rights_id, std::vector<u8>& tik_out, std::vector<u8>& cert_out) {
|
Result GetPersonalisedTicketAndCertificate(const FsRightsId& rights_id, std::vector<u8>& tik_out, std::vector<u8>& cert_out) {
|
||||||
R_THROW(0x1);
|
// todo: finish this off and fetch the cirtificate chain.
|
||||||
|
// todo: find out what ticket_list.bin is (offsets?)
|
||||||
#if 0
|
#if 0
|
||||||
fs::FsStdio fs;
|
|
||||||
|
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
std::vector<u8> tik_buf;
|
|
||||||
// R_TRY(fs.read_entire_file("system:/save/80000000000000e1", tik_buf));
|
u64 ticket_bin_size;
|
||||||
R_TRY(fs.read_entire_file("SYSTEM:/save/80000000000000e2", tik_buf));
|
allocation_table_storage_ctx_t ticket_bin_storage;
|
||||||
log_write("[ES] size: %zu\n", tik_buf.size());
|
if (!g_common_save.GetTicketBin(ticket_bin_storage, ticket_bin_size)) {
|
||||||
|
log_write("\t\tFAILED TO GET SAVE\n");
|
||||||
|
R_THROW(0x1);
|
||||||
|
}
|
||||||
log_write("\t\t[ticket read] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
log_write("\t\t[ticket read] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
ts.Update();
|
ts.Update();
|
||||||
|
|
||||||
for (u32 i = 0; i < tik_buf.size() - 0x400; i += 0x400) {
|
std::vector<u8> tik_buf(std::min<u64>(ticket_bin_size, 1024 * 256));
|
||||||
const auto tikRsa2048 = (const TicketRsa2048*)(tik_buf.data() + i);
|
for (u64 off = 0; off < ticket_bin_size; off += tik_buf.size()) {
|
||||||
if (tikRsa2048->signature_block.sig_type != SigType_Rsa2048Sha256) {
|
const auto size = save_allocation_table_storage_read(&ticket_bin_storage, tik_buf.data(), off, tik_buf.size());
|
||||||
continue;
|
if (!size) {
|
||||||
|
log_write("\t\tfailed to read ticket bin\n");
|
||||||
|
R_THROW(0x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!std::memcmp(&rights_id, &tikRsa2048->data.rights_id, sizeof(rights_id))) {
|
for (u32 i = 0; i < size - 0x400; i += 0x400) {
|
||||||
log_write("\t[ES] tikRsa2048, found at: %u\n", i);
|
const auto tikRsa2048 = (const TicketRsa2048*)(tik_buf.data() + i);
|
||||||
|
if (tikRsa2048->signature_block.sig_type != SigType_Rsa2048Sha256) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::memcmp(&rights_id, &tikRsa2048->data.rights_id, sizeof(rights_id))) {
|
||||||
|
log_write("\t[ES] tikRsa2048, found at: %zu\n", off + i);
|
||||||
|
// log_write("[ES] finished es search\n");
|
||||||
|
log_write("\t\t[ticket search] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log_write("[ES] finished es search\n");
|
|
||||||
log_write("\t\t[ticket search] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
|
|
||||||
R_THROW(0x1);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
R_THROW(0x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sphaira::es
|
} // namespace sphaira::es
|
||||||
|
|||||||
1839
sphaira/source/yati/nx/nxdumptool/save.c
Normal file
1839
sphaira/source/yati/nx/nxdumptool/save.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user