Gros fichier sur microSD en FAT32 pour Nintendo Switch

Une Nintendo Switch libéré du joug de Nintendo via le CFW (custom firmware) Atmosphere peut se servir de la carte microSD pour plus de choses que de base. Cela peut amener à vouloir y mettre un ou plusieurs gros fichiers, plus précisément qui fasse 4Go ou plus.

Mais les données ne peuvent pas être mises en vrac sur un support de stockage ordinatique. Il faut qu'elles soient organisées, ce qui permet de les gérer. Pour ce faire, il y a des systèmes de fichiers. Dans notre cas, 2 sont gérés : l'exFAT et le FAT32.

L'exFAT est le plus moderne des 2, mais aussi le plus compliqué. Cela accroit le risque qu'il soit mal géré et donc que ça occasionne une perte de données, donc il n'est pas recommandé sur Nintendo Switch. C'est dommage car il a incontestablement l'avantage de pouvoir gérer des énormes fichiers.

Le FAT32 est vieux, mais aussi plus simple et du coup très bien géré à peu près partout. Cela fait qu'il est conseillé qu'une carte microSD soit en FAT32 et non en exFAT. Cela vient cependant avec un problème : le FAT32 ne gère pas les fichiers de plus de 4Go.

Si vous avez un gros fichier, comme une grosse vidéo ou un gros fichier NSP, cela peut sembler rédibitoire. Mais étant donné le risque de perte de données avec l'exFAT, un contournement a été mis en place pour gérer les gros fichiers avec FAT32.

Pour y mettre un gros fichier, il faut tout d'abord le découper en bouts qui ont individuellement une taille gérée par FAT32, par exemple un bout peut être de 3Go, ce qui peut se faire en ligne de commande avec split -b3G fichier (sous GNU/Linux, *BSD, ainsi que peut-être Android et également Apple macOS, mais aussi Windows avec une couche de compatibilité dont celle de Microsoft est nommée WSL pour Windows Subsystem [GNU/]Linux) ou via le langage Python avec splitNSP. Ensuite, sur la carte microSD, il faut créer un dossier vide qui représentera le fichier complet. Après ça, il faut y mettre les parties. Enfin, il faut indiquer dans le système de fichiers que ce dossier est une archive, ce qui permettra de le considérer comme un fichier et donc d'avoir un simili-fichier qui excéde la taille maximale gérée par FAT32. Mais faut-il après encore que ce mécanisme de dossier-archive à considérer comme un fichier soit géré d'un point de vue logiciel et de la manière dont ça a été fait (il peut en effet potentiellement falloir un nom du dossier de dossier adéquat et/ou des noms adéquats pour les parties qui peuvent par exemple être 00 puis 01 et ainsi de suite).

