joeylib2/docs/ApiFile.adoc

193 lines
6.1 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.
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\VENDOR\NAME` (`VENDOR` its maker's, <<_a_project>>), 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");
}
----