193 lines
6.1 KiB
Text
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 folder in DVX's user folder,
|
|
`USER\APPS\VENDOR\NAME` (`VENDOR` its maker's, <<_a_project>>), which
|
|
DVX keeps apart from the program files so an upgrade 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");
|
|
}
|
|
----
|