Quick answer: Never save directly over the existing file. Write to save.dat.tmp, verify the write completed with buffer_save_async, append a SHA-256 footer, then rename the temp over the live save. Keep one backup generation.

A player reports their 30-hour save is unreadable after a crash during saving. You look at the bytes and the file is truncated halfway through — clearly the game died mid-write. GameMaker’s buffer_save is a single file-overwrite and gives no corruption protection. Safe saves need atomicity.

The Fix: Temp-and-Rename

function save_game_safe(slot) {
    var buf = build_save_buffer();
    var payload_size = buffer_tell(buf);

    // Append SHA-256 footer
    var hash = sha1_string_utf8(buffer_base64_encode(buf, 0, payload_size));
    buffer_write(buf, buffer_text, hash);

    var tmp_path = "save_" + string(slot) + ".dat.tmp";
    var live_path = "save_" + string(slot) + ".dat";
    var bak_path = "save_" + string(slot) + ".dat.bak";

    buffer_save(buf, tmp_path);
    buffer_delete(buf);

    if (file_exists(live_path)) {
        if (file_exists(bak_path)) file_delete(bak_path);
        file_rename(live_path, bak_path);
    }
    file_rename(tmp_path, live_path);
}

The sequence:

  1. Write to .tmp with hash footer.
  2. Move current live to .bak.
  3. Move .tmp to live.

A crash between any two steps leaves a recoverable state — either the old save, the new save, or the temp.

Verifying on Load

function load_game_safe(slot) {
    var path = "save_" + string(slot) + ".dat";
    if (!file_exists(path)) path = "save_" + string(slot) + ".dat.bak";
    var buf = buffer_load(path);
    if (!verify_hash_footer(buf)) {
        // fall back to .bak if live is corrupted
        buffer_delete(buf);
        var bak = "save_" + string(slot) + ".dat.bak";
        if (file_exists(bak)) buf = buffer_load(bak);
    }
    return parse_save_buffer(buf);
}

Async Saves

For larger saves, use buffer_save_async and listen for the completion event in Async - Save/Load. Only perform the rename step when the async event fires success. This prevents the rename from happening before the write physically completes to disk.

“Every save system that doesn’t use temp-and-rename eventually eats a player’s save. The fix is five lines; the cost of skipping it is a refund.”

Related Issues

For broader save strategy, see GameMaker save best practices. For preventing file corruption in general, see how to prevent save file corruption bugs.

Keep exactly one backup generation (.bak). More backups waste disk, zero backups loses data.