Si vous êtes sous GNU/Linux ou *BSD, mais probablement aussi sur Android, voire également sur Apple macOS, vous pourriez ne pas trouver comment graphiquement passer un dossier comme une archive du point de vue du système de fichiers FAT32. Et s'il peut tout simplement que votre interface graphique ne le permette pas, car c'est un besoin pour le moins rare. Pour GNU/Linux, il y a le petit utilitaire en ligne de commande mattrib. Alternativement, il y en a un autre du même genre : fatattr. Comme son nom l'indique en partie, il permet de gérer les attributs d'un noeud d'un système de fichiers FAT32. En l'occurrence, il suffit de faire fatattr +A dossier pour passer le dit dossier en mode archive. Mais avant ça, il faut pouvoir exécuter fatattr et qu'il gère bien l'option (ce qui peut ne pas être le cas par la version fournie par votre distribution). Pour passer du code source à un exécutable, ce qu'on appelle compilation, ça se fait en l'occurrence très simplement avec scons à condition d'avoir aussi préalablement installé clang, via apt install scons clang sous Debian GNU/Linux et ses dérivés (Trisquel, Ubuntu, Mint, etc.). Mais il se pourrait que ça échoue à cause d'un print sans parenthèses d'un fichier Python d'une veille époque (quand Python 2 régnait et que Python 3 était encore peu utilisé ou n'existait encore tout simplement pas), mais il se corrige très simplement en ajoutant les parenthèses autour du paramètre de la fonction print.

Mais vous pouvez aussi alternativement faire cette opération d'indication qu'un dossier devrait être considéré comme une archive directement sur le système de la Nintendo Switch. Au moins 2 logiciels libres le permettent : GoldLeaf et NX Shell.

Annexes

Code source C de fatattr

/**
 * Copyright 2013 David Caro Martinez
 * Copyright 2024 Nicola Spanti
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

// https://gitlab.com/Terseus/fatattr/
// cc fatattr.c -o fatattr


#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/msdos_fs.h>


#define PROGRAM_NAME        "fatattr"

#ifndef DIR_ENTRY_SIZE
#define DIR_ENTRY_SIZE 256
#endif
#ifndef REAL_DIR_ENTRY_SIZE
#define REAL_DIR_ENTRY_SIZE 1025
#endif
#define ERRMSG_MAX 1025

#define DIRENT_SIZE 2


/* Redefinition of attribute macros, so we don't depend on msdos_fs.h macros. */
enum {
    DOSFS_ENOERR = 0,
    DOSFS_EOPEN,
    DOSFS_EIOCTL_GET_ATTRIBUTES,
    DOSFS_EIOCTL_SET_ATTRIBUTES,
    DOSFS_EIOCTL_READDIR_BOTH,
    DOSFS_EBUFFER,
};
/* Attributes checks for lazyness. */
#define DOSFS_HAS_ATTR(x, a)        ((x) & (a))
#define DOSFS_HAS_ATTR_RO(x)        DOSFS_HAS_ATTR(x, ATTR_RO)
#define DOSFS_HAS_ATTR_HIDDEN(x)    DOSFS_HAS_ATTR(x, ATTR_HIDDEN)
#define DOSFS_HAS_ATTR_SYS(x)       DOSFS_HAS_ATTR(x, ATTR_SYS)
#define DOSFS_HAS_ATTR_VOLUME(x)    DOSFS_HAS_ATTR(x, ATTR_VOLUME)
#define DOSFS_HAS_ATTR_DIR(x)       DOSFS_HAS_ATTR(x, ATTR_DIR)
#define DOSFS_HAS_ATTR_ARCH(x)      DOSFS_HAS_ATTR(x, ATTR_ARCH)

typedef enum {
    MADD,
    MREMOVE,
} tDosfsModifyType;

enum {
    MAIN_ENOERR = 0,
    MAIN_EALLOC,
};

enum {
    FLAG_VERBOSE = 0x01,
    FLAG_RECURSIVE = 0x02,
    FLAG_HELP = 0x04,
    FLAG_COPYRIGHT = 0x08,
};

struct programArgs {
	char **fileList;
	size_t fileListSize;
	uint32_t attrsToAdd;
	uint32_t attrsToRemove;
	unsigned int flags;
};

static char errmsg[ERRMSG_MAX] = {0};


/**
 * Modify the attributes of a file descriptor, 'modifyType' specifies if the
 * attributes are added (MADD) or removed (MREMOVE).
 * Returns 0 on success, !0 if an error happens.
 */
static
int
dosfsModifyAttributes
(int fd, uint32_t attrs,
 tDosfsModifyType modifyType)
{
	assert(fd != -1);
	uint32_t currentAttrs = 0;
	int ioctlRet = ioctl(fd, FAT_IOCTL_GET_ATTRIBUTES, &currentAttrs);
	if (ioctlRet < 0) {
		return DOSFS_EIOCTL_GET_ATTRIBUTES;
	}
	uint32_t newAttrs = 0;
	switch (modifyType) {
	case MADD:
		newAttrs = currentAttrs | attrs;
		break;
	case MREMOVE:
		newAttrs = (~attrs) & currentAttrs;
		break;
	}
	ioctlRet = ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &newAttrs);
	if (ioctlRet < 0) {
		return DOSFS_EIOCTL_SET_ATTRIBUTES;
	}
	return DOSFS_ENOERR;
}

