7. File System

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.

Table 7-1. Classes Used for File Access

Class

Description

nn::fs::FileInputStream

Opens a file for reading.

nn::fs::FileOutputStream

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.

nn::fs::FileStream

Opens a file for reading and writing, depending on the access mode specified.

Warning:

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 make 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.

Table 7-2. Specifying the Base Position

Setting Value

Description

POSITION_BASE_BEGIN

Sets the current position based on the start of the file.

POSITION_BASE_CURRENT

Sets the current position based on the current position in the file.

POSITION_BASE_END

Sets the current position based on the end of the file.

Call the Finalize() function to close the file after you have finished using it.

The following code sample shows opening a file using TryInitialize, checking whether it opened properly, and then reading from it.

Code 7-1. File Reading
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.

Note:

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, make 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 have finished using it.

The following code sample shows opening a file using TryInitialize, checking if it opened properly, and then writing to it.

Code 7-2. File Writing
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.

Table 7-3. Access Mode Flags

Flag

Description

OPEN_MODE_READ

Opens a file for reading.

OPEN_MODE_WRITE

Opens a file for writing. Reading is also possible.

OPEN_MODE_CREATE

When combined with OPEN_MODE_WRITE, if the file specified at initialization does not already exist, it is created.

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. After all entries have been obtained, calls to the Read() function return 0.

Call the Finalize() function to close the directory after you have finished using it.

The following code sample shows opening a directory using TryInitialize, checking whether it opened properly, and then getting its entries.

Code 7-3. Directory Access
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.

Code 7-4. Checking the SD Card Insertion State, Notifying of Insertion/Removal, and Checking for Writability
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.

Code 7-5. Initializing Latency Emulation
void nn::fs::InitializeLatencyEmulation(void); 

Use Debug Mode in the Config tool’s Debug Settings 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 shown in the following table, in priority order.

Table 7-4. Access Priority Types

Type

Definition

Description

Real-time Priority

PRIORITY_APP_REALTIME

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_APP_NORMAL

Priority level used for accessing general purpose files, such as model data, scene data, and all types of save data.

Low Priority

PRIORITY_APP_LOW

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.

Note:

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.

Table 7-5. Access Priority Setting Targets and Functions Used in Settings

Setting Target

Function Used in Setting

Overall File System

nn::fs::SetPriority

Archive

nn::fs::SetArchivePriority

File Stream and Directory

TrySetPriority or SetPriority of all classes

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 extended 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

 

Note:

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 nn::fs::WORKING_MEMORY_ALIGNMENT (4 bytes) alignment, call nn::fs::MountRom() to mount the ROM archive.

Code 7-6. Mounting a 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.

Note:

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.

Code 7-7. Mounting, Formatting, and Committing Save Data Regions
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.

Note:

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.

If an error belonging to the nn::fs::ResultNotFormatted, nn::fs::ResultBadFormat, or nn::fs::ResultVerificationFailed is returned in the save data region mount, 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.

Warning:

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."

Note:

Depending on the media, some errors are not returned.
For example, currently, CARD1 may return nn::fs::ResultBadFormat when it is mounted, but CARD2 and downloaded applications will not return the error nn::fs::ResultBadFormat.
We recommend that you write media independent processing that can adapt to future changes in error handling even in such situations.

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.

Warning:

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 certain 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.

Code 7-8. Getting an Archive’s Available Capacity
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.

Code 7-9. Unmounting
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

Note:

See the File System: Save Data Rollback Prevention Feature section in the CTR-SDK API Reference.

Warning:

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. Extended Save Data

The term extended save data refers to data that is created on an SD card and managed separately from the save data. The extended save data region can only be used on the system it was created on. We recommend that the extended 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 extended save data region to store data that is required in order to make progress in the game. The system never automatically creates extended save data.

Code 7-10. Mounting, Creating, and Deleting Extended 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 extended save data, you must first mount it by specifying the extended 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 extended save data has already been created, delete extended save data using the nn::fs::DeleteExtSaveData() function and create extended save data by calling the nn::fs::CreateExtSaveData() function and then remount. If an error belonging to nn::fs::ResultOperationDenied has been returned, it may be that the SD card or file cannot be written to, or that there is a contact fault in the SD card. See the File System: Error Handling section in the API Reference.

The extended 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 extended save data ID in the id parameter. The extended save data ID is a number that identifies the extended save data. The extended save data ID for extended 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 extended save data IDs created by applications. Consequently, when sharing extended save data among multiple applications, you must specify the unique ID for one of those applications.

When creating extended 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 extended 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 extended 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 extended save data. You must re-create any corrupted extended save data after deleting using the nn::fs::DeleteExtSaveData() function.

When you finish accessing the extended save data region, call the nn::fs::Unmount() function, specifying the archive name as an argument, to unmount the extended save data.

Deleting downloaded applications has no effect on extended save data. Extended save data can be deleted from the Data Management screen of System Settings.

Note:

The application is free to use up to a total of 32 MB of extended save data. If you want to use more than 32 MB, please contact Nintendo.

7.4.1. Uses of Extended Save Data

Extended 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 extended 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 obtained from that download task, but you can make this data accessible to other applications that share the extended save data by moving it to extended 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 prioritizes 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 extended save data.

Specifically, situations like the following may occur.

  • The program may run out of control if the integrity of save data and extended save data is lost.
  • If you use fixed filenames for save data, files stored in extended 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 is 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 extended 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 extended save data in save data, note that the extended save data associated with that information may not exist on the SD card.

Warning:

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 Extended 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
  • Extended save data with the same extended save data IDs as the unique IDs specified

 

Warning:

Individual items of extended save data can be deleted in System Settings. Avoid saving data that needs to be as consistent as extended save data whenever possible.

For titles that use CTR contextual banners, create extended save data that takes the title's unique ID as its extended save data ID in addition to the extended save data for shared access. This is because the files used to display the contextual banner must be in the ExBanner directory for extended save data created with the title’s unique ID.

Note:

For information about how to write the RSF file, see the reference for the ctr_makerom CTR-SDK tool or the 3DS Programming Manual: Creating 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.

Code 7-11. Mounting an Archive That Directly Accesses an SD Card
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 extended 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 extended save data, and you can handle these errors in the same way as for extended 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.

Note:

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

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:
    1. Enable DebugMode using the Config tool's Debug Setting (with latency emulation enabled) and play through all modes.
    2. 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:
    1. Enable DebugMode using the Config tool's Debug Setting (with latency emulation enabled) and play through all modes.
    2. 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.
    3. Set the development card speed to Fast and play through all modes of the Release build of the application written to a development card

 

Warning:

Make 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 extended 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 downloadable 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.

Note:

For the developers using the NW4C Sound Library, see the description in the nw::snd::SoundArchivePlayer class reference.

7.7. Difference Between Save Data and Extended 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 extended save data (due to storage capacity issues) in the past. With this change in specifications, you must carefully distinguish between save data and extended save data based on the type and purpose of the data being saved. This chapter describes the distinguishing characteristics of save data and extended save data and where to save each type of data.

7.7.1. Characteristics of Save Data and Extended Save Data

Save data and extended save data have the following distinguishing characteristics.

Table 7-1. Characteristics of Save Data and Extended Save Data

Item

Save Data

Extended 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 extended 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 downloadable content 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 extended save data. Because extended 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 must also consider 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 extended 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 extended save data or save data without redundancy.

 

We recommend that the following data be saved in extended 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 extended save data. Developers must note 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 extended 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, incompatibilities can occur depending on the particular combination of titles. For this reason, use extended 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.

 


CONFIDENTIAL