== Files and Saves Declared in ``. Two directories sit beside the executable, staged there by each platform's disk packager: * `DATA/` -- game content: levels, sprites, audio. * `SAVES/` -- writable saves and preferences. Where the calls look for them: * *The hosted ports* make the program's own folder the current directory in `jlInit`, so both are found beside the program wherever it was started from. * *DOS, the ST, the Amiga, and the X68000* look in the current directory. A program started from its own folder (as a desktop starts it) finds them; one started from a command line elsewhere finds no `DATA/` and makes its `SAVES/` there. * *The IIgs* looks for `DATA/` under GS/OS prefix 0 and puts `SAVES/` in the application's folder (prefix 1). When the two differ, data and saves come from different folders. * *DVX* is the exception, because its programs share one current directory: there `DATA/` is beside the program, in its folder under DVX's `APPS`, and `SAVES/` is in the program's own settings folder, `CONFIG\APPS\JOEYLIB\NAME`, which DVX keeps apart from the program files so a reinstall never overwrites it. The calls below find both there by themselves. Both are addressed by a *bare name relative to their directory*. The prefix is added for you, so never hard-code `"DATA/"` or `"SAVES/"` yourself. [IMPORTANT] ==== Keep names portable across the retro filesystems: at most 8 characters plus an extension of up to 3, letters and digits only, starting with a letter, so they satisfy both ProDOS (whose names must start with a letter) and DOS 8.3. The save calls refuse any other name on every port, as if the file could not be opened; names in `DATA/`, which ship with the program, are passed through as they are. Do not depend on case being preserved, and use the same case every time: on Linux, Haiku, BeOS, and the web, `HERO.SAV` and `hero.sav` are different files. ==== A failed open or write is non-fatal and simply reports failure, so save code can degrade gracefully on a platform or volume where `SAVES/` is unavailable. On the IIgs at most 21 files can be open at once, and saves are created as text files (type TXT). === jlDataOpen [source,c] ---- FILE *jlDataOpen(const char *name, const char *mode); ---- Opens a data file. The `DATA/` prefix is prepended automatically. The mode is passed to `fopen` as it is, so a writing mode works where the medium allows it, but `DATA/` is meant to be read: on the web, for instance, what is written there is lost when the page closes. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`name` |`const char *` |Name relative to `DATA/`, for example `"levels/title.dat"`. |`mode` |`const char *` |Standard `fopen` mode string. |=== *Returns:* a `FILE *`, or `NULL` if the path would overflow the internal buffer or the file cannot be opened. === jlSaveOpen [source,c] ---- FILE *jlSaveOpen(const char *name, const char *mode); ---- Opens a save file for streaming. For write and append modes the `SAVES/` directory is created on demand. Use this for large or incremental saves; for a whole-file blob prefer `jlSaveWrite` and `jlSaveRead`. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`name` |`const char *` |Name relative to `SAVES/`, for example `"hero.sav"`. |`mode` |`const char *` |Standard `fopen` mode string. |=== *Returns:* a `FILE *`, or `NULL` on failure. === jlSaveWrite [source,c] ---- bool jlSaveWrite(const char *name, const void *buf, uint32_t len); ---- Writes a whole save file, replacing any existing contents. The write is not atomic: if it fails part way (a full disk, say) the save is left short. A `NULL` `buf` with a non-zero `len` returns `false` and leaves the save alone. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`name` |`const char *` |Name relative to `SAVES/`. |`buf` |`const void *` |Data to write. |`len` |`uint32_t` |Byte count. |=== *Returns:* `true` only if all `len` bytes were written. === jlSaveRead [source,c] ---- uint32_t jlSaveRead(const char *name, void *buf, uint32_t max); ---- Reads a whole save file. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`name` |`const char *` |Name relative to `SAVES/`. |`buf` |`void *` |Destination buffer. |`max` |`uint32_t` |Buffer capacity. |=== *Returns:* the number of bytes read; 0 if the file is missing or empty. === jlSaveExists [source,c] ---- bool jlSaveExists(const char *name); ---- [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`name` |`const char *` |Name relative to `SAVES/`. |=== *Returns:* `true` if the file exists and can be opened for reading. === jlSaveDelete [source,c] ---- bool jlSaveDelete(const char *name); ---- [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`name` |`const char *` |Name relative to `SAVES/`. |=== *Returns:* `true` if the file was removed. === jlDiskFree [source,c] ---- uint32_t jlDiskFree(void); ---- *Parameters:* none. *Returns:* free space in bytes on the volume holding `SAVES/`. Returns 0 if it cannot be determined, and saturates at `UINT32_MAX` on volumes larger than 4 GB. On DOS and Win16 the query (DOS's own) stops near 2 GB on a larger volume, and on the web it is the browser's own estimate of what the site may still store (its quota less what it uses), which the browser may round or pad. [source,c] ---- // Check before writing a large save on a floppy-based target. if (jlDiskFree() < neededBytes) { showMessage("Disk full"); } ----