/**
 * Returns a descriptive message associated with an error code.
 */
static
const char *
dosfsGetError
(const int err)
{
	switch (err) {
	case DOSFS_ENOERR:
		snprintf(errmsg, ERRMSG_MAX,
		         "No error occurred");
		break;
	case DOSFS_EOPEN:
		snprintf(errmsg, ERRMSG_MAX,
		         "Error opening file: %s",
		         strerror(errno));
		break;
	case DOSFS_EIOCTL_GET_ATTRIBUTES:
		snprintf(errmsg, ERRMSG_MAX,
		         "Error in ioctl call 'FAT_IOCTL_GET_ATTRIBUTES': %s",
		         strerror(errno));
		break;
	case DOSFS_EIOCTL_SET_ATTRIBUTES:
		snprintf(errmsg, ERRMSG_MAX,
		         "Error in ioctl call 'FAT_IOCTL_SET_ATTRIBUTES': %s",
		         strerror(errno));
		break;
	case DOSFS_EIOCTL_READDIR_BOTH:
		snprintf(errmsg, ERRMSG_MAX,
		         "Error in ioctl call 'VFAT_IOCTL_READDIR_BOTH': %s",
		         strerror(errno));
		break;
	case DOSFS_EBUFFER:
		snprintf(errmsg, ERRMSG_MAX,
		         "The entry name is bigger than the read buffer");
		break;
	default:
		snprintf(errmsg, ERRMSG_MAX,
		         "Unknown error");
	}
	return errmsg;
}

/**
 * Open a file and save its file descriptor in fd.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
dosfsOpen(const char *file, int *fd)
{
	assert(file != NULL);
	assert(fd != NULL);
	/* O_RDONLY works with files and directories (write doesn't) and let us
	   modify FAT attributes. */
	*fd = open(file, O_RDONLY);
	if (*fd == -1) {
		return DOSFS_EOPEN;
	}
	return DOSFS_ENOERR;
}

/**
 * Close a file descriptor.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
dosfsClose(int fd)
{
	assert(fd != -1);
	close(fd);
	return DOSFS_ENOERR;
}

/**
 * Get the FAT attributes from a file descriptor and save them in 'attrs'.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
dosfsGetAttributes(int fd, uint32_t *attrs)
{
	assert(attrs != NULL);
	int ioctlRet = ioctl(fd, FAT_IOCTL_GET_ATTRIBUTES, attrs);
	if (ioctlRet < 0) {
		return DOSFS_EIOCTL_GET_ATTRIBUTES;
	}
	return DOSFS_ENOERR;
}

/**
 * Add FAT attributes to a file descriptor.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
dosfsAddAttributes(int fd, uint32_t attrs)
{
	return dosfsModifyAttributes(fd, attrs, MADD);
}

/**
 * Remove FAT attributes from a file descriptor.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
dosfsRemoveAttributes(int fd, uint32_t attrs)
{
	return dosfsModifyAttributes(fd, attrs, MREMOVE);
}

/**
 * Read the next entry of the directory associated with a file descriptor.
 * Write the long name entry in 'entry', writing at most 'pathSize' - 1
 * characters.
 * Returns 0 on success, !0 if an error happens or if the complete long name
 * doesn't fit in 'entry'.
 */
static
int
dosfsReadDir
(int fd, char *entry, size_t pathSize)
{
	assert(entry != NULL);
	assert(fd != -1);
	/* VFAT_IOCTL_READDIR_BOTH expects 2 __fat_dirent objects, one for the
	   short name entry an one for the long name entry. */
	struct __fat_dirent dirEnt[DIRENT_SIZE];
	int ioctlRet = ioctl(fd, VFAT_IOCTL_READDIR_BOTH, dirEnt);
	if (ioctlRet < 0) {
		close(fd);
		return DOSFS_EIOCTL_READDIR_BOTH;
	} if (ioctlRet == 0) {
		memset(entry, '\0', pathSize);
		return DOSFS_ENOERR;
	}
	/* If the *real* file name have 8 characters or less, the long name entry
	   will have d_name empty. */
	int dirEntRealName = strlen(dirEnt[1].d_name) == 0 ? 0 : 1;
	if (strlen(dirEnt[dirEntRealName].d_name) >= pathSize) {
		return DOSFS_EBUFFER;
	}
	strncpy(entry, dirEnt[dirEntRealName].d_name, pathSize - 1);
	return DOSFS_ENOERR;
}


/**
 * Appends a file to the file list of 'args'.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
appendFileToList
(struct programArgs *args,
 char *file)
{
	args->fileList = realloc(args->fileList,
	                         sizeof(char *) * (++(args->fileListSize)));
	if (args->fileList == NULL) {
		return MAIN_EALLOC;
	}
	args->fileList[args->fileListSize - 1] = file;
	return MAIN_ENOERR;
}

/**
 * Print FAT attributes in a readable format.
 */
static
void
printAttrs
(const uint32_t attrs)
{
	printf("%c%c%c%c%c%c",
	       DOSFS_HAS_ATTR_RO(attrs) ? 'R' : '-',
	       DOSFS_HAS_ATTR_HIDDEN(attrs) ? 'H' : '-',
	       DOSFS_HAS_ATTR_SYS(attrs) ? 'S' : '-',
	       DOSFS_HAS_ATTR_ARCH(attrs) ? 'A' : '-',
	       DOSFS_HAS_ATTR_DIR(attrs) ? 'D' : '-',
	       DOSFS_HAS_ATTR_VOLUME(attrs) ? 'V' : '-'
	      );
}


/**
 * Internal function, sub of processPrintAttributes.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
processPrintAttributesFd
(const struct programArgs *const args,
 char *file,
 int fd,
 int processDir);


/**
 * Print a file's attributes with the configuration saved in 'args'.
 * If 'processDir' != 0 and 'file' is a directory, process the files inside it.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
processPrintAttributes
(const struct programArgs *const args,
 char *file,
 int processDir);

static
int
processPrintAttributesFd
(const struct programArgs *const args,
 char *file,
 int fd,
 int processDir)
{
	uint32_t fileAttrs = 0;
	int dosfsErrno = dosfsGetAttributes(fd, &fileAttrs);
	if (dosfsErrno) {
		return dosfsErrno;
	}
	printAttrs(fileAttrs);
	printf("  %s\n", file);
	if (DOSFS_HAS_ATTR_DIR(fileAttrs) && processDir) {
		char dirEntry[DIR_ENTRY_SIZE] = {0};
		char realDirEntry[REAL_DIR_ENTRY_SIZE] = {0};
		while (!(dosfsErrno = dosfsReadDir(fd, dirEntry, DIR_ENTRY_SIZE)) &&
		        strlen(dirEntry) > 0) {
			snprintf(realDirEntry, REAL_DIR_ENTRY_SIZE,
			         "%s/%s",
			         file, dirEntry);
			int recursive = (args->flags & FLAG_RECURSIVE) &&
			                strcmp(dirEntry, ".") != 0 &&
			                strcmp(dirEntry, "..") != 0;
			dosfsErrno = processPrintAttributes(args, realDirEntry, recursive);
			if (dosfsErrno) {
				fprintf(stderr, "Error processing file '%s': %s\n",
				        realDirEntry, dosfsGetError(dosfsErrno));
			}
		}
	}
	return MAIN_ENOERR;
}

static
int
processPrintAttributes
(const struct programArgs *const args,
 char *file,
 int processDir)
{
	int fd = 0;
	int dosfsErrno = dosfsOpen(file, &fd);
	if (dosfsErrno) {
		return dosfsErrno;
	}
	dosfsErrno = processPrintAttributesFd(args, file, fd, processDir);
	dosfsClose(fd);
	return dosfsErrno;
}


/**
 * Internal function, sub of processModifyAttributes.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
processModifyAttributesFd
(const struct programArgs *const args,
 char *file,
 int fd,
 int processDir);

/**
 * Modify the attributes of a file based on the configuration saved in 'args'.
 * If 'processDir' != 0 and 'file' is a directory, process the files inside it.
 * Returns 0 on success, !0 if an error happens.
 */
int
processModifyAttributes
(const struct programArgs *const args,
 char *file,
 int processDir);

static
int
processModifyAttributesFd
(const struct programArgs *const args,
 char *file,
 int fd,
 int processDir)
{
	uint32_t fileAttrs = 0;
	int dosfsErrno = dosfsGetAttributes(fd, &fileAttrs);
	if (dosfsErrno) {
		return dosfsErrno;
	}
	if (args->attrsToAdd != 0 &&
	        (args->attrsToAdd & fileAttrs) != args->attrsToAdd) {
		dosfsErrno = dosfsAddAttributes(fd, args->attrsToAdd);
		if (dosfsErrno) {
			return dosfsErrno;
		}
	}
	if (args->attrsToRemove != 0 &&
	        (args->attrsToRemove & fileAttrs) != 0) {
		dosfsErrno = dosfsRemoveAttributes(fd, args->attrsToRemove);
		if (dosfsErrno) {
			return dosfsErrno;
		}
	}
	uint32_t newAttrs = 0;
	dosfsErrno = dosfsGetAttributes(fd, &newAttrs);
	if (dosfsErrno) {
		return dosfsErrno;
	}
	if (args->flags & FLAG_VERBOSE) {
		printAttrs(fileAttrs);
		printf(" => ");
		printAttrs(newAttrs);
		printf("  %s\n", file);
	}
	if (DOSFS_HAS_ATTR_DIR(newAttrs) && processDir) {
		char dirEntry[DIR_ENTRY_SIZE] = {0};
		char realDirEntry[REAL_DIR_ENTRY_SIZE] = {0};
		while (!(dosfsErrno = dosfsReadDir(fd, dirEntry, DIR_ENTRY_SIZE)) &&
		        strlen(dirEntry) > 0) {
			snprintf(realDirEntry, REAL_DIR_ENTRY_SIZE,
			         "%s/%s",
			         file, dirEntry);
			int recursive = (args->flags & FLAG_RECURSIVE) &&
			                strcmp(dirEntry, ".") != 0 &&
			                strcmp(dirEntry, "..") != 0;
			dosfsErrno = processModifyAttributes(args, realDirEntry, recursive);
			if (dosfsErrno) {
				fprintf(stderr, "Error processing file '%s': %s\n",
				        realDirEntry, dosfsGetError(dosfsErrno));
			}
		}
	}
	return MAIN_ENOERR;
}

int
processModifyAttributes
(const struct programArgs *const args,
 char *file,
 int processDir)
{
	int fd = 0;
	int dosfsErrno = dosfsOpen(file, &fd);
	if (dosfsErrno) {
		return dosfsErrno;
	}
	dosfsErrno = processModifyAttributesFd(args, file, fd, processDir);
	dosfsClose(fd);
	return dosfsErrno;
}


/**
 * Prints the program name and copyright/license notices.
 */
static
void
showCopyright
(void)
{
	puts("FAT attributes utility\n"
	     "https://gitlab.com/Terseus/fatattr/"
	     "Copyright 2013 David Caro Martinez\n"
	     "Copyright 2024 Nicola Spanti\n"
	     "\n"
	     "This software comes with ABSOLUTELY NO WARRANTY.\n"
	     "This is free software, and you are welcome to redistribute it \n"
	     "under certain conditions. See the GNU General Public License \n"
	     "for details."
	     );
}

/**
 * Prints the same as showCopyright along with the program help.
 */
static
void
showHelp
(void)
{
	showCopyright();
	printf("Usage: %s [options] FILE ...\n", PROGRAM_NAME);
	puts("Accepted options:\n"
	     "\t+R: Sets the read-only attribute.\n"
	     "\t-R: Remove the read-only attribute.\n"
	     "\t+A: Sets the archive attribute.\n"
	     "\t-A: Remove the archive attribute.\n"
	     "\t+S: Sets the system attribute.\n"
	     "\t-S: Remove the system attribute.\n"
	     "\t+H: Sets the hidden attribute.\n"
	     "\t-H: Remove the system attribute.\n"
	     "\t+D: Sets the directory attribute (warning! see below).\n"
	     "\t-D: Remove the directory attribute (warning! see below).\n"
	     "\t+V: Sets the volume label attribute (warning! see below).\n"
	     "\t-V: Remove the volume label attribute (warning! see below).\n"
	     "\t--recursive: If FILE is a directory, process it recursively.\n"
	     "\t--verbose: Verbose attribute changes.\n"
	     "\t--help: Show this help.\n"
	     "\t--copyright: Show only the program name and credits.\n"
	     "\t--: Forces all arguments past this one to be interpreted as "
	     "files.\n"
	     "If no attribute change is specified, the program prints the "
	     "file's attributes.\n"
	     "Do NOT use the +D, -D, +V and -V options if you don't know "
	     "EXACTLY what you are doing."
	     );
}


static
void
processAttributesArg
(uint32_t *attrs,
 const char *arg)
{
	assert(attrs != NULL);
	assert(arg != NULL);

	const size_t argLen = strlen(arg);
	for (size_t i = 0; i < argLen; i++) {
		switch (arg[i]) {
		case 'R':
			*attrs |= ATTR_RO;
			break;
		case 'A':
			*attrs |= ATTR_ARCH;
			break;
		case 'S':
			*attrs |= ATTR_SYS;
			break;
		case 'H':
			*attrs |= ATTR_HIDDEN;
			break;
		case 'D':
			*attrs |= ATTR_DIR;
			break;
		case 'V':
			*attrs |= ATTR_VOLUME;
			break;
		default:
			fprintf(stderr, "Invalid attribute '%c' in '%s'\n",
			        arg[i], arg);
			exit(EXIT_FAILURE);
		}
	}
}

/**
 * Process the program's arguments and saved the readed values in 'result'.
 * Returns 0 on success, !0 if an error happens.
 */
static
int
processArgs
(const int argc,
 char **argv,
 struct programArgs *result)
{
	result->fileList = NULL;
	result->fileListSize = 0;
	result->attrsToAdd = 0;
	result->attrsToRemove = 0;
	result->flags = 0;
	bool skipArgs = false;
	int mainErrno = 0;
	for (int i = 1; i < argc; i++) {
		if (skipArgs || (argv[i][0] != '-' && argv[i][0] != '+')) {
			mainErrno = appendFileToList(result, argv[i]);
			if (mainErrno) {
				return mainErrno;
			}
		} else if (argv[i][0] == '-') {
			if (argv[i][1] == '\0') {
				fprintf(stderr, "Missing attribute in argument '%s'\n", argv[i]);
				exit(EXIT_FAILURE);
			} else if (argv[i][1] == '-') {
				if (argv[i][2] == '\0') {
					skipArgs = true;
					continue;
				} else if (strcmp(argv[i], "--recursive") == 0) {
					result->flags |= FLAG_RECURSIVE;
					continue;
				} else if (strcmp(argv[i], "--verbose") == 0) {
					result->flags |= FLAG_VERBOSE;
					continue;
				} else if (strcmp(argv[i], "--help") == 0) {
					result->flags |= FLAG_HELP;
					continue;
				} else if (strcmp(argv[i], "--copyright") == 0) {
					result->flags |= FLAG_COPYRIGHT;
					continue;
				} else {
					fprintf(stderr, "Invalid option '%s'\n", argv[i]);
					exit(EXIT_FAILURE);
				}
			} else {
				processAttributesArg(&result->attrsToRemove,
				                     argv[i] + 1);
			}
		} else if (argv[i][0] == '+') {
			if (argv[i][1] == '\0') {
				fprintf(stderr, "Missing attribute in argument '%s'\n", argv[i]);
				exit(EXIT_FAILURE);
			}
			processAttributesArg(&result->attrsToAdd,
				             argv[i] + 1);
		}
	}
	return MAIN_ENOERR;
}

/**
 * Returns a descriptive message associated with an error code.
 * This is only used for the errors that happens purely in this source file,
 * like a memory alloc error.
 */
static
const char *
mainGetError
(const int err)
{
	switch (err) {
	case MAIN_ENOERR:
		snprintf(errmsg, ERRMSG_MAX,
		         "No error occurred");
		break;
	case MAIN_EALLOC:
		snprintf(errmsg, ERRMSG_MAX,
		         "Error allocating memory: %s",
		         strerror(errno));
		break;
	default:
		snprintf(errmsg, ERRMSG_MAX,
		         "Unknown error");
	}
	return errmsg;
}

int
main
(int argc,
 char **argv)
{
	if (argc < 2) {
		showHelp();
		exit(EXIT_SUCCESS);
	}
	
	struct programArgs args;
	int mainErrno = processArgs(argc, argv, &args);
	if (mainErrno) {
		fprintf(stderr, "Error processing arguments: %s\n",
		        mainGetError(mainErrno));
		exit(EXIT_FAILURE);
	}
	if (args.flags & FLAG_HELP) {
		showHelp();
		exit(EXIT_SUCCESS);
	}
	if (args.flags & FLAG_COPYRIGHT) {
		showCopyright();
		exit(0);
	}
	if (args.fileListSize == 0) {
		fputs("Error processing arguments: No file(s) specified\n",
		      stderr);
		showHelp();
		exit(EXIT_FAILURE);
	}
	if ((args.attrsToAdd & args.attrsToRemove) != 0) {
		fputs("Error processing arguments: Overlapping attribute changes\n",
		      stderr);
		exit(EXIT_FAILURE);
	}
	
	int dosfsErrno = 0;
	if (args.attrsToRemove == 0 && args.attrsToAdd == 0) {
		for (size_t i = 0; i < args.fileListSize; i++) {
			dosfsErrno = processPrintAttributes(&args, args.fileList[i], true);
			if (dosfsErrno) {
				fprintf(stderr, "Error processing file '%s': %s\n",
				        args.fileList[i], dosfsGetError(dosfsErrno));
			}
		}
	} else {
		for (size_t i = 0; i < args.fileListSize; i++) {
			dosfsErrno = processModifyAttributes(&args, args.fileList[i],
			                                     args.flags & FLAG_RECURSIVE);
			if (dosfsErrno) {
				fprintf(stderr, "Error processing file '%s': %s\n",
				        args.fileList[i], dosfsGetError(dosfsErrno));
			}
		}
	}
	free(args.fileList);
	exit(dosfsErrno);
}

Code source Python de splitNSP

#!/usr/bin/env python3
# License: https://unlicense.org/
# Source: https://github.com/AnalogMan151/splitNSP
# Author: AnalogMan
# Modified Date: 2018-10-08
# Purpose: Splits Nintendo Switch NSP files into parts for installation on FAT32

import os
import argparse
import shutil

splitSize = 0xFFFF0000 # 4,294,901,760 bytes
chunkSize = 0x8000 # 32,768 bytes

def splitQuick(filepath):
    fileSize = os.path.getsize(filepath)
    info = shutil.disk_usage(os.path.dirname(os.path.abspath(filepath)))
    if info.free < splitSize:
        print('Not enough temporary space. Needs 4GiB of free space\n')
        return
    print('Calculating number of splits…\n')
    splitNum = int(fileSize / splitSize)
    if splitNum == 0:
        print('This NSP is under 4GiB and does not need to be split.\n')
        return

    print('Splitting NSP into {0} parts…\n'.format(splitNum + 1))

    # Create directory, delete if already exists
    dir = filepath[:-4] + '_split.nsp'
    if os.path.exists(dir):
        shutil.rmtree(dir)
    os.makedirs(dir)

    # Move input file to directory and rename it to first part
    filename = os.path.basename(filepath)
    shutil.move(filepath, os.path.join(dir, '00'))
    filepath = os.path.join(dir, '00')

    # Calculate size of final part to copy first
    finalSplitSize = fileSize - (splitSize * splitNum)

    # Copy final part and trim from main file
    with open(filepath, 'r+b') as nspFile:
        nspFile.seek(finalSplitSize * -1, os.SEEK_END)
        outFile = os.path.join(dir, '{:02}'.format(splitNum))
        partSize = 0
        print('Starting part {:02}'.format(splitNum))
        with open(outFile, 'wb') as splitFile:
            while partSize < finalSplitSize:
                splitFile.write(nspFile.read(chunkSize))
                partSize += chunkSize
        nspFile.seek(finalSplitSize * -1, os.SEEK_END)
        nspFile.truncate()
        print('Part {:02} complete'.format(splitNum))

    # Loop through additional parts and trim
    with open(filepath, 'r+b') as nspFile:
        for i in range(splitNum - 1):
            nspFile.seek(splitSize * -1, os.SEEK_END)
            outFile = os.path.join(dir, '{:02}'.format(splitNum - (i + 1)))
            partSize = 0
            print('Starting part {:02}'.format(splitNum - (i + 1)))
            with open(outFile, 'wb') as splitFile:
                 while partSize < splitSize:
                    splitFile.write(nspFile.read(chunkSize))
                    partSize += chunkSize
            nspFile.seek(splitSize * -1, os.SEEK_END)
            nspFile.truncate()
            print('Part {:02} complete'.format(splitNum - (i + 1)))

    # Print assurance statement for user
    print('Starting part 00\nPart 00 complete')

    print('\nNSP successfully split!\n')

def splitCopy(filepath, output_dir=""):
    fileSize = os.path.getsize(filepath)
    info = shutil.disk_usage(os.path.dirname(os.path.abspath(filepath)))
    if info.free < fileSize*2:
        print('Not enough free space to run. Will require twice the space as the NSP file\n')
        return
    print('Calculating number of splits…\n')
    splitNum = int(fileSize/splitSize)
    if splitNum == 0:
        print('This NSP is under 4GiB and does not need to be split.\n')
        return

    print('Splitting NSP into {0} parts…\n'.format(splitNum + 1))

    # Create directory, delete if already exists
    if output_dir == "":
        dir = filepath[:-4] + '_split.nsp'
    else:
        if output_dir[-4:] != '.nsp':
            output_dir+= ".nsp"
        dir = output_dir
    if os.path.exists(dir):
        shutil.rmtree(dir)
    os.makedirs(dir)

    remainingSize = fileSize

    # Open source file and begin writing to output files stoping at splitSize
    with open(filepath, 'rb') as nspFile:
        for i in range(splitNum + 1):
            partSize = 0
            print('Starting part {:02}'.format(i))
            outFile = os.path.join(dir, '{:02}'.format(i))
            with open(outFile, 'wb') as splitFile:
                if remainingSize > splitSize:
                    while partSize < splitSize:
                        splitFile.write(nspFile.read(chunkSize))
                        partSize += chunkSize
                    remainingSize -= splitSize
                else:
                    while partSize < remainingSize:
                        splitFile.write(nspFile.read(chunkSize))
                        partSize += chunkSize
            print('Part {:02} complete'.format(i))
    print('\nNSP successfully split!\n')

def main():
    print('\n========== NSP Splitter ==========\n')

    # Arg parser for program options
    parser = argparse.ArgumentParser(description='Split NSP files into FAT32 compatible sizes')
    parser.add_argument('filepath', help='Path to NSP file')
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-q', '--quick', action='store_true',
                       help=(
                           'Splits file in-place without creating a copy. '
                           'Only requires 4GiB free space to run'))
    group.add_argument('-o', '--output-dir', type=str, default="",
                       help="Set alternative output dir")

    # Check passed arguments
    args = parser.parse_args()

    filepath = args.filepath

    # Check if required files exist
    if not os.path.isfile(filepath):
        print('NSP cannot be found!\n')
        return 1

    # Split NSP file
    if args.quick:
        splitQuick(filepath)
    else:
        splitCopy(filepath, args.output_dir)

if __name__ == "__main__":
    main()