Saved games

A ProDOS write is not atomic. A power loss or a disk error can leave a file that is only partly written. Write the saves to two files in turn. Load the newer file that is valid.

Two slots

#include "libtini.h"

#define SAVE_MAGIC 0x5A
typedef struct save_data {
    uint8_t magic, sequence;
    uint32_t best_score;
    uint8_t level, checksum;            /* checksum is the last field */
} save_data;

static save_data current;
static const char *const slot_name[2] = {"SAVE.A", "SAVE.B"};
static uint8_t newest = 1;              /* the slot that holds current; saves use the other slot */

static uint8_t checksum(const save_data *s)
{
    const uint8_t *p = (const uint8_t *)s;
    uint8_t i, sum = 0xA5;
    for (i = 0; i < sizeof(*s) - 1; ++i) sum = (uint8_t)((sum << 1 | sum >> 7) ^ p[i]);
    return sum;
}

/* Loads the newer valid slot into current; 0 when no slot is valid. */
uint8_t load_game(void)
{
    save_data slot[2];
    uint16_t length;
    uint8_t i, valid[2];
    for (i = 0; i < 2; ++i)
        valid[i] = ti_file_read(slot_name[i], &slot[i], sizeof(slot[i]), &length) == TI_OK &&
                   length == sizeof(slot[i]) && slot[i].magic == SAVE_MAGIC &&
                   slot[i].checksum == checksum(&slot[i]);
    if (!valid[0] && !valid[1]) return 0;
    if (valid[0] && valid[1]) i = (int8_t)(slot[1].sequence - slot[0].sequence) > 0;
    else i = valid[1];
    current = slot[i];
    newest = i;
    return 1;
}

ti_error save_game(void)
{
    ti_error e;
    current.magic = SAVE_MAGIC;
    ++current.sequence;
    current.checksum = checksum(&current);
    e = ti_file_write(slot_name[newest ^ 1], &current, sizeof(current));
    if (e == TI_OK) newest ^= 1;
    return e;
}

The sequence comparison is correct when the number goes from 255 to 0. File names use the ProDOS rule: a capital letter, then capitals, digits or periods, with a maximum of 15 characters. A name is relative to the current prefix. This prefix is usually the volume of the boot disk.

What a file call needs

Time during a save

The audio driver pauses while ProDOS runs. The music becomes silent, effects and speech stop, and the clock stops. When the call returns, the clock starts again without a gap, and the music continues. The picture comes back as it was.

A save needs disk time. Thus save at a natural pause, for example the game-over screen. Expect a short silence.

When a save fails

ti_file_write returns TI_E_IO for a ProDOS failure. ti_file_last_error gives the ProDOS code, for example $2B for a write-protected disk or $48 for a full volume.

Tell the player about the failure in words, and continue the game. Do not change the other slot, because it holds the last valid save.