185 lines
5.4 KiB
Text
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");
|
|
}
|
|
----
|