Bedtime. /Storage API

Storage API

File I/O and browser operations

The storage API provides file I/O for reading and writing files on the SD card.

Path Format

Reads

A read (storage_read_file, storage_file_exists, storage_file_size, storage_list_dir) looks in the tapp's folder first and falls back to the path as given if nothing is there. This allows loading a file the user picked with browser_open, or a ROM/sample/WAV located anywhere on the card. For a folder it is all-or-nothing: once the tapp's folder has a directory of that name, storage_list_dir lists only that one, never the card's - so a tapp must not create a folder named like a card folder the user is expected to fill.

Writes have no such fallback: storage_write_file, storage_append_file, storage_delete_file and storage_mkdir always act inside the tapp's folder. A tapp cannot modify or delete anything belonging to the user or to the firmware.

Safe writes

storage_write_file() writes ATOMICALLY and wraps the data in a 16-byte integrity envelope (magic + length + CRC32): the write goes through "<path>.tmp" and the previous version is kept as "<path>.bak" - those siblings must be left untouched, they provide crash recovery. storage_read_file() and storage_file_size() strip the envelope, so a write/read round-trip returns exactly the bytes written, and fall back to the .bak copy if the file is damaged. Foreign/raw files (assets, ROMs, files not written with storage_write_file()) read back raw, partial reads allowed.

bool browser_open(const char* start_path, const char* file_extension, browser_callback_t callback)

Open file browser to select a file

const char*start_pathStarting directory path (e.g., "/" for root)
const char*file_extensionFile extension filter (e.g., ".gb", ".txt") Set to NULL to show all files
browser_callback_tcallbackCallback function called when file is selected

The browser will filter files by the given extension and call the callback when the user selects a file. The callback receives the full file path.

Returns — true if browser launched successfully

bool on_file_selected(const char* path) {
    // Load the file
    storage_read_file(path, buffer, sizeof(buffer));
    return true;
}

browser_open("/", ".txt", on_file_selected);
uint32_t storage_read_file(const char* path, void* buffer, size_t buffer_size)

Read entire file into buffer

const char*pathFile path (max 255 chars)
void*bufferOutput buffer
size_tbuffer_sizeSize of output buffer

For a file written by storage_write_file() this returns the PAYLOAD (see Safe writes); any other file returns its raw bytes, truncated to buffer_size.

Returns — Number of bytes read, or 0 on error

uint8_t data[1024];
uint32_t bytes = storage_read_file("/config.bin", data, sizeof(data));
if (bytes > 0) {
    // Process data
}
uint32_t storage_write_file(const char* path, const void* buffer, size_t size)

Write entire file from buffer (overwrites existing)

const char*pathFile path (max 255 chars)
const void*bufferData to write
size_tsizeNumber of bytes to write (must be > 0)

Atomic and integrity-enveloped (see Safe writes): read it back with storage_read_file() - a raw reader would see the 16-byte envelope prepended.

Returns — Number of bytes written, or 0 on error

uint32_t storage_append_file(const char* path, const void* buffer, size_t size)

Append data to existing file

const char*pathFile path (max 255 chars)
const void*bufferData to append
size_tsizeNumber of bytes to append

RAW append - do NOT mix with storage_write_file() on the same path: appending to an enveloped file breaks its length/CRC, and reads of it degrade to raw (envelope bytes included). Use one API per file.

Returns — Number of bytes written, or 0 on error

bool storage_delete_file(const char* path)

Delete a file

const char*pathFile path (max 255 chars)

Deletes only <path> itself - a "<path>.bak" sibling from storage_write_file is left behind (harmless for reads; delete it as well to leave no trace).

Returns — true on success, false if file doesn't exist or error

bool storage_mkdir(const char* path)

Create directory (and parent directories if needed)

const char*pathDirectory path (max 255 chars)

Returns — true on success (also returns true if directory already exists)

bool storage_file_exists(const char* path)

Check if file exists

const char*pathFile path (max 255 chars)

Returns — true if file exists

uint32_t storage_file_size(const char* path)

Get file size in bytes

const char*pathFile path (max 255 chars)

For a file written by storage_write_file() this is the PAYLOAD size - i.e. exactly what storage_read_file() will return.

Returns — File size in bytes, or 0 if not found

uint32_t storage_list_dir(const char* path, const char* ext, char* names, size_t stride, uint32_t max)

List the files in a folder

const char*pathFolder path (max 255 chars)
const char*extExtension filter, e.g. ".wav"; NULL lists every file
char*namesOutput buffer, written as `max` slots of `stride` bytes
size_tstrideBytes per slot - name `i` is at `names + i * stride`
uint32_tmaxMaximum number of names to write

Returns — How many names were written

char names[8][32];
const uint32_t n = storage_list_dir("/kits", ".wav", &names[0][0], 32, 8);
for (uint32_t i = 0; i < n; i++) { ... }   // names[i] is a filename

Note — *The order is the filesystem's**, which on FAT is directory-entry order - arbitrary, and it changes as files are created and deleted. If anything is indexed by position (a saved preset pointing at "kit 3", for example), SORT the names first, or that preset will point elsewhere after a copy.

Note — *The tapp's own `.bak` files are already excluded.** storage_write_file() leaves `<path>.bak` and `<path>.tmp` beside what it writes; `ext` matches the END of the name, so ".wav" does not match "kit.wav.bak". Passing NULL for `ext` turns that off and they are listed.

Note — Directories, hidden files and dotfiles never appear. Blocks on the SD card, so treat it like storage_read_file(): init() or tick(), never process().