mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 10:16:39 +00:00
Create BIS storage interface.
Takes care of retrieving a FsStorage object for any FAT eMMC BIS partition, mounting it via FatFs and creating a virtual devoptab device that can be used to carry out FS operations. All write operations have been stubbed, disabled or ifdef'd out of the code. Other changes include: * cert: update code to use the new BIS storage interface. * defines: remove BIS_SYSTEM_PARTITION_MOUNT_NAME macro. * devoptab: slightly improve macros. * devoptab: add operation table for FatFs devices. * devoptab: add rodev_fstat(). * devoptab: add devoptabMountFatFsDevice(). * fatfs: update diskio code to use the new BIS storage interface. * fatfs: update configuration. * save: update code to use regular C I/O calls instead of FatFs calls. * tik: update code to use the new BIS storage interface. * utils: remove eMMC BIS System partition (un)mount code.
This commit is contained in:
parent
160236c4de
commit
596928a3c6
19 changed files with 1138 additions and 291 deletions
54
include/core/bis_storage.h
Normal file
54
include/core/bis_storage.h
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* bis_storage.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 __BIS_STORAGE_H__
|
||||||
|
#define __BIS_STORAGE_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Mounts an eMMC BIS partition using its ID and provides a pointer to a string that holds its mount name, which can be used to carry out FS operations.
|
||||||
|
/// Only eMMC BIS partition IDs `CalibrationFile` (28) through `System` (31) are supported.
|
||||||
|
bool bisStorageMountPartition(u8 bis_partition_id, const char **out_mount_name);
|
||||||
|
|
||||||
|
/// Unmounts a previously mounted eMMC BIS partition.
|
||||||
|
/// Only eMMC BIS partition IDs `CalibrationFile` (28) through `System` (31) are supported.
|
||||||
|
void bisStorageUnmountPartition(u8 bis_partition_id);
|
||||||
|
|
||||||
|
/// Unmounts all previously mounted eMMC BIS partitions.
|
||||||
|
void bisStorageUnmountAllPartitions(void);
|
||||||
|
|
||||||
|
/// Returns a pointer to a FsStorage object that matches the provided FatFs drive number, or NULL if it hasn't been mounted.
|
||||||
|
/// Only used by FatFs's diskio operations.
|
||||||
|
FsStorage *bisStorageGetFsStorageByFatFsDriveNumber(u8 drive_number);
|
||||||
|
|
||||||
|
/// (Un)locks the BIS storage mutex. Can be used to block other threads and prevent them from altering the internal status of this interface.
|
||||||
|
/// Use with caution.
|
||||||
|
void bisStorageControlMutex(bool lock);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __BIS_STORAGE_H__ */
|
|
@ -27,54 +27,56 @@
|
||||||
#include "../pfs.h"
|
#include "../pfs.h"
|
||||||
#include "../hfs.h"
|
#include "../hfs.h"
|
||||||
#include "../romfs.h"
|
#include "../romfs.h"
|
||||||
|
#include "../fatfs/ff.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DEVOPTAB_MOUNT_NAME_LENGTH 32 // Including NULL terminator.
|
#define DEVOPTAB_MOUNT_NAME_LENGTH 32 // Including NULL terminator.
|
||||||
|
|
||||||
#define DEVOPTAB_DECL_ERROR_STATE int _errno = 0
|
#define DEVOPTAB_INIT_ERROR_STATE r->_errno = 0
|
||||||
#define DEVOPTAB_DECL_DEV_CTX DevoptabDeviceContext *dev_ctx = (DevoptabDeviceContext*)r->deviceData
|
#define DEVOPTAB_DECL_DEV_CTX DevoptabDeviceContext *dev_ctx = (DevoptabDeviceContext*)r->deviceData
|
||||||
#define DEVOPTAB_DECL_FS_CTX(type) type *fs_ctx = (type*)dev_ctx->fs_ctx
|
#define DEVOPTAB_DECL_FS_CTX(type) type *fs_ctx = (type*)dev_ctx->fs_ctx
|
||||||
#define DEVOPTAB_DECL_FILE_STATE(type) type *file = (type*)fd
|
#define DEVOPTAB_DECL_FILE_STATE(type) type *file = (type*)fd
|
||||||
#define DEVOPTAB_DECL_DIR_STATE(type) type *dir = (type*)dirState->dirStruct
|
#define DEVOPTAB_DECL_DIR_STATE(type) type *dir = (type*)dirState->dirStruct
|
||||||
|
|
||||||
#define DEVOPTAB_SET_ERROR(x) r->_errno = _errno = (x)
|
#define DEVOPTAB_SET_ERROR(x) r->_errno = (x)
|
||||||
#define DEVOPTAB_IS_ERROR_SET (_errno != 0)
|
#define DEVOPTAB_IS_ERROR_SET (r->_errno != 0)
|
||||||
|
|
||||||
#define DEVOPTAB_EXIT goto end
|
#define DEVOPTAB_EXIT goto end
|
||||||
#define DEVOPTAB_SET_ERROR_AND_EXIT(x) \
|
#define DEVOPTAB_SET_ERROR_AND_EXIT(x) \
|
||||||
do { \
|
do { \
|
||||||
DEVOPTAB_SET_ERROR(x); \
|
DEVOPTAB_SET_ERROR(x); \
|
||||||
DEVOPTAB_EXIT; \
|
DEVOPTAB_EXIT; \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
#define DEVOPTAB_RETURN_INT(x) return (DEVOPTAB_IS_ERROR_SET ? -1 : (x))
|
#define DEVOPTAB_RETURN_INT(x) return (DEVOPTAB_IS_ERROR_SET ? -1 : (x))
|
||||||
#define DEVOPTAB_RETURN_PTR(x) return (DEVOPTAB_IS_ERROR_SET ? NULL : (x))
|
#define DEVOPTAB_RETURN_PTR(x) return (DEVOPTAB_IS_ERROR_SET ? NULL : (x))
|
||||||
#define DEVOPTAB_RETURN_BOOL return (DEVOPTAB_IS_ERROR_SET ? false : true)
|
#define DEVOPTAB_RETURN_BOOL return (DEVOPTAB_IS_ERROR_SET ? false : true)
|
||||||
#define DEVOPTAB_RETURN_UNSUPPORTED_OP r->_errno = ENOSYS; \
|
#define DEVOPTAB_RETURN_UNSUPPORTED_OP r->_errno = ENOSYS; \
|
||||||
return -1;
|
return -1
|
||||||
|
|
||||||
#define DEVOPTAB_INIT_VARS(type) devoptabControlMutex(true); \
|
#define DEVOPTAB_INIT_VARS devoptabControlMutex(true); \
|
||||||
DEVOPTAB_DECL_ERROR_STATE; \
|
DEVOPTAB_INIT_ERROR_STATE; \
|
||||||
DEVOPTAB_DECL_DEV_CTX; \
|
DEVOPTAB_DECL_DEV_CTX; \
|
||||||
if (!dev_ctx->initialized) DEVOPTAB_SET_ERROR_AND_EXIT(ENODEV);
|
if (!dev_ctx->initialized) DEVOPTAB_SET_ERROR_AND_EXIT(ENODEV)
|
||||||
|
|
||||||
#define DEVOPTAB_INIT_FILE_VARS(fs_type, file_type) DEVOPTAB_INIT_VARS(fs_type); \
|
#define DEVOPTAB_INIT_FILE_VARS(type) DEVOPTAB_INIT_VARS; \
|
||||||
DEVOPTAB_DECL_FILE_STATE(file_type)
|
DEVOPTAB_DECL_FILE_STATE(type)
|
||||||
|
|
||||||
#define DEVOPTAB_INIT_DIR_VARS(fs_type, dir_type) DEVOPTAB_INIT_VARS(fs_type); \
|
#define DEVOPTAB_INIT_DIR_VARS(type) DEVOPTAB_INIT_VARS; \
|
||||||
DEVOPTAB_DECL_DIR_STATE(dir_type)
|
if (!dirState) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); \
|
||||||
|
DEVOPTAB_DECL_DIR_STATE(type)
|
||||||
|
|
||||||
#define DEVOPTAB_DEINIT_VARS devoptabControlMutex(false)
|
#define DEVOPTAB_DEINIT_VARS devoptabControlMutex(false)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool initialized; ///< Device initialization flag.
|
bool initialized; ///< Device initialization flag.
|
||||||
char name[DEVOPTAB_MOUNT_NAME_LENGTH]; ///< Mount name string, without a trailing colon (:).
|
char name[DEVOPTAB_MOUNT_NAME_LENGTH]; ///< Mount name string, without a trailing colon (:).
|
||||||
time_t mount_time; ///< Mount time.
|
time_t mount_time; ///< Mount time.
|
||||||
devoptab_t device; ///< Devoptab virtual device interface. Provides a way to use libcstd I/O calls on the mounted filesystem.
|
devoptab_t device; ///< Devoptab virtual device interface. Provides a way to use libcstd I/O calls on the mounted filesystem.
|
||||||
void *fs_ctx; ///< Pointer to actual type-specific filesystem context (PartitionFileSystemContext, HashFileSystemContext, RomFileSystemContext).
|
void *fs_ctx; ///< Pointer to actual type-specific filesystem context (PartitionFileSystemContext, HashFileSystemContext, RomFileSystemContext, FATFS).
|
||||||
} DevoptabDeviceContext;
|
} DevoptabDeviceContext;
|
||||||
|
|
||||||
/// Mounts a virtual Partition FS device using the provided Partition FS context and a mount name.
|
/// Mounts a virtual Partition FS device using the provided Partition FS context and a mount name.
|
||||||
|
@ -86,6 +88,9 @@ bool devoptabMountHashFileSystemDevice(HashFileSystemContext *hfs_ctx, const cha
|
||||||
/// Mounts a virtual RomFS device using the provided RomFS context and a mount name.
|
/// Mounts a virtual RomFS device using the provided RomFS context and a mount name.
|
||||||
bool devoptabMountRomFileSystemDevice(RomFileSystemContext *romfs_ctx, const char *name);
|
bool devoptabMountRomFileSystemDevice(RomFileSystemContext *romfs_ctx, const char *name);
|
||||||
|
|
||||||
|
/// Mounts a virtual FatFs device using the provided FATFS object and a mount name.
|
||||||
|
bool devoptabMountFatFsDevice(FATFS *fatfs, const char *name);
|
||||||
|
|
||||||
/// Unmounts a previously mounted virtual device.
|
/// Unmounts a previously mounted virtual device.
|
||||||
void devoptabUnmountDevice(const char *name);
|
void devoptabUnmountDevice(const char *name);
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ extern "C" {
|
||||||
/* We don't provide support for relative directories, so chdir is discarded as well. */
|
/* We don't provide support for relative directories, so chdir is discarded as well. */
|
||||||
|
|
||||||
ssize_t rodev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
|
ssize_t rodev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
|
||||||
|
int rodev_fstat(struct _reent *r, void *fd, struct stat *st);
|
||||||
int rodev_link(struct _reent *r, const char *existing, const char *newLink);
|
int rodev_link(struct _reent *r, const char *existing, const char *newLink);
|
||||||
int rodev_unlink(struct _reent *r, const char *name);
|
int rodev_unlink(struct _reent *r, const char *name);
|
||||||
int rodev_chdir(struct _reent *r, const char *name);
|
int rodev_chdir(struct _reent *r, const char *name);
|
||||||
|
|
|
@ -166,12 +166,11 @@
|
||||||
/ Drive/Volume Configurations
|
/ Drive/Volume Configurations
|
||||||
/---------------------------------------------------------------------------*/
|
/---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
#define FF_VOLUMES 1
|
#define FF_VOLUMES 4
|
||||||
/* Number of volumes (logical drives) to be used. (1-10) */
|
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||||
|
|
||||||
|
|
||||||
#define FF_STR_VOLUME_ID 1
|
#define FF_STR_VOLUME_ID 1
|
||||||
#define FF_VOLUME_STRS "sys"
|
|
||||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||||
|
|
|
@ -114,9 +114,6 @@ bool utilsIsTerraUnit(void);
|
||||||
/// Returns true if the application is running under applet mode.
|
/// Returns true if the application is running under applet mode.
|
||||||
bool utilsIsAppletMode(void);
|
bool utilsIsAppletMode(void);
|
||||||
|
|
||||||
/// Returns a pointer to the FsStorage object for the eMMC BIS System partition.
|
|
||||||
FsStorage *utilsGetEmmcBisSystemPartitionStorage(void);
|
|
||||||
|
|
||||||
/// Blocks HOME button presses, disables screen dimming and auto sleep and overclocks system CPU/MEM.
|
/// Blocks HOME button presses, disables screen dimming and auto sleep and overclocks system CPU/MEM.
|
||||||
/// Must be called before starting long-running processes.
|
/// Must be called before starting long-running processes.
|
||||||
/// If state is set to false, regular system behavior is restored.
|
/// If state is set to false, regular system behavior is restored.
|
||||||
|
|
|
@ -25,8 +25,6 @@
|
||||||
#ifndef __SAVE_H__
|
#ifndef __SAVE_H__
|
||||||
#define __SAVE_H__
|
#define __SAVE_H__
|
||||||
|
|
||||||
#include "fatfs/ff.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
@ -45,7 +43,7 @@ extern "C" {
|
||||||
#define MAGIC_RMAP 0x50414D52
|
#define MAGIC_RMAP 0x50414D52
|
||||||
#define MAGIC_IVFC 0x43465649
|
#define MAGIC_IVFC 0x43465649
|
||||||
|
|
||||||
#define ACTION_VERIFY (1<<2)
|
#define ACTION_VERIFY (1 << 2)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
VALIDITY_UNCHECKED = 0,
|
VALIDITY_UNCHECKED = 0,
|
||||||
|
@ -232,7 +230,7 @@ typedef struct {
|
||||||
enum base_storage_type type;
|
enum base_storage_type type;
|
||||||
u64 base_storage_offset;
|
u64 base_storage_offset;
|
||||||
duplex_storage_ctx_t *duplex;
|
duplex_storage_ctx_t *duplex;
|
||||||
FIL *file;
|
FILE *file;
|
||||||
} remap_storage_ctx_t;
|
} remap_storage_ctx_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -333,7 +331,7 @@ typedef struct {
|
||||||
u32 block_size;
|
u32 block_size;
|
||||||
u64 journal_data_offset;
|
u64 journal_data_offset;
|
||||||
u64 _length;
|
u64 _length;
|
||||||
FIL *file;
|
FILE *file;
|
||||||
} journal_storage_ctx_t;
|
} journal_storage_ctx_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -470,9 +468,9 @@ typedef struct {
|
||||||
|
|
||||||
struct save_ctx_t {
|
struct save_ctx_t {
|
||||||
save_header_t header;
|
save_header_t header;
|
||||||
FIL *file;
|
FILE *file;
|
||||||
struct {
|
struct {
|
||||||
FIL *file;
|
FILE *file;
|
||||||
u32 action;
|
u32 action;
|
||||||
} tool_ctx;
|
} tool_ctx;
|
||||||
validity_t header_cmac_validity;
|
validity_t header_cmac_validity;
|
||||||
|
@ -549,7 +547,7 @@ bool save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_tab
|
||||||
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_file_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);
|
save_ctx_t *save_open_savefile(const char *path, u32 action);
|
||||||
void save_close_savefile(save_ctx_t *ctx);
|
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);
|
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
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -104,8 +104,6 @@
|
||||||
#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */
|
#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */
|
||||||
#define LOG_FORCE_FLUSH 0 /* Forces a log buffer flush each time the logfile is written to. */
|
#define LOG_FORCE_FLUSH 0 /* Forces a log buffer flush each time the logfile is written to. */
|
||||||
|
|
||||||
#define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:"
|
|
||||||
|
|
||||||
/// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits.
|
/// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits.
|
||||||
/// Reference: https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits.
|
/// Reference: https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits.
|
||||||
/// Most modern filesystems use a 255-byte limit instead of 255-character/codepoint limit, so that's what we're gonna use.
|
/// Most modern filesystems use a 255-byte limit instead of 255-character/codepoint limit, so that's what we're gonna use.
|
||||||
|
|
240
source/core/bis_storage.c
Normal file
240
source/core/bis_storage.c
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
/*
|
||||||
|
* bis_storage.c
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <core/nxdt_utils.h>
|
||||||
|
#include <core/bis_storage.h>
|
||||||
|
#include <core/devoptab/nxdt_devoptab.h>
|
||||||
|
|
||||||
|
#define BIS_STORAGE_INDEX(type) ((type) - FsBisPartitionId_CalibrationFile)
|
||||||
|
#define BIS_STORAGE_FATFS_CTX(type) g_bisStorageContexts[BIS_STORAGE_INDEX(type)]
|
||||||
|
#define BIS_STORAGE_MOUNT_NAME(type) VolumeStr[BIS_STORAGE_INDEX(type)]
|
||||||
|
|
||||||
|
/* Type definitions. */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 bis_partition_id; ///< FsBisPartitionId.
|
||||||
|
FsStorage bis_storage;
|
||||||
|
FATFS fatfs;
|
||||||
|
} BisStorageFatFsContext;
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static Mutex g_bisStorageMutex = 0;
|
||||||
|
|
||||||
|
static BisStorageFatFsContext *g_bisStorageContexts[FF_VOLUMES] = {0};
|
||||||
|
|
||||||
|
/// Required by FatFs.
|
||||||
|
const char *VolumeStr[FF_VOLUMES] = {
|
||||||
|
"prodinfof",
|
||||||
|
"safe",
|
||||||
|
"user",
|
||||||
|
"system",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static void _bisStorageUnmountPartition(u8 bis_partition_id);
|
||||||
|
|
||||||
|
static BisStorageFatFsContext *bisStorageInitializeFatFsContext(u8 bis_partition_id);
|
||||||
|
static void bisStorageFreeFatFsContext(BisStorageFatFsContext **bis_fatfs_ctx);
|
||||||
|
|
||||||
|
bool bisStorageMountPartition(u8 bis_partition_id, const char **out_mount_name)
|
||||||
|
{
|
||||||
|
if (bis_partition_id < FsBisPartitionId_CalibrationFile || bis_partition_id > FsBisPartitionId_System || !out_mount_name)
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Invalid parameters!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_bisStorageMutex)
|
||||||
|
{
|
||||||
|
BisStorageFatFsContext *bis_fatfs_ctx = NULL;
|
||||||
|
|
||||||
|
/* Check if we have already mounted this eMMC partition. */
|
||||||
|
bis_fatfs_ctx = BIS_STORAGE_FATFS_CTX(bis_partition_id);
|
||||||
|
if (bis_fatfs_ctx)
|
||||||
|
{
|
||||||
|
*out_mount_name = BIS_STORAGE_MOUNT_NAME(bis_partition_id);
|
||||||
|
ret = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize BIS FatFs context. */
|
||||||
|
bis_fatfs_ctx = bisStorageInitializeFatFsContext(bis_partition_id);
|
||||||
|
if (!bis_fatfs_ctx) break;
|
||||||
|
|
||||||
|
/* Update output. */
|
||||||
|
*out_mount_name = BIS_STORAGE_MOUNT_NAME(bis_partition_id);
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bisStorageUnmountPartition(u8 bis_partition_id)
|
||||||
|
{
|
||||||
|
if (bis_partition_id < FsBisPartitionId_CalibrationFile || bis_partition_id > FsBisPartitionId_System)
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Invalid parameters!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_bisStorageMutex) _bisStorageUnmountPartition(bis_partition_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bisStorageUnmountAllPartitions(void)
|
||||||
|
{
|
||||||
|
SCOPED_LOCK(&g_bisStorageMutex)
|
||||||
|
{
|
||||||
|
for(u8 i = 0; i < FF_VOLUMES; i++) _bisStorageUnmountPartition(i + FsBisPartitionId_CalibrationFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FsStorage *bisStorageGetFsStorageByFatFsDriveNumber(u8 drive_number)
|
||||||
|
{
|
||||||
|
FsStorage *bis_storage = NULL;
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_bisStorageMutex)
|
||||||
|
{
|
||||||
|
for(u8 i = 0; i < FF_VOLUMES; i++)
|
||||||
|
{
|
||||||
|
if (!g_bisStorageContexts[i] || g_bisStorageContexts[i]->fatfs.pdrv != drive_number) continue;
|
||||||
|
bis_storage = &(g_bisStorageContexts[i]->bis_storage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bis_storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bisStorageControlMutex(bool lock)
|
||||||
|
{
|
||||||
|
bool locked = mutexIsLockedByCurrentThread(&g_bisStorageMutex);
|
||||||
|
|
||||||
|
if (!locked && lock)
|
||||||
|
{
|
||||||
|
mutexLock(&g_bisStorageMutex);
|
||||||
|
} else
|
||||||
|
if (locked && !lock)
|
||||||
|
{
|
||||||
|
mutexUnlock(&g_bisStorageMutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _bisStorageUnmountPartition(u8 bis_partition_id)
|
||||||
|
{
|
||||||
|
/* Check if we have already mounted this eMMC partition. */
|
||||||
|
BisStorageFatFsContext *bis_fatfs_ctx = BIS_STORAGE_FATFS_CTX(bis_partition_id);
|
||||||
|
if (!bis_fatfs_ctx) return;
|
||||||
|
|
||||||
|
/* Free BIS FatFs context. This will take care of unmounting the partition. */
|
||||||
|
bisStorageFreeFatFsContext(&bis_fatfs_ctx);
|
||||||
|
|
||||||
|
/* Update context array. */
|
||||||
|
BIS_STORAGE_FATFS_CTX(bis_partition_id) = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BisStorageFatFsContext *bisStorageInitializeFatFsContext(u8 bis_partition_id)
|
||||||
|
{
|
||||||
|
if (bis_partition_id < FsBisPartitionId_CalibrationFile || bis_partition_id > FsBisPartitionId_System)
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Invalid parameters!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
BisStorageFatFsContext *bis_fatfs_ctx = NULL;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
FRESULT fr = FR_OK;
|
||||||
|
const char *name = BIS_STORAGE_MOUNT_NAME(bis_partition_id);
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
/* Allocate memory for our output context. */
|
||||||
|
bis_fatfs_ctx = calloc(1, sizeof(BisStorageFatFsContext));
|
||||||
|
if (!bis_fatfs_ctx)
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Failed to allocate memory for BIS FatFs context! (partition ID %u).", bis_partition_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set BIS partition ID. */
|
||||||
|
bis_fatfs_ctx->bis_partition_id = bis_partition_id;
|
||||||
|
|
||||||
|
/* Open BIS storage. */
|
||||||
|
rc = fsOpenBisStorage(&(bis_fatfs_ctx->bis_storage), bis_fatfs_ctx->bis_partition_id);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Failed to open eMMC BIS partition storage! (0x%X, partition ID %u).", rc, bis_partition_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update context array. */
|
||||||
|
/* FatFs diskio demands we do this here. */
|
||||||
|
BIS_STORAGE_FATFS_CTX(bis_partition_id) = bis_fatfs_ctx;
|
||||||
|
|
||||||
|
/* Mount BIS partition using FatFs. */
|
||||||
|
fr = f_mount(&(bis_fatfs_ctx->fatfs), name, 1);
|
||||||
|
if (fr != FR_OK)
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Failed to mount eMMC BIS partition via FatFs! (%u, partition ID %u).", fr, bis_partition_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mount devoptab device. */
|
||||||
|
if (!devoptabMountFatFsDevice(&(bis_fatfs_ctx->fatfs), name))
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Failed to mount devoptab device for eMMC BIS partition %u!", bis_partition_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update flag. */
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
bisStorageFreeFatFsContext(&bis_fatfs_ctx);
|
||||||
|
BIS_STORAGE_FATFS_CTX(bis_partition_id) = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bis_fatfs_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bisStorageFreeFatFsContext(BisStorageFatFsContext **bis_fatfs_ctx)
|
||||||
|
{
|
||||||
|
if (!bis_fatfs_ctx || !*bis_fatfs_ctx) return;
|
||||||
|
|
||||||
|
if ((*bis_fatfs_ctx)->fatfs.fs_type)
|
||||||
|
{
|
||||||
|
const char *name = BIS_STORAGE_MOUNT_NAME((*bis_fatfs_ctx)->bis_partition_id);
|
||||||
|
devoptabUnmountDevice(name);
|
||||||
|
f_unmount(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceIsActive(&((*bis_fatfs_ctx)->bis_storage.s))) fsStorageClose(&((*bis_fatfs_ctx)->bis_storage));
|
||||||
|
|
||||||
|
free(*bis_fatfs_ctx);
|
||||||
|
*bis_fatfs_ctx = NULL;
|
||||||
|
}
|
|
@ -23,8 +23,9 @@
|
||||||
#include <core/cert.h>
|
#include <core/cert.h>
|
||||||
#include <core/save.h>
|
#include <core/save.h>
|
||||||
#include <core/gamecard.h>
|
#include <core/gamecard.h>
|
||||||
|
#include <core/bis_storage.h>
|
||||||
|
|
||||||
#define CERT_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e0"
|
#define CERT_BIS_SYSTEM_SAVEFILE_PATH "/save/80000000000000e0"
|
||||||
#define CERT_SAVEFILE_STORAGE_BASE_PATH "/certificate/"
|
#define CERT_SAVEFILE_STORAGE_BASE_PATH "/certificate/"
|
||||||
|
|
||||||
#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig##_PubKeyRsa4096 : \
|
#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig##_PubKeyRsa4096 : \
|
||||||
|
@ -32,8 +33,8 @@
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static save_ctx_t *g_esCertSaveCtx = NULL;
|
|
||||||
static Mutex g_esCertSaveMutex = 0;
|
static Mutex g_esCertSaveMutex = 0;
|
||||||
|
static save_ctx_t *g_esCertSaveCtx = NULL;
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
@ -60,9 +61,15 @@ bool certRetrieveCertificateByName(Certificate *dst, const char *name)
|
||||||
|
|
||||||
SCOPED_LOCK(&g_esCertSaveMutex)
|
SCOPED_LOCK(&g_esCertSaveMutex)
|
||||||
{
|
{
|
||||||
if (!certOpenEsCertSaveFile()) break;
|
bisStorageControlMutex(true);
|
||||||
ret = _certRetrieveCertificateByName(dst, name);
|
|
||||||
certCloseEsCertSaveFile();
|
if (certOpenEsCertSaveFile())
|
||||||
|
{
|
||||||
|
ret = _certRetrieveCertificateByName(dst, name);
|
||||||
|
certCloseEsCertSaveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bisStorageControlMutex(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -80,9 +87,15 @@ bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const
|
||||||
|
|
||||||
SCOPED_LOCK(&g_esCertSaveMutex)
|
SCOPED_LOCK(&g_esCertSaveMutex)
|
||||||
{
|
{
|
||||||
if (!certOpenEsCertSaveFile()) break;
|
bisStorageControlMutex(true);
|
||||||
ret = _certRetrieveCertificateChainBySignatureIssuer(dst, issuer);
|
|
||||||
certCloseEsCertSaveFile();
|
if (certOpenEsCertSaveFile())
|
||||||
|
{
|
||||||
|
ret = _certRetrieveCertificateChainBySignatureIssuer(dst, issuer);
|
||||||
|
certCloseEsCertSaveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bisStorageControlMutex(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -190,21 +203,44 @@ static bool certOpenEsCertSaveFile(void)
|
||||||
{
|
{
|
||||||
if (g_esCertSaveCtx) return true;
|
if (g_esCertSaveCtx) return true;
|
||||||
|
|
||||||
g_esCertSaveCtx = save_open_savefile(CERT_SAVEFILE_PATH, 0);
|
const char *mount_name = NULL;
|
||||||
|
char savefile_path[64] = {0};
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
/* Mount eMMC BIS System partition. */
|
||||||
|
if (!bisStorageMountPartition(FsBisPartitionId_System, &mount_name))
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Failed to mount eMMC BIS System partition!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate savefile path. */
|
||||||
|
snprintf(savefile_path, sizeof(savefile_path), "%s:%s", mount_name, CERT_BIS_SYSTEM_SAVEFILE_PATH);
|
||||||
|
|
||||||
|
/* Initialize savefile context. */
|
||||||
|
g_esCertSaveCtx = save_open_savefile(savefile_path, 0);
|
||||||
if (!g_esCertSaveCtx)
|
if (!g_esCertSaveCtx)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to open ES certificate system savefile!");
|
LOG_MSG_ERROR("Failed to open ES certificate system savefile!");
|
||||||
return false;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
/* Update flag. */
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (!success && mount_name) bisStorageUnmountPartition(FsBisPartitionId_System);
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void certCloseEsCertSaveFile(void)
|
static void certCloseEsCertSaveFile(void)
|
||||||
{
|
{
|
||||||
if (!g_esCertSaveCtx) return;
|
if (!g_esCertSaveCtx) return;
|
||||||
save_close_savefile(g_esCertSaveCtx);
|
|
||||||
g_esCertSaveCtx = NULL;
|
save_close_savefile(&g_esCertSaveCtx);
|
||||||
|
|
||||||
|
bisStorageUnmountPartition(FsBisPartitionId_System);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
|
static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
|
||||||
|
|
532
source/core/devoptab/fat_dev.c
Normal file
532
source/core/devoptab/fat_dev.c
Normal file
|
@ -0,0 +1,532 @@
|
||||||
|
/*
|
||||||
|
* fat_dev.c
|
||||||
|
*
|
||||||
|
* Loosely based on ff_dev.c from libusbhsfs.
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <core/nxdt_utils.h>
|
||||||
|
#include <core/devoptab/nxdt_devoptab.h>
|
||||||
|
#include <core/devoptab/ro_dev.h>
|
||||||
|
#include <core/fatfs/ff.h>
|
||||||
|
|
||||||
|
/* Helper macros. */
|
||||||
|
|
||||||
|
#define FAT_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(FIL)
|
||||||
|
#define FAT_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(FDIR)
|
||||||
|
#define FAT_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(FATFS)
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static int fatdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode);
|
||||||
|
static int fatdev_close(struct _reent *r, void *fd);
|
||||||
|
static ssize_t fatdev_read(struct _reent *r, void *fd, char *ptr, size_t len);
|
||||||
|
static off_t fatdev_seek(struct _reent *r, void *fd, off_t pos, int dir);
|
||||||
|
static int fatdev_stat(struct _reent *r, const char *file, struct stat *st);
|
||||||
|
static DIR_ITER* fatdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path);
|
||||||
|
static int fatdev_dirreset(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int fatdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat);
|
||||||
|
static int fatdev_dirclose(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int fatdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf);
|
||||||
|
|
||||||
|
static const char *fatdev_get_fixed_path(struct _reent *r, const char *path, FATFS *fatfs);
|
||||||
|
|
||||||
|
static void fatdev_fill_stat(struct stat *st, const FILINFO *info);
|
||||||
|
|
||||||
|
static int fatdev_translate_error(FRESULT res);
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
__thread char g_fatDevicePathBuffer[FS_MAX_PATH] = {0};
|
||||||
|
|
||||||
|
static const devoptab_t fatdev_devoptab = {
|
||||||
|
.name = NULL,
|
||||||
|
.structSize = sizeof(FIL),
|
||||||
|
.open_r = fatdev_open,
|
||||||
|
.close_r = fatdev_close,
|
||||||
|
.write_r = rodev_write, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.read_r = fatdev_read,
|
||||||
|
.seek_r = fatdev_seek,
|
||||||
|
.fstat_r = rodev_fstat, ///< Not supported by FatFs.
|
||||||
|
.stat_r = fatdev_stat,
|
||||||
|
.link_r = rodev_link, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.unlink_r = rodev_unlink, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.chdir_r = rodev_chdir, ///< No need to deal with cwd shenanigans, so we won't support it.
|
||||||
|
.rename_r = rodev_rename, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.mkdir_r = rodev_mkdir, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.dirStateSize = sizeof(FDIR),
|
||||||
|
.diropen_r = fatdev_diropen,
|
||||||
|
.dirreset_r = fatdev_dirreset,
|
||||||
|
.dirnext_r = fatdev_dirnext,
|
||||||
|
.dirclose_r = fatdev_dirclose,
|
||||||
|
.statvfs_r = fatdev_statvfs,
|
||||||
|
.ftruncate_r = rodev_ftruncate, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.fsync_r = rodev_fsync, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.deviceData = NULL,
|
||||||
|
.chmod_r = rodev_chmod, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.fchmod_r = rodev_fchmod, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.rmdir_r = rodev_rmdir, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.lstat_r = fatdev_stat, ///< Symlinks aren't supported, so we'll just alias lstat() to stat().
|
||||||
|
.utimes_r = rodev_utimes, ///< Supported by FatFs, but disabled on purpose.
|
||||||
|
.fpathconf_r = rodev_fpathconf, ///< Not supported by FatFs.
|
||||||
|
.pathconf_r = rodev_pathconf, ///< Not supported by FatFs.
|
||||||
|
.symlink_r = rodev_symlink, ///< Not supported by FatFs.
|
||||||
|
.readlink_r = rodev_readlink ///< Not supported by FatFs.
|
||||||
|
};
|
||||||
|
|
||||||
|
const devoptab_t *fatdev_get_devoptab()
|
||||||
|
{
|
||||||
|
return &fatdev_devoptab;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode)
|
||||||
|
{
|
||||||
|
NX_IGNORE_ARG(mode);
|
||||||
|
|
||||||
|
BYTE fatdev_flags = (FA_READ | FA_OPEN_EXISTING);
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_FILE_VARS;
|
||||||
|
FAT_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
|
/* Validate input. */
|
||||||
|
if (!file || (flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC | O_EXCL))) DEVOPTAB_SET_ERROR_AND_EXIT(EROFS);
|
||||||
|
|
||||||
|
/* Get fixed path. */
|
||||||
|
if (!(path = fatdev_get_fixed_path(r, path, fs_ctx))) DEVOPTAB_EXIT;
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Opening \"%s\" with flags 0x%X (volume \"%s:\").", path, fatdev_flags, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Reset file descriptor. */
|
||||||
|
memset(file, 0, sizeof(FIL));
|
||||||
|
|
||||||
|
/* Open file. */
|
||||||
|
res = f_open(file, path, fatdev_flags);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_close(struct _reent *r, void *fd)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_FILE_VARS;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Closing file from \"%u:\" (volume \"%s:\").", file->obj.fs->pdrv, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Close file. */
|
||||||
|
res = f_close(file);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR_AND_EXIT(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Reset file descriptor. */
|
||||||
|
memset(file, 0, sizeof(FIL));
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t fatdev_read(struct _reent *r, void *fd, char *ptr, size_t len)
|
||||||
|
{
|
||||||
|
UINT br = 0;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_FILE_VARS;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !ptr || !len) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the file was opened with read access. */
|
||||||
|
if (!(file->flag & FA_READ)) DEVOPTAB_SET_ERROR_AND_EXIT(EBADF);
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from file in \"%u:\" (volume \"%s:\").", len, f_tell(file), ((FATFS*)dev_ctx->fs_ctx)->pdrv, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Read file data. */
|
||||||
|
res = f_read(file, ptr, (UINT)len, &br);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT((ssize_t)br);
|
||||||
|
}
|
||||||
|
|
||||||
|
static off_t fatdev_seek(struct _reent *r, void *fd, off_t pos, int dir)
|
||||||
|
{
|
||||||
|
off_t offset = 0;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_FILE_VARS;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
/* Find the offset to seek from. */
|
||||||
|
switch(dir)
|
||||||
|
{
|
||||||
|
case SEEK_SET: /* Set absolute position relative to zero (start offset). */
|
||||||
|
break;
|
||||||
|
case SEEK_CUR: /* Set position relative to the current position. */
|
||||||
|
offset = (off_t)f_tell(file);
|
||||||
|
break;
|
||||||
|
case SEEK_END: /* Set position relative to EOF. */
|
||||||
|
offset = (off_t)f_size(file);
|
||||||
|
break;
|
||||||
|
default: /* Invalid option. */
|
||||||
|
DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't allow negative seeks beyond the beginning of file. */
|
||||||
|
if (pos < 0 && offset < -pos) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
/* Calculate actual offset. */
|
||||||
|
offset += pos;
|
||||||
|
|
||||||
|
/* Don't allow positive seeks beyond the end of file. */
|
||||||
|
if (offset > (off_t)f_size(file)) DEVOPTAB_SET_ERROR_AND_EXIT(EOVERFLOW);
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Seeking to offset 0x%lX from file in \"%u:\" (volume \"%s:\").", offset, file->obj.fs->pdrv, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Perform file seek. */
|
||||||
|
res = f_lseek(file, (FSIZE_t)offset);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_stat(struct _reent *r, const char *file, struct stat *st)
|
||||||
|
{
|
||||||
|
FILINFO info = {0};
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
DEVOPTAB_INIT_VARS;
|
||||||
|
FAT_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !st) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
/* Get fixed path. */
|
||||||
|
if (!(file = fatdev_get_fixed_path(r, file, fs_ctx))) DEVOPTAB_EXIT;
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Getting file stats for \"%s\" (volume \"%s:\").", file, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Get stats. */
|
||||||
|
res = f_stat(file, &info);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR_AND_EXIT(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
fatdev_fill_stat(st, &info);
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DIR_ITER *fatdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
DIR_ITER *ret = NULL;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_DIR_VARS;
|
||||||
|
FAT_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
|
/* Get fixed path. */
|
||||||
|
if (!(path = fatdev_get_fixed_path(r, path, fs_ctx))) DEVOPTAB_EXIT;
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Opening directory \"%s\" (volume \"%s:\").", path, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
memset(dir, 0, sizeof(FDIR));
|
||||||
|
|
||||||
|
/* Open directory. */
|
||||||
|
res = f_opendir(dir, path);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR_AND_EXIT(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = dirState;
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_PTR(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_dirreset(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_DIR_VARS;
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Resetting state for directory in \"%u:\" (volume \"%s:\").", dir->obj.fs->pdrv, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
res = f_rewinddir(dir);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat)
|
||||||
|
{
|
||||||
|
FILINFO info = {0};
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_DIR_VARS;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!filename || !filestat) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Getting info from next directory entry in \"%u:\" (volume \"%s:\").", dir->obj.fs->pdrv, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Read directory. */
|
||||||
|
res = f_readdir(dir, &info);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR_AND_EXIT(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Check if we haven't reached EOD. */
|
||||||
|
/* FatFs returns an empty string if so. */
|
||||||
|
if (info.fname[0])
|
||||||
|
{
|
||||||
|
/* Copy filename. */
|
||||||
|
strcpy(filename, info.fname);
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
fatdev_fill_stat(filestat, &info);
|
||||||
|
} else {
|
||||||
|
/* ENOENT signals EOD. */
|
||||||
|
DEVOPTAB_SET_ERROR(ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_dirclose(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
FAT_DEV_INIT_DIR_VARS;
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Closing directory from \"%s:\" (volume \"%u:\").", dir->obj.fs->pdrv, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Close directory. */
|
||||||
|
res = f_closedir(dir);
|
||||||
|
if (res != FR_OK) DEVOPTAB_SET_ERROR_AND_EXIT(fatdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
memset(dir, 0, sizeof(FDIR));
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf)
|
||||||
|
{
|
||||||
|
NX_IGNORE_ARG(path);
|
||||||
|
|
||||||
|
DEVOPTAB_INIT_VARS;
|
||||||
|
FAT_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Getting filesystem stats for \"%u:\" (volume \"%s:\").", fs_ctx->pdrv, dev_ctx->name);
|
||||||
|
|
||||||
|
/* Fill filesystem stats. */
|
||||||
|
memset(buf, 0, sizeof(struct statvfs));
|
||||||
|
|
||||||
|
buf->f_bsize = FF_MIN_SS; /* Sector size. */
|
||||||
|
buf->f_frsize = FF_MIN_SS; /* Sector size. */
|
||||||
|
buf->f_blocks = ((fs_ctx->n_fatent - 2) * (DWORD)fs_ctx->csize); /* Total cluster count * cluster size in sectors. */
|
||||||
|
buf->f_bfree = 0;
|
||||||
|
buf->f_bavail = 0;
|
||||||
|
buf->f_files = 0;
|
||||||
|
buf->f_ffree = 0;
|
||||||
|
buf->f_favail = 0;
|
||||||
|
buf->f_fsid = 0;
|
||||||
|
buf->f_flag = ST_NOSUID;
|
||||||
|
buf->f_namemax = FF_LFN_BUF;
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_DEINIT_VARS;
|
||||||
|
DEVOPTAB_RETURN_INT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *fatdev_get_fixed_path(struct _reent *r, const char *path, FATFS *fatfs)
|
||||||
|
{
|
||||||
|
const u8 *p = (const u8*)path;
|
||||||
|
ssize_t units = 0;
|
||||||
|
u32 code = 0;
|
||||||
|
size_t len = 0;
|
||||||
|
char name[DEVOPTAB_MOUNT_NAME_LENGTH] = {0};
|
||||||
|
|
||||||
|
if (!r || !path || !*path || !fatfs) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Input path: \"%s\".", path);
|
||||||
|
|
||||||
|
/* Generate FatFs mount name ID. */
|
||||||
|
snprintf(name, sizeof(name), "%u:", fatfs->pdrv);
|
||||||
|
|
||||||
|
/* Move the path pointer to the start of the actual path. */
|
||||||
|
do {
|
||||||
|
units = decode_utf8(&code, p);
|
||||||
|
if (units < 0) DEVOPTAB_SET_ERROR_AND_EXIT(EILSEQ);
|
||||||
|
p += units;
|
||||||
|
} while(code >= ' ' && code != ':');
|
||||||
|
|
||||||
|
/* We found a colon; p points to the actual path. */
|
||||||
|
if (code == ':') path = (const char*)p;
|
||||||
|
|
||||||
|
/* Make sure the provided path starts with a slash. */
|
||||||
|
if (path[0] != '/') DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
|
/* Make sure there are no more colons and that the remainder of the string is valid UTF-8. */
|
||||||
|
p = (const u8*)path;
|
||||||
|
|
||||||
|
do {
|
||||||
|
units = decode_utf8(&code, p);
|
||||||
|
if (units < 0) DEVOPTAB_SET_ERROR_AND_EXIT(EILSEQ);
|
||||||
|
if (code == ':') DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
p += units;
|
||||||
|
} while(code >= ' ');
|
||||||
|
|
||||||
|
/* Verify fixed path length. */
|
||||||
|
len = (strlen(name) + strlen(path));
|
||||||
|
if (len >= sizeof(g_fatDevicePathBuffer)) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG);
|
||||||
|
|
||||||
|
/* Generate fixed path. */
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wformat-truncation"
|
||||||
|
snprintf(g_fatDevicePathBuffer, sizeof(g_fatDevicePathBuffer), "%s%s", name, path);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("Fixed path: \"%s\".", g_fatDevicePathBuffer);
|
||||||
|
|
||||||
|
end:
|
||||||
|
DEVOPTAB_RETURN_PTR(g_fatDevicePathBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fatdev_fill_stat(struct stat *st, const FILINFO *info)
|
||||||
|
{
|
||||||
|
struct tm timeinfo = {0};
|
||||||
|
|
||||||
|
/* Clear stat struct. */
|
||||||
|
memset(st, 0, sizeof(struct stat));
|
||||||
|
|
||||||
|
/* Fill stat struct. */
|
||||||
|
st->st_nlink = 1;
|
||||||
|
|
||||||
|
if (info->fattrib & AM_DIR)
|
||||||
|
{
|
||||||
|
/* We're dealing with a directory entry. */
|
||||||
|
st->st_mode = (S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH);
|
||||||
|
} else {
|
||||||
|
/* We're dealing with a file entry. */
|
||||||
|
st->st_size = (off_t)info->fsize;
|
||||||
|
st->st_mode = (S_IFREG | S_IRUSR | S_IRGRP | S_IROTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert date/time into an actual UTC POSIX timestamp using the system local time. */
|
||||||
|
timeinfo.tm_year = (((info->fdate >> 9) & 0x7F) + 80); /* DOS time: offset since 1980. POSIX time: offset since 1900. */
|
||||||
|
timeinfo.tm_mon = (((info->fdate >> 5) & 0xF) - 1); /* DOS time: 1-12 range (inclusive). POSIX time: 0-11 range (inclusive). */
|
||||||
|
timeinfo.tm_mday = (info->fdate & 0x1F);
|
||||||
|
timeinfo.tm_hour = ((info->ftime >> 11) & 0x1F);
|
||||||
|
timeinfo.tm_min = ((info->ftime >> 5) & 0x3F);
|
||||||
|
timeinfo.tm_sec = ((info->ftime & 0x1F) << 1); /* DOS time: 2-second intervals with a 0-29 range (inclusive, 58 seconds max). POSIX time: 0-59 range (inclusive). */
|
||||||
|
|
||||||
|
st->st_atime = 0; /* Not returned by FatFs + only available under exFAT. */
|
||||||
|
st->st_mtime = mktime(&timeinfo);
|
||||||
|
st->st_ctime = 0; /* Not returned by FatFs + only available under exFAT. */
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("DOS timestamp: 0x%04X%04X. Generated POSIX timestamp: %lu.", info->fdate, info->ftime, st->st_mtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fatdev_translate_error(FRESULT res)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
switch(res)
|
||||||
|
{
|
||||||
|
case FR_OK:
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
case FR_DISK_ERR:
|
||||||
|
case FR_NOT_READY:
|
||||||
|
ret = EIO;
|
||||||
|
break;
|
||||||
|
case FR_INT_ERR:
|
||||||
|
case FR_INVALID_NAME:
|
||||||
|
case FR_INVALID_PARAMETER:
|
||||||
|
ret = EINVAL;
|
||||||
|
break;
|
||||||
|
case FR_NO_FILE:
|
||||||
|
case FR_NO_PATH:
|
||||||
|
ret = ENOENT;
|
||||||
|
break;
|
||||||
|
case FR_DENIED:
|
||||||
|
ret = EACCES;
|
||||||
|
break;
|
||||||
|
case FR_EXIST:
|
||||||
|
ret = EEXIST;
|
||||||
|
break;
|
||||||
|
case FR_INVALID_OBJECT:
|
||||||
|
ret = EFAULT;
|
||||||
|
break;
|
||||||
|
case FR_WRITE_PROTECTED:
|
||||||
|
ret = EROFS;
|
||||||
|
break;
|
||||||
|
case FR_INVALID_DRIVE:
|
||||||
|
ret = ENODEV;
|
||||||
|
break;
|
||||||
|
case FR_NOT_ENABLED:
|
||||||
|
ret = ENOEXEC;
|
||||||
|
break;
|
||||||
|
case FR_NO_FILESYSTEM:
|
||||||
|
ret = ENFILE;
|
||||||
|
break;
|
||||||
|
case FR_TIMEOUT:
|
||||||
|
ret = EAGAIN;
|
||||||
|
break;
|
||||||
|
case FR_LOCKED:
|
||||||
|
ret = EBUSY;
|
||||||
|
break;
|
||||||
|
case FR_NOT_ENOUGH_CORE:
|
||||||
|
ret = ENOMEM;
|
||||||
|
break;
|
||||||
|
case FR_TOO_MANY_OPEN_FILES:
|
||||||
|
ret = EMFILE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = EPERM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//LOG_MSG_DEBUG("FRESULT: %u. Translated errno: %d.", res, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -27,9 +27,8 @@
|
||||||
|
|
||||||
/* Helper macros. */
|
/* Helper macros. */
|
||||||
|
|
||||||
#define HFS_DEV_INIT_VARS DEVOPTAB_INIT_VARS(HashFileSystemContext)
|
#define HFS_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(HashFileSystemFileState)
|
||||||
#define HFS_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(HashFileSystemContext, HashFileSystemFileState)
|
#define HFS_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(HashFileSystemDirectoryState)
|
||||||
#define HFS_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(HashFileSystemContext, HashFileSystemDirectoryState)
|
|
||||||
#define HFS_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(HashFileSystemContext)
|
#define HFS_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(HashFileSystemContext)
|
||||||
|
|
||||||
/* Type definitions. */
|
/* Type definitions. */
|
||||||
|
@ -236,7 +235,7 @@ static int hfsdev_stat(struct _reent *r, const char *file, struct stat *st)
|
||||||
u32 index = 0;
|
u32 index = 0;
|
||||||
HashFileSystemEntry *hfs_entry = NULL;
|
HashFileSystemEntry *hfs_entry = NULL;
|
||||||
|
|
||||||
HFS_DEV_INIT_VARS;
|
DEVOPTAB_INIT_VARS;
|
||||||
HFS_DEV_INIT_FS_ACCESS;
|
HFS_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
/* Sanity check. */
|
/* Sanity check. */
|
||||||
|
@ -368,13 +367,13 @@ static int hfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *bu
|
||||||
|
|
||||||
u64 ext_fs_size = 0;
|
u64 ext_fs_size = 0;
|
||||||
|
|
||||||
HFS_DEV_INIT_VARS;
|
DEVOPTAB_INIT_VARS;
|
||||||
HFS_DEV_INIT_FS_ACCESS;
|
HFS_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
/* Sanity check. */
|
/* Sanity check. */
|
||||||
if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
//LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name);
|
//LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\".", dev_ctx->name);
|
||||||
|
|
||||||
/* Get Hash FS total data size. */
|
/* Get Hash FS total data size. */
|
||||||
if (!hfsGetTotalDataSize(fs_ctx, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO);
|
if (!hfsGetTotalDataSize(fs_ctx, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO);
|
||||||
|
@ -407,8 +406,6 @@ static const char *hfsdev_get_truncated_path(struct _reent *r, const char *path)
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
bool path_sep_skipped = false;
|
bool path_sep_skipped = false;
|
||||||
|
|
||||||
DEVOPTAB_DECL_ERROR_STATE;
|
|
||||||
|
|
||||||
if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
//LOG_MSG_DEBUG("Input path: \"%s\".", path);
|
//LOG_MSG_DEBUG("Input path: \"%s\".", path);
|
||||||
|
@ -440,7 +437,7 @@ static const char *hfsdev_get_truncated_path(struct _reent *r, const char *path)
|
||||||
p += units;
|
p += units;
|
||||||
} while(code >= ' ');
|
} while(code >= ' ');
|
||||||
|
|
||||||
/* Verify fixed path length. */
|
/* Verify truncated path length. */
|
||||||
len = strlen(path);
|
len = strlen(path);
|
||||||
if (!len && !path_sep_skipped) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!len && !path_sep_skipped) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG);
|
if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG);
|
||||||
|
|
|
@ -30,12 +30,14 @@ typedef enum {
|
||||||
DevoptabDeviceType_PartitionFileSystem = 0,
|
DevoptabDeviceType_PartitionFileSystem = 0,
|
||||||
DevoptabDeviceType_HashFileSystem = 1,
|
DevoptabDeviceType_HashFileSystem = 1,
|
||||||
DevoptabDeviceType_RomFileSystem = 2,
|
DevoptabDeviceType_RomFileSystem = 2,
|
||||||
DevoptabDeviceType_Count = 3 ///< Total values supported by this enum.
|
DevoptabDeviceType_FatFs = 3,
|
||||||
|
DevoptabDeviceType_Count = 4 ///< Total values supported by this enum.
|
||||||
} DevoptabDeviceType;
|
} DevoptabDeviceType;
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static Mutex g_devoptabMutex = 0;
|
static Mutex g_devoptabMutex = 0;
|
||||||
|
|
||||||
static DevoptabDeviceContext g_devoptabDevices[DEVOPTAB_DEVICE_COUNT] = {0};
|
static DevoptabDeviceContext g_devoptabDevices[DEVOPTAB_DEVICE_COUNT] = {0};
|
||||||
static const u32 g_devoptabDeviceCount = MAX_ELEMENTS(g_devoptabDevices);
|
static const u32 g_devoptabDeviceCount = MAX_ELEMENTS(g_devoptabDevices);
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ static const u32 g_devoptabDeviceCount = MAX_ELEMENTS(g_devoptabDevices);
|
||||||
const devoptab_t *pfsdev_get_devoptab();
|
const devoptab_t *pfsdev_get_devoptab();
|
||||||
const devoptab_t *hfsdev_get_devoptab();
|
const devoptab_t *hfsdev_get_devoptab();
|
||||||
const devoptab_t *romfsdev_get_devoptab();
|
const devoptab_t *romfsdev_get_devoptab();
|
||||||
|
const devoptab_t *fatdev_get_devoptab();
|
||||||
|
|
||||||
static bool devoptabMountDevice(void *fs_ctx, const char *name, u8 type);
|
static bool devoptabMountDevice(void *fs_ctx, const char *name, u8 type);
|
||||||
static DevoptabDeviceContext *devoptabFindDevice(const char *name);
|
static DevoptabDeviceContext *devoptabFindDevice(const char *name);
|
||||||
|
@ -94,6 +97,21 @@ bool devoptabMountRomFileSystemDevice(RomFileSystemContext *romfs_ctx, const cha
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool devoptabMountFatFsDevice(FATFS *fatfs, const char *name)
|
||||||
|
{
|
||||||
|
if (!fatfs || !fatfs->fs_type || !name || !*name)
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Invalid parameters!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_devoptabMutex) ret = devoptabMountDevice(fatfs, name, DevoptabDeviceType_FatFs);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void devoptabUnmountDevice(const char *name)
|
void devoptabUnmountDevice(const char *name)
|
||||||
{
|
{
|
||||||
if (!name || !*name)
|
if (!name || !*name)
|
||||||
|
@ -111,7 +129,7 @@ void devoptabUnmountDevice(const char *name)
|
||||||
/* Reset device. */
|
/* Reset device. */
|
||||||
devoptabResetDevice(dev_ctx);
|
devoptabResetDevice(dev_ctx);
|
||||||
} else {
|
} else {
|
||||||
LOG_MSG_ERROR("Error: unable to find devoptab device \"%s\".", name);
|
LOG_MSG_ERROR("Unable to find devoptab device \"%s\".", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +172,7 @@ static bool devoptabMountDevice(void *fs_ctx, const char *name, u8 type)
|
||||||
/* Retrieve a pointer to the first unused device entry. */
|
/* Retrieve a pointer to the first unused device entry. */
|
||||||
if (!(dev_ctx = devoptabFindDevice(NULL)))
|
if (!(dev_ctx = devoptabFindDevice(NULL)))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Error: unable to find an empty device slot for \"%s\" (type 0x%02X).", name, type);
|
LOG_MSG_ERROR("Unable to find an empty device slot for \"%s\" (type 0x%02X).", name, type);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,13 +188,16 @@ static bool devoptabMountDevice(void *fs_ctx, const char *name, u8 type)
|
||||||
case DevoptabDeviceType_RomFileSystem:
|
case DevoptabDeviceType_RomFileSystem:
|
||||||
device = romfsdev_get_devoptab();
|
device = romfsdev_get_devoptab();
|
||||||
break;
|
break;
|
||||||
|
case DevoptabDeviceType_FatFs:
|
||||||
|
device = fatdev_get_devoptab();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!device)
|
if (!device)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Error: unable to retrieve a devoptab interface for \"%s\" (type 0x%02X).", name, type);
|
LOG_MSG_ERROR("Unable to retrieve a devoptab interface for \"%s\" (type %u).", name, type);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +216,7 @@ static bool devoptabMountDevice(void *fs_ctx, const char *name, u8 type)
|
||||||
int res = AddDevice(&(dev_ctx->device));
|
int res = AddDevice(&(dev_ctx->device));
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Error: AddDevice failed! (%d).", res);
|
LOG_MSG_ERROR("AddDevice failed! (%d).", res);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,8 @@
|
||||||
|
|
||||||
/* Helper macros. */
|
/* Helper macros. */
|
||||||
|
|
||||||
#define ROMFS_DEV_INIT_VARS DEVOPTAB_INIT_VARS(RomFileSystemContext)
|
#define ROMFS_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(RomFileSystemFileState)
|
||||||
#define ROMFS_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(RomFileSystemContext, RomFileSystemFileState)
|
#define ROMFS_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(RomFileSystemDirectoryState)
|
||||||
#define ROMFS_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(RomFileSystemContext, RomFileSystemDirectoryState)
|
|
||||||
#define ROMFS_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(RomFileSystemContext)
|
#define ROMFS_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(RomFileSystemContext)
|
||||||
|
|
||||||
#define ROMFS_FILE_INODE(file) ((u64)(file - fs_ctx->file_table) + (fs_ctx->dir_table_size / 4))
|
#define ROMFS_FILE_INODE(file) ((u64)(file - fs_ctx->file_table) + (fs_ctx->dir_table_size / 4))
|
||||||
|
@ -242,7 +241,7 @@ static int romfsdev_stat(struct _reent *r, const char *file, struct stat *st)
|
||||||
{
|
{
|
||||||
RomFileSystemFileEntry *file_entry = NULL;
|
RomFileSystemFileEntry *file_entry = NULL;
|
||||||
|
|
||||||
ROMFS_DEV_INIT_VARS;
|
DEVOPTAB_INIT_VARS;
|
||||||
ROMFS_DEV_INIT_FS_ACCESS;
|
ROMFS_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
/* Sanity check. */
|
/* Sanity check. */
|
||||||
|
@ -397,13 +396,13 @@ static int romfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *
|
||||||
|
|
||||||
u64 ext_fs_size = 0;
|
u64 ext_fs_size = 0;
|
||||||
|
|
||||||
ROMFS_DEV_INIT_VARS;
|
DEVOPTAB_INIT_VARS;
|
||||||
ROMFS_DEV_INIT_FS_ACCESS;
|
ROMFS_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
/* Sanity check. */
|
/* Sanity check. */
|
||||||
if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
//LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name);
|
//LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\".", dev_ctx->name);
|
||||||
|
|
||||||
/* Get RomFS total data size. */
|
/* Get RomFS total data size. */
|
||||||
if (!romfsGetTotalDataSize(fs_ctx, false, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO);
|
if (!romfsGetTotalDataSize(fs_ctx, false, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO);
|
||||||
|
@ -435,8 +434,6 @@ static const char *romfsdev_get_truncated_path(struct _reent *r, const char *pat
|
||||||
u32 code = 0;
|
u32 code = 0;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
DEVOPTAB_DECL_ERROR_STATE;
|
|
||||||
|
|
||||||
if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
//LOG_MSG_DEBUG("Input path: \"%s\".", path);
|
//LOG_MSG_DEBUG("Input path: \"%s\".", path);
|
||||||
|
@ -464,7 +461,7 @@ static const char *romfsdev_get_truncated_path(struct _reent *r, const char *pat
|
||||||
p += units;
|
p += units;
|
||||||
} while(code >= ' ');
|
} while(code >= ' ');
|
||||||
|
|
||||||
/* Verify fixed path length. */
|
/* Verify truncated path length. */
|
||||||
len = strlen(path);
|
len = strlen(path);
|
||||||
if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG);
|
if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG);
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,8 @@
|
||||||
|
|
||||||
/* Helper macros. */
|
/* Helper macros. */
|
||||||
|
|
||||||
#define PFS_DEV_INIT_VARS DEVOPTAB_INIT_VARS(PartitionFileSystemContext)
|
#define PFS_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(PartitionFileSystemFileState)
|
||||||
#define PFS_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(PartitionFileSystemContext, PartitionFileSystemFileState)
|
#define PFS_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(PartitionFileSystemDirectoryState)
|
||||||
#define PFS_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(PartitionFileSystemContext, PartitionFileSystemDirectoryState)
|
|
||||||
#define PFS_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(PartitionFileSystemContext)
|
#define PFS_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(PartitionFileSystemContext)
|
||||||
|
|
||||||
/* Type definitions. */
|
/* Type definitions. */
|
||||||
|
@ -236,7 +235,7 @@ static int pfsdev_stat(struct _reent *r, const char *file, struct stat *st)
|
||||||
u32 index = 0;
|
u32 index = 0;
|
||||||
PartitionFileSystemEntry *pfs_entry = NULL;
|
PartitionFileSystemEntry *pfs_entry = NULL;
|
||||||
|
|
||||||
PFS_DEV_INIT_VARS;
|
DEVOPTAB_INIT_VARS;
|
||||||
PFS_DEV_INIT_FS_ACCESS;
|
PFS_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
/* Sanity check. */
|
/* Sanity check. */
|
||||||
|
@ -368,13 +367,13 @@ static int pfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *bu
|
||||||
|
|
||||||
u64 ext_fs_size = 0;
|
u64 ext_fs_size = 0;
|
||||||
|
|
||||||
PFS_DEV_INIT_VARS;
|
DEVOPTAB_INIT_VARS;
|
||||||
PFS_DEV_INIT_FS_ACCESS;
|
PFS_DEV_INIT_FS_ACCESS;
|
||||||
|
|
||||||
/* Sanity check. */
|
/* Sanity check. */
|
||||||
if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
//LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name);
|
//LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\".", dev_ctx->name);
|
||||||
|
|
||||||
/* Get Partition FS total data size. */
|
/* Get Partition FS total data size. */
|
||||||
if (!pfsGetTotalDataSize(fs_ctx, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO);
|
if (!pfsGetTotalDataSize(fs_ctx, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO);
|
||||||
|
@ -407,8 +406,6 @@ static const char *pfsdev_get_truncated_path(struct _reent *r, const char *path)
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
bool path_sep_skipped = false;
|
bool path_sep_skipped = false;
|
||||||
|
|
||||||
DEVOPTAB_DECL_ERROR_STATE;
|
|
||||||
|
|
||||||
if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
|
|
||||||
//LOG_MSG_DEBUG("Input path: \"%s\".", path);
|
//LOG_MSG_DEBUG("Input path: \"%s\".", path);
|
||||||
|
@ -440,7 +437,7 @@ static const char *pfsdev_get_truncated_path(struct _reent *r, const char *path)
|
||||||
p += units;
|
p += units;
|
||||||
} while(code >= ' ');
|
} while(code >= ' ');
|
||||||
|
|
||||||
/* Verify fixed path length. */
|
/* Verify truncated path length. */
|
||||||
len = strlen(path);
|
len = strlen(path);
|
||||||
if (!len && !path_sep_skipped) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
if (!len && !path_sep_skipped) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL);
|
||||||
if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG);
|
if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG);
|
||||||
|
|
|
@ -31,6 +31,14 @@ ssize_t rodev_write(struct _reent *r, void *fd, const char *ptr, size_t len)
|
||||||
DEVOPTAB_RETURN_UNSUPPORTED_OP;
|
DEVOPTAB_RETURN_UNSUPPORTED_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int rodev_fstat(struct _reent *r, void *fd, struct stat *st)
|
||||||
|
{
|
||||||
|
NX_IGNORE_ARG(fd);
|
||||||
|
NX_IGNORE_ARG(st);
|
||||||
|
|
||||||
|
DEVOPTAB_RETURN_UNSUPPORTED_OP;
|
||||||
|
}
|
||||||
|
|
||||||
int rodev_link(struct _reent *r, const char *existing, const char *newLink)
|
int rodev_link(struct _reent *r, const char *existing, const char *newLink)
|
||||||
{
|
{
|
||||||
NX_IGNORE_ARG(existing);
|
NX_IGNORE_ARG(existing);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
/*-----------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
#include <core/nxdt_utils.h>
|
#include <core/nxdt_utils.h>
|
||||||
|
#include <core/bis_storage.h>
|
||||||
|
|
||||||
#include <core/fatfs/ff.h> /* Obtains integer types */
|
#include <core/fatfs/ff.h> /* Obtains integer types */
|
||||||
#include <core/fatfs/diskio.h> /* Declarations of disk functions */
|
#include <core/fatfs/diskio.h> /* Declarations of disk functions */
|
||||||
|
@ -20,7 +21,7 @@ DSTATUS disk_status (
|
||||||
BYTE pdrv /* Physical drive number to identify the drive */
|
BYTE pdrv /* Physical drive number to identify the drive */
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
(void)pdrv;
|
NX_IGNORE_ARG(pdrv);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ DSTATUS disk_initialize (
|
||||||
BYTE pdrv /* Physical drive number to identify the drive */
|
BYTE pdrv /* Physical drive number to identify the drive */
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
(void)pdrv;
|
NX_IGNORE_ARG(pdrv);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,15 +48,34 @@ DRESULT disk_read (
|
||||||
UINT count /* Number of sectors to read */
|
UINT count /* Number of sectors to read */
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
(void)pdrv;
|
FsStorage *bis_storage = NULL;
|
||||||
|
u64 offset = 0, size = 0;
|
||||||
Result rc = 0;
|
Result rc = 0;
|
||||||
u64 start_offset = ((u64)FF_MAX_SS * (u64)sector);
|
DRESULT ret = RES_PARERR;
|
||||||
u64 read_size = ((u64)FF_MAX_SS * (u64)count);
|
|
||||||
|
|
||||||
rc = fsStorageRead(utilsGetEmmcBisSystemPartitionStorage(), start_offset, buff, read_size);
|
bisStorageControlMutex(true);
|
||||||
|
|
||||||
return (R_SUCCEEDED(rc) ? RES_OK : RES_ERROR);
|
/* Get pointer to FsStorage object. */
|
||||||
|
bis_storage = bisStorageGetFsStorageByFatFsDriveNumber(pdrv);
|
||||||
|
if (!bis_storage)
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Failed to retrieve FsStorage object for drive number %u!", pdrv);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate data offset and size. */
|
||||||
|
offset = ((u64)FF_MAX_SS * (u64)sector);
|
||||||
|
size = ((u64)FF_MAX_SS * (u64)count);
|
||||||
|
|
||||||
|
/* Read BIS storage. */
|
||||||
|
rc = fsStorageRead(bis_storage, (s64)offset, buff, size);
|
||||||
|
ret = (R_SUCCEEDED(rc) ? RES_OK : RES_ERROR);
|
||||||
|
if (ret == RES_ERROR) LOG_MSG_ERROR("Failed to read 0x%lX-byte long block at offset 0x%lX from drive number %u!", offset, size, pdrv);
|
||||||
|
|
||||||
|
end:
|
||||||
|
bisStorageControlMutex(false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------*/
|
||||||
|
@ -71,10 +91,10 @@ DRESULT disk_write (
|
||||||
UINT count /* Number of sectors to write */
|
UINT count /* Number of sectors to write */
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
(void)pdrv;
|
NX_IGNORE_ARG(pdrv);
|
||||||
(void)buff;
|
NX_IGNORE_ARG(buff);
|
||||||
(void)sector;
|
NX_IGNORE_ARG(sector);
|
||||||
(void)count;
|
NX_IGNORE_ARG(count);
|
||||||
return RES_OK;
|
return RES_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +110,8 @@ DRESULT disk_ioctl (
|
||||||
void *buff /* Buffer to send/receive control data */
|
void *buff /* Buffer to send/receive control data */
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
(void)pdrv;
|
NX_IGNORE_ARG(pdrv);
|
||||||
(void)cmd;
|
NX_IGNORE_ARG(cmd);
|
||||||
(void)buff;
|
NX_IGNORE_ARG(buff);
|
||||||
return RES_OK;
|
return RES_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
#include <core/nxdt_bfsar.h>
|
#include <core/nxdt_bfsar.h>
|
||||||
#include <core/system_update.h>
|
#include <core/system_update.h>
|
||||||
#include <core/devoptab/nxdt_devoptab.h>
|
#include <core/devoptab/nxdt_devoptab.h>
|
||||||
#include <core/fatfs/ff.h>
|
#include <core/bis_storage.h>
|
||||||
|
|
||||||
/* Type definitions. */
|
/* Type definitions. */
|
||||||
|
|
||||||
|
@ -75,9 +75,6 @@ static bool g_isTerraUnit = false, g_isDevUnit = false;
|
||||||
|
|
||||||
static AppletType g_programAppletType = AppletType_None;
|
static AppletType g_programAppletType = AppletType_None;
|
||||||
|
|
||||||
static FsStorage g_emmcBisSystemPartitionStorage = {0};
|
|
||||||
static FATFS *g_emmcBisSystemPartitionFatFsObj = NULL;
|
|
||||||
|
|
||||||
static AppletHookCookie g_systemOverclockCookie = {0};
|
static AppletHookCookie g_systemOverclockCookie = {0};
|
||||||
|
|
||||||
static bool g_longRunningProcess = false;
|
static bool g_longRunningProcess = false;
|
||||||
|
@ -111,9 +108,6 @@ static bool utilsGetDevelopmentUnitFlag(void);
|
||||||
|
|
||||||
static bool utilsGetTerraUnitFlag(void);
|
static bool utilsGetTerraUnitFlag(void);
|
||||||
|
|
||||||
static bool utilsMountEmmcBisSystemPartitionStorage(void);
|
|
||||||
static void utilsUnmountEmmcBisSystemPartitionStorage(void);
|
|
||||||
|
|
||||||
static void utilsOverclockSystem(bool overclock);
|
static void utilsOverclockSystem(bool overclock);
|
||||||
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param);
|
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param);
|
||||||
|
|
||||||
|
@ -275,9 +269,6 @@ bool utilsInitializeResources(void)
|
||||||
/* Initialize system update interface. */
|
/* Initialize system update interface. */
|
||||||
if (!systemUpdateInitialize()) break;
|
if (!systemUpdateInitialize()) break;
|
||||||
|
|
||||||
/* Mount eMMC BIS System partition. */
|
|
||||||
if (!utilsMountEmmcBisSystemPartitionStorage()) break;
|
|
||||||
|
|
||||||
/* Mount application RomFS. */
|
/* Mount application RomFS. */
|
||||||
rc = romfsInit();
|
rc = romfsInit();
|
||||||
if (R_FAILED(rc))
|
if (R_FAILED(rc))
|
||||||
|
@ -347,6 +338,9 @@ void utilsCloseResources(void)
|
||||||
/* Unmount all custom devoptab devices. */
|
/* Unmount all custom devoptab devices. */
|
||||||
devoptabUnmountAllDevices();
|
devoptabUnmountAllDevices();
|
||||||
|
|
||||||
|
/* Unmount all eMMC BIS partitions. */
|
||||||
|
bisStorageUnmountAllPartitions();
|
||||||
|
|
||||||
/* Unset long running process state. */
|
/* Unset long running process state. */
|
||||||
utilsSetLongRunningProcessState(false);
|
utilsSetLongRunningProcessState(false);
|
||||||
|
|
||||||
|
@ -359,9 +353,6 @@ void utilsCloseResources(void)
|
||||||
/* Unmount application RomFS. */
|
/* Unmount application RomFS. */
|
||||||
romfsExit();
|
romfsExit();
|
||||||
|
|
||||||
/* Unmount eMMC BIS System partition. */
|
|
||||||
utilsUnmountEmmcBisSystemPartitionStorage();
|
|
||||||
|
|
||||||
/* Deinitialize system update interface. */
|
/* Deinitialize system update interface. */
|
||||||
systemUpdateExit();
|
systemUpdateExit();
|
||||||
|
|
||||||
|
@ -484,11 +475,6 @@ bool utilsIsAppletMode(void)
|
||||||
return (g_programAppletType > AppletType_Application && g_programAppletType < AppletType_SystemApplication);
|
return (g_programAppletType > AppletType_Application && g_programAppletType < AppletType_SystemApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
FsStorage *utilsGetEmmcBisSystemPartitionStorage(void)
|
|
||||||
{
|
|
||||||
return &g_emmcBisSystemPartitionStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
void utilsSetLongRunningProcessState(bool state)
|
void utilsSetLongRunningProcessState(bool state)
|
||||||
{
|
{
|
||||||
SCOPED_LOCK(&g_resourcesMutex)
|
SCOPED_LOCK(&g_resourcesMutex)
|
||||||
|
@ -1476,51 +1462,6 @@ static bool utilsGetTerraUnitFlag(void)
|
||||||
return R_SUCCEEDED(rc);
|
return R_SUCCEEDED(rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool utilsMountEmmcBisSystemPartitionStorage(void)
|
|
||||||
{
|
|
||||||
Result rc = 0;
|
|
||||||
FRESULT fr = FR_OK;
|
|
||||||
|
|
||||||
rc = fsOpenBisStorage(&g_emmcBisSystemPartitionStorage, FsBisPartitionId_System);
|
|
||||||
if (R_FAILED(rc))
|
|
||||||
{
|
|
||||||
LOG_MSG_ERROR("Failed to open eMMC BIS System partition storage! (0x%X).", rc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_emmcBisSystemPartitionFatFsObj = calloc(1, sizeof(FATFS));
|
|
||||||
if (!g_emmcBisSystemPartitionFatFsObj)
|
|
||||||
{
|
|
||||||
LOG_MSG_ERROR("Unable to allocate memory for FatFs element!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fr = f_mount(g_emmcBisSystemPartitionFatFsObj, BIS_SYSTEM_PARTITION_MOUNT_NAME, 1);
|
|
||||||
if (fr != FR_OK)
|
|
||||||
{
|
|
||||||
LOG_MSG_ERROR("Failed to mount eMMC BIS System partition! (%u).", fr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void utilsUnmountEmmcBisSystemPartitionStorage(void)
|
|
||||||
{
|
|
||||||
if (g_emmcBisSystemPartitionFatFsObj)
|
|
||||||
{
|
|
||||||
f_unmount(BIS_SYSTEM_PARTITION_MOUNT_NAME);
|
|
||||||
free(g_emmcBisSystemPartitionFatFsObj);
|
|
||||||
g_emmcBisSystemPartitionFatFsObj = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serviceIsActive(&(g_emmcBisSystemPartitionStorage.s)))
|
|
||||||
{
|
|
||||||
fsStorageClose(&g_emmcBisSystemPartitionStorage);
|
|
||||||
memset(&g_emmcBisSystemPartitionStorage, 0, sizeof(FsStorage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void utilsOverclockSystem(bool overclock)
|
static void utilsOverclockSystem(bool overclock)
|
||||||
{
|
{
|
||||||
u32 cpu_rate = ((overclock ? CPU_CLKRT_OVERCLOCKED : CPU_CLKRT_NORMAL) * 1000000);
|
u32 cpu_rate = ((overclock ? CPU_CLKRT_OVERCLOCKED : CPU_CLKRT_NORMAL) * 1000000);
|
||||||
|
|
|
@ -237,7 +237,7 @@ static remap_entry_ctx_t *save_remap_get_map_entry(remap_storage_ctx_t *ctx, u64
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 save_remap_read(remap_storage_ctx_t *ctx, void *buffer, u64 offset, size_t count)
|
static u64 save_remap_read(remap_storage_ctx_t *ctx, void *buffer, u64 offset, size_t count)
|
||||||
{
|
{
|
||||||
if (!ctx || (ctx->type == STORAGE_BYTES && !ctx->file) || (ctx->type == STORAGE_DUPLEX && !ctx->duplex) || (ctx->type != STORAGE_BYTES && ctx->type != STORAGE_DUPLEX) || !buffer || !count)
|
if (!ctx || (ctx->type == STORAGE_BYTES && !ctx->file) || (ctx->type == STORAGE_DUPLEX && !ctx->duplex) || (ctx->type != STORAGE_BYTES && ctx->type != STORAGE_DUPLEX) || !buffer || !count)
|
||||||
{
|
{
|
||||||
|
@ -253,42 +253,43 @@ static u32 save_remap_read(remap_storage_ctx_t *ctx, void *buffer, u64 offset, s
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 in_pos = offset;
|
u64 in_pos = offset;
|
||||||
u32 out_pos = 0;
|
u64 out_pos = 0;
|
||||||
u32 remaining = count;
|
u64 remaining = count;
|
||||||
|
|
||||||
UINT br = 0;
|
int res = 0;
|
||||||
FRESULT fr;
|
|
||||||
|
|
||||||
while(remaining)
|
while(remaining)
|
||||||
{
|
{
|
||||||
u64 entry_pos = (in_pos - entry->virtual_offset);
|
u64 entry_pos = (in_pos - entry->virtual_offset);
|
||||||
u32 bytes_to_read = ((entry->virtual_offset_end - in_pos) < remaining ? (u32)(entry->virtual_offset_end - in_pos) : remaining);
|
u64 bytes_to_read = ((entry->virtual_offset_end - in_pos) < remaining ? (entry->virtual_offset_end - in_pos) : remaining);
|
||||||
|
u64 read_bytes = 0;
|
||||||
|
|
||||||
switch (ctx->type)
|
switch (ctx->type)
|
||||||
{
|
{
|
||||||
case STORAGE_BYTES:
|
case STORAGE_BYTES:
|
||||||
fr = f_lseek(ctx->file, ctx->base_storage_offset + entry->physical_offset + entry_pos);
|
res = fseek(ctx->file, ctx->base_storage_offset + entry->physical_offset + entry_pos, SEEK_SET);
|
||||||
if (fr || f_tell(ctx->file) != (ctx->base_storage_offset + entry->physical_offset + entry_pos))
|
if (res || ftell(ctx->file) != (ctx->base_storage_offset + entry->physical_offset + entry_pos))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to seek to offset 0x%lX in savefile! (%u).", ctx->base_storage_offset + entry->physical_offset + entry_pos, fr);
|
LOG_MSG_ERROR("Failed to seek to offset 0x%lX in savefile! (%d).", ctx->base_storage_offset + entry->physical_offset + entry_pos, errno);
|
||||||
return out_pos;
|
return out_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
fr = f_read(ctx->file, (u8*)buffer + out_pos, bytes_to_read, &br);
|
read_bytes = fread((u8*)buffer + out_pos, 1, bytes_to_read, ctx->file);
|
||||||
if (fr || br != bytes_to_read)
|
if (read_bytes != bytes_to_read)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read %u bytes chunk from offset 0x%lX in savefile! (%u).", bytes_to_read, ctx->base_storage_offset + entry->physical_offset + entry_pos, fr);
|
LOG_MSG_ERROR("Failed to read 0x%lX-byte long chunk from offset 0x%lX in savefile! (read 0x%lX, errno %d).", bytes_to_read, ctx->base_storage_offset + entry->physical_offset + entry_pos, read_bytes, errno);
|
||||||
return (out_pos + br);
|
return (out_pos + read_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case STORAGE_DUPLEX:
|
case STORAGE_DUPLEX:
|
||||||
br = save_duplex_storage_read(ctx->duplex, (u8*)buffer + out_pos, ctx->base_storage_offset + entry->physical_offset + entry_pos, bytes_to_read);
|
read_bytes = save_duplex_storage_read(ctx->duplex, (u8*)buffer + out_pos, ctx->base_storage_offset + entry->physical_offset + entry_pos, bytes_to_read);
|
||||||
if (br != bytes_to_read)
|
if (read_bytes != bytes_to_read)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read remap data from duplex storage!");
|
LOG_MSG_ERROR("Failed to read remap data from duplex storage!");
|
||||||
return (out_pos + br);
|
return (out_pos + read_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -304,7 +305,7 @@ static u32 save_remap_read(remap_storage_ctx_t *ctx, void *buffer, u64 offset, s
|
||||||
return out_pos;
|
return out_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_ctx_t *remap, void *buffer, u64 offset, size_t count)
|
static u64 save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_ctx_t *remap, void *buffer, u64 offset, size_t count)
|
||||||
{
|
{
|
||||||
if (!ctx || !ctx->block_size || !remap || !buffer || !count)
|
if (!ctx || !ctx->block_size || !remap || !buffer || !count)
|
||||||
{
|
{
|
||||||
|
@ -313,22 +314,21 @@ static u32 save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_c
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 in_pos = offset;
|
u64 in_pos = offset;
|
||||||
u32 out_pos = 0;
|
u64 out_pos = 0;
|
||||||
u32 remaining = count;
|
u64 remaining = count;
|
||||||
u32 br;
|
|
||||||
|
|
||||||
while(remaining)
|
while(remaining)
|
||||||
{
|
{
|
||||||
u32 block_num = (u32)(in_pos / ctx->block_size);
|
u64 block_num = (in_pos / ctx->block_size);
|
||||||
u32 block_pos = (u32)(in_pos % ctx->block_size);
|
u64 block_pos = (in_pos % ctx->block_size);
|
||||||
u64 physical_offset = (ctx->map.entries[block_num].physical_index * ctx->block_size + block_pos);
|
u64 physical_offset = (ctx->map.entries[block_num].physical_index * ctx->block_size + block_pos);
|
||||||
u32 bytes_to_read = ((ctx->block_size - block_pos) < remaining ? (ctx->block_size - block_pos) : remaining);
|
u64 bytes_to_read = ((ctx->block_size - block_pos) < remaining ? (ctx->block_size - block_pos) : remaining);
|
||||||
|
|
||||||
br = save_remap_read(remap, (u8*)buffer + out_pos, ctx->journal_data_offset + physical_offset, bytes_to_read);
|
u64 read_bytes = save_remap_read(remap, (u8*)buffer + out_pos, ctx->journal_data_offset + physical_offset, bytes_to_read);
|
||||||
if (br != bytes_to_read)
|
if (read_bytes != bytes_to_read)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read journal storage data!");
|
LOG_MSG_ERROR("Failed to read journal storage data!");
|
||||||
return (out_pos + br);
|
return (out_pos + read_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
out_pos += bytes_to_read;
|
out_pos += bytes_to_read;
|
||||||
|
@ -462,42 +462,43 @@ static size_t save_ivfc_level_fread(ivfc_level_save_ctx_t *ctx, void *buffer, u6
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT br = 0;
|
size_t read_bytes = 0;
|
||||||
FRESULT fr;
|
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
switch (ctx->type)
|
switch (ctx->type)
|
||||||
{
|
{
|
||||||
case STORAGE_BYTES:
|
case STORAGE_BYTES:
|
||||||
fr = f_lseek(ctx->save_ctx->file, ctx->hash_offset + offset);
|
res = fseek(ctx->save_ctx->file, ctx->hash_offset + offset, SEEK_SET);
|
||||||
if (fr || f_tell(ctx->save_ctx->file) != (ctx->hash_offset + offset))
|
if (res || ftell(ctx->save_ctx->file) != (ctx->hash_offset + offset))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to seek to offset 0x%lX in savefile! (%u).", ctx->hash_offset + offset, fr);
|
LOG_MSG_ERROR("Failed to seek to offset 0x%lX in savefile! (%d).", ctx->hash_offset + offset, errno);
|
||||||
return (size_t)br;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fr = f_read(ctx->save_ctx->file, buffer, count, &br);
|
read_bytes = fread(buffer, 1, count, ctx->save_ctx->file);
|
||||||
if (fr || br != count)
|
if (read_bytes != count)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read IVFC level data from offset 0x%lX in savefile! (%u).", ctx->hash_offset + offset, fr);
|
LOG_MSG_ERROR("Failed to read 0x%lX-byte long IVFC level data chunk from offset 0x%lX in savefile! (read 0x%lX, errno %d).", count, ctx->hash_offset + offset, read_bytes, errno);
|
||||||
return (size_t)br;
|
return read_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case STORAGE_REMAP:
|
case STORAGE_REMAP:
|
||||||
br = save_remap_read(&ctx->save_ctx->meta_remap_storage, buffer, ctx->data_offset + offset, count);
|
read_bytes = save_remap_read(&ctx->save_ctx->meta_remap_storage, buffer, ctx->data_offset + offset, count);
|
||||||
if (br != count)
|
if (read_bytes != count)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read IVFC level data from remap storage!");
|
LOG_MSG_ERROR("Failed to read IVFC level data from remap storage!");
|
||||||
return (size_t)br;
|
return read_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case STORAGE_JOURNAL:
|
case STORAGE_JOURNAL:
|
||||||
br = save_journal_storage_read(&ctx->save_ctx->journal_storage, &ctx->save_ctx->data_remap_storage, buffer, ctx->data_offset + offset, count);
|
read_bytes = save_journal_storage_read(&ctx->save_ctx->journal_storage, &ctx->save_ctx->data_remap_storage, buffer, ctx->data_offset + offset, count);
|
||||||
if (br != count)
|
if (read_bytes != count)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read IVFC level data from journal storage!");
|
LOG_MSG_ERROR("Failed to read IVFC level data from journal storage!");
|
||||||
return (size_t)br;
|
return read_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -1224,34 +1225,34 @@ bool save_process(save_ctx_t *ctx)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT br = 0;
|
size_t read_bytes = 0;
|
||||||
FRESULT fr;
|
int res = 0;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
/* Try to parse Header A. */
|
/* Try to parse Header A. */
|
||||||
f_rewind(ctx->file);
|
rewind(ctx->file);
|
||||||
|
|
||||||
fr = f_read(ctx->file, &ctx->header, sizeof(ctx->header), &br);
|
read_bytes = fread(&(ctx->header), 1, sizeof(ctx->header), ctx->file);
|
||||||
if (fr || br != sizeof(ctx->header))
|
if (read_bytes != sizeof(ctx->header))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read savefile header A! (%u).", fr);
|
LOG_MSG_ERROR("Failed to read savefile header A! (read 0x%lX, errno %d).", read_bytes, errno);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!save_process_header(ctx) || ctx->header_hash_validity == VALIDITY_INVALID)
|
if (!save_process_header(ctx) || ctx->header_hash_validity == VALIDITY_INVALID)
|
||||||
{
|
{
|
||||||
/* Try to parse Header B. */
|
/* Try to parse Header B. */
|
||||||
fr = f_lseek(ctx->file, 0x4000);
|
res = fseek(ctx->file, 0x4000, SEEK_SET);
|
||||||
if (fr || f_tell(ctx->file) != 0x4000)
|
if (res || ftell(ctx->file) != 0x4000)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to seek to offset 0x4000 in savefile! (%u).", fr);
|
LOG_MSG_ERROR("Failed to seek to offset 0x4000 in savefile! (%d).", errno);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
fr = f_read(ctx->file, &ctx->header, sizeof(ctx->header), &br);
|
read_bytes = fread(&(ctx->header), 1, sizeof(ctx->header), ctx->file);
|
||||||
if (fr || br != sizeof(ctx->header))
|
if (read_bytes != sizeof(ctx->header))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read savefile header B! (%u).", fr);
|
LOG_MSG_ERROR("Failed to read savefile header B! (read 0x%lX, errno %d).", read_bytes, errno);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1280,19 +1281,19 @@ bool save_process(save_ctx_t *ctx)
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
fr = f_lseek(ctx->file, ctx->header.layout.file_map_entry_offset);
|
res = fseek(ctx->file, ctx->header.layout.file_map_entry_offset, SEEK_SET);
|
||||||
if (fr || f_tell(ctx->file) != ctx->header.layout.file_map_entry_offset)
|
if (res || ftell(ctx->file) != ctx->header.layout.file_map_entry_offset)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to seek to file map entry offset 0x%lX in savefile! (%u).", ctx->header.layout.file_map_entry_offset, fr);
|
LOG_MSG_ERROR("Failed to seek to file map entry offset 0x%lX in savefile! (%d).", ctx->header.layout.file_map_entry_offset, errno);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(u32 i = 0; i < ctx->data_remap_storage.header->map_entry_count; i++)
|
for(u32 i = 0; i < ctx->data_remap_storage.header->map_entry_count; i++)
|
||||||
{
|
{
|
||||||
fr = f_read(ctx->file, &ctx->data_remap_storage.map_entries[i], 0x20, &br);
|
read_bytes = fread(&(ctx->data_remap_storage.map_entries[i]), 1, 0x20, ctx->file);
|
||||||
if (fr || br != 0x20)
|
if (read_bytes != 0x20)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read data remap storage entry #%u! (%u).", i, fr);
|
LOG_MSG_ERROR("Failed to read data remap storage entry #%u! (read 0x%lX, errno %d).", i, read_bytes, errno);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1417,19 +1418,19 @@ bool save_process(save_ctx_t *ctx)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
fr = f_lseek(ctx->file, ctx->header.layout.meta_map_entry_offset);
|
res = fseek(ctx->file, ctx->header.layout.meta_map_entry_offset, SEEK_SET);
|
||||||
if (fr || f_tell(ctx->file) != ctx->header.layout.meta_map_entry_offset)
|
if (res || ftell(ctx->file) != ctx->header.layout.meta_map_entry_offset)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to seek to meta map entry offset 0x%lX in savefile! (%u).", ctx->header.layout.meta_map_entry_offset, fr);
|
LOG_MSG_ERROR("Failed to seek to meta map entry offset 0x%lX in savefile! (%d).", ctx->header.layout.meta_map_entry_offset, errno);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(u32 i = 0; i < ctx->meta_remap_storage.header->map_entry_count; i++)
|
for(u32 i = 0; i < ctx->meta_remap_storage.header->map_entry_count; i++)
|
||||||
{
|
{
|
||||||
fr = f_read(ctx->file, &ctx->meta_remap_storage.map_entries[i], 0x20, &br);
|
read_bytes = fread(&(ctx->meta_remap_storage.map_entries[i]), 1, 0x20, ctx->file);
|
||||||
if (fr || br != 0x20)
|
if (read_bytes != 0x20)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read meta remap storage entry #%u! (%u).", i, fr);
|
LOG_MSG_ERROR("Failed to read meta remap storage entry #%u! (read 0x%lX, errno %d).", i, read_bytes, errno);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1726,53 +1727,46 @@ save_ctx_t *save_open_savefile(const char *path, u32 action)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
FRESULT fr = FR_OK;
|
FILE *save_fp = NULL;
|
||||||
FIL *save_fd = NULL;
|
|
||||||
save_ctx_t *save_ctx = NULL;
|
save_ctx_t *save_ctx = NULL;
|
||||||
bool open_savefile = false, success = false;
|
bool success = false;
|
||||||
|
|
||||||
save_fd = calloc(1, sizeof(FIL));
|
save_fp = fopen(path, "rb");
|
||||||
if (!save_fd)
|
if (!save_fp)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Unable to allocate memory for FatFs file descriptor!");
|
LOG_MSG_ERROR("Failed to open savefile \"%s\"! (%d).", path, errno);
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
fr = f_open(save_fd, path, FA_READ | FA_OPEN_EXISTING);
|
|
||||||
if (fr != FR_OK)
|
|
||||||
{
|
|
||||||
LOG_MSG_ERROR("Failed to open \"%s\" savefile from BIS System partition! (%u).", path, fr);
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
open_savefile = true;
|
|
||||||
|
|
||||||
/* Code to dump the requested file in its entirety. Useful to retrieve protected system savefiles without exiting HOS. */
|
/* Code to dump the requested file in its entirety. Useful to retrieve protected system savefiles without exiting HOS. */
|
||||||
/*char sd_path[FS_MAX_PATH] = {0};
|
/*char sd_path[FS_MAX_PATH] = {0};
|
||||||
sprintf(sd_path, DEVOPTAB_SDMC_DEVICE "/%s", strrchr(path, '/') + 1);
|
snprintf(sd_path, MAX_ELEMENTS(sd_path), DEVOPTAB_SDMC_DEVICE "/%s", strrchr(path, '/') + 1);
|
||||||
|
|
||||||
UINT blksize = 0x100000;
|
utilsCreateDirectoryTree(sd_path, false);
|
||||||
|
|
||||||
|
u64 blksize = 0x100000;
|
||||||
u8 *buf = malloc(blksize);
|
u8 *buf = malloc(blksize);
|
||||||
FILE *fd = fopen(sd_path, "wb");
|
FILE *sd_fp = fopen(sd_path, "wb");
|
||||||
|
|
||||||
if (buf && fd)
|
if (buf && sd_fp)
|
||||||
{
|
{
|
||||||
u64 size = f_size(save_fd);
|
fseek(save_fp, 0, SEEK_END);
|
||||||
UINT br = 0;
|
u64 size = ftell(save_fp);
|
||||||
|
rewind(save_fp);
|
||||||
|
|
||||||
for(u64 i = 0; i < size; i += blksize)
|
for(u64 offset = 0; offset < size; offset += blksize)
|
||||||
{
|
{
|
||||||
if ((size - i) < blksize) blksize = (size - i);
|
if ((size - offset) < blksize) blksize = (size - offset);
|
||||||
if (f_read(save_fd, buf, blksize, &br) != FR_OK || br != blksize) break;
|
if (fread(buf, 1, blksize, save_fp) != blksize) break;
|
||||||
fwrite(buf, 1, blksize, fd);
|
fwrite(buf, 1, blksize, sd_fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
f_rewind(save_fd);
|
rewind(save_fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fd)
|
if (sd_fp)
|
||||||
{
|
{
|
||||||
fclose(fd);
|
fclose(sd_fp);
|
||||||
utilsCommitSdCardFileSystemChanges();
|
utilsCommitSdCardFileSystemChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1785,7 +1779,7 @@ save_ctx_t *save_open_savefile(const char *path, u32 action)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
save_ctx->file = save_fd;
|
save_ctx->file = save_fp;
|
||||||
save_ctx->tool_ctx.action = action;
|
save_ctx->tool_ctx.action = action;
|
||||||
|
|
||||||
success = save_process(save_ctx);
|
success = save_process(save_ctx);
|
||||||
|
@ -1800,29 +1794,22 @@ end:
|
||||||
save_ctx = NULL;
|
save_ctx = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (save_fd)
|
if (save_fp) fclose(save_fp);
|
||||||
{
|
|
||||||
if (open_savefile) f_close(save_fd);
|
|
||||||
free(save_fd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return save_ctx;
|
return save_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void save_close_savefile(save_ctx_t *ctx)
|
void save_close_savefile(save_ctx_t **ctx)
|
||||||
{
|
{
|
||||||
if (!ctx) return;
|
if (!ctx || !*ctx) return;
|
||||||
|
|
||||||
if (ctx->file)
|
if ((*ctx)->file) fclose((*ctx)->file);
|
||||||
{
|
|
||||||
f_close(ctx->file);
|
|
||||||
free(ctx->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
save_free_contexts(ctx);
|
save_free_contexts(*ctx);
|
||||||
|
|
||||||
free(ctx);
|
free(*ctx);
|
||||||
|
*ctx = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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)
|
||||||
|
|
|
@ -30,15 +30,16 @@
|
||||||
#include <core/mem.h>
|
#include <core/mem.h>
|
||||||
#include <core/aes.h>
|
#include <core/aes.h>
|
||||||
#include <core/rsa.h>
|
#include <core/rsa.h>
|
||||||
|
#include <core/bis_storage.h>
|
||||||
|
|
||||||
#define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e1"
|
#define TIK_COMMON_BIS_SYSTEM_SAVEFILE_PATH "/save/80000000000000e1"
|
||||||
#define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e2"
|
#define TIK_PERSONALIZED_BIS_SYSTEM_SAVEFILE_PATH "/save/80000000000000e2"
|
||||||
|
|
||||||
#define TIK_LIST_STORAGE_PATH "/ticket_list.bin"
|
#define TIK_LIST_SAVEFILE_STORAGE_PATH "/ticket_list.bin"
|
||||||
#define TIK_DB_STORAGE_PATH "/ticket.bin"
|
#define TIK_DB_SAVEFILE_STORAGE_PATH "/ticket.bin"
|
||||||
|
|
||||||
#define TIK_COMMON_CERT_NAME "XS00000020"
|
#define TIK_COMMON_CERT_NAME "XS00000020"
|
||||||
#define TIK_DEV_CERT_ISSUER "CA00000004"
|
#define TIK_DEV_CERT_ISSUER "CA00000004"
|
||||||
|
|
||||||
/* Type definitions. */
|
/* Type definitions. */
|
||||||
|
|
||||||
|
@ -320,6 +321,8 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
||||||
|
|
||||||
u8 titlekey_type = 0;
|
u8 titlekey_type = 0;
|
||||||
|
|
||||||
|
const char *mount_name = NULL;
|
||||||
|
char savefile_path[64] = {0};
|
||||||
save_ctx_t *save_ctx = NULL;
|
save_ctx_t *save_ctx = NULL;
|
||||||
|
|
||||||
u64 buf_size = (SIGNED_TIK_MAX_SIZE * 0x100);
|
u64 buf_size = (SIGNED_TIK_MAX_SIZE * 0x100);
|
||||||
|
@ -328,6 +331,8 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
|
bisStorageControlMutex(true);
|
||||||
|
|
||||||
/* Allocate memory to retrieve the ticket. */
|
/* Allocate memory to retrieve the ticket. */
|
||||||
if (!(buf = malloc(buf_size)))
|
if (!(buf = malloc(buf_size)))
|
||||||
{
|
{
|
||||||
|
@ -342,8 +347,18 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mount eMMC BIS System partition. */
|
||||||
|
if (!bisStorageMountPartition(FsBisPartitionId_System, &mount_name))
|
||||||
|
{
|
||||||
|
LOG_MSG_ERROR("Failed to mount eMMC BIS System partition!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate savefile path. */
|
||||||
|
snprintf(savefile_path, sizeof(savefile_path), "%s:%s", mount_name, titlekey_type == TikTitleKeyType_Common ? TIK_COMMON_BIS_SYSTEM_SAVEFILE_PATH : TIK_PERSONALIZED_BIS_SYSTEM_SAVEFILE_PATH);
|
||||||
|
|
||||||
/* Open ES common/personalized system savefile. */
|
/* Open ES common/personalized system savefile. */
|
||||||
if (!(save_ctx = save_open_savefile(titlekey_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0)))
|
if (!(save_ctx = save_open_savefile(savefile_path, 0)))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to open ES %s ticket system savefile!", g_tikTitleKeyTypeStrings[titlekey_type]);
|
LOG_MSG_ERROR("Failed to open ES %s ticket system savefile!", g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -352,7 +367,7 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
||||||
/* Get ticket entry offset from ticket_list.bin. */
|
/* Get ticket entry offset from ticket_list.bin. */
|
||||||
if (!tikGetTicketEntryOffsetFromTicketList(save_ctx, buf, buf_size, id, titlekey_type, &ticket_offset))
|
if (!tikGetTicketEntryOffsetFromTicketList(save_ctx, buf, buf_size, id, titlekey_type, &ticket_offset))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Unable to find an entry with a matching Rights ID in \"%s\" from ES %s ticket system save!", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
LOG_MSG_ERROR("Unable to find an entry with a matching Rights ID in \"%s\" from ES %s ticket system save!", TIK_LIST_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,10 +389,14 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
||||||
memcpy(dst->data, buf, dst->size);
|
memcpy(dst->data, buf, dst->size);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
if (save_ctx) save_close_savefile(save_ctx);
|
if (save_ctx) save_close_savefile(&save_ctx);
|
||||||
|
|
||||||
|
if (mount_name) bisStorageUnmountPartition(FsBisPartitionId_System);
|
||||||
|
|
||||||
if (buf) free(buf);
|
if (buf) free(buf);
|
||||||
|
|
||||||
|
bisStorageControlMutex(false);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,8 +560,8 @@ static bool tikGetDecryptedTitleKey(void *dst, const void *src, u8 key_generatio
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out)
|
static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out)
|
||||||
|
|
||||||
{
|
{
|
||||||
if (!id || !out)
|
if (!id || !out)
|
||||||
{
|
{
|
||||||
|
@ -661,16 +680,16 @@ static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf,
|
||||||
bool last_entry_found = false, success = false;
|
bool last_entry_found = false, success = false;
|
||||||
|
|
||||||
/* Get FAT storage info for the ticket_list.bin stored within the opened system savefile. */
|
/* Get FAT storage info for the ticket_list.bin stored within the opened system savefile. */
|
||||||
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_LIST_STORAGE_PATH, &fat_storage, &ticket_list_bin_size))
|
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_LIST_SAVEFILE_STORAGE_PATH, &fat_storage, &ticket_list_bin_size))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to locate \"%s\" in ES %s ticket system save!", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
LOG_MSG_ERROR("Failed to locate \"%s\" in ES %s ticket system save!", TIK_LIST_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validate ticket_list.bin size. */
|
/* Validate ticket_list.bin size. */
|
||||||
if (ticket_list_bin_size < sizeof(TikListEntry) || (ticket_list_bin_size % sizeof(TikListEntry)) != 0)
|
if (ticket_list_bin_size < sizeof(TikListEntry) || (ticket_list_bin_size % sizeof(TikListEntry)) != 0)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_list_bin_size);
|
LOG_MSG_ERROR("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_LIST_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_list_bin_size);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,7 +702,7 @@ static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf,
|
||||||
/* Read current chunk. */
|
/* Read current chunk. */
|
||||||
if ((br = save_allocation_table_storage_read(&fat_storage, buf, total_br, buf_size)) != buf_size)
|
if ((br = save_allocation_table_storage_read(&fat_storage, buf, total_br, buf_size)) != buf_size)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
LOG_MSG_ERROR("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_LIST_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,23 +756,23 @@ static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u
|
||||||
bool is_volatile = false, success = false;
|
bool is_volatile = false, success = false;
|
||||||
|
|
||||||
/* Get FAT storage info for the ticket.bin stored within the opened system savefile. */
|
/* Get FAT storage info for the ticket.bin stored within the opened system savefile. */
|
||||||
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_DB_STORAGE_PATH, &fat_storage, &ticket_bin_size))
|
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_DB_SAVEFILE_STORAGE_PATH, &fat_storage, &ticket_bin_size))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to locate \"%s\" in ES %s ticket system save!", TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
LOG_MSG_ERROR("Failed to locate \"%s\" in ES %s ticket system save!", TIK_DB_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validate ticket.bin size. */
|
/* Validate ticket.bin size. */
|
||||||
if (ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0 || ticket_bin_size < (ticket_offset + SIGNED_TIK_MAX_SIZE))
|
if (ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0 || ticket_bin_size < (ticket_offset + SIGNED_TIK_MAX_SIZE))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_bin_size);
|
LOG_MSG_ERROR("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_DB_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_bin_size);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read ticket data. */
|
/* Read ticket data. */
|
||||||
if ((br = save_allocation_table_storage_read(&fat_storage, buf, ticket_offset, SIGNED_TIK_MAX_SIZE)) != SIGNED_TIK_MAX_SIZE)
|
if ((br = save_allocation_table_storage_read(&fat_storage, buf, ticket_offset, SIGNED_TIK_MAX_SIZE)) != SIGNED_TIK_MAX_SIZE)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to read 0x%X-byte long ticket at offset 0x%lX from \"%s\" in ES %s ticket system save!", SIGNED_TIK_MAX_SIZE, ticket_offset, TIK_DB_STORAGE_PATH, \
|
LOG_MSG_ERROR("Failed to read 0x%X-byte long ticket at offset 0x%lX from \"%s\" in ES %s ticket system save!", SIGNED_TIK_MAX_SIZE, ticket_offset, TIK_DB_SAVEFILE_STORAGE_PATH, \
|
||||||
g_tikTitleKeyTypeStrings[titlekey_type]);
|
g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
@ -768,7 +787,7 @@ static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u
|
||||||
/* Attempt to decrypt the ticket. */
|
/* Attempt to decrypt the ticket. */
|
||||||
if (!tikDecryptVolatileTicket(buf, ticket_offset))
|
if (!tikDecryptVolatileTicket(buf, ticket_offset))
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Unable to decrypt volatile ticket at offset 0x%lX in \"%s\" from ES %s ticket system save!", ticket_offset, TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
LOG_MSG_ERROR("Unable to decrypt volatile ticket at offset 0x%lX in \"%s\" from ES %s ticket system save!", ticket_offset, TIK_DB_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue