== 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. The calls find them in the program's own folder, wherever the program was started from, and before `jlInit` as well as after (the log, `joeylog.txt`, goes there too): * *DOS* takes the folder from the path it started the program by, *the Amiga* uses `PROGDIR:` (Kickstart 2.0 and later; under 1.3, the current directory), *the X68000* reads it from the program's process block, *the IIgs* uses GS/OS prefix 1 (the application's folder), and *the hosted ports* take it from their executable's path. The hosted ports also make it the current directory in `jlInit`, for the program's own relative paths. * *The ST* looks in the current directory: TOS tells a program nothing of where it was started from. A program the desktop starts runs in its own folder; one started from a command line elsewhere finds no `DATA/` and makes its `SAVES/` there. * *DVX* keeps the two apart, 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 web* keeps them apart too: `DATA/` is packed into the program, and `SAVES/` and the log are in a folder named after the page (`games/hero.html` has `/games/hero/`), its `SAVES/` kept in the browser's storage for that page alone (<<_web_programs>>). 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 bytes go to a temporary file first, and only once all of them are written does it take the save's place, so a write that fails part way (a full disk, say, or the machine switched off) leaves the old save as it was. A `NULL` `buf` with a non-zero `len` returns `false` and leaves the save alone. A save written with `jlSaveOpen` is written in place, as `fopen` writes. [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"); } ----