7. ファイルシステム

この章では、各メディアに対するアクセスで使用する FS ライブラリと、メディアごとの注意点などについて説明します。

7.1. FS ライブラリ

3DS に搭載されている記憶装置(3DS カード、SD カード)に対するアクセスは、必ず FS ライブラリを介して行わなければなりません。

7.1.1. 初期化

FS ライブラリの初期化は nn::fs::Initialize() を呼び出して行います。すでに初期化されているときに、再度呼び出しても問題はありません。

ファイル、ディレクトリに対する関数の呼び出しは、FS ライブラリの初期化後でなければエラーとなります。

7.1.2. 終了

FS ライブラリの使用を終了する場合は、開いていたファイルやディレクトリを閉じ、すべてのアーカイブのマウントを解除してください。

アプリケーションの終了時やスリープ状態への移行時にも FS ライブラリの使用を終了もしくは中断しなければなりません、詳細については「5.3.1.1. 終了処理の注意事項」、「5.3.3.4. スリープ中に禁止されている処理」を参照してください。

7.1.3. パスの指定について

パス名(ファイル名やディレクトリ名)の指定は絶対パスでなければなりません。パスの区切りには "/"(スラッシュ)を用います。

FS ライブラリの各関数でパス名を指定する際の文字列には、ワイド文字列とマルチバイト文字列(ASCII 文字のみ)を使用することができます。ただし、マルチバイト文字列で指定すると、ワイド文字列への変換でライブラリがスタック上に大きなバッファを確保するため、スタックの大きさに注意しなければなりません。そのため、特別な理由がない限りはワイド文字列で指定するようにしてください。

7.1.4. ファイルへのアクセス

メディア上のファイルへのアクセスに使用するクラスは、以下の 3 種類から目的に応じて選択します。

表 7-1. ファイルへのアクセスに使用できるクラス

クラス

説明

nn::fs::FileInputStream

読み込み専用でファイルを開きます。

nn::fs::FileOutputStream

書き込み専用でファイルを開きます。指定したファイルが存在していなかった場合、初期化時の指定でファイルを新規に作成して開くことができます。

nn::fs::FileStream

アクセスモードの指定によって、読み込みと書き込みの両用でファイルを開くことができます。

注意:

ファイルアクセスのクラスには、Initialize()TryInitialize() のように、関数名の先頭に Try の付加された関数とそうでない関数が存在します。行われる処理に違いはありませんが、アプリケーションで使用する関数には、Try の付加されている関数を使用してください。

基本的に、FS ライブラリの関数は実行が完了するまで処理を戻しません。また、ファイルへのアクセスはリクエストの到着順に処理されるため、優先度の高いスレッドからのファイルアクセスであっても、先にリクエストされたファイルアクセスが完了するまで処理されません。

7.1.4.1. nn::fs::FileInputStream クラス

ファイルからデータを読み込むには、このクラスを使用してファイルを開いてください。読み込むファイルの指定は、コンストラクタの引数や、Initialize() または TryInitialize() の引数で行います。引数ありのコンストラクタや Initialize() でファイルを開くときに、存在しないファイルを指定した場合はアプリケーションが停止します。TryInitialize() でファイルを開いたときに限り、返り値でエラーを判定することができます。正常に開くことのできなかったインスタンスでファイルにアクセスした場合や、すでにファイルを開いているインスタンスで新たにファイルを開こうとした場合はアプリケーションが停止します。

ファイルのサイズは GetSize() または TryGetSize() で取得することができます。読み込みに使用するバッファのサイズを決定する際などに利用することができます。

ファイルの読み込みは Read() または TryRead() で行います。引数として、ファイルの内容をコピーするバッファとバッファのサイズを渡してください。返り値には実際にバッファにコピーされたバイト数が返され、ファイルの終端に達していた場合は 0 を返します。バッファはなるべく 4 Byte アライメントで確保してください。デバイスやメモリ領域によって変化しますが、4 Byte アライメントではないバッファかつ、カレント位置が 4 Byte 単位で変化しない読み込みの速度はかなり遅くなります。

ファイルの読み込み開始位置(カレント位置)は GetPosition() または TryGetPosition() で取得、SetPosition() または TrySetPosition() で設定することができます。カレント位置はファイルの先頭からのバイト数です。また、Seek() または TrySeek() では基準となる位置(ベース位置)と、そこからのオフセットで設定することもできます。設定されたカレント位置がファイルの先頭から 4 の倍数ではない場合、デバイスやメモリ領域によって変化しますが、読み込み速度はかなり遅くなりますので注意してください。カレント位置がファイルの先頭より前に設定された場合や、ファイルの終端以降に設定された場合は次のアクセス時にアプリケーションが停止します。

表 7-2. ベース位置の指定

設定値

説明

POSITION_BASE_BEGIN

ファイルの先頭を基準にカレント位置を設定します。

POSITION_BASE_CURRENT

ファイルのカレント位置を基準にカレント位置を設定します。

POSITION_BASE_END

ファイルの終端を基準にカレント位置を設定します。

ファイルへのアクセスを終了するときは Finalize() を呼び出し、ファイルを閉じてください。

以下のコード例では、TryInitialize() で正常にファイルを開くことができたかを確認してから読み込みを行っています。

コード 7-1. ファイル読み込みのコード例
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 クラス

ファイルにデータを書き込むには、このクラスを使用してファイルを開いてください。書き込み先のファイルの指定は、コンストラクタの引数や、Initialize() または TryInitialize() の引数で行います。TryInitialize() でファイルを開いたときに限り、返り値でエラーを判定することができます。ファイルの指定時に createIfNotExist 引数に true を渡すと、指定のファイルが存在していない場合でもサイズが 0 のファイルを新規に作成して開くことができます。引数ありのコンストラクタや Initialize() でファイルを開くときに存在しないファイルを指定した場合、createIfNotExist 引数に false を渡しているとアプリケーションが停止します。正常に開くことのできなかったインスタンスでファイルにアクセスした場合や、すでにファイルを開いているインスタンスで新たにファイルを開こうとした場合はアプリケーションが停止します。

ファイルに書き込みを行う前に SetSize() または TrySetSize() でファイルサイズを指定することができます。書き込みの途中でファイルサイズを元のサイズより小さく変更した場合、ファイルの書き込み位置が調整されます。GetSize() または TryGetSize() は現在のファイルサイズを返します。

補足:

TryRead() は、TryInitialize() で作成されたサイズが 0 のファイルを正常に読み込みます。そのため、TryInitialize()createIfNotExist 引数に true を渡し、あとから TrySetSize() でファイルサイズを設定する場合は注意が必要です。

TryInitialize()TrySetSize() の間にゲームカードが抜かれるなどした場合、次回読み込み時に TryRead() を呼び出すと、サイズが 0 のファイルを読み込んだ状態になります。

固定長のファイルを扱うため、TryRead() 前に TryGetSize() でサイズを確認しないアプリケーションは、createIfNotExist 引数によるファイル作成ではなく、ファイルサイズが指定可能な TryCreateFile() でファイル作成することを推奨します。

ファイルへの書き込みは Write() または TryWrite() の呼び出しで行います。引数として、ファイルに書き込む内容が格納されているバッファの先頭アドレスと書き込む内容のバイト数を渡してください。返り値には実際にファイルに書き込まれたバイト数が返されます。ファイルの終端を越えるような書き込みを行った場合、可能ならばファイルを拡張して書き込みます。バッファはなるべく 4 Byte アライメントで確保してください。デバイスやメモリ領域によって変化しますが、4 Byte アライメントではないバッファからの書き込み速度はかなり遅くなります。また、ファイルの書き込みと同時に、ファイルキャッシュの内容をメディアに書き込む(フラッシュする)かどうかを、引数 flush で指定することができます。メディアの磨耗や書き込み中のカード抜けによるデータ破壊を防ぐためにも、false を指定しておき、ファイルを閉じる前に Flush() または TryFlush() を呼び出してフラッシュを行うことを推奨します。ただし、ファイルの書き込みと同時にフラッシュを行っていない場合は、ファイルを閉じるまでに必ずフラッシュを行わなければならないことに注意してください。

ファイルの書き込み開始位置(カレント位置)は GetPosition() または TryGetPosition() で取得、SetPosition() または TrySetPosition() で設定することができます。カレント位置はファイルの先頭からのバイト数です。また、Seek() または TrySeek() ではベース位置と、そこからのオフセットで設定することもできます。設定されたカレント位置がファイルの先頭から 4 の倍数ではない場合、デバイスやメモリ領域によって変化しますが、書き込み速度はかなり遅くなりますので注意してください。カレント位置がファイルの先頭より前に設定された場合や、ファイルの終端以降に設定された場合は次のアクセス時にアプリケーションが停止します。

ファイルへのアクセスを終了するときは Finalize() を呼び出し、ファイルを閉じてください。

以下のコード例では、TryInitialize() で正常にファイルを開くことができたかを確認してから書き込みを行っています。

コード 7-2. ファイル書き込みのコード例
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 クラス

このクラスではファイルを読み書き両用で開くことができます。初期化時の引数指定を除いて、各メンバ関数の動作は前 2 つのクラスの動作と違いはありません。

コンストラクタおよび Initialize()TryInitialize() の引数で、アクセスするファイルの指定とアクセスモードの指定を行います。アクセスモードの指定は以下のフラグの組み合わせで行います。

表 7-3. アクセスモードの指定フラグ

フラグ

説明

OPEN_MODE_READ

読み込み可能状態でファイルを開きます。

OPEN_MODE_WRITE

書き込み可能状態でファイルを開きます。読み込みも可能です。

OPEN_MODE_CREATE

OPEN_MODE_WRITE と組み合わせることで、初期化時に指定されたファイルが存在しない場合にファイルを新規作成します。

7.1.5. ディレクトリへのアクセス

nn::fs::Directory クラスを用いることで、ディレクトリとそのエントリ(ディレクトリとファイル)の情報を取得することができます。

7.1.5.1. nn::fs::Directory クラス

このクラスではディレクトリを開き、ディレクトリに格納されているエントリの一覧を取得することができます。ディレクトリの指定は、コンストラクタの引数や、Initialize() または TryInitialize() の引数で行います。引数ありのコンストラクタや Initialize() でディレクトリを開くときに、存在しないディレクトリを指定した場合はアプリケーションが停止します。TryInitialize() でディレクトリを開いたときに限り、返り値でエラーを判定することができます。正常に開くことのできなかったインスタンスでディレクトリにアクセスした場合や、すでにディレクトリを開いているインスタンスで新たにディレクトリを開こうとした場合はアプリケーションが停止します。

メディアのルートディレクトリを指定する場合、SD カードならば "sdmc:/" のように、末尾に "/"(スラッシュ)が必要です。ルートディレクトリ以外の指定ではスラッシュは不要ですが、付けていても開くことができます。

エントリ情報の取得は Read() の呼び出しで行います。引数として、エントリ情報(nn::fs::DirectoryEntry 構造体)の配列と配列の要素数を渡してください。返り値には実際に配列に格納されたエントリの数が返されます。すべてのエントリを取得し終わると、以降 Read() は 0 を返し続けます。

ディレクトリへのアクセスを終了するときは Finalize() を呼び出し、ディレクトリを閉じてください。

以下のコード例では、TryInitialize() で正常にディレクトリを開くことができたかを確認してからエントリの取得を行っています。

コード 7-3. ディレクトリアクセスのコード例
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. ファイルとディレクトリの操作

ファイル名とディレクトリ名の変更、ファイルとディレクトリの作成と削除を行うことができます。

7.1.6.1. ファイルの作成

nn::fs::CreateFile() または nn::fs::TryCreateFile() の呼び出しで、指定されたサイズのファイルを作成することができます。nn::fs::CreateFile() の呼び出しではエラーが発生するとアプリケーションが停止しますが、nn::fs::TryCreateFile() の呼び出しでは返り値でエラーを判定することができます。

すでに存在しているファイルと同じ名前のファイルを作成しようとした場合はエラーとなります。同一ファイル名のファイル作成以外でエラーが発生したときには、不要なファイルが作成されている可能性がありますので、そのファイルの削除を行ってください。その際、不要なファイルが作成されていなかった場合には nn::fs::ResultNotFound が返されます。

7.1.6.2. ファイル名の変更

nn::fs::RenameFile() または nn::fs::TryRenameFile() の呼び出しでファイル名を変更することができます。nn::fs::RenameFile() の呼び出しではエラーが発生するとアプリケーションが停止しますが、nn::fs::TryRenameFile() の呼び出しでは返り値でエラーを判定することができます。

開かれた状態のファイルに対して呼び出した場合や、すでに存在しているファイルと同じ名前(自身を含む)に変更しようとした場合はエラーとなります。

7.1.6.3. ファイルの削除

nn::fs::DeleteFile() または nn::fs::TryDeleteFile() の呼び出しでファイルを削除することができます。nn::fs::DeleteFile() の呼び出しではエラーが発生するとアプリケーションが停止しますが、nn::fs::TryDeleteFile() の呼び出しでは返り値でエラーを判定することができます。

開かれた状態のファイルに対して呼び出した場合はエラーとなります。

7.1.6.4. ディレクトリの作成

nn::fs::CreateDirectory() または nn::fs::TryCreateDirectory() の呼び出しでディレクトリを作成することができます。nn::fs::CreateDirectory() の呼び出しではエラーが発生するとアプリケーションが停止しますが、nn::fs::TryCreateDirectory() の呼び出しでは返り値でエラーを判定することができます。

存在していないディレクトリの配下にディレクトリを作成しようとした場合や、すでに存在しているディレクトリと同じ名前のディレクトリを作成しようとした場合はエラーとなります。

7.1.6.5. ディレクトリ名の変更

nn::fs::RenameDirectory() または nn::fs::TryRenameDirectory() の呼び出しでディレクトリ名を変更することができます。nn::fs::RenameDirectory() の呼び出しではエラーが発生するとアプリケーションが停止しますが、nn::fs::TryRenameDirectory() の呼び出しでは返り値でエラーを判定することができます。

開かれた状態のディレクトリに対して呼び出した場合や、すでに存在しているディレクトリと同じ名前(自身を含む)に変更しようとした場合はエラーとなります。

7.1.6.6. ディレクトリの削除

nn::fs::DeleteDirectory() または nn::fs::TryDeleteDirectory() の呼び出しでディレクトリを削除することができます。削除するディレクトリ内にエントリが残されていると削除することができません。nn::fs::DeleteDirectory() の呼び出しではエラーが発生するとアプリケーションが停止しますが、nn::fs::TryDeleteDirectory() の呼び出しでは返り値でエラーを判定することができます。

開かれた状態のディレクトリに対して呼び出した場合はエラーとなります。

nn::fs::TryDeleteDirectoryRecursively() は、指定されたディレクトリにエントリが残されていても、エントリを再帰的に削除することで指定されたディレクトリを完全に削除しようとします。削除の途中でエラーが発生した場合、この関数はそのエラーを返します。

7.1.7. SD カードの状態チェック

SD カードの挿抜状態の確認や挿入・排出を通知するイベントを登録・解除する関数、書き込み可能かを確認する関数が用意されています。

コード 7-4. SD カードの挿抜状態の確認、挿入・排出の通知、書き込み可能の確認
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);

nn::fs::IsSdmcInserted() を呼び出すことで、SD カードの挿抜状態を確認することができます。壊れた SD カードや空の SD カードアダプタのような SD カード以外のものでも、SD カードスロットに機器が挿入されていれば true を返します。この関数呼び出しによる挿抜状態の確認処理は負荷が高いため、SD カードの挿抜を待つ場合は以下の関数を利用し、イベントクラスで待機するようにしてください。

nn::fs::RegisterSdmcInsertedEvent()nn::fs::UnregisterSdmcInsertedEvent() は、SD カード挿入の通知を受け取る nn::os::LightEvent クラスの登録と登録の解除を行います。

nn::fs::RegisterSdmcEjectedEvent()nn::fs::UnregisterSdmcEjectedEvent() は、SD カード排出の通知を受け取る nn::os::LightEvent クラスの登録と登録の解除を行います。

nn::fs::IsSdmcWritable() は、SD カードが挿入された状態で、かつ書き込みが可能ならば true を返します。

nn::fs::GetSdmcSize() は、SD カードの総容量(pTotal)と空き容量(pFree)を返します。

7.1.8. レイテンシエミュレーション

アクセス速度の変化に対するデバッグ用に、いつの間に通信などのバックグラウンド処理で行われるファイルシステムへのアクセスと競合した場合に発生する、アプリケーションからのファイルアクセスの遅延を擬似的に再現する機能を以下の関数で有効にすることができます。

コード 7-5. レイテンシエミュレーションの初期化
void nn::fs::InitializeLatencyEmulation(void);

レイテンシエミュレーションの有効/無効は、Config ツール「Debug Setting」の「Debug Mode」で切り替えます。この項目が「enable」に設定され、さらにこの関数によって初期化されている場合にのみ、アプリケーションの動作時にファイルアクセスが「FS Latency Emulation」で指定された時間(ミリ秒単位)遅延するエミュレーション機能が有効になります。

7.1.9. アクセス優先度の設定

ファイルシステムのアクセス優先度がサポートされています。これにより、複数のスレッドから複数のファイルアクセスを行った場合に実行順序が調整され、設定したアクセス優先度の高いものほど優先的に処理されるようになります。

アクセス優先度には、ストリーミング再生のようにリアルタイム性を要求する(一定の周期で一定の処理量を必要とする)ファイルアクセスに適した設定が用意されています。この設定では、ファイルアクセスにかかる遅延時間を最小にすることができます。

7.1.9.1. アクセス優先度の種類

設定可能なアクセス優先度は、優先度の高い順に以下のものが用意されています。

表 7-4. アクセス優先度の種類

種類

定義

説明

リアルタイム優先度

PRIORITY_APP_REALTIME

ストリーミングデータの読み込みなど、読み込みが遅れるとユーザーエクスペリエンスに影響のあるファイルアクセスをするために使用される、特殊な優先度です。

ただし、使用する際には、いくつかの制限があります。

通常優先度

PRIORITY_APP_NORMAL

モデルデータやシーンデータの読み込みや、各種セーブデータへのアクセスなど、一般的なファイルアクセスをするために使用される優先度です。

低優先度

PRIORITY_APP_LOW

通常優先度より低く、オートセーブなど、ファイルアクセスが比較的空いているときに実行されればよいファイルアクセスをするために使用される優先度です。

アクセス優先度の指定に使用する定義は、nn::fs::PriorityForApplication 列挙子に定義されています。

補足:

リアルタイム優先度の制限については、CTR-SDK の関数リファレンスを参照してください。

7.1.9.2. アクセス優先度の設定対象

アクセス優先度の設定対象となる範囲には以下のものがあります。

表 7-5. アクセス優先度の設定対象と設定に使用する関数

設定対象

設定に使用する関数

ファイルシステム全体

nn::fs::SetPriority()

アーカイブ

nn::fs::SetArchivePriority()

ファイルストリーム、ディレクトリ

各クラスの TrySetPriority() または SetPriority()

設定対象の「ファイルシステム全体」とは、そのアプリケーションからのアクセス全般と、セーブデータのフォーマットのようにアーカイブ名を指定せずに行うアーカイブへのアクセスが対象です。なお、アクセス優先度の設定を明示的に行っていない場合は通常優先度が適用されます。

「アーカイブ」は、マウントされているアーカイブに対して、アーカイブ名を指定して行うアクセスが対象です。アクセス優先度は、アーカイブごとに異なる設定が可能です。アクセス優先度の設定を明示的に行わなければ、マウント時点のファイルシステム全体の設定が適用されます。マウント後にシステム全体の設定を変更しても、アーカイブの設定には影響しません。同様に、アーカイブの設定を変更しても、ファイルシステム全体の設定には影響しません。

「ファイルストリーム、ディレクトリ」は、ファイルストリームのオブジェクト(nn::fs::FileStream など)やディレクトリのオブジェクト(nn::fs::Directory)を介して行うアクセスが対象です。アクセス優先度は、同じファイルやディレクトリであっても、オブジェクトごとに異なる設定が可能です。アクセス優先度の設定を明示的に行わなければ、オブジェクト作成時点のアーカイブの設定が適用されます。オブジェクト作成後にアーカイブの設定を変更しても、オブジェクトごとの設定には影響しません。同様に、オブジェクトごとの設定を変更しても、アーカイブの設定には影響しません。

7.1.9.3. 注意点

アクセス優先度はファイルシステム内での優先度を変更するものです。ファイルアクセス関数を呼び出すスレッドの優先度が高くなければ、リアルタイム優先度であっても、ファイルへのアクセスがすぐに行われません。そのため、処理の目的(ファイルアクセスの優先度)に合わせて、ファイルアクセス関数を呼び出すスレッドの優先度を高くする必要があります。

また、要求されたファイルアクセスが完了する順序は保証されていません。アクセス優先度の高いファイルアクセスよりも、あとから要求されたアクセス優先度の低いファイルアクセスの方が先に完了する可能性があります。複数のファイルに平行してアクセスする場合は、ファイルアクセスの終了順序に依存するような実装にはしないでください。

ファイルアクセスの性能設計を実測値をもとに行わないでください。アクセス時間の見積もりについては、CTR-SDK の関数リファレンスを参照してください。

7.1.10. ファイル_ディレクトリの同時オープン数の制限

ファイルシステムを用いて同時にオープンできるファイルおよびディレクトリの数には限りがあります。

安全に動作させるために、アプリケーションは同時に開くファイルとディレクトリの数を下記の制限以内に抑えてください。

  • SD カードを直接参照するアーカイブと拡張セーブデータアーカイブのファイルは合わせて 4 個まで
  • セーブデータアーカイブのファイルは 10 個まで(ほかのアプリケーションのセーブデータを含む)
  • 全部合わせて 10 個まで

 

補足:

ROM アーカイブは上記の制限に含まれません。MountRom() での指定に準じます。

7.2. ROM アーカイブ

ROM アーカイブは、ビルド時に作成される ROMFS を参照するための読み取り専用のアーカイブです。ROM アーカイブはアプリケーションで明示的にマウントしなければなりません。アプリケーションがカードアプリ、ダウンロードアプリのいずれであっても、マウントする手順や引数に違いはありません。

ROM アーカイブを使用する際は、アプリケーションで用意した作業メモリが必要となります。必要となる作業メモリのサイズは同時に開くことのできるファイルとディレクトリ数によって変化し、nn::fs::GetRomRequiredMemorySize() を呼び出すことで必要とされるメモリサイズを取得することができます。負の値が返されたときは、ROM アーカイブをマウントすることができません。アプリケーションは必要とされるサイズ以上のメモリ領域を nn::fs::WORKING_MEMORY_ALIGNMENT ( 4 Byte )  アライメントで確保してから、nn::fs::MountRom() を呼び出して ROM アーカイブをマウントしてください。

コード 7-6. ROM アーカイブのマウント
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);

maxFile maxDirectory には、同時に開くことができるファイルの数とディレクトリの数をそれぞれ指定します。同時に開くことのできる数はファイル名の長さなどで変化することはなく、作業メモリのサイズにのみ依存しています。

useCache true を指定するとメタデータをキャッシュし、ファイルを開くときやディレクトリの走査にかかる時間を短縮することができますが、必要となる作業メモリは大きくなります。

archiveName にはマウント先のアーカイブ名を指定します。この引数のないオーバーロードを呼び出したときは、アーカイブ名 "rom:" にマウントされます。

workingMemory workingMemorySize には、作業メモリとそのサイズを渡します。

補足:

これらの関数のエラーハンドリングは不要です。関数内でエラーが発生するとエラー画面が表示され、アプリケーションにエラーは返されません。

7.2.1. アーカイブ名の指定について

アーカイブ名に使用することのできる文字はアーカイブ名の区切りである ":" を除いた一部の記号と半角の英数字で、大文字と小文字は区別されます。1 文字以上、区切り文字の ":" を含めて 8 文字以内で指定しなければなりません。

アーカイブ名の先頭には "$" を使用しないでください。アーカイブ名やファイル名、ディレクトリ名に使用できない文字や名前については関数リファレンスを参照してください。

7.3. セーブデータ

アプリケーション固有のセーブデータ領域は、カードアプリならばバックアップメモリに、ダウンロードアプリならば SD カード内のアーカイブファイルに存在します。セーブデータ領域はアーカイブとして FS ライブラリでアクセスすることができ、どのメディア(3DS カード、SD カード)にアプリケーションが存在していても、アクセスに使用する関数や引数に違いはありません。

コード 7-7. セーブデータ領域のマウント、フォーマット、コミット
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:");

nn::fs::MountSaveData() でセーブデータ領域を archiveName で指定したアーカイブ名でマウントします。引数に uniqueId を持つオーバーロードを呼び出すことで、ほかのアプリケーションのセーブデータをマウントすることができます。nn::fs::MountDemoSaveData では、体験版のセーブデータ領域をマウントすることができます。demoIndex には、体験版のインデックスを指定します。

補足:

セーブデータ領域のマウント時に uniqueId で指定する値は、それぞれのアプリケーションの RSF ファイルで指定されたユニーク ID です。ほかのアプリケーションのユニーク ID を指定する場合は、マウントする側のアプリケーションの RSF ファイルに、アクセスしたいアプリケーションのユニーク ID をあらかじめ指定する必要があります。

セーブデータ領域のマウントで nn::fs::ResultNotFormattednn::fs::ResultBadFormatnn::fs::ResultVerificationFailed に属するエラーが返された場合は、nn::fs::FormatSaveData() でセーブデータ領域のフォーマットを行ったあとで再度マウントしてください。フォーマットの際には、ファイルとディレクトリの最大数を maxFilesmaxDirectories で指定します。指定可能な値に制限はありません。アーカイブ名の指定については「7.2.1. アーカイブ名の指定について」を参照してください。

注意:

体験版を含むダウンロードアプリからカードアプリのセーブデータ領域をマウントする際は、3DS カードが挿入されていない場合と、挿入されている 3DS カードのセーブデータ領域がフォーマットされていない場合とが、同じエラー(nn::fs::ResultNotFound)を返すことに注意してください。

一度カードアプリを起動してセーブデータ領域のフォーマットを行わなければ、この状況から抜け出すことができませんので、「(メディア名)のセーブデータが見つかりません。まだ一度も起動していない場合はゲームを起動し、セーブデータを作成してからもう一度試してください。」のように、エラーの原因がどちらであっても違和感のないメッセージを表示することで対応してください。

補足:

メディアによっては返さないエラーがあります。
例えば、現状ではマウント時に CARD1 は nn::fs::ResultBadFormat を返すことがありますが、CARD2 やダウンロードアプリでは nn::fs::ResultBadFormat を返すことはありません。
上述のような場合であっても、エラーハンドリングにおいて将来の変更に対応できるようメディアに依存しない処理の記述を推奨します。

セーブデータ領域全体の二重化をライブラリでサポートしています。フォーマット時に isDuplicateAlltrue を指定した場合、セーブデータ領域の全域に対して、ライブラリによる二重化が行われます。二重化を有効にしている状態でファイルの書き込みを行った場合は、セーブデータ領域のマウントを解除するまでに nn::fs::CommitSaveData() を呼び出さなければ更新が有効になりません。また、更新が有効になる前に電源が切断された場合などには、次回の起動時に元の古いセーブデータがマウントされます。

注意:

セーブデータがファイル間に依存関係のある複数のファイルで構成されている場合、それらのファイル間で矛盾がない状態で nn::fs::CommitSaveData() を呼び出す必要があります。

二重化を有効にした場合、セーブデータの保存に使用することのできる領域はバックアップ領域の物理的な容量の半分です。さらにそこからファイルシステムの管理領域が差し引かれます。実際に使用可能なバックアップ領域の容量は、関数リファレンスの「セーブデータ容量計算シート」で計算することができます。現在、ファイルを保存すると 512 Byte 単位で領域を使用します。

マウントされたセーブデータ領域はアーカイブとして扱うことができます。アーカイブ内には、自由にファイルを作成することができます。ファイル名とディレクトリ名は 16 文字まで、パスの最大長は 253 文字です。ファイル名とディレクトリ名に使用することのできる文字は、半角の英数字と一部の記号のみで、パスの区切りには "/(スラッシュ)" を用います。作成可能なファイルのサイズと一度に書き込むことのできるサイズには、セーブデータの保存に使用可能な領域の容量以外による制限はありません。

アーカイブの空き容量は nn::fs::GetArchiveFreeBytes() で取得することができます。

コード 7-8. アーカイブの空き容量の取得
nn::Result nn::fs::GetArchiveFreeBytes(s64* pOut, const char* archiveName);

pOut には空き容量を受け取る s64 型の変数へのポインタを、archiveName にはマウント時に指定したアーカイブ名をそれぞれ指定します。

アーカイブ内のファイルは、ハッシュ値による改竄チェックで保護されています。アクセスしたデータのハッシュ値が合わないときは、改竄されていると判断されてエラーが返されます。二重化を有効にしていない場合、ハッシュ不整合のエラーは、セーブ中の電源断やカード抜けでデータ破損が起こったデータへのアクセスでも発生します。

セーブデータ領域へのアクセスを完了し、マウントを解除するときはアーカイブ名を引数に nn::fs::Unmount() を呼び出してください。

コード 7-9. マウントの解除
nn::Result nn::fs::Unmount(const char* archiveName);

DSi ウェアと同様に、ダウンロードアプリのセーブデータ領域は、インポートと同時にインポートされたメディア上に自動的に作成されます。ダウンロードアプリが削除されたときは、そのセーブデータも一緒に削除されます。

7.3.1. セーブデータの巻き戻し対策

補足:

CTR-SDK の関数リファレンスの「ファイルシステム : セーブデータ巻き戻し防止支援機能」を参照してください。

注意:

セーブデータの巻き戻し対策機能を使用するアプリケーションは、基本的にバナースペックファイルでセーブデータのバックアップ機能に対応しないように(DisableSaveDataBackupTrue に)設定してください。詳細については、「3DS オーバービュー」および ctr_makebanner の説明を参照してください。なお、セーブデータの巻き戻し対策機能の使用・不使用を、パッチの適用時に切り替えることは禁止されています。

7.3.2. ファイル、ディレクトリが存在しない場合のエラーハンドリング

アプリケーションでセーブデータの作成手順を工夫していても、以下のような操作で、マウントに成功した上でファイルが存在しない状態になり得ます。

  • セーブデータ初回作成時などでセーブデータのフォーマットは完了している。
  • フォーマット後のファイル、ディレクトリの作成中(セーブデータを二重化している場合は初回コミットまでの間)に下記理由でアプリケーションが強制的に終了した。
    • 電源ボタンを長押しした。
    • カードを抜いた。
    • 処理途中で電池切れとなった。
      特にスリープを許可しているアプリケーションにおいて、本体を閉じた状態のまま放置する。

上記のような場合には、ファイル、ディレクトリ操作時に nn::fs::ResultNotFound が返される可能性がありますので、ユーザーの手元でセーブデータを正常な状態に復帰できるようにしてください。

7.4. 拡張セーブデータ

SD カードに作成し、セーブデータとは別に管理されるデータ領域です。作成した本体でのみ使用可能なデータ領域ですので、本体内にのみ存在する情報(フレンド情報など)とアプリケーション独自のデータを結び付ける場合は、そのデータを拡張セーブデータに保存することを推奨しています。拡張セーブデータにはゲームの進行に必要なデータはなるべく保存しないでください。拡張セーブデータをシステムが自動的に作成することはありません。

コード 7-10. 拡張セーブデータのマウント、作成、削除
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);

拡張セーブデータを使用するには、拡張セーブデータ ID を指定して nn::fs::MountExtSaveData() を呼び出し、拡張セーブデータをマウントしなければなりません。返り値に nn::fs::ResultNotFormattednn::fs::ResultNotFoundnn::fs::ResultBadFormatnn::fs::ResultVerificationFailed が返された場合は、すでに拡張セーブデータが作成されているならば nn::fs::DeleteExtSaveData() で拡張セーブデータを削除し、nn::fs::CreateExtSaveData() で拡張セーブデータを作成してから再度マウントを行ってください。nn::fs::ResultOperationDenied に属するエラーが返された場合は、SDカードやファイルに書込めない状態になっているか、SDカードの接触不良などの可能性があります。詳細は関数リファレンス「ファイルシステム : エラーハンドリングについて」を参照してください。

archiveName に指定されたアーカイブ名で拡張セーブデータがマウントされます。アーカイブ名の指定については「7.2.1. アーカイブ名の指定について」を参照してください。

id には拡張セーブデータ ID を指定します。拡張セーブデータ ID とは拡張セーブデータを識別するための番号です。アプリケーションからアクセスすることのできる拡張セーブデータの拡張セーブデータ ID は、makerom で使用する RSF ファイルの "ExtSaveDataNumber"(1 つのみ指定可能)もしくは "AccessControlInfo" の "AccessibleSaveDataIds"(複数指定可能)に記述する必要があります。アプリケーションで作成する拡張セーブデータ ID には通常、弊社より発行されたユニーク ID を指定します。そのため、複数のアプリケーションで拡張セーブデータを共有する場合は、それらのアプリケーションのいずれかのユニーク ID を指定する必要があります。

拡張セーブデータを作成する際には、iconDataiconDataSize にデータ管理画面で表示されるアイコンデータ(ctr_makebanner32 で作成された icn ファイル)とそのサイズを指定します。また、entryDirectoryentryFile には拡張セーブデータに格納したいディレクトリとファイルの数をそれぞれ指定します。

マウントされた拡張セーブデータはアーカイブとして扱うことができます。アーカイブ内には、自由にファイルを作成することができますが、ファイル作成には nn::fs::TryCreateFile() を使用しなければならず、作成後はファイルサイズを変更することができません。ファイル名とディレクトリ名は 16 文字まで、パスの最大長は 248 文字です。ファイル名とディレクトリ名に使用することのできる文字は、半角の英数字と一部の記号のみで、パスの区切りには "/(スラッシュ)" を用います。アーカイブのサイズは可変です。

アーカイブ内のファイルは、ハッシュ値による改竄チェックで保護されています。アクセスしたデータのハッシュ値が合わないときは、改竄されていると判断されてエラーが返されます。ハッシュ不整合のエラーは、データ書き込み中の電源断やカード抜けでデータ破損が起こったデータへのアクセスでも発生します。セーブデータとは異なり、拡張セーブデータはライブラリによる二重化がサポートされていません。

データの保護機能はありませんので、ファイルを書き込んでいる途中で SD カードが抜かれたときは拡張セーブデータが高い確率で壊れてしまいます。壊れてしまった場合は nn::fs::DeleteExtSaveData() で削除してから、拡張セーブデータを作り直す必要があります。

拡張セーブデータへのアクセスが完了し、マウントを解除するときは、アーカイブ名を引数に nn::fs::Unmount() を呼び出してください。

ダウンロードアプリの削除は拡張セーブデータに影響しません。拡張セーブデータは本体設定のデータ管理画面で削除される可能性があります。

補足:

拡張セーブデータの総サイズが 32 MByte まであれば、アプリケーションで自由に使用することができます。32 MByte を超えて使用したい場合は、弊社窓口までお問い合わせください。

7.4.1. 拡張セーブデータの用途

拡張セーブデータには以下のようなデータを保存するという用途があります。

  • アプリケーション独自のデータ
  • シリーズ共通のデータ(異なるバージョンのアプリケーションなどと共通で使いたいデータ)
  • ダウンロードしたデータ(追加アイテム、追加コースなど)
  • CTR 拡張バナーデータ(アプリケーションで作成、もしくはサーバーからダウンロード)

 

7.4.1.1. アプリケーション独自のデータ

ゲームの進行とは無関係で、あまり重要度の高くないデータや本体にのみ保存されている情報と結び付けたデータ、サイズの大きなユーザー作成のデータなどです。

7.4.1.2. シリーズ共通のデータ

異なるバージョンやナンバリングタイトルのアプリケーションと共通で使用するデータです。シリーズで共通の拡張セーブデータ ID を使用することで、シリーズ間でのデータの共有を行うことができます。

7.4.1.3. ダウンロードしたデータ

追加コースなど、ダウンロードタスクを登録してダウンロードしたデータです。ダウンロードタスクで取得したデータはタスクを登録したアプリケーションでなければアクセスできませんが、拡張セーブデータ内に移動させることで拡張セーブデータを共有するアプリケーションからもアクセスすることができるようになります。

7.4.1.4. CTR 拡張バナーデータ

CTR タイトルバナーのデータの一部を置き換えることができるデータです。置き換えることができるのは、上画面の下部でスクロール表示されるテキストと、差し替え用に決められている名前のテクスチャ(1 枚のみ)です。アイコンデータの差し替えには対応していません。

CTR 拡張バナーデータには、アプリケーションで作成するローカル拡張バナーと、サーバーからダウンロードする DL 拡張バナーの 2 種類が存在します。

ローカル拡張バナー

ゲームの進行状況に合わせてメッセージを表示したり、CTR タイトルバナーで表示されるテクスチャの一部を変更したりすることができます。テキストは最大 255 文字(全角半角問わず)、データのサイズは最大 128 KByte です。1 枚のテクスチャの中に複数の画像を入れ、複数モデルに貼り付けることは可能です。全言語で共通の COMMON データと、必要ならばリージョンで有効となっている言語分のデータをそれぞれ用意してください。

DL 拡張バナー

サーバーが用意したデータで表示の変更を行うことができます。テキストとテクスチャはローカル拡張バナーと同じ仕様です。DL 拡張バナーには有効期限(年月日)を設定することができます。

CTR 拡張バナーデータの表示について

CTR 拡張バナーデータのテキストは、DL 拡張バナーのテキスト、ローカル拡張バナーのテキストの順に表示されます。ローカル拡張バナーと DL 拡張バナーに同じ名前のテクスチャが含まれていた場合、DL 拡張バナーのテクスチャが優先されます。

7.4.2. 複数のゲームカードからのアクセス

1 台の 3DS と 1 枚の SD カードの組み合わせを複数人で共有し、それぞれが同じタイトルのゲームカードを所持している可能性があります。セーブデータは各ゲームカードのバックアップメモリに書き込まれるため、このような場合でも問題はありません。しかし、拡張セーブデータは SD カードに書き込まれるため、拡張セーブデータに保存するデータの構成によっては不具合を引き起こす可能性があることに注意してください。

具体的には、以下のような状況が考えられます。

  • セーブデータ、拡張セーブデータの整合性が取れずにプログラムが暴走する。
  • ファイル名を固定にしていると、別のゲームカードで作成された拡張セーブデータ内のファイルを、ユーザーの意図に関係なく上書きしてしまう。
  • 開発者の意図に反して、1 台の 3DS で複数枚のゲームカードで遊ぶために、同じ枚数の SD カードが必要となってしまう。

例えば、拡張セーブデータ内にファイルを作成する場合は、ディレクトリ名やファイル名にユニークな名前を付与することで不用意な上書きを回避することができます。ただし、名前の付与基準には、キャラクタ名などのユーザーが任意で付けられるものではなく、現在時刻を加工した文字列などを適用してください。また、セーブデータ内に拡張セーブデータと結び付けるための情報を書き込んだ場合は、その情報に該当する拡張セーブデータが SD カード上に存在しない可能性があることに注意してください。

注意:

CTR 拡張バナーデータに関しては、このような状況に対応することができません。そのため、どのゲームカードが挿入されたとしても、最後に作成されたローカル拡張バナーデータや DL 拡張バナーデータが有効になります。

7.4.3. 複数の拡張セーブデータへのアクセス

RSF ファイルのアクセス可能セーブデータ属性("AccessControlInfo" の "AccessibleSaveDataIds")に複数のユニーク ID(最大 6 個)を記述することで、以下のデータにアクセスすることができるようになります。

  • ユニーク ID が記述されたユニーク ID と同じであるセーブデータ
  • 拡張セーブデータ ID が記述されたユニーク ID と同じである拡張セーブデータ

 

注意:

本体設定で拡張セーブデータが個別に消去される可能性がありますので、整合性が必要なデータはなるべく拡張セーブデータに保存しないでください。

CTR 拡張バナーデータを使用するタイトルは、共通でアクセスする拡張セーブデータのほかに、自身のユニーク ID を拡張セーブデータ ID とする拡張セーブデータを作成してください。これは拡張バナーの表示に使用するファイルが、自身のユニーク ID で作成された拡張セーブデータの "ExBanner" ディレクトリになければならないためです。

補足:

RSF ファイルの記述方法については、CTR-SDK のツール「ctr_makerom」のリファレンスおよび「3DS プログラミングマニュアル - アプリケーション作成フロー編」を参照してください。

7.5. SD カードを直接参照するアーカイブ

nn::fs::MountSdmcWriteOnly() を呼び出して、挿入されている SD カードを直接参照するアーカイブをマウントすることができます。ただし、この関数を使用するには、RSF ファイルのファイルシステムアクセス権利属性("AccessControlInfo" の "FileSystemAccess")に "- DirectSdmcWrite" が記述されている必要があります。

コード 7-11. SD カードを直接参照するアーカイブのマウント
nn::Result nn::fs::MountSdmcWriteOnly(const char* archiveName = "sdmcwo:");

この関数でマウントされたアーカイブでは、ファイルやディレクトリの読み込みはできませんが、書き込まれたファイルが暗号化されないため、PC などでそのまま読み込むことができます。また、同じ SD カード上に存在する拡張セーブデータとは異なり、ファイルの作成後にサイズを変更することができます。

マウント時に返される可能性があるエラーは拡張セーブデータのマウント時よりも種類が少なく、エラーのハンドリングは拡張セーブデータのマウント時と同じ方法で行うことができます。ただし、読み込みが禁止されているため、一度書き込みを行わなければディレクトリやファイルの存在確認ができませんので、ディレクトリやファイルの作成時には nn::fs::ResultAlreadyExists に属するエラーをハンドリングする必要があります。

マウントを解除するときは、アーカイブ名を引数に nn::fs::Unmount() を呼び出してください。

補足:

アプリケーションから書き込みを行うことのできるパスは制限されています。制限や運用上の注意点はガイドラインの「ファイルシステム」を参照してください。

7.6. メディアごとの注意点

7.6.1. 3DS カード

アクセス速度の変化に対する検証

FS ライブラリによる 3DS カード上の ROM アーカイブへのアクセスは、メディアの状態が良好なときと劣化したときのパフォーマンスに大きな開きが発生することがあります。また、本体更新によりパフォーマンスが改善されることもあります。そのため 3DS カードでの販売を予定しているアプリケーションは、このアクセス速度の変化によって不具合が起こらないかどうかを、以下の検証を行って確認してください。

  • CARD1 アプリケーションの場合
    1. Config ツールで 「Debug Setting」の「Debug Mode」を「enable」 に設定(レイテンシエミュレーションが有効な状態)して一通りプレイする。
    2. 開発カードスピードを Fast 設定にし、開発カードに書き込まれたアプリケーション(Release ビルド)を一通りプレイする。
  • CARD2 アプリケーションの場合
    1. Config ツールで「Debug Setting」の「Debug Mode」を「enable」に設定(レイテンシエミュレーションが有効な状態)して一通りプレイする。
    2. PARTNER-CTR Debugger のカード制御ダイアログのカードエミュレーション制御タブでエミュレーションメモリスピード設定の転送速度を「Fast」に設定し、エミュレーションメモリ上にアプリケーション(Release ビルドの CCI ファイル)をロードした状態で一通りプレイする。
    3. 開発カードスピードを Fast 設定にし、開発カードに書き込まれたアプリケーション(Release ビルド)を一通りプレイする。

 

注意:

なるべく最新バージョンの PARTNER-CTR Debugger/Writer ソフトウェアで検証を行ってください。

7.6.2. SD カード

SD カードに保存される拡張セーブデータやダウンロードアプリを含め、SD カードへのアクセスが行われるアプリケーションの実装では以下の点に注意してください。

SD メモリカードを直接参照する際の注意点

nn::fs::MountSdmc() でマウント(引数省略時は "sdmc:"にマウント)された、SD メモリカードを直接参照するためのアーカイブはデバッグ用途専用です。製品版ではアクセスすることはできませんので注意してください。

スリープ中に SD カードが差し替えられる可能性があることに注意してください。マウントされていた SD カードが抜かれ、再挿入された SD カードをマウントするときは、nn::fs::Unmount() でアンマウントしてから再マウントを行わなければなりません。

ストリーミング再生時の注意点

SD カードの空き容量がほとんどない、著しく断片化が進んでいる、SD カードの劣化などの要因によりファイルアクセスの速度が低下することがあります。SD カードへアクセスする関数の多くはこの影響を受けて速度が低下し、特にファイルを新規作成するような関数(nn::fs::CreateExtSaveData() など)への影響は大きくなります。

そのため、このような関数をサウンドやムービーのストリーミング再生を行っているときに呼び出した場合、実行完了までファイルアクセスがブロックされるため、音切れやフレーム落ちの原因のひとつとなり得ることに注意してください。

リード・ディスターブへの対策

SD カードには読み出し回数に対するデータの保持能力に限界があり、それを超えて読み出そうとすると、データの一部が変わってしまう「リード・ディスターブ」という劣化現象が起こることがあります。リード・ディスターブ現象が起こる箇所によっては、SD カードのデータがすべて読み込めなくなる場合もあります。

特に気を付けなければならないのは、SD カードからの読み出しはファイルのオープンを行うだけでも発生することです。市場にはリード・ディスターブ現象への耐性が十分でない SD カードも流通していますので、この現象が発生するリスクを低減させるためにも、アプリケーション側でファイルの開閉処理をできる限り削減するようにしてください。たとえば、同じファイルに対して異なるオフセットで複数回のアクセスを行う場合は、アクセスの度にファイルの開閉処理を行わず、なるべく 1 回の開閉処理の間にすべてのアクセスを行うようにしてください。

また、SD カード上の同じ領域を繰り返し読み出すことでデータの維持が不安定になるため、基本的に書き換えが行われない ROM アーカイブや追加コンテンツへのアクセスがもっとも問題を起こしやすいと言えます。カードアプリであっても、将来的にダウンロードアプリとして販売する可能性がある場合には注意が必要です。

データを長く維持させる工夫としては、SD カードから特定のデータを繰り返し読み出す場合(短い波形データを繰り返しストリーミング再生する場合など)には、一度 SD カードからメモリ上のバッファにデータを読み出し、そのバッファのデータを使用するという方法があります。このとき、バッファのサイズを 16 KByte 以上にすることが効果的で、サイズを大きくすることでさらに改善していきます。

この対策は制限事項ではありませんので、メモリの空きに余裕がない状況でも無理に対応しなければならないものではありません。

補足:

NW4C サウンドライブラリを利用している開発者は、nw::snd::SoundArchivePlayer クラスのリファレンスに記載されている説明を参照してください。

7.7. セーブデータと拡張セーブデータの使い分け

アプリケーションを販売する際に選択可能なメディアとして 3DSカード(CARD2)が用意されるようになり、アプリケーション固有のセーブデータ領域として利用可能な容量が大きくなりました。そのため、以前は容量の関係で拡張セーブデータに保存せざるを得なかったデータをセーブデータとして保存することができるようになります。それに伴い、保存するデータの種類や用途によって、セーブデータと拡張セーブデータを適切に使い分ける必要があります。この節では、セーブデータと拡張セーブデータそれぞれの特性を説明し、どのようなデータをどちらに保存すべきかを説明します。

7.7.1. セーブデータと拡張セーブデータの特性

セーブデータと拡張セーブデータには、以下のような特性の違いがあります。

表 7-6. セーブデータと拡張セーブデータの特性

項目

セーブデータ

拡張セーブデータ

二重化機能

対応

非対応

バックアップ機能

対応(ダウンロードアプリのみ)。

原則有効(下記参照)

非対応

巻き戻し対策機能

対応(ダウンロードアプリのみ)。

利用する場合はバックアップ機能を無効にすること

非対応

保存先メディア

カードアプリ: カードのバックアップ領域

ダウンロードアプリ: SDカード

SDカード

容量確保のタイミング

カードアプリ: 最初から

ダウンロードアプリ: インストール時

アプリケーションが確保したとき

削除

アプリケーション内でのみ可能

アプリケーション内、本体設定のデータ管理画面で可能

データ破壊

バックアップ、二重化によりデータ破壊のリスクを軽減可能

データ破壊のリスクは軽減できない

データ破壊時に必要な対応

二重化あり: アーカイブ全体が破壊された場合のハンドリングのみ必要

二重化なし: アーカイブ全体の破壊に加えて、ファイル単位、ディレクトリ単位での破壊に対するハンドリングが必要

アーカイブ全体の破壊に加えて、ファイル単位、ディレクトリ単位での破壊に対するハンドリングが必要

容量不足への対応

不要(確保した容量内での対応は必要)

ファイル作成のたびに必要

これまでに、以下のような理由でバックアップ機能を無効にしたアプリケーションがあります。

  • 巻き戻し防止機能を使用している
  • セーブデータ側だけバックアップ機能により巻き戻ることで、拡張セーブデータとセーブデータ間で不整合が生じる可能性がある
  • バックアップからセーブデータを復元されると都合の悪いデータがある
    配信データやレアキャラなど、ほかのユーザーに譲渡したあとにセーブデータを復元させて、無限に増殖できてしまうなど

7.7.2. データの保存先を決定する際の方針

基本的に、消えてしまうことでゲームの進行が不可能になるようなデータや、重要度の高いデータはセーブデータに保存してください。消えてしまってもゲームの進行に問題のないデータや最終的に必要となる容量が不定となるデータは拡張セーブデータに保存することを推奨します。なお、拡張セーブデータは本体設定のデータ管理画面で消去することができるため、アプリケーションの制御が及ばないタイミングで消されることを考慮する必要があります。また、カードアプリのパッケージ版の場合、異なる本体でプレイする可能性や、前回アクセスした拡張セーブデータが保存されている SDカードとは異なる SDカードが挿入されている可能性があることにも注意が必要です。

以下のデータは、ユーザーのデータを保護するためにも、セーブデータに保存します。

  • ゲームの進行データ
  • ゲームの進行データとの整合性が必要なデータ
    拡張セーブデータに整合性が必要なデータを保存した場合、拡張セーブデータが削除されたことによりセーブデータの初期化が必要となる可能性やバックアップ機能を使用した際に整合性が保てなくなる可能性があります。そのため、ゲーム内の各所でセーブデータを巻き戻したり、拡張セーブデータを削除したりして整合性の確認をするなど、デバッグの作業量が膨大になることが考えられます。

 

以下のようなデータは、セーブデータに保存することを推奨します。

  • ゲームを楽しむ上で重要と思われるデータ
    ステージ初クリア時のスクリーンショットやフレンドから送られてきた記念日のメッセージなど。
  • 複数のファイル間で整合性が必要なデータ
    リプレイシーンを構成するテクスチャとコマンドデータなど。
    セーブデータの方が、ファイルごとに壊れる可能性は低くなります。ただし、1 つのファイルに結合して保存するならば、ファイル消失のリスクは拡張セーブデータでも二重化なしのセーブデータと同程度になります。

 

以下のようなデータは、消えてしまってもゲームの進行に影響がなければ、拡張セーブデータに保存することを推奨します。

  • ネットから再ダウンロードが可能なデータやゲーム中に再作成が可能なデータ
  • ほかのアプリケーションと共有するデータ
    特に複数のゲームを並行してプレイしながらも、互いに共有するデータは拡張セーブデータに保存することを推奨します。ただし、セーブデータのバックアップ機能を用いると、アイテム増殖などの不正を行うことができるようになる可能性があることに注意が必要です。
  • ファイル数やサイズが不定のデータ
    ユーザーが作成したステージデータや楽譜データ、ユーザーが録画した動画など。
    拡張セーブデータは、保存可能なディレクトリ数とファイル数を領域の作成時に指定しますが、ファイルサイズには制限がありません。一方、セーブデータは保存可能なディレクトリおよびファイルの数を初期化時に指定するのは同様ですが、領域の容量が有限であるため、サイズ不定のデータを複数保存するような用途には向いていません。
  • ほかのアプリケーションのセーブデータから移行するデータ
    ほかのアプリケーションのセーブデータへのアクセスは可能ですが、パッケージ版同士の組み合わせでは不可能なため、旧作からのデータ移行などでも拡張セーブデータを利用するようにしてください。ただし、ダウンロード専売のアプリケーション同士やダウンロード体験版からセーブデータを移行する場合はセーブデータへのアクセスを利用してください。