joeylib2/docs/ApiFile.adoc

185 lines
5.4 KiB
Text

== Files and Saves
Declared in `<joey/file.h>`.
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");
}
----