Toggle menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Save3DS: Difference between revisions

From GameBrew
(Created page with "{{Infobox 3DS homebrew | title = Save3DS | image = https://dlhb.gamebrew.org/3dshomebrew/save3ds-.jpg|250px | type = PC Utilities | version = v1.3.0 | licence = Mixed | author...")
 
m (Text replacement - "Category:PC utilities for 3DS homebrew" to "")
 
(18 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{Infobox 3DS homebrew
{{Infobox 3DS Homebrews
| title = Save3DS
|title=save3ds
| image = https://dlhb.gamebrew.org/3dshomebrew/save3ds-.jpg|250px
|image=3dspc.png
| type = PC Utilities
|description=Extract/Import/FUSE for 3DS save/extdata/database.
| version = v1.3.0
|author=wwylele
| licence = Mixed
|lastupdated=2019/12/22
| author = wwylele
|type=File Operation
| website = https://github.com/wwylele/save3ds
|version=1.3.0
| download = https://dlhb.gamebrew.org/3dshomebrew/save3ds-1.3.0.rar
|license=Apache-2.0
| source = https://dlhb.gamebrew.org/3dshomebrew/save3ds-1.3.0.rar
|download=https://dlhb.gamebrew.org/3dshomebrews/save3ds.7z
|website=https://github.com/wwylele/save3ds
|source=https://github.com/wwylele/save3ds
}}
}}
<youtube>0L9cJ2DGiX0</youtube>
Save3DS is an extract, import and FUSE program for common save format for 3DS, written in rust. Inspired by [https://github.com/3dshax/3ds/tree/master/3dsfuse 3dsfuse] but rewritten with a newer research.


# Save3DS
There are two main components in the project: the library <code>libsave3ds</code>, and the FUSE program + extract/import tool <code>save3ds_fuse</code> that builds on top of it (the FUSE feature is not available on Windows). Both the library and the program currently supports the following operations:
* Full filesystem operation on save data and extdata stored on NAND, on SD, on cartridge or standalone.
* Editing title database and tickets.


[![Build Status](https://travis-ci.com/wwylele/save3ds.svg?branch=master)](https://travis-ci.com/wwylele/save3ds)
Note that the supported NAND format is in unpacked cleartext filesystem. If you want to read/write on the original NAND FAT image, you need to use other tools to extract the NAND data, or map another layer of virtual filesystem ([[ninfs 3DS|ninfs]] by ihaveamac).


Extract, import and FUSE program for common save format for 3DS, written in rust.
==Building==
'''Unix-like:'''


There are two main components in the project: the library `libsave3ds`, and the FUSE program + extract/import tool `save3ds_fuse` that builds on top of it. The FUSE feature is not available on Windows.
Install pkg-config and FUSE library.
* Debian: <code>sudo apt-get install libfuse-dev pkg-config</code>
* CentOS: <code>sudo yum install fuse-devel pkgconfig</code>
* macOS: <code>brew cask install osxfuse &amp;&amp; brew install pkg-config</code>
* FreeBSD: <code>pkg install fusefs-libs pkgconf</code>


Both the library and the program currently supports the following operations:
<pre>cargo build</pre>
- Full filesystem operation on save data and extdata stored on NAND, on SD, on cartridge or standalone
- Editing title database and tickets


Note that the supported NAND format is in unpacked cleartext filesystem. If you want to read/write on the original NAND FAT image, you need to use other tools to extract the NAND data, or map another layer of virtual filesystem (e.g. https://github.com/ihaveamac/ninfs)
'''Unix-like (no FUSE):'''
<pre>cd save3ds_fuse &amp;&amp; cargo build --no-default-features</pre>


## Build
'''Windows (no FUSE):'''
<pre>cargo build</pre>


'''Tips: '''


### Unix-like
This AES crate this program depends on chooses hardware/software implementation at compile time.  
1. install pkg-config and FUSE library.
- Debian: `sudo apt-get install libfuse-dev pkg-config`
- CentOS: `sudo yum install fuse-devel pkgconfig`
- macOS: `brew cask install osxfuse && brew install pkg-config`
- FreeBSD: `pkg install fusefs-libs pkgconf`
2.
```
cargo build
```


### Unix-like (no FUSE)
Supply compiler options <code>-C target-feature=+aes</code> to enable hardware AES feature for better performance.
```
cd save3ds_fuse && cargo build --no-default-features
```


### Windows (no FUSE)
==User guide==
<pre>save3ds_fuse ARCHIVE_NAME MOUNT_PATH [MODE] [RESOURCE_PATHS] [FORMAT_PARAM]</pre>


```
You can put options in arbitrary order. The detail description of them are:
cargo build
```


### Tip
<code>ARCHIVE_NAME</code>
* It specifies the archive to operate on. It can be one of the following:
* <code>--sdsave ID</code>: a game save data stored on SD. <code>ID</code> is the game title ID in 16-digit hex.
* <code>--sdext ID</code>: a game extdata stored on SD. <code>ID</code> is the extdata ID in 16-digit hex.
* <code>--nandsave ID</code>: a system save data stored on NAND. <code>ID</code> is the save ID in 8-digit hex.
* <code>--nandext ID</code>: a shared extdata stored on NAND. <code>ID</code> is the extdata ID in 16-digit hex.
* <code>--bare FILE</code>: a stand-alone save data file with path <code>FILE</code>. Note that modification to this archive will result in invalid signature in the file, and you need other tools to fix the signature.
* <code>--db DB_TYPE</code>: a title database archive. <code>DB_TYPE</code> can be one of the following:
** <code>nandtitle</code> refers to the file <code>NAND:/dbs/title.db</code>
** <code>nandimport</code> refers to the file <code>NAND:/dbs/import.db</code>
** <code>tmptitle</code> refers to the file <code>NAND:/dbs/tmp_t.db</code>
** <code>tmpimport</code> refers to the file <code>NAND:/dbs/tmp_i.db</code>
** <code>sdtitle</code> refers to the file <code>SDMC:/Nintendo 3DS/&lt;ID0&gt;/&lt;ID1&gt;/dbs/title.db</code>
** <code>sdimport</code> refers to the file <code>SDMC:/Nintendo 3DS/&lt;ID0&gt;/&lt;ID1&gt;/dbs/import.db</code>
** <code>ticket</code> refers to the file <code>NAND:/dbs/ticket.db</code>
* <code>--cart FILE</code>:a cartridge save data file with path <code>FILE</code>.


This AES crate this program depends on chooses hardware/software implementation at compile time. Supply compiler options `-C target-feature=+aes` to enable hardware AES feature for better performance.
<code>MOUNT_PATH</code>
* It is a directory to mount/extract/import the archive content.


## Usage
<code>MODE</code>
* It specifies the operation mode on the archive. It can be one of the following:
* mount mode (default). Mount the archive to <code>MOUNT_PATH</code> as a virtual filesystem, allowing browsing and editing the content.
** Upon unmounting, the program saves the modification. This mode is not supported on Windows.
** With additional flag <code>--readonly</code>, the program opens the archive in read-only mode and prevents any modification.
* extract mode (<code>--extract</code>). Extracts all content of the archive to <code>MOUNT_PATH</code>.
* import mode (<code>--import</code>). Clear the content of the archive, and import the content from <code>MOUNT_PATH</code>.
* touch mode (<code>--touch</code>). Just open and close the archive. Useful for testing the correctness of other specified resources. No need to specify <code>MOUNT_PATH</code> in this mode.


```
<code>RESOURCE_PATHS</code>
save3ds_fuse ARCHIVE_NAME MOUNT_PATH [MODE] [RESOURCE_PATHS] [FORMAT_PARAM]
* It contains multiple supporting directories/files. Different archive types require different portion of them. It can contain any of the following:
```
* <code>--nand DIR</code>: NAND root path, required by all archive types except <code>--bare</code>. However, if <code>--movable</code> is provided, this can be omitted for SD-related archives (<code>--db sdtitle|sdimport</code>, <code>--sdsave</code> and <code>--sdext</code>).
* <code>--sd DIR</code>: SD root path, required by SD-related archives.
* <code>--boot9 FILE</code>: the <code>boot9.bin</code> file dumped from 3DS, required by all archive types except <code>--bare</code>
* <code>--otp FILE</code>: the <code>otp.bin</code> file dumped from 3DS, required by <code>--db nandtitle|nandimport|ticket</code>
* <code>--movable FILE</code>: the <code>movable.sed</code> file dumped from 3DS, optionally required by SD-related archives, if <code>--nand</code> is not provided.
* <code>--game FILE</code>: the game dumped from the cartridge in CCI format, required by cartridge save.
* <code>--priv FILE</code>: the private header dumped from the cartrdige, required by cartridge save.
* <code>--key FILE|HEX</code>: AES slot 0x2F key Y for decrypting v6.0 cartridge save.
* <code>--key19x FILE|HEX</code>: AES slot 0x19 key X for decrypting New3DS exclusive cartridge save.
* <code>--key1ax FILE|HEX</code>: AES slot 0x1A key X for decrypting New3DS exclusive cartridge save.


You can put options in arbitrary order. The detail description of them are:
<code>FORMAT_PARAM</code>
* It is an optional group of options in the form of <code>--format param1:value1,param2:value2,...</code>, used in conjuntion with mount mode or import mode.
* When the flag <code>--format</code> presents, the archive will be formatted using the given parameters before mounting/importing.
* This is useful for creating a completely new archives. If an archive already exists in the place, it will be deleted.
* The difference between <code>--import</code> and <code>--import --format</code> is that, although both clearing the content, <code>--import</code> retains the archive layout and capacity that depends on the formatting parameters, while the addition <code>--format</code> flag can change the layout and capacity.


`ARCHIVE_NAME` specifies the archive to operate on. It can be one of the following:
The parameters supported by <code>--format</code> are:
- `--sdsave ID`: a game save data stored on SD. `ID` is the game title ID in 16-digit hex.
* <code>max_dir</code>/<code>max_file</code>: the maximum number of directories/files. The default is <code>100</code>
- `--sdext ID`: a game extdata stored on SD. `ID` is the extdata ID in 16-digit hex.
* <code>dir_buckets</code>/<code>file_buckets</code>: the bucket count of the hash table for directories/files. The default value is calculated from <code>max_dir</code>/<code>max_file</code> using the common algorithm games use.
- `--nandsave ID`: a system save data stored on NAND. `ID` is the save ID in 8-digit hex.
* <code>len</code>: only for save data archive. Limits the physical size in bytes of the save data file. The defualt is <code>524288</code> (512 KiB). For Card1 cartridge save, only <code>131072</code> (128 KiB), <code>524288</code> (512 KiB), and <code>1048576</code> (1 MiB) are allowed, and must match the cartidge chip type.
- `--nandext ID`: a shared extdata stored on NAND. `ID` is the extdata ID in 16-digit hex.
* <code>block_len</code>: only for save data archive. The value can only be <code>512</code> or <code>4096</code>. The default is <code>512</code> for <code>--sdsave</code>, <code>--bare</code> and, <code>--cart</code>, and <code>4096</code> for <code>--nandsave</code>.
- `--bare FILE`: a stand-alone save data file with path `FILE`. Note that modification to this archive will result in invalid signature in the file, and you need other tools to fix the signature.
* <code>duplicate_data</code>: only for save data archive. The value can only be <code>true</code> or <code>false</code>. The default is <code>true</code>
- `--db DB_TYPE`: a title database archive. `DB_TYPE` can be one of the following:
- `nandtitle` refers to the file `NAND:/dbs/title.db`
- `nandimport` refers to the file `NAND:/dbs/import.db`
- `tmptitle` refers to the file `NAND:/dbs/tmp_t.db`
- `tmpimport` refers to the file `NAND:/dbs/tmp_i.db`
- `sdtitle` refers to the file `SDMC:/Nintendo 3DS/<ID0>/<ID1>/dbs/title.db`
- `sdimport` refers to the file `SDMC:/Nintendo 3DS/<ID0>/<ID1>/dbs/import.db`
- `ticket` refers to the file `NAND:/dbs/ticket.db`
- `--cart FILE`:a cartridge save data file with path `FILE`.


`MOUNT_PATH` is a directory to mount/extract/import the archive content
If you want leave all parameters in default values, you can specify an empty option, e.g. <code>--format &quot;&quot;</code>


`MODE` specifies the operation mode on the archive. It can be one of the following:
These parameters behave the same as those in the <code>fs:USER</code> 3DS service functions: <code>FormatSaveData</code>, <code>CreateSystemSaveData</code> and <code>CreateExtSaveData</code>. However, the <code>max_dir</code>/<code>max_file</code> specified here is two/one larger than the one in <code>CreateExtSaveData</code>, as the latter one automatically counts the required <code>/user</code>, <code>/boss</code> and <code>/icon</code>.
- mount mode (default). Mount the archive to `MOUNT_PATH` as a virtual filesystem, allowing browsing and editing the content. Upon unmounting, the program saves the modification. This mode is not supported on Windows.
- with additional flag `--readonly`, the program opens the archive in read-only mode and prevents any modification.
- extract mode (`--extract`). Extracts all content of the archive to `MOUNT_PATH`.
- import mode (`--import`). Clear the content of the archive, and import the content from `MOUNT_PATH`.
- touch mode (`--touch`). Just open and close the archive. Useful for testing the correctness of other specified resources. No need to specify `MOUNT_PATH` in this mode.


`RESOURCE_PATHS` contains multiple supporting directories/files. Different archive types require different portion of them. It can contain any of the following:
Title database files currently don't support <code>--format</code>.
- `--nand DIR`: NAND root path, required by all archive types except `--bare`. However, if `--movable` is provided, this can be omitted for SD-related archives (`--db sdtitle|sdimport`, `--sdsave` and `--sdext`).
- `--sd DIR`: SD root path, required by SD-related archives.
- `--boot9 FILE`: the `boot9.bin` file dumped from 3DS, required by all archive types except `--bare`
- `--otp FILE`: the `otp.bin` file dumped from 3DS, required by `--db nandtitle|nandimport|ticket`
- `--movable FILE`: the `movable.sed` file dumped from 3DS, optionally required by SD-related archives , if `--nand` is not provided.
- `--game FILE`: the game dumped from the cartridge in CCI format, required by cartridge save
- `--priv FILE`: the private header dumped from the cartrdige, required by cartridge save
- `--key FILE|HEX`: AES slot 0x2F key Y for decrypting v6.0 cartridge save
- `--key19x FILE|HEX`: AES slot 0x19 key X for decrypting New3DS exclusive cartridge save
- `--key1ax FILE|HEX`: AES slot 0x1A key X for decrypting New3DS exclusive cartridge save


`FORMAT_PARAM` is an optional group of options in the form of `--format param1:value1,param2:value2,...`, used in conjuntion with mount mode or import mode. When the flag `--format` presents, the archive will be formatted using the given parameters before mounting/importing. This is useful for creating a completely new archives. If an archive already exists in the place, it will be deleted. The difference between `--import` and `--import --format` is that, although both clearing the content, `--import` retains the archive layout and capacity that depends on the formatting parameters, while the addition `--format` flag can change the layout and capacity.
===Example command===
<source lang="bash">
save3ds_fuse \
    # Sets the path to NAND root, extracted/mounted from an NAND image.
    # For save/ext data on SD,
    # the only purpose of the NAND path is to provide movable.sed.
    # You can also provide the movable.sed file directly by
    # --movable /path/to/movable.sed
    --nand /home/wwylele/3ds-nand \


The parameters supported by `--format` are
    # Sets the path to SD root.
- `max_dir`/`max_file`: the maximum number of directories/files. The default is `100`
    # This can be the direct path to the SD card mounted on PC.
- `dir_buckets`/`file_buckets`: the bucket count of the hash table for directories/files. The default value is calculated from `max_dir`/`max_file` using the common algorithm games use.
    --sd /media/wwylele/6339-6261 \
- `len`: only for save data archive. Limits the physical size in bytes of the save data file. The defualt is `524288` (512 KiB). For Card1 cartridge save, only `131072` (128 KiB), `524288` (512 KiB), and `1048576` (1 MiB) are allowed, and must match the cartidge chip type.
- `block_len`: only for save data archive. The value can only be `512` or `4096`. The default is `512` for `--sdsave`, `--bare` and, `--cart`, and `4096` for `--nandsave`.
- `duplicate_data`: only for save data archive. The value can only be `true` or `false`. The default is `true`


If you want leave all parameters in default values, you can specify an empty option, e.g. `--format ""`
    # Sets the path to the bootrom.
    # This is necessary for decryption &amp; signing.
    --boot9 /home/wwylele/3dsbootrom/boot9.bin \


These parameters behave the same as those in the `fs:USER` 3DS service functions: `FormatSaveData`, `CreateSystemSaveData` and `CreateExtSaveData`. However, the `max_dir`/`max_file` specified here is two/one larger than the one in `CreateExtSaveData`, as the latter one automatically counts the required `/user`, `/boss` and `/icon`.
    # Informs the program that we want to mount.
    # the SD save data with title ID 0004000000164800 (Pokemon Sun).
    # The ID is a 16-digit hex number.
    --sdsave 0004000000164800 \


Title database files currently don't support `--format`.
    # The target path. The directory must exist and be empty.
    # When the program is running,
    # the content of the mounted data will be shown in this directory.
    /home/wwylele/mount \


## Example command
    # Optional &quot;read-only&quot; flag.
```bash
    # When this flag presents, all write operations are disabled.
save3ds_fuse \
    # Please always backup your data if you don't set this flag!
# Sets the path to NAND root, extracted/mounted from an NAND image.
    -r
# For save/ext data on SD,
</source>
# the only purpose of the NAND path is to provide movable.sed.
# You can also provide the movable.sed file directly by
# --movable /path/to/movable.sed
--nand /home/wwylele/3ds-nand \


# Sets the path to SD root.
===Quirks and Limitations===
# This can be the direct path to the SD card mounted on PC.
'''Directory/file name:'''
--sd /media/wwylele/6339-6261 \


# Sets the path to the bootrom.
Save data and extdata support 16-byte directory / file name, interpreted in ASCII. As it techincally supports special characters like <code>'/'</code> in the name, special mappings are implemented to display them on the host system: characters <code>'/'</code> and <code>'\'</code>, ASCII control characters, and characters beyond <code>0x7F</code> are translated to the escape sequence <code>\x??</code>, where <code>??</code> is the byte value in two-digit hex.  
# This is necessary for decryption & signing.
--boot9 /home/wwylele/3dsbootrom/boot9.bin \


# Informs the program that we want to mount.
These escaped characters will be used when displaying the directory / file name, and you can use them when editing the name. Names longer than 16-bytes are always rejected.
# the SD save data with title ID 0004000000164800 (Pokemon Sun).
# The ID is a 16-digit hex number.
--sdsave 0004000000164800 \


# The target path. The directory must exist and be empty.
Prohibited characters specific to Windows are not taken care of. They are usually not used in games, but if they are unfortunately used, the program will likely crash / error out.
# When the program is running,
# the content of the mounted data will be shown in this directory.
/home/wwylele/mount \


# Optional "read-only" flag.
Files in title database archives are named with title ID in 16-digit hex. File names that contains non-hex characters or that is too long are rejected.
# When this flag presents, all write operations are disabled.
# Please always backup your data if you don't set this flag!
-r
```
 
## Quirks and Limitations


### Directory / file name
'''Cartridge save wear leveling:'''


Save data and extdata support 16-byte directory / file name, interpreted in ASCII. As it techincally supports special characters like `'/'` in the name, special mappings are implemented to display them on the host system: characters `'/'` and `'\'`, ASCII control characters, and characters beyond `0x7F` are translated to the escape sequence `\x??`, where `??` is the byte value in two-digit hex. These escaped characters will be used when displaying the directory / file name, and you can use them when editing the name. Names longer than 16-bytes are always rejected.
The exact mechanism of Card1 wear leveling is unclear yet. When writing a Card1 cartridge save data, save3ds will simply clear the journal and flush everything into the block map, without updating the allocation count or the two unknown integers at the beginning.  


Prohibited characters specific to Windows are not taken care of. They are usually not used in games, but if they are unfortunately used, the program will likely crash / error out.
3DS seems fine with this during testing, but it might cause unexpected things.


Files in title database archives are named with title ID in 16-digit hex. File names that contains non-hex characters or that is too long are rejected.
'''Extdata file size:'''


### Cartridge save wear leveling
Due to the format design, extdata does not support resizing files natively on 3DS, nor creating files with zero size.  
The exact mechanism of Card1 wear leveling is unclear yet. When writing a Card1 cartridge save data, save3ds will simply clear the journal and flush everything into the block map, without updating the allocation count or the two unknown integers at the beginning. 3DS seems fine with this in my test, but it might cause unexpected things.


### Extdata file size
This program works around the issue by deleting and recreating files on resizing, which is stupidly slow if the user appends a file on every write operation. Zero-size files created by this program can't be opened on 3DS either, so one needs to make sure there is no such file before importing the data back to 3DS.


Due to the format design, extdata does not support resizing files natively on 3DS, nor creating files with zero size. This program works around the issue by deleting and recreating files on resizing, which is stupidly slow if the user appends a file on every write operation. Zero-size files created by this program can't be opened on 3DS either, so one needs to make sure there is no such file before importing the data back to 3DS.
One can create a file with a specific size, similar to the <code>CreateFile</code> operation on 3DS. This is done by specifying a special sequence <code>\+size</code> in the file name. For example, <code>a.bin\+123</code> creates the file <code>a.bin</code> with size of 123 bytes. This, however, doesn't comply with the expected filesystem behaviour, and breaks file name cache in browsers etc.


One can create a file with a specific size, similar to the `CreateFile` operation on 3DS. This is done by specifying a special sequence `\+size` in the file name. For example, `a.bin\+123` creates the file `a.bin` with size of 123 bytes. This, however, doesn't comply with the expected filesystem behaviour, and breaks file name cache in browsers etc.
Because of all the mess, it is recommended to use <code>--import</code> mode instead of mount mode if you intend to modify the content of an extdata.


Because of all the mess, it is recommended to use `--import` mode instead of mount mode if you intend to modify the content of an extdata.
'''Extdata filesystem structure:'''


### Extdata filesystem structure
3DS system expects every extdata to have directories <code>/boss</code> and <code>/user</code>, and the file <code>/icon</code>.


3DS system expects every extdata to have directories `/boss` and `/user`, and the file `/icon`. These directories and file are not automatically created when the program formats an extdata. One needs to manually create them, otherwise 3DS would likely fail to open the archive.
These directories and file are not automatically created when the program formats an extdata.  


### Broken block of title database
One needs to manually create them, otherwise 3DS would likely fail to open the archive.


Due to a bug (?) in 3DS, the last free block (128 bytes) of a title database archive (except for `ticket.db`) is broken. If the archive is almost full and data starts to be written to this block, they will not be saved.
'''Broken block of title database:'''


### `Quota.dat` for NAND extdata
Due to a bug (?) in 3DS, the last free block (128 bytes) of a title database archive (except for <code>ticket.db</code>) is broken.  


The format and function of the `Quota.dat` file is not fully investigated, and the program probably doesn't parse and update it properly for NAND extdata. This can potentially cause inconsistency if you modify a NAND extdata.
If the archive is almost full and data starts to be written to this block, they will not be saved.


## License
'''Quota.dat for NAND extdata:'''


Licensed under either of
The format and function of the <code>Quota.dat</code> file is not fully investigated, and the program probably doesn't parse and update it properly for NAND extdata.


- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
This can potentially cause inconsistency if you modify a NAND extdata.
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)


at your option.
==External links==
* GitHub - https://github.com/wwylele/save3ds
* GitHub (former) - https://github.com/wwylele/3dsfuse-ex

Latest revision as of 04:31, 6 Mayıs 2024

save3ds
3dspc.png
General
Authorwwylele
TypeFile Operation
Version1.3.0
LicenseApache-2.0
Last Updated2019/12/22
Links
Download
Website
Source

Save3DS is an extract, import and FUSE program for common save format for 3DS, written in rust. Inspired by 3dsfuse but rewritten with a newer research.

There are two main components in the project: the library libsave3ds, and the FUSE program + extract/import tool save3ds_fuse that builds on top of it (the FUSE feature is not available on Windows). Both the library and the program currently supports the following operations:

  • Full filesystem operation on save data and extdata stored on NAND, on SD, on cartridge or standalone.
  • Editing title database and tickets.

Note that the supported NAND format is in unpacked cleartext filesystem. If you want to read/write on the original NAND FAT image, you need to use other tools to extract the NAND data, or map another layer of virtual filesystem (ninfs by ihaveamac).

Building

Unix-like:

Install pkg-config and FUSE library.

  • Debian: sudo apt-get install libfuse-dev pkg-config
  • CentOS: sudo yum install fuse-devel pkgconfig
  • macOS: brew cask install osxfuse && brew install pkg-config
  • FreeBSD: pkg install fusefs-libs pkgconf
cargo build

Unix-like (no FUSE):

cd save3ds_fuse && cargo build --no-default-features

Windows (no FUSE):

cargo build

Tips:

This AES crate this program depends on chooses hardware/software implementation at compile time.

Supply compiler options -C target-feature=+aes to enable hardware AES feature for better performance.

User guide

save3ds_fuse ARCHIVE_NAME MOUNT_PATH [MODE] [RESOURCE_PATHS] [FORMAT_PARAM]

You can put options in arbitrary order. The detail description of them are:

ARCHIVE_NAME

  • It specifies the archive to operate on. It can be one of the following:
  • --sdsave ID: a game save data stored on SD. ID is the game title ID in 16-digit hex.
  • --sdext ID: a game extdata stored on SD. ID is the extdata ID in 16-digit hex.
  • --nandsave ID: a system save data stored on NAND. ID is the save ID in 8-digit hex.
  • --nandext ID: a shared extdata stored on NAND. ID is the extdata ID in 16-digit hex.
  • --bare FILE: a stand-alone save data file with path FILE. Note that modification to this archive will result in invalid signature in the file, and you need other tools to fix the signature.
  • --db DB_TYPE: a title database archive. DB_TYPE can be one of the following:
    • nandtitle refers to the file NAND:/dbs/title.db
    • nandimport refers to the file NAND:/dbs/import.db
    • tmptitle refers to the file NAND:/dbs/tmp_t.db
    • tmpimport refers to the file NAND:/dbs/tmp_i.db
    • sdtitle refers to the file SDMC:/Nintendo 3DS/<ID0>/<ID1>/dbs/title.db
    • sdimport refers to the file SDMC:/Nintendo 3DS/<ID0>/<ID1>/dbs/import.db
    • ticket refers to the file NAND:/dbs/ticket.db
  • --cart FILE:a cartridge save data file with path FILE.

MOUNT_PATH

  • It is a directory to mount/extract/import the archive content.

MODE

  • It specifies the operation mode on the archive. It can be one of the following:
  • mount mode (default). Mount the archive to MOUNT_PATH as a virtual filesystem, allowing browsing and editing the content.
    • Upon unmounting, the program saves the modification. This mode is not supported on Windows.
    • With additional flag --readonly, the program opens the archive in read-only mode and prevents any modification.
  • extract mode (--extract). Extracts all content of the archive to MOUNT_PATH.
  • import mode (--import). Clear the content of the archive, and import the content from MOUNT_PATH.
  • touch mode (--touch). Just open and close the archive. Useful for testing the correctness of other specified resources. No need to specify MOUNT_PATH in this mode.

RESOURCE_PATHS

  • It contains multiple supporting directories/files. Different archive types require different portion of them. It can contain any of the following:
  • --nand DIR: NAND root path, required by all archive types except --bare. However, if --movable is provided, this can be omitted for SD-related archives (--db sdtitle|sdimport, --sdsave and --sdext).
  • --sd DIR: SD root path, required by SD-related archives.
  • --boot9 FILE: the boot9.bin file dumped from 3DS, required by all archive types except --bare
  • --otp FILE: the otp.bin file dumped from 3DS, required by --db nandtitle|nandimport|ticket
  • --movable FILE: the movable.sed file dumped from 3DS, optionally required by SD-related archives, if --nand is not provided.
  • --game FILE: the game dumped from the cartridge in CCI format, required by cartridge save.
  • --priv FILE: the private header dumped from the cartrdige, required by cartridge save.
  • --key FILE|HEX: AES slot 0x2F key Y for decrypting v6.0 cartridge save.
  • --key19x FILE|HEX: AES slot 0x19 key X for decrypting New3DS exclusive cartridge save.
  • --key1ax FILE|HEX: AES slot 0x1A key X for decrypting New3DS exclusive cartridge save.

FORMAT_PARAM

  • It is an optional group of options in the form of --format param1:value1,param2:value2,..., used in conjuntion with mount mode or import mode.
  • When the flag --format presents, the archive will be formatted using the given parameters before mounting/importing.
  • This is useful for creating a completely new archives. If an archive already exists in the place, it will be deleted.
  • The difference between --import and --import --format is that, although both clearing the content, --import retains the archive layout and capacity that depends on the formatting parameters, while the addition --format flag can change the layout and capacity.

The parameters supported by --format are:

  • max_dir/max_file: the maximum number of directories/files. The default is 100
  • dir_buckets/file_buckets: the bucket count of the hash table for directories/files. The default value is calculated from max_dir/max_file using the common algorithm games use.
  • len: only for save data archive. Limits the physical size in bytes of the save data file. The defualt is 524288 (512 KiB). For Card1 cartridge save, only 131072 (128 KiB), 524288 (512 KiB), and 1048576 (1 MiB) are allowed, and must match the cartidge chip type.
  • block_len: only for save data archive. The value can only be 512 or 4096. The default is 512 for --sdsave, --bare and, --cart, and 4096 for --nandsave.
  • duplicate_data: only for save data archive. The value can only be true or false. The default is true

If you want leave all parameters in default values, you can specify an empty option, e.g. --format ""

These parameters behave the same as those in the fs:USER 3DS service functions: FormatSaveData, CreateSystemSaveData and CreateExtSaveData. However, the max_dir/max_file specified here is two/one larger than the one in CreateExtSaveData, as the latter one automatically counts the required /user, /boss and /icon.

Title database files currently don't support --format.

Example command

save3ds_fuse \
    # Sets the path to NAND root, extracted/mounted from an NAND image.
    # For save/ext data on SD,
    # the only purpose of the NAND path is to provide movable.sed.
    # You can also provide the movable.sed file directly by
    # --movable /path/to/movable.sed
    --nand /home/wwylele/3ds-nand \

    # Sets the path to SD root.
    # This can be the direct path to the SD card mounted on PC.
    --sd /media/wwylele/6339-6261 \

    # Sets the path to the bootrom.
    # This is necessary for decryption &amp; signing.
    --boot9 /home/wwylele/3dsbootrom/boot9.bin \

    # Informs the program that we want to mount.
    # the SD save data with title ID 0004000000164800 (Pokemon Sun).
    # The ID is a 16-digit hex number.
    --sdsave 0004000000164800 \

    # The target path. The directory must exist and be empty.
    # When the program is running,
    # the content of the mounted data will be shown in this directory.
    /home/wwylele/mount \

    # Optional &quot;read-only&quot; flag.
    # When this flag presents, all write operations are disabled.
    # Please always backup your data if you don't set this flag!
    -r

Quirks and Limitations

Directory/file name:

Save data and extdata support 16-byte directory / file name, interpreted in ASCII. As it techincally supports special characters like '/' in the name, special mappings are implemented to display them on the host system: characters '/' and '\', ASCII control characters, and characters beyond 0x7F are translated to the escape sequence \x??, where ?? is the byte value in two-digit hex.

These escaped characters will be used when displaying the directory / file name, and you can use them when editing the name. Names longer than 16-bytes are always rejected.

Prohibited characters specific to Windows are not taken care of. They are usually not used in games, but if they are unfortunately used, the program will likely crash / error out.

Files in title database archives are named with title ID in 16-digit hex. File names that contains non-hex characters or that is too long are rejected.

Cartridge save wear leveling:

The exact mechanism of Card1 wear leveling is unclear yet. When writing a Card1 cartridge save data, save3ds will simply clear the journal and flush everything into the block map, without updating the allocation count or the two unknown integers at the beginning.

3DS seems fine with this during testing, but it might cause unexpected things.

Extdata file size:

Due to the format design, extdata does not support resizing files natively on 3DS, nor creating files with zero size.

This program works around the issue by deleting and recreating files on resizing, which is stupidly slow if the user appends a file on every write operation. Zero-size files created by this program can't be opened on 3DS either, so one needs to make sure there is no such file before importing the data back to 3DS.

One can create a file with a specific size, similar to the CreateFile operation on 3DS. This is done by specifying a special sequence \+size in the file name. For example, a.bin\+123 creates the file a.bin with size of 123 bytes. This, however, doesn't comply with the expected filesystem behaviour, and breaks file name cache in browsers etc.

Because of all the mess, it is recommended to use --import mode instead of mount mode if you intend to modify the content of an extdata.

Extdata filesystem structure:

3DS system expects every extdata to have directories /boss and /user, and the file /icon.

These directories and file are not automatically created when the program formats an extdata.

One needs to manually create them, otherwise 3DS would likely fail to open the archive.

Broken block of title database:

Due to a bug (?) in 3DS, the last free block (128 bytes) of a title database archive (except for ticket.db) is broken.

If the archive is almost full and data starts to be written to this block, they will not be saved.

Quota.dat for NAND extdata:

The format and function of the Quota.dat file is not fully investigated, and the program probably doesn't parse and update it properly for NAND extdata.

This can potentially cause inconsistency if you modify a NAND extdata.

External links

Advertising: