This chapter describes the FS library used to access media and cautions for each media type.
7.1. FS Library
You must use the FS library to access 3DS system memory devices (Nintendo 3DS Cards and SD cards).
7.1.1. Initializing
Call the nn::fs::Initialize
function to initialize the FS library. It is not a problem to call this function again even after the library is already initialized.
Function calls against files or directories cause an error unless they are made after the FS library is initialized.
7.1.2. Finalizing
To finish using the FS library, close all open files and directories, and unmount all archives.
You must also either finalize or cancel use of the FS library when shutting down the application and when transitioning to Sleep Mode. For more information, see 5.3.1.1. Cautions When Shutting Down and 5.3.3.4. Prohibited Processes While in Sleep Mode.
7.1.3. Specifying Paths
All paths (to files or directories) must be specified as absolute paths. The “/
” symbol (slash) is used as the path delimiter.
You can use wide or multiple-byte characters (ASCII characters only) in path specification strings used with FS library functions. However, keep track of the stack size when using multiple-byte strings, because the library must convert multiple-byte strings to wide-character strings, which requires the allocation of a large buffer from the stack. Consequently, use wide-character strings unless you have a good reason not to.
7.1.4. Accessing Files
Choose the best class for your purposes from the following three types for accessing files on media.
Class |
Description |
---|---|
|
Opens a file for reading. |
|
Opens a file for writing. If the specified file does not exist, one may be created depending on the settings specified in the class initialization. |
|
Opens a file for reading and writing, depending on the access mode specified. |
Much as with Initialize
and TryInitialize
, the functions used for file access have versions prepended with and without Try
. Both versions operate the same way, but be sure to use the Try
versions within your application.
FS library functions generally do not return until they complete their execution. The library also processes file-access requests in the order they arrive, so a file access by a high-priority thread will not be processed until the file accesses requested before it have completed.
7.1.4.1. nn::fs::FileInputStream Class
Use this class to open a file for reading. Specify the file for reading in the arguments to the constructor or to the Initialize
or TryInitialize
functions. The application halts if the file specified in the arguments to the constructor or to the Initialize
function does not exist. Use the return value from the TryInitialize
function instead for error checking. The application halts if file access is attempted from an instance that did not successfully open the file or if the file is opened again from an instance that already has it open.
Use the GetSize
or TryGetSize
functions to get the size of the file. This can be used to determine the size to use for the read buffer.
Use the Read
or TryRead
functions to read a file. The arguments specify the buffer into which to copy file contents, and the size of the buffer. The return value is the number of bytes actually copied to the buffer, or 0
if the end of the file has been reached. If at all possible, allocate the buffer with a 4-byte alignment. Though dependent on device and memory region, read speeds are substantially slower for buffers that are not 4-byte aligned and whose current position does not change in 4-byte units.
Use the GetPosition
or TryGetPosition
functions to get, or the SetPosition
or TrySetPosition
functions to set, the file read start position (current position). The current position is the number of bytes from the start of the file. You can also use the Seek
or TrySeek
functions to set the base position and the offset from there. Note that although read speeds are dependent on device and memory region, read speeds are substantially slower if the current position is not set to a multiple of 4 bytes from the start of the file. If the current position is set before the start of the file or after the end of the file, the application halts on the next attempt at accessing the file.
Value |
Description |
---|---|
|
Sets the current position based on the start of the file. |
|
Sets the current position based on the current position in the file. |
|
Sets the current position based on the end of the file. |
Call the Finalize
function to close the file after you are done using it.
The following code sample shows opening a file using TryInitialize
, checking if it opened properly, and then reading from it.
nn::fs::FileInputStream fis; nn::Result result = fis.TryInitialize(L"rom:/test.txt"); if (result.IsSuccess()) { s64 fileSize; result = fis.TryGetSize(&fileSize); NN_LOG("FileSize=%lld\n", fileSize); buf = heap.Allocate(fileSize); s32 ret; result = fis.TryRead(&ret, buf, fileSize); ... heap.Free(buf); } fis.Finalize();
7.1.4.2. nn::fs::FileOutputStream Class
Use this class to open a file for writing. Specify the file for writing in the arguments to the constructor or to the Initialize
or TryInitialize
functions. Use the return value from the TryInitialize
function instead for error checking. If the specified file does not exist and the createIfNotExist
parameter is set to true
, a new file of size zero is created. The application halts if the file specified in the arguments to the constructor or to the Initialize
function does not exist and the createIfNotExist
parameter is set to false
. The application halts if file access is attempted from an instance that did not successfully open the file or if the file is opened again from an instance that already has it open.
Use the SetSize
or TrySetSize
functions before writing to set the size of the file. The file writing position will be adjusted if the file size is reset smaller in the middle of a write operation. The GetSize
or TryGetSize
functions return the current file size.
The TryRead
function successfully reads in the file of size zero created by the TryInitialize
function. You must be careful if the TryInitialize
function is called with true
passed to the createIfNotExist
parameter, and then later the file size is set using the TrySetSize
function.
When the Game Card is removed or the process is otherwise interrupted between calls to TryInitialize
and TrySetSize
, the next time TryRead
is called, the result is that a file of size zero is read.
For handling files of fixed size, if the application is not checking the size using the TryGetSize
function before calling TryRead
, we recommend creating the file using the TryCreateFile
function, which can set the file size, rather than using the createIfNotExist
parameter to create the file.
Use the Write
or TryWrite
functions to write data to the file. The arguments specify the starting address of a buffer that contains data to write to the file and the number of bytes of data to write. The return value is the number of bytes actually written to the file. When writing past the end of the file, the file is expanded if possible. If at all possible, allocate the buffer with a 4-byte alignment. Though dependent on device and memory region, write speeds are substantially slower for buffers that are not 4-byte aligned. You can specify whether to flush the file cache (write the cache contents to media) while the file is written to by passing the appropriate value in the flush
parameter. We recommend specifying false
for this value to avoid wearing out the memory media and to prevent data corruption from memory cards possibly being removed during write operations. Instead, call the Flush
or TryFlush
functions just before closing the file. However, if you chose not to write and flush concurrently, be sure to flush the cache before you close the file.
Use the GetPosition
or TryGetPosition
functions to get, or the SetPosition
or TrySetPosition
functions to set, the file write start position (current position). The current position is the number of bytes from the start of the file. You can also use the Seek
or TrySeek
functions to set the base position and the offset from there. Note that although write speeds are dependent on device and memory region, write speeds are substantially slower if the current position is not set to a multiple of 4 bytes from the start of the file. If the current position is set before the start of the file or after the end of the file, the application halts on the next attempt at accessing the file.
Call the Finalize
function to close the file after you are done using it.
The following code sample shows opening a file using TryInitialize
, checking if it opened properly, and then writing to it.
nn::fs::FileOutputStream fos; nn::Result result = fos.TryInitialize(L"sdmc:/test.txt", true); if (result.IsSuccess()) { s32 ret; result = fos.TryWrite(&ret, buf, sizeof(buf)); } fos.Finalize();
7.1.4.3. nn::fs::FileStream Class
Use this class to open a file for both reading and writing. The initialization arguments are different, but the member functions operate the same way as for the previous two classes.
Specify the file to access and the access mode in the arguments to the constructor or to the Initialize
or TryInitialize
functions. Specify the access mode as a combination of the following flags.
Flag |
Description |
---|---|
|
Opens a file for reading. |
|
Opens a file for writing. Reading is also possible. |
|
When combined with |
7.1.5. Accessing Directories
Use the nn::fs::Directory
class to get information about directories and directory contents (subdirectories and files).
7.1.5.1. nn::fs::Directory Class
Use this class to open directories and list their contents. Specify the directory in the arguments to the constructor or to the Initialize
or TryInitialize
functions. The application halts if the directory specified in the arguments to the constructor or to the Initialize
function does not exist. Use the return value of the TryInitialize
function instead for error checking. The application halts if directory access is attempted from an instance that did not successfully open the directory, or if the directory is opened again from an instance that already has it open.
A slash is needed at the end of the path when specifying the root directory for media, such as the "/
" at the end of the "sdmc:/
" path. The ending slash may also be added for non-root directories, but it is not needed.
Use the Read
function to get directory entries. The arguments specify an array for directory entry information (an nn::fs::DirectoryEntry
structure) and the number of elements in the array. The return value is the number of entries actually stored in the array. Once all entries have been obtained, calls to the Read
function return 0
.
Call the Finalize
function to close the directory after you are done using it.
The following code sample shows opening a directory using TryInitialize
, checking whether it opened properly, and then getting its entries.
nn::fs::Directory dir; nn::fs::DirectoryEntry entry[ENTRY_MAX]; nn::Result result = dir.TryInitialize(L"sdmc:/TestDirectory"); if (result.IsSuccess()) { s32 readCount; while (true) { result = dir.TryRead(&readCount, entry, ENTRY_MAX); if (readCount == 0) break; ... } } dir.Finalize();
7.1.6. File and Directory Operations
Use these functions to create, rename, or delete files and directories.
7.1.6.1. Creating Files
Use the nn::fs::CreateFile
or nn::fs::TryCreateFile
functions to create a file of the specified size. The application halts if an error occurs on a call to the nn::fs::CreateFile
function. Use the return value from the nn::fs::TryCreateFile
function instead for error checking.
An error occurs if you attempt to create a file of the same full name as an existing file. If an error occurs for some reason other than during an attempt to create a file with the same name as an existing file, it is possible that an unnecessary file has been created, so delete any such unnecessary files. If no unnecessary files have been created, the delete operation returns nn::fs::ResultNotFound
.
7.1.6.2. Renaming Files
Use the nn::fs::RenameFile
or nn::fs::TryRenameFile
functions to rename a file. The application halts if an error occurs on a call to the nn::fs::RenameFile
function. Use the return value from the nn::fs::TryRenameFile
function instead for error checking.
An error occurs if you call these functions on a file that is open or if you attempt to rename a file to a name already used by another file in the same directory (including attempting to rename a file and specifying the name it already has).
7.1.6.3. Deleting Files
Use the nn::fs::DeleteFile
or nn::fs::TryDeleteFile
functions to delete a file. The application halts if an error occurs on a call to the nn::fs::DeleteFile
function. Use the return value from the nn::fs::TryDeleteFile
function instead for error checking.
An error occurs if you call these functions on a file that is open.
7.1.6.4. Creating Directories
Use the nn::fs::CreateDirectory
or nn::fs::TryCreateDirectory
functions to create a directory. The application halts if an error occurs on a call to the nn::fs::CreateDirectory
function. Use the return value from the nn::fs::TryCreateDirectory
function instead for error checking.
An error occurs if you call these functions on a directory located in a nonexistent directory or if you attempt to create a directory with a name already used by another directory in the same parent directory.
7.1.6.5. Renaming Directories
Use the nn::fs::RenameDirectory
or nn::fs::TryRenameDirectory
functions to rename a directory. The application halts if an error occurs on a call to the nn::fs::RenameDirectory
function. Use the return value from the nn::fs::TryRenameDirectory
function instead for error checking.
An error occurs if you call these functions on a directory that is open or if you attempt to rename a directory to a name already used by another directory in the same parent directory (including attempting to rename a directory and specifying the name it already has).
7.1.6.6. Deleting Directories
Use the nn::fs::DeleteDirectory
or nn::fs::TryDeleteDirectory
functions to delete a directory. A directory cannot be deleted unless it is empty. The application halts if an error occurs on a call to the nn::fs::DeleteDirectory
function. Use the return value from the nn::fs::TryDeleteDirectory
function instead for error checking.
An error occurs if you call these functions on a directory that is open.
The nn::fs::TryDeleteDirectoryRecursively
function attempts to completely delete the specified directory by recursively deleting all entries. If an error occurs during this process, the function returns that error.
7.1.7. Checking the SD card State
The SDK provides functions to check whether an SD card is inserted, to send notification when an SD card is inserted or removed, and to check whether an SD card can be written to.
bool nn::fs::IsSdmcInserted(); void RegisterSdmcInsertedEvent(nn::os::LightEvent* p); void UnregisterSdmcInsertedEvent(); void RegisterSdmcEjectedEvent(nn::os::LightEvent* p); void UnregisterSdmcEjectedEvent(); bool nn::fs::IsSdmcWritable(); nn::Result nn::fs::GetSdmcSize(s64* pTotal, s64* pFree);
Call the nn::fs::IsSdmcInserted
function to check whether an SD card is inserted. The function returns true
if a device is currently inserted in the SD card slot, even if it is a broken SD card or something other than an SD card, such as an empty SD card Adapter. The processing to check whether an SD card is inserted entails a heavy processor load; we recommend using the following functions to register an event class that waits for notification when an SD card is inserted or removed.
Call the nn::fs::RegisterSdmcInsertedEvent
and nn::fs::UnregisterSdmcInsertedEvent
to register and unregister an instance of the nn::os::LightEvent
class to receive notifications of SD card insertions.
Call nn::fs::RegisterSdmcEjectedEvent
and nn::fs::UnregisterSdmcEjectedEvent
to register and unregister an instance of the nn::os::LightEvent
class to receive notifications of SD card ejections.
The nn::fs::IsSdmcWritable
function returns true
if an SD card is inserted and can be written to.
The nn::fs::GetSdmcSize
function returns the total capacity of an SD card (pTotal
) and the available capacity (pFree
).
7.1.8. Latency Emulation
You can use the following function to debug changes in access speeds. It emulates file access latency in an application caused by conflicting file system access with SpotPass or another background process.
void nn::fs::InitializeLatencyEmulation(void);
Use the Config tool’s Debug Mode to enable and disable latency emulation. While the application is running, file accesses are delayed by the number of milliseconds specified by FS Latency Emulation only when the corresponding item is set to enable in the Config tool and this function has enabled latency emulation.
7.1.9. Access Priority Settings
File system access priority is supported. This allows you to access multiple files from multiple threads, because the execution order is appropriately adjusted, and items with a higher priority setting are processed more quickly.
Access priority requires real-time capability in the same way as streaming (an established number of cycles is required for an established amount of processing). Appropriate settings are provided for file access. Using these settings keeps delay time resulting from file access to a minimum.
7.1.9.1. Types of Access Priority
The access priorities that can be set are listed below, in priority order.
Type |
Definition |
Description |
---|---|---|
Real-time Priority |
|
Special priority level used for accessing files which, if loading is delayed, could detract from the user’s experience. This includes the loading of streaming data. There are, however, some restrictions on its use. |
Normal Priority |
|
Priority level used for accessing general purpose files, such as model data, scene data, and all types of save data. |
Low Priority |
|
Priority level used for accessing files which can be executed anytime the system is available for processing, for which the priority is lower than normal. This includes autosave. |
The definition used to specify access priority level is defined in the nn::fs::PriorityForApplication
enumerator.
For the restrictions on real-time priority see the CTR-SDK API Reference.
7.1.9.2. Access Priority Setting Targets
The following list shows a range of targets for access priority settings.
Setting Target |
Function Used in Setting |
---|---|
Overall File System |
|
Archive |
|
File Stream and Directory |
|
The Overall File System setting target refers to general access from the application and accessing of archives performed without specifying an archive name. This includes save data formats. If an access priority has not been set explicitly, the normal priority is applied.
The Archive setting target refers to archives whose name must be specified for an archive that is already mounted. Different access priority settings are possible for each archive. If the access setting is not set explicitly, the overall file system setting at the time the archive was mounted is applied. Even if the setting is changed for the overall system after the archive is mounted, the archive setting itself is not affected. Similarly, even if the archive setting is changed, the setting for the overall file system is not affected.
The File Stream and Directory setting is applied to objects accessed in the file stream (such as nn::fs::FileStream
) and directory objects (nn::fs::Directory
). Access priority settings can differ for each object even if it resides in the same file or directory. Unless the access priority setting is set explicitly, the archive setting at the point when the object was created is applied. Even if the archive setting is changed after creating an object, the various settings for each object is not affected. Similarly, even if individual settings of various objects are changed, the archive setting is not affected.
7.1.9.3. Cautions
Access priority changes the priorities within the file system. If the priority of the threads that call file access functions is not set at a high level, the file access process will not immediately start (even if a particular item has real-time priority). Set the priority of threads that call up file access functions at a high enough priority to meet the demands of their particular processing.
In addition, the order of completing access of desired files is not guaranteed. There is always a possibility that access of files with a lower priority level may be completed before files with a higher access priority, even though they were requested at a later time. Do not implement an application so that it is dependent on the order of file access completion, when multiple files are accessed in parallel.
Also, do not perform file access performance design based on measured performance values. For estimated access times, see CTR-SDK API Reference.
7.1.10. Restriction on the Number of Files and Directories Opened Simultaneously
There are limits to the number of files and directories that can be opened at the same time using the file system.
For safe operation, limit the number of files and directories that an application opens simultaneously to the following.
- Up to four files of the archive that directly access the SD card and the expanded save data archive combined
- Up to 10 files for the save data archive (including the save data of other applications)
- Up to 10 total files
A ROM archive is not subject to these limits. Its limits are in accordance with the parameters defined by calling the MountRom
function.
7.2. ROM Archives
ROM archives are read-only archives for accessing ROMFS that are created at build time. ROM archives must be mounted explicitly by applications. The mounting procedure and arguments are the same regardless of whether the application is card-based software or a downloadable application.
The use of ROM archives requires working memory allocated by the application. The required size of this working memory depends on the number of files and directories that can be opened simultaneously. You can get the required working memory size by calling nn::fs::GetRomRequiredMemorySize
. If this function returns a negative value, you cannot mount a ROM archive. After your application has allocated the required amount of memory (or more) with 4-byte alignment, call nn::fs::MountRom
to mount the ROM archive.
s32 nn::fs::GetRomRequiredMemorySize(size_t maxFile, size_t maxDirectory, bool useCache = true); nn::Result nn::fs::MountRom(const char* archiveName, size_t maxFile, size_t maxDirectory, void* workingMemory, size_t workingMemorySize, bool useCache = true);
Specify the number of files and directories that can be opened simultaneously in maxFile
and maxDirectory
, respectively. The number of files that can be opened simultaneously only depends on the amount of working memory. This number is not affected by factors like the length of filenames.
Specify a value of true
in the useCache
parameter to cache metadata and shorten the time required to open files or scan directories. Note that this increases the amount of working memory required.
Specify the name of the archive to mount in the archiveName
parameter. The archive is mounted to rom:
if you call the overloaded version that omits this parameter.
Pass the working memory and its size to workingMemory
and workingMemorySize
.
You do not need to handle errors for these functions. If an error occurs in a function, an error screen displays but an error is not returned to the application.
7.2.1. Specifying Archive Names
Only single-byte alphanumeric characters and some symbols excluding the colon as archive name delimiter may be used to specify archive names. Names are case-sensitive. Names must be specified as at least one character and no more than eight, including the colon delimiter.
Do not use archive names that start with the dollar symbol ($). For information about characters and words that cannot be used in archive, file, and directory names, see the API Reference.
7.3. Save Data
For card-based software, the application-specific save data region is located in the backup memory. For downloaded applications, the save data region is located in an archive file on an SD card. The save data region can be accessed by the FS library as an archive. The function and parameters used to access this archive are the same regardless of whether the accessing application is stored on a Nintendo 3DS Card or an SD card.
nn::Result nn::fs::MountSaveData(const char* archiveName = "data:"); nn::Result nn::fs::MountSaveData(const char* archiveName, bit32 uniqueId); nn::Result nn::fs::MountDemoSaveData(const char* archiveName, bit32 uniqueId, bit8 demoIndex); nn::Result nn::fs::FormatSaveData(size_t maxFiles, size_t maxDirectories, bool isDuplicateAll = false); nn::Result nn::fs::CommitSaveData(const char* archiveName = "data:");
Call the nn::fs::MountSaveData
function to mount a save data region to the archive path name specified in the archiveName
parameter. The save data from another application can be mounted by calling the overloaded function with the uniqueId
parameter. Call the nn::fs::MountDemoSaveData
function to mount the save data region of the demos. Specify the index of the demo for demoIndex
.
If an error belonging to the class nn::fs::ResultNotFormatted
is returned, format the save data region by calling the nn::fs::FormatSaveData
function and then try mounting it again. When formatting, specify the maximum number of files and directories using the maxFiles
and maxDirectories
parameters. There are no restrictions on the values that can be specified. For more information about specifying archive names, see 7.2.1. Specifying Archive Names.
The values to be specified in uniqueId
upon mounting the save data region are based on the unique ID specified in each application’s RSF file. To specify another application’s unique ID, you must first specify that application’s unique ID in the RSF file of the application to be mounted.
The same error class (nn::fs::ResultNotFound
) is returned when mounting the save data region from a downloaded application (including demos) if a Nintendo 3DS Card is not inserted, or if the inserted 3DS Card does not have a formatted save data region.
You cannot escape from this state if you do not start the card-based software application, or start the card-based software application but do not format the save data region. Be sure to display a message that accommodates the reason the caused the error. For example, your message could say something like: “Could not find save data for (media name). If you have never run the game before, run it now, create save data, and try again.”
The library supports automatic redundancy for the entire save data region. The library mirrors data over the entire save data region when the isDuplicateAll
parameter during formatting was specified as true
. When writing files with automatic redundancy enabled, you must call nn::fs::CommitSaveData
before unmounting the save data region or the file updates will not be written to the media. Likewise, if the power is cut before the updates are committed to memory, only the old data will be available the next time the system is booted and the save data is mounted.
If save data consists of multiple interdependent files, you must call nn::fs::CommitSaveData
when these dependencies are not contradictory.
When automatic redundancy is enabled, the memory available for save data files is half of the physical capacity of the backup region. A portion of this space is also reserved for file system management. Use the Worksheet for Calculating the Save Data Capacity, found in the API Reference, to calculate the actual available capacity of the backup region. The current implementation uses regions of 512 byte blocks when saving files.
The mounted save data region can be treated as an archive. You can create files freely within the archive. Filenames and directory names can be up to 16 characters long, and the maximum path length is 253 characters. Only half-width alphanumeric characters and some symbols can be used for filenames and directory names. Slashes ("/
") are used to delimit folders in paths. Other than the amount of available capacity for save data, there are no restrictions on the maximum size of the file that can be created or the maximum size that can be written at one time.
Call the nn::fs::GetArchiveFreeBytes
function to get the available capacity of an archive.
nn::Result nn::fs::GetArchiveFreeBytes(s64* pOut, const char* archiveName);
For the pOut
parameter, specify a pointer to a variable of type s64
to receive the number of available bytes. For the archiveName
parameter, use the name of the archive specified when it was mounted.
The files within the archive are protected by a tamper detection feature that uses hash values. If the hash value of the data that is accessed does not match the expected value, the system determines that the data has been tampered with and returns an error. When automatic redundancy is not enabled, mismatching hash errors can also occur when trying to access data that was corrupted if the system was turned off during a save or if the card was removed during a save.
When you finish accessing the save data region, call the nn::fs::Unmount
function, specifying the archive name as an argument, to unmount the save data.
nn::Result nn::fs::Unmount(const char* archiveName);
Downloaded applications, much like Nintendo DSiWare titles, create a save data region automatically as soon as they are imported into the target media. When a downloaded application is deleted, its save data is also deleted.
7.3.1. Measures Against Rollback of Save Data
See the File System: Save Data Rollback Prevention Feature section in the CTR-SDK API Reference.
In general, if an application uses the save data rollback prevention feature, do not support backing up save data in the banner specifications file. (Set DisableSaveDataBackup
to True
.) For more information, see the 3DS Overview developer manual included in the CTR Overview package and the description of ctr_makebanner
included in the ../documents/tools/
folder of the CTR-SDK. Switching between using and not using the save data rollback prevention feature when applying a patch is prohibited.
7.3.2. Handling Errors When Files or Directories Do Not Exist
Even if an application takes various measures to create save data correctly, if the following conditions are met, it is still possible to reach a state where mounting is completed successfully, but save data files do not exist.
- The save data was formatted the first time it is created.
- After the formatting is completed, while creating files or directories (and before the save data is committed when automatic redundancy is enabled), the application crashes for one of the following reasons.
- The Power Button was held down.
- The card was removed.
- Power was cut off partway through the process.
This occurs particularly in applications that allow the system to go into a sleep state, when the system is closed and then left alone.
In cases like these, nn::fs::ResultNotFound
could be returned for file or directory operations. Implement your application so that the user’s save data can be restored to its normal state.
7.4. Expanded Save Data
The term expanded save data refers to data that is created on an SD card and managed separately from the save data. The expanded save data region can only be used on the system it was created on. We recommend that the expanded save data region be used to store any application-specific data that is linked to (relies on) information that only exists on a specific system (for example, friend information). Do not use the expanded save data region to store data that is required in order to make progress in the game. The system never automatically creates expanded save data.
nn::Result nn::fs::MountExtSaveData(const char* archiveName, nn::fs::ExtSaveDataId id); nn::Result nn::fs::CreateExtSaveData(nn::fs::ExtSaveDataId id, const void* iconData, size_t iconDataSize, u32 entryDirectory, u32 entryFile); nn::Result nn::fs::DeleteExtSaveData(nn::fs::ExtSaveDataId id);
To use expanded save data, you must first mount it by specifying the expanded save data ID in a call to the nn::fs::MountExtSaveData
function. If the function returns a value of nn::fs::ResultNotFormatted
, nn::fs::ResultNotFound
, nn::fs::ResultBadFormat
, or nn::fs::ResultVerificationFailed
and expanded save data has already been created, delete expanded save data using the nn::fs::DeleteExtSaveData
function and create expanded save data by calling the nn::fs::CreateExtSaveData
function and then re-mount.
The expanded save data is mounted to the archive path specified in the archiveName
parameter. For more information about specifying archive names, see 7.2.1. Specifying Archive Names.
Specify the expanded save data ID in the id
parameter. The expanded save data ID is a number that identifies the expanded save data. The expanded save data ID for expanded save data that applications can access must be set as either ExtSaveDataNumber
in the makerom
RSF file (only one ID can be specified) or AccessibleSaveDataIds
in AccessControlInfo
(where multiple IDs can be specified). Normally you would specify a unique ID issued by Nintendo for expanded save data IDs created by applications. Consequently, when sharing expanded save data among multiple applications, you must specify the unique ID for one of those applications.
When creating expanded save data, the iconData
and iconDataSize
parameters are used to specify an icon for display on the data management screen (as an icn
file created using the ctr_makebanner32
tool) and the icon’s size. Use the entryDirectory
and entryFile
parameters to specify the number of directories and files to store in the save data
The mounted expanded save data region can be treated as an archive. You can create files freely within the archive. However, the size of files cannot be changed after they are created unless the nn::fs::TryCreateFile
function is used to create them. Filenames and directory names can be up to 16 characters long, and the maximum path length is 248 characters. Only half-width alphanumeric characters and certain symbols can be used for filenames and directory names. Slashes ("/
") are used to delimit folders in paths. Archive sizes are variable.
The files within the archive are protected by a tamper detection feature that uses hash values. If the hash value of the data that is accessed does not match the expected value, the system determines that the data has been tampered with and returns an error. Mismatching hash errors call also occur when trying to access data that was corrupted if the system was turned off during while writing data or if the card was removed while writing data. Unlike save data, the library does not support expanded save data mirroring.
There are no data protection features, so removing an SD card while a file is writing will very likely result in corrupted expanded save data. You must re-create any corrupted expanded save data after deleting using the nn::fs::DeleteExtSaveData
function.
When you finish accessing the expanded save data region, call the nn::fs::Unmount
function, specifying the archive name as an argument, to unmount the expanded save data.
Deleting downloaded applications has no effect on expanded save data. Expanded save data can be deleted from the Data Management screen of System Settings.
The application is free to use up to a total of 32 MB of expanded save data. If you want to use more than 32 MB, please contact Nintendo.
7.4.1. Uses of Expanded Save Data
Expanded save data can be used to save information such as the following.
- Application-specific data
- Data shared by a series (shared among multiple titles or versions of titles in the same series)
- Downloaded data (such as additional items or levels)
- Contextual CTR banner data (created in the application, or downloaded from a server)
7.4.1.1. Application-Specific Data
This can include relatively unimportant data that is unrelated to the game's progress, data that is linked to information that is only saved on the system, or large user-created data.
7.4.1.2. Data Shared by a Series
This refers to data that is shared by multiple titles in the same series (including different versions of a title). You can share data between titles in a series by using the common expanded save data ID throughout the series.
7.4.1.3. Downloaded Data
This refers to additional levels or other such data that are downloaded by the application by registering a download task. Only the application that registered a download task can access the data acquired from that download task, but you can make this data accessible to other applications that share the expanded save data by moving it to expanded save data.
7.4.1.4. Contextual CTR Banner Data
This refers to data that can replace portions of the data in CTR title banners. The portions that can be replaced are the scrolling text that is displayed at the bottom of the upper screen and a single texture for the replacement name. The system does not support replacing icon data.
There are two types of contextual CTR banner data: local contextual banners, which are created by applications, and downloaded contextual banners, which are downloaded from servers.
Local Contextual Banners
Local data can be used to display a message based on the game's progress or to change part of the texture that is displayed in the CTR title banner. The text can be up to 255 characters (either single byte or double byte), and the data size can be up to 128 KB. It is possible to include multiple images within a single texture and to apply these images to multiple models. Prepare "COMMON" data used for all languages in addition to any language-specific data that may be enabled for the target region.
Downloaded Contextual Banners
This is provided to a server and can be used to change the display. The text and textures have the same specifications as for local contextual banners. You can specify an expiration date (year, month, and day) for downloaded contextual banners.
Displaying Contextual CTR Banner Data
The text displayed for the contextual banner data alternates in the following order: (1) text for downloaded contextual banners, (2) text for local contextual banners. If the texture included within a local contextual banner has the same name as a texture included within a downloaded contextual banner, the system gives priority to the downloaded contextual banner’s texture.
7.4.2. Access From Multiple Game Cards
More than one person may share a single 3DS system using a single SD card, with both people owning game cards having the same title. This alone does not represent a problem because save data is written to the backup memory of each separate game card. Note, however, that bugs may arise depending on the configuration of the data saved in expanded save data.
Specifically, situations like the following may occur.
- The program may run out of control if the integrity of save data and expanded save data is lost.
- If you use fixed filenames for save data, files stored in expanded save data created on one game card may overwrite that of the other, regardless of the user’s intention.
- Contrary to the developer’s intention, the same number of SD cards will be required for multiple game cards to be played on the same 3DS system.
For example, unintentional overwriting of data can be avoided by assigning unique directory and filenames when creating files in expanded save data. However, rather than using a name freely chosen by the user, such as a character name, as the basis for naming such files, apply a text string such as one that includes the current time. In addition, when writing information linked with expanded save data in save data, note that the expanded save data associated with that information may not exist on the SD card.
As for contextual CTR banner data, there is no way to handle situations like this. The downloaded contextual banner data or local contextual banner data last created is enabled, regardless of which game card is inserted.
7.4.3. Accessing Multiple Items of Expanded Save Data
You can specify multiple unique IDs (up to a maximum of six) for the accessible save data attributes (AccessibleSaveDataIds
in AccessControlInfo
) in the RSF file to enable access to the following data.
- Save data with the same unique IDs as the unique IDs specified
- Expanded save data with the same expanded save data IDs as the unique IDs specified
Individual items of expanded save data can be deleted in System Settings. Avoid saving data that needs to be as consistent as expanded save data whenever possible.
For titles that use CTR contextual banners, create expanded save data that takes the title's unique ID as its expanded save data ID in addition to the expanded save data for shared access. This is because the files used to display the contextual banner must be in the ExBanner
directory for expanded save data created with the title’s unique ID.
For information about how to write the RSF file, see the reference for the ctr_makerom CTR-SDK tool or the 3DS Programming Manual: How to Create Applications.
7.5. Archives That Directly Access SD Cards
Call the nn::fs::MountSdmcWriteOnly
function to mount an archive that directly accesses the inserted SD card. Note that the FileSystemAccess
permission attribute under AccessControlInfo
in the RSF file must specify – DirectSdmcWrite
.
nn::Result nn::fs::MountSdmcWriteOnly(const char* archiveName = "sdmcwo:");
No files or directories can be read in archives mounted using this function, but written files are not encrypted, so they can be read from a PC. Unlike any expanded save data on the same SD card, you can change the size of files after they are created.
There are fewer errors possible when mounting this kind of archive than when mounting expanded save data, and you can handle these errors in the same way as for expanded save data. However, because reading is prohibited, you cannot directly check for the existence of any file or directory. You must attempt writing once and then handle any nn::fs::ResultAlreadyExists
errors returned by the write function.
Call the nn::fs::Unmount
function and pass the archive name as an argument to unmount an archive.
Applications are restricted from writing depending on the path. For more information about restrictions and cautions, see the File System section of the guidelines.
7.6. Media-Specific Cautions
7.6.1. Nintendo 3DS Cards
When save data is mounted and the return value is nn::fs::ResultVerificationFailed
, the save data is corrupted due to the memory card being removed or the power being cut while data was writing. In such cases, you must reformat the save data. If data automatic redundancy was enabled, you would normally never see this return value due to user actions such as removing the memory card. However, it is possible that data was tampered with. As an exception, handle this situation by forcibly formatting the save data or taking similar measures.
Verifying Different Access Speeds
The performance of access to ROM archives on Nintendo 3DS Cards by the FS Library can vary greatly depending on the condition of the media. System updates may also improve performance. If you plan to sell your application on Nintendo 3DS Card, verify the following to make sure that changes in access speed do not cause issues.
- For CARD1 applications:
- Enable DebugMode using the config tool (with latency emulation enabled) and play through all modes.
- Set the development card speed to Fast and play through all modes of the Release build of the application written to a development card
- For CARD2 applications:
- Enable DebugMode using the config tool (with latency emulation enabled) and play through all modes.
- From the PARTNER-CTR Debugger Card Control dialog box, in the Card Emulation Control tab, set the transfer speed in the emulation memory speed settings to Fast, and play through all modes of the application (Release build CCI file) loaded in emulation memory.
- Set the development card speed to Fast and play through all modes of the Release build of the application written to a development card
Be sure to test using the latest version of the PARTNER-CTR Debugger/Writer.
7.6.2. SD Memory Cards
Note the following points when you implement an application that accesses an SD card, including expanded save data or downloadable application stored in the SD card.
Cautions for Directly Accessing SD Memory Cards
Archives for accessing SD cards mounted using the nn::fs::MountSdmc
function (mounted under sdmc:/
if not otherwise specified) are provided for debugging purposes only. Note that these archives are not accessible from production versions.
Note that this mount point cannot be accessed in retail versions of games. Note also that SD cards may be swapped out while the system is asleep. To remount an SD card that has been removed, first call nn::fs::Unmount
to unmount it from the file system, and then call the appropriate mounting function to remount.
Streaming Playback Cautions
In some situations, SD card file access may become slower, such as when the SD card is almost full, it is significantly fragmented, or it is near the end of its service life. Most functions that access SD cards are affected by this and slow down; the effect is particularly large with functions for creating files (for example, nn::fs::CreateExtSaveData
).
Note that calling these functions while the system is streaming sound or movies may cause choppy audio or dropped frames, because file access blocks until the functions complete executing.
“Read Disturb” Measures
SD cards have a limit to their data storage capacity in relation to their read capacity. When trying to read beyond the limit, the data can become degraded in a phenomenon known as read disturb. Depending on the area where the read disturb phenomenon occurs, all data on the SD card might become unreadable.
Of particular concern is that this can occur just by opening a file to be loaded from the SD card. Some SD cards on the market are not sufficiently resistant to the read disturb phenomenon. The best way to lower the risk of this phenomenon occurring is to reduce, as much as possible, the amount of file opening and closing that occurs on the application side. For example, when multiple accesses are required at different offsets to the same file, perform the accesses all within a small number of open and close processes, rather than opening and closing the file for each access.
In addition, by repeatedly reading the same region on the SD card, data retention becomes unstable. Problems can occur most often in access to ROM archives and add-on content that has not been rewritten. Even if the application is card-based software, be careful if there is a possibility of selling the card-based software as a downloadable application in the future.
As a measure to retain the data longer when the particular data is repeatedly read from the SD card (such as when repeatedly streaming playback of short waveform data), you can read the data from the SD card to the buffer in internal memory and use the data on that buffer. Making the buffer size 16 KB or more is effective, because the situation improves with a larger size.
This measure is not a restriction, and you are not required to implement it when memory is limited.
For the developers using the NW4C Sound Library, see the explanation in the nw::snd::SoundArchivePlayer
class reference.
7.7. Difference Between Save Data and Expanded Save Data
When an application goes on sale, you can now select the 3DS Card (CARD2) as the storage media for that application, thereby increasing the storage capacity available for application-specific save data. This allows you to save data that had to be saved as expanded save data (due to storage capacity issues) in the past. With this change in specifications, you must carefully distinguish between save data and expanded save data based on the type and purpose of the data being saved. This chapter describes the distinguishing characteristics of save data and expanded save data and where to save each type of data.
7.7.1. Characteristics of Save Data and Expanded Save Data
Save data and expanded save data have the following distinguishing characteristics.
Item |
Save Data |
Expanded Save Data |
---|---|---|
Redundancy |
Supported |
Not supported |
Backup |
Supported. (Downloadable applications only.) Usually enabled (See below.) |
Not supported |
Rollback prevention |
Supported. (Downloadable applications only.) Backup must be disabled if this feature is used. |
Not supported |
Storage media |
Card-based applications: Card backup region Downloadable applications: SD card |
SD card |
When storage is allocated |
Card-based applications: From the first use. Downloadable applications: At time of installation. |
When allocated by the application. |
Removal |
Only from inside the application. |
Can be removed from inside an application or on the Data Management screen in System Settings. |
Data loss |
The risk of losing data can be reduced by using the backup and redundancy features. |
The risk of losing data cannot be reduced. |
Lost data recovery |
With redundancy: Lost data needs to be recovered only when an entire archive is lost. Without redundancy: Lost data needs to be recovered when an entire archive is lost or when individual files or directories are lost. |
Lost data needs to be recovered when an entire archive is lost or when individual files or directories are lost. |
Ensuring sufficient storage capacity |
Unnecessary. (Save data must fit into a fixed amount of allocated storage.) |
Must be performed each time a file is created. |
In some applications, the backup feature has been disabled for the following reasons.
- They are using rollback prevention.
- Inconsistencies may arise between expanded save data and save data if only save data is rolled back using the backup feature.
- Restoring save data from backups may be problematic.
For example, users might repeatedly transfer DLC or rare or hidden characters or items to other users over and over again, restoring their save data each time.
7.7.2. Policy for Selecting the Storage Location for Data
In general, save important data in save data. Important data may include data that can make further progress in a game impossible if lost. For data that can be lost and the player can still progress in the game, and data where the amount of storage required is variable, save in expanded save data. Because expanded save data can be deleted on the Data Management screen under System Settings, developers must note that this data may disappear at times that are beyond the control of the application. In addition, developers also must consider the fact that packaged versions of applications available on cards may be played on other consoles, and that an SD card other than the one on which the expanded save data accessed last time is stored may be inserted in a console the next time a particular game is played.
Save the following information in save data to protect a user’s data.
- Data necessary for progress in a game
- Data necessary for consistency with game progress data
If data necessary for consistency with game progress is saved in expanded save data and save data is deleted, initialization may be required or consistency of data may be lost if the backup feature is used. This can massively increase work involved in the debugging process, such as requiring you to roll back save data in each location of the game or having to delete save data and then verify the consistency of data.
We recommend that the following data also be saved in save data.
- Important data required to enjoy the game
Such data includes screenshots taken when a stage is first cleared or congratulatory messages sent from friends. - Data that requires consistency to be maintained across multiple files
Such data includes command data and textures used to replay scenes.
The chance of losing data at the file level is lower with save data. However, with data linked to a single file and saved, the risk of losing the file is the same whether you use expanded save data or save data without redundancy.
We recommend that the following data be saved in expanded save data if losing it will not adversely affect game progress.
- Data that can be redownloaded from the network or that can be re-created while playing the game
- Data shared with other applications
We advise developers to save data shared between games, particularly games played in parallel, in expanded save data. Developers must keep in mind that using shared data in conjunction with the save data backup feature may lead to dishonest activity such as generating an excessive number of items. - Data where the number of files or amount of storage required is not fixed
Such data includes music or stages created by the user or videos recorded by the user.
With expanded save data, you specify the number of files and directories that can be saved, but there is no limit on the size of files. In the case of save data, on the other hand, even though the number of directories and files that can be saved is similarly specified when save data is initialized, save data is unsuitable for saving multiple instances of data having an unfixed size because storage capacity is limited. - Data imported from the save data of another application
Although you can access the save data of other applications, because incompatibilities may arise depending on the particular combination of titles, use expanded save data even when transferring data from an earlier version. Save data may be accessed, however, when transferring save data from a downloadable demo or another download-only application.