add snapshots to savegames (#741)

* add snapshots to savegames

* WIP

* revert debugging aid

* clean up

* replace strncpy() with memcpy()

We *are* going to truncate before the terminating NUL.
This isn't a string, it's an indicator for the following snapshot data.

* fix initializer to be compile time constant

* limit maximum snapshot size

* array length should be const

* show savegame modification time below snapshots

* suppress the most useless compiler warning ever

* restrict warning suppression to GCC, sigh

* use M_stat() in M_ReadSavegameTime()
This commit is contained in:
Fabian Greffrath 2022-09-24 21:51:36 +02:00 committed by GitHub
parent 21f0b0e754
commit c815ac2102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 314 additions and 3 deletions

View File

@ -50,6 +50,7 @@ set(WOOF_SOURCES
m_misc.c m_misc.h
m_misc2.c m_misc2.h
m_random.c m_random.h
m_snapshot.c m_snapshot.h
m_swap.h
memio.c memio.h
midifile.c midifile.h

View File

@ -65,6 +65,7 @@
#include "u_mapinfo.h"
#include "m_input.h"
#include "memio.h"
#include "m_snapshot.h"
#define SAVEGAMESIZE 0x20000
#define SAVESTRINGSIZE 24
@ -1909,6 +1910,11 @@ static void G_DoSaveGame(void)
CheckSaveGame(sizeof extrakills);
saveg_write32(extrakills);
// [FG] save snapshot
CheckSaveGame(M_SnapshotDataSize());
M_WriteSnapshot(save_p);
save_p += M_SnapshotDataSize();
length = save_p - savebuffer;
if (!M_WriteFile(name, savebuffer, length))

View File

@ -201,6 +201,7 @@ int M_stat(const char *path, struct stat *buf)
// incompatible with struct stat*. We copy only the required compatible
// field.
buf->st_mode = wbuf.st_mode;
buf->st_mtime = wbuf.st_mtime;
free(wpath);

View File

@ -61,6 +61,7 @@
#include "r_sky.h" // [FG] R_InitSkyMap()
#include "r_plane.h" // [FG] R_InitPlanes()
#include "m_argv.h"
#include "m_snapshot.h"
// [crispy] remove DOS reference from the game quit confirmation dialogs
#include "SDL_platform.h"
@ -145,6 +146,8 @@ boolean menuactive; // The menus are up
#define M_THRM_STEP 6
#define M_THRM_WIDTH (M_THRM_STEP * (M_THRM_SIZE + 2))
#define M_X_THRM (M_X - M_THRM_WIDTH)
#define M_X_LOADSAVE 80
#define M_LOADSAVE_WIDTH (24 * 8 + 8) // [FG] c.f. M_DrawSaveLoadBorder()
#define DISABLE_ITEM(condition, item) \
((condition) ? (item.m_flags |= S_DISABLE) : (item.m_flags &= ~S_DISABLE))
@ -797,12 +800,78 @@ menu_t LoadDef =
&MainDef,
LoadMenu,
M_DrawLoad,
80,34, //jff 3/15/98 move menu up
M_X_LOADSAVE,34, //jff 3/15/98 move menu up
0
};
#define LOADGRAPHIC_Y 8
// [FG] draw a snapshot of the n'th savegame into a separate window,
// or fill window with solid color and "n/a" if snapshot not available
static int snapshot_width, snapshot_height;
static void M_DrawBorderedSnapshot (int n)
{
int x, y;
patch_t *patch;
char *txt;
const int snapshot_x = MAX((WIDESCREENDELTA + SaveDef.x + SKULLXOFF - snapshot_width) / 2, 8);
const int snapshot_y = LoadDef.y + MAX((load_end * LINEHEIGHT - snapshot_height) * n / load_end, 0);
// [FG] a snapshot window smaller than 80*48 px is considered too small
if (snapshot_width < ORIGWIDTH/4)
return;
if (!M_DrawSnapshot(n, snapshot_x, snapshot_y, snapshot_width, snapshot_height))
{
txt = "n/a";
M_WriteText(snapshot_x + snapshot_width/2 - M_StringWidth(txt)/2 - WIDESCREENDELTA,
snapshot_y + snapshot_height/2 - M_StringHeight(txt)/2,
txt);
}
txt = M_GetSavegameTime(n);
M_DrawString(snapshot_x + snapshot_width/2 - M_GetPixelWidth(txt)/2 - WIDESCREENDELTA,
snapshot_y + snapshot_height + M_StringHeight(txt),
CR_GOLD, txt);
// [FG] draw the view border around the snapshot
patch = W_CacheLumpName("brdr_t", PU_CACHE);
for (x = 0; x < snapshot_width; x += 8)
V_DrawPatch(snapshot_x + x - WIDESCREENDELTA, snapshot_y - 8, 0, patch);
patch = W_CacheLumpName("brdr_b", PU_CACHE);
for (x = 0; x < snapshot_width; x += 8)
V_DrawPatch(snapshot_x + x - WIDESCREENDELTA, snapshot_y + snapshot_height, 0, patch);
patch = W_CacheLumpName("brdr_l", PU_CACHE);
for (y = 0; y < snapshot_height; y += 8)
V_DrawPatch(snapshot_x - 8 - WIDESCREENDELTA, snapshot_y + y, 0, patch);
patch = W_CacheLumpName("brdr_r", PU_CACHE);
for (y = 0; y < snapshot_height; y += 8)
V_DrawPatch(snapshot_x + snapshot_width - WIDESCREENDELTA, snapshot_y + y, 0, patch);
V_DrawPatch(snapshot_x - 8 - WIDESCREENDELTA,
snapshot_y - 8,
0, W_CacheLumpName("brdr_tl", PU_CACHE));
V_DrawPatch(snapshot_x + snapshot_width - WIDESCREENDELTA,
snapshot_y - 8,
0, W_CacheLumpName("brdr_tr", PU_CACHE));
V_DrawPatch(snapshot_x - 8 - WIDESCREENDELTA,
snapshot_y + snapshot_height,
0, W_CacheLumpName("brdr_bl", PU_CACHE));
V_DrawPatch(snapshot_x + snapshot_width - WIDESCREENDELTA,
snapshot_y + snapshot_height,
0, W_CacheLumpName("brdr_br", PU_CACHE));
}
// [FG] delete a savegame
static boolean delete_verify = false;
@ -839,7 +908,7 @@ void M_DrawSaveLoadBottomLine(void)
M_DrawString(LoadDef.x+(SAVESTRINGSIZE-2)*8, y, CR_GOLD, "->");
M_snprintf(pagestr, sizeof(pagestr), "page %d/%d", savepage + 1, savepage_max + 1);
M_DrawString(ORIGWIDTH/2-M_StringWidth(pagestr)/2, y, CR_GOLD, pagestr);
M_DrawString(LoadDef.x+M_LOADSAVE_WIDTH/2-M_StringWidth(pagestr)/2, y, CR_GOLD, pagestr);
}
//
@ -858,6 +927,8 @@ void M_DrawLoad(void)
M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
}
M_DrawBorderedSnapshot(itemOn);
M_DrawSaveLoadBottomLine();
if (delete_verify)
@ -983,7 +1054,7 @@ menu_t SaveDef =
&MainDef,
SaveMenu,
M_DrawSave,
80,34, //jff 3/15/98 move menu up
M_X_LOADSAVE,34, //jff 3/15/98 move menu up
0
};
@ -995,14 +1066,25 @@ void M_ReadSaveStrings(void)
{
int i;
// [FG] shift savegame descriptions a bit to the right
// to make room for the snapshots on the left
SaveDef.x = LoadDef.x = M_X_LOADSAVE + MIN(M_LOADSAVE_WIDTH/2, WIDESCREENDELTA);
// [FG] fit the snapshots into the resulting space
snapshot_width = MIN((WIDESCREENDELTA + SaveDef.x + 2 * SKULLXOFF) & ~7, ORIGWIDTH/2); // [FG] multiple of 8
snapshot_height = MIN((snapshot_width * ORIGHEIGHT / ORIGWIDTH) & ~7, ORIGHEIGHT/2);
for (i = 0 ; i < load_end ; i++)
{
FILE *fp; // killough 11/98: change to use stdio
char *name = G_SaveGameName(i); // killough 3/22/98
fp = M_fopen(name,"rb");
M_ReadSavegameTime(i, name);
if (name) free(name);
M_ResetSnapshot(i);
if (!fp)
{ // Ty 03/27/98 - externalized:
name = G_MBFSaveGameName(i);
@ -1022,6 +1104,10 @@ void M_ReadSaveStrings(void)
LoadMenu[i].status = 0;
continue;
}
if (!M_ReadSnapshot(i, fp))
M_ResetSnapshot(i);
fclose(fp);
LoadMenu[i].status = 1;
}
@ -1048,6 +1134,8 @@ void M_DrawSave(void)
M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*saveSlot,"_");
}
M_DrawBorderedSnapshot(itemOn);
M_DrawSaveLoadBottomLine();
if (delete_verify)

180
src/m_snapshot.c Normal file
View File

@ -0,0 +1,180 @@
//
// Copyright (C) 2022 Fabian Greffrath
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
// DESCRIPTION:
// Savegame snapshots
//
#include <sys/stat.h>
#include <time.h>
#include "doomtype.h"
#include "i_video.h"
#include "m_io.h"
#include "v_video.h"
static const char snapshot_str[] = "WOOF_SNAPSHOT";
static const int snapshot_len = arrlen(snapshot_str);
static const int snapshot_size = ORIGWIDTH * ORIGHEIGHT;
static byte *snapshots[10];
static byte *current_snapshot;
static char savegametimes[10][32];
const int M_SnapshotDataSize (void)
{
return snapshot_len + snapshot_size;
}
void M_ResetSnapshot (int i)
{
if (snapshots[i])
{
free(snapshots[i]);
snapshots[i] = NULL;
}
}
// [FG] try to read snapshot data from the end of a savegame file
boolean M_ReadSnapshot (int i, FILE *fp)
{
char str[16] = {0};
M_ResetSnapshot (i);
if (fseek(fp, -M_SnapshotDataSize(), SEEK_END) != 0)
return false;
if (fread(str, 1, snapshot_len, fp) != snapshot_len)
return false;
if (strncasecmp(str, snapshot_str, snapshot_len) != 0)
return false;
if ((snapshots[i] = malloc(snapshot_size * sizeof(**snapshots))) == NULL)
return false;
if (fread(snapshots[i], 1, snapshot_size, fp) != snapshot_size)
return false;
return true;
}
void M_ReadSavegameTime (int i, char *name)
{
struct stat st;
if (M_stat(name, &st) == -1)
savegametimes[i][0] = '\0';
else
// [FG] suppress the most useless compiler warning ever
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-y2k"
#endif
strftime(savegametimes[i], sizeof(savegametimes[i]), "%x %X", localtime(&st.st_mtime));
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}
char *M_GetSavegameTime (int i)
{
return savegametimes[i];
}
// [FG] take a snapshot in ORIGWIDTH*ORIGHEIGHT resolution, i.e.
// in hires mode only only each second pixel in each second row is saved,
// in widescreen mode only the non-widescreen part in the middle is saved
static void M_TakeSnapshot (void)
{
const int inc = hires ? 2 : 1;
int x, y;
byte *p;
const byte *s = screens[0];
if (!current_snapshot)
{
current_snapshot = malloc(snapshot_size * sizeof(**snapshots));
}
p = current_snapshot;
for (y = 0; y < (SCREENHEIGHT << hires); y += inc)
{
for (x = 0; x < (NONWIDEWIDTH << hires); x += inc)
{
*p++ = s[y * (SCREENWIDTH << hires) + (WIDESCREENDELTA << hires) + x];
}
}
}
void M_WriteSnapshot (byte *p)
{
M_TakeSnapshot();
memcpy(p, snapshot_str, snapshot_len);
p += snapshot_len;
memcpy(p, current_snapshot, snapshot_size);
p += snapshot_size;
}
// [FG] draw snapshot for the n'th savegame, if no snapshot is found
// fill the area with palette index 0 (i.e. mostly black)
boolean M_DrawSnapshot (int n, int x, int y, int w, int h)
{
byte *dest = screens[0] + y * (SCREENWIDTH << (2 * hires)) + (x << hires);
if (!snapshots[n])
{
int desty;
for (desty = 0; desty < (h << hires); desty++)
{
memset(dest, 0, w << hires);
dest += SCREENWIDTH << hires;
}
return false;
}
else
{
const fixed_t step_x = (ORIGWIDTH << FRACBITS) / (w << hires);
const fixed_t step_y = (ORIGHEIGHT << FRACBITS) / (h << hires);
int destx, desty;
fixed_t srcx, srcy;
byte *destline, *srcline;
for (desty = 0, srcy = 0; desty < (h << hires); desty++, srcy += step_y)
{
destline = dest + desty * (SCREENWIDTH << hires);
srcline = snapshots[n] + (srcy >> FRACBITS) * ORIGWIDTH;
for (destx = 0, srcx = 0; destx < (w << hires); destx++, srcx += step_x)
{
*destline++ = srcline[srcx >> FRACBITS];
}
}
}
return true;
}

35
src/m_snapshot.h Normal file
View File

@ -0,0 +1,35 @@
//
// Copyright (C) 2022 Fabian Greffrath
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
// DESCRIPTION:
// Savegame snapshots
//
#ifndef __M_SNAPSHOT__
#define __M_SNAPSHOT__
const int M_SnapshotDataSize (void);
void M_ResetSnapshot (int i);
boolean M_ReadSnapshot (int i, FILE *fp);
void M_WriteSnapshot (byte *p);
boolean M_DrawSnapshot (int i, int x, int y, int w, int h);
void M_ReadSavegameTime (int i, char *name);
char *M_GetSavegameTime (int i);
#endif