- implemented saving of new format savegames as zips. The code for that was adapted from zipdir.c.

This commit is contained in:
Christoph Oelckers 2016-09-21 17:37:56 +02:00
parent 810ef8f775
commit ac3c00883d
15 changed files with 253 additions and 104 deletions

View file

@ -2580,7 +2580,7 @@ void D_DoomMain (void)
{
FString file(v);
FixPathSeperator (file);
DefaultExtension (file, ".zds" SAVEGAME_EXT);
DefaultExtension (file, "." SAVEGAME_EXT);
G_LoadGame (file);
}

View file

@ -962,32 +962,6 @@ PClass *FArchive::ReadStoredClass (const PClass *wanttype)
return type;
}
void FArchive::UserWriteClass (PClass *type)
{
BYTE id;
if (type == NULL)
{
id = 2;
Write (&id, 1);
}
else
{
DWORD *arcid;
if (NULL == (arcid = ClassToArchive.CheckKey(type)))
{
id = 1;
Write (&id, 1);
WriteClass (type);
}
else
{
id = 0;
Write (&id, 1);
WriteCount (*arcid);
}
}
}
void FArchive::UserReadClass (PClass *&type)
{

View file

@ -152,7 +152,6 @@ virtual void Read (void *mem, unsigned int len);
void WriteCount (DWORD count);
DWORD ReadCount ();
void UserWriteClass (PClass *info);
void UserReadClass (PClass *&info);
template<typename T> void UserReadClass(T *&info)
{

View file

@ -85,6 +85,8 @@
#include "p_spec.h"
#include "r_data/colormaps.h"
#include "serializer.h"
#include "w_zip.h"
#include "resourcefiles/resourcefile.h"
#include <zlib.h>
@ -112,6 +114,7 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio
void G_DoAutoSave ();
void STAT_Serialize(FSerializer &file);
bool WriteZip(const char *filename, TArray<FString> &filenames, TArray<FCompressedBuffer> &content);
FIntCVar gameskill ("skill", 2, CVAR_SERVERINFO|CVAR_LATCH);
CVAR (Int, deathmatch, 0, CVAR_SERVERINFO|CVAR_LATCH);
@ -1886,7 +1889,7 @@ void G_DoLoadGame ()
SaveVersion = 0;
if (!M_GetPNGText (png, "ZDoom Save Version", sigcheck, 20) ||
0 != strncmp (sigcheck, SAVESIG, 9) || // ZDOOMSAVE is the first 9 chars
0 != strncmp (sigcheck, "SAVEVER", 9) || // ZDOOMSAVE is the first 9 chars
(SaveVersion = atoi (sigcheck+9)) < MINSAVEVER)
{
delete png;
@ -2045,7 +2048,7 @@ FString G_BuildSaveName (const char *prefix, int slot)
name << prefix;
if (slot >= 0)
{
name.AppendFormat("%d.zds" SAVEGAME_EXT, slot);
name.AppendFormat("%d." SAVEGAME_EXT, slot);
}
return name;
}
@ -2166,6 +2169,7 @@ static void PutSavePic (FileWriter *file, int width, int height)
void G_DoSaveGame (bool okForQuicksave, FString filename, const char *description)
{
TArray<FCompressedBuffer> savegame_content;
TArray<FString> savegame_filenames;
char buf[100];
@ -2203,9 +2207,10 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio
M_AppendPNGText(&savepic, "Current Map", level.MapName);
M_FinishPNG(&savepic);
int ver = SAVEVER;
savegameinfo.AddString("Software", buf)
.AddString("Engine", GAMESIG)
.AddString("Save Version", SAVESIG)
("Save Version", ver)
.AddString("Title", description)
.AddString("Current Map", level.MapName);
@ -2232,17 +2237,36 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio
FRandom::StaticWriteRNGState(savegameglobals);
P_WriteACSDefereds(savegameglobals);
P_WriteACSVars(savegameglobals);
G_WriteVisited(savegameglobals);
if (NextSkill != -1)
{
savegameglobals("nextskill", NextSkill);
}
//G_WriteSnapshots (stdfile);
auto picdata = savepic.GetBuffer();
FCompressedBuffer bufpng = { picdata->Size(), picdata->Size(), METHOD_STORED, 0, crc32(0, &(*picdata)[0], picdata->Size()), (char*)&(*picdata)[0] };
savegame_content.Push(bufpng);
savegame_filenames.Push("savepic.png");
savegame_content.Push(savegameinfo.GetCompressedOutput());
savegame_filenames.Push("info.json");
savegame_content.Push(savegameglobals.GetCompressedOutput());
savegame_filenames.Push("globals.json");
G_WriteSnapshots (savegame_filenames, savegame_content);
WriteZip(filename, savegame_filenames, savegame_content);
M_NotifyNewSave (filename.GetChars(), description, okForQuicksave);
// delete the JSON buffers we created just above. Everything else will
// either still be needed or taken care of automatically.
delete[] savegame_content[1].mBuffer;
delete[] savegame_content[2].mBuffer;
// Check whether the file is ok. (todo when new format is ready)
bool success = true;
if (success)
@ -2252,6 +2276,8 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio
}
else Printf(PRINT_HIGH, "Save failed\n");
FResourceFile *test = FResourceFile::OpenResourceFile(filename, nullptr);
BackupSaveName = filename;
// We don't need the snapshot any longer.

View file

@ -1487,8 +1487,6 @@ void G_SnapshotLevel ()
if (level.info->isValid())
{
level.info->snapshotVer = SAVEVER;
FSerializer arc;
if (arc.OpenWriter())
@ -1514,7 +1512,6 @@ void G_UnSnapshotLevel (bool hubLoad)
if (level.info->isValid())
{
SaveVersion = level.info->snapshotVer;
FSerializer arc;
if (!arc.OpenReader(&level.info->Snapshot)) return;
@ -1563,10 +1560,28 @@ void G_UnSnapshotLevel (bool hubLoad)
//
//==========================================================================
static void writeSnapShot (FArchive &arc, level_info_t *i)
void G_WriteSnapshots(TArray<FString> &filenames, TArray<FCompressedBuffer> &buffers)
{
arc << i->snapshotVer << i->MapName;
//i->snapshot->Serialize (arc);
unsigned int i;
FString filename;
for (i = 0; i < wadlevelinfos.Size(); i++)
{
if (wadlevelinfos[i].Snapshot.mCompressedSize > 0)
{
filename << wadlevelinfos[i].MapName << ".json";
filename.ToLower();
filenames.Push(filename);
buffers.Push(wadlevelinfos[i].Snapshot);
}
}
if (TheDefaultLevelInfo.Snapshot.mCompressedSize > 0)
{
filename << TheDefaultLevelInfo.MapName << ".json";
filename.ToLower();
filenames.Push(filename);
buffers.Push(TheDefaultLevelInfo.Snapshot);
}
}
//==========================================================================
@ -1574,72 +1589,39 @@ static void writeSnapShot (FArchive &arc, level_info_t *i)
//
//==========================================================================
void G_WriteSnapshots (FILE *file)
void G_WriteVisited(FSerializer &arc)
{
unsigned int i;
#if 0
for (i = 0; i < wadlevelinfos.Size(); i++)
if (arc.BeginArray("visited"))
{
if (wadlevelinfos[i].snapshot)
// Write out which levels have been visited
for (auto & wi : wadlevelinfos)
{
FPNGChunkArchive arc (file, SNAP_ID);
writeSnapShot (arc, (level_info_t *)&wadlevelinfos[i]);
}
}
if (TheDefaultLevelInfo.snapshot != NULL)
{
FPNGChunkArchive arc (file, DSNP_ID);
writeSnapShot(arc, &TheDefaultLevelInfo);
}
#endif
FPNGChunkArchive *arc = NULL;
// Write out which levels have been visited
for (i = 0; i < wadlevelinfos.Size(); ++i)
{
if (wadlevelinfos[i].flags & LEVEL_VISITED)
{
if (arc == NULL)
if (wi.flags & LEVEL_VISITED)
{
arc = new FPNGChunkArchive (file, VIST_ID);
arc.AddString(nullptr, wi.MapName);
}
(*arc) << wadlevelinfos[i].MapName;
}
}
if (arc != NULL)
{
FString empty = "";
(*arc) << empty;
delete arc;
arc.EndArray();
}
// Store player classes to be used when spawning a random class
if (multiplayer)
{
FPNGChunkArchive arc2 (file, RCLS_ID);
for (i = 0; i < MAXPLAYERS; ++i)
{
SBYTE cnum = SinglePlayerClass[i];
arc2 << cnum;
}
arc.Array("randomclasses", SinglePlayerClass, MAXPLAYERS);
}
// Store player classes that are currently in use
FPNGChunkArchive arc3 (file, PCLS_ID);
for (i = 0; i < MAXPLAYERS; ++i)
if (arc.BeginObject("playerclasses"))
{
BYTE pnum;
if (playeringame[i])
for (int i = 0; i < MAXPLAYERS; ++i)
{
pnum = i;
arc3 << pnum;
arc3.UserWriteClass (players[i].cls);
if (playeringame[i])
{
FString key;
key.Format("%d", i);
arc(key, players[i].cls);
}
}
pnum = 255;
arc3 << pnum;
arc.EndObject();
}
}
@ -1665,7 +1647,6 @@ void G_ReadSnapshots (PNGHandle *png)
arc << snapver;
arc << MapName;
i = FindLevelInfo (MapName);
i->snapshotVer = snapver;
#if 0
i->snapshot = new FCompressedMemFile;
i->snapshot->Serialize (arc);
@ -1681,7 +1662,6 @@ void G_ReadSnapshots (PNGHandle *png)
arc << snapver;
arc << MapName;
TheDefaultLevelInfo.snapshotVer = snapver;
#if 0
TheDefaultLevelInfo.snapshot = new FCompressedMemFile;
TheDefaultLevelInfo.snapshot->Serialize (arc);

View file

@ -295,7 +295,6 @@ struct level_info_t
SBYTE WallVertLight, WallHorizLight;
int musicorder;
FCompressedBuffer Snapshot;
DWORD snapshotVer;
TArray<acsdefered_t> deferred;
float skyspeed1;
float skyspeed2;
@ -535,7 +534,8 @@ void G_SnapshotLevel (void);
void G_UnSnapshotLevel (bool keepPlayers);
struct PNGHandle;
void G_ReadSnapshots (PNGHandle *png);
void G_WriteSnapshots (FILE *file);
void G_WriteSnapshots (TArray<FString> &, TArray<FCompressedBuffer> &);
void G_WriteVisited(FSerializer &arc);
void G_ClearHubInfo();
enum ESkillProperty

View file

@ -248,8 +248,7 @@ void level_info_t::Reset()
WallVertLight = +8;
F1Pic = "";
musicorder = 0;
Snapshot = { 0,0,0,0,nullptr };
snapshotVer = 0;
Snapshot = { 0,0,0,0,0,nullptr };
deferred.Clear();
skyspeed1 = skyspeed2 = 0.f;
fadeto = 0;

View file

@ -241,7 +241,7 @@ void DLoadSaveMenu::ReadSaveStrings ()
title[SAVESTRINGSIZE] = 0;
if (NULL != (png = M_VerifyPNG (file)))
if (false)//NULL != (png = M_VerifyPNG (file)))
{
char *ver = M_GetPNGText (png, "ZDoom Save Version");
char *engine = M_GetPNGText (png, "Engine");
@ -251,7 +251,7 @@ void DLoadSaveMenu::ReadSaveStrings ()
{
strncpy (title, I_FindName(&c_file), SAVESTRINGSIZE);
}
if (strncmp (ver, SAVESIG, 9) == 0 &&
if (strncmp (ver, "SAVESIG", 9) == 0 &&
atoi (ver+9) >= MINSAVEVER &&
engine != NULL)
{

View file

@ -898,6 +898,7 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
I_Error("Savegame is from a different level");
}
}
arc("saveversion", SaveVersion);
Renderer->StartSerialize(arc);
if (arc.isReading())

View file

@ -33,6 +33,7 @@
**
*/
#include <time.h>
#include "file_zip.h"
#include "cmdlib.h"
#include "templates.h"
@ -265,6 +266,7 @@ bool FZipFile::Open(bool quiet)
lump_p->Flags = LUMPF_ZIPFILE | LUMPFZIP_NEEDFILESTART;
lump_p->Method = BYTE(zip_fh->Method);
lump_p->GPFlags = zip_fh->Flags;
lump_p->CRC32 = zip_fh->CRC32;
lump_p->CompressedSize = LittleLong(zip_fh->CompressedSize);
lump_p->Position = LittleLong(zip_fh->LocalHeaderOffset);
lump_p->CheckEmbedded();
@ -308,10 +310,11 @@ FCompressedBuffer FZipFile::GetRawLump(int lumpnum)
{
if ((unsigned)lumpnum >= NumLumps)
{
return{ 0,0,0,0,nullptr };
return{ 0,0,0,0,0,nullptr };
}
FZipLump *lmp = &Lumps[lumpnum];
FCompressedBuffer cbuf = { (unsigned)lmp->LumpSize, (unsigned)lmp->CompressedSize, lmp->Method, lmp->GPFlags, new char[lmp->CompressedSize] };
FCompressedBuffer cbuf = { (unsigned)lmp->LumpSize, (unsigned)lmp->CompressedSize, lmp->Method, lmp->GPFlags, lmp->CRC32, new char[lmp->CompressedSize] };
Reader->Seek(lmp->Position, SEEK_SET);
Reader->Read(cbuf.mBuffer, lmp->CompressedSize);
return cbuf;
@ -426,3 +429,169 @@ FResourceFile *CheckZip(const char *filename, FileReader *file, bool quiet)
//==========================================================================
//
// time_to_dos
//
// Converts time from struct tm to the DOS format used by zip files.
//
//==========================================================================
static void time_to_dos(struct tm *time, unsigned short *dosdate, unsigned short *dostime)
{
if (time == NULL || time->tm_year < 80)
{
*dosdate = *dostime = 0;
}
else
{
*dosdate = LittleShort((time->tm_year - 80) * 512 + (time->tm_mon + 1) * 32 + time->tm_mday);
*dostime = LittleShort(time->tm_hour * 2048 + time->tm_min * 32 + time->tm_sec / 2);
}
}
//==========================================================================
//
// append_to_zip
//
// Write a given file to the zipFile.
//
// zipfile: zip object to be written to
//
// returns: position = success, -1 = error
//
//==========================================================================
int AppendToZip(FILE *zip_file, const char *filename, FCompressedBuffer &content, uint16_t date, uint16_t time)
{
FZipLocalFileHeader local;
int position;
local.Magic = ZIP_LOCALFILE;
local.VersionToExtract[0] = 20;
local.VersionToExtract[1] = 0;
local.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort(2) : LittleShort((uint16_t)content.mZipFlags);
local.Method = LittleShort(content.mMethod);
local.ModDate = date;
local.ModTime = time;
local.CRC32 = content.mCRC32;
local.UncompressedSize = LittleLong(content.mSize);
local.CompressedSize = LittleLong(content.mCompressedSize);
local.NameLength = LittleShort((unsigned short)strlen(filename));
local.ExtraLength = 0;
// Fill in local directory header.
position = (int)ftell(zip_file);
// Write out the header, file name, and file data.
if (fwrite(&local, sizeof(local), 1, zip_file) != 1 ||
fwrite(filename, strlen(filename), 1, zip_file) != 1 ||
fwrite(content.mBuffer, 1, content.mCompressedSize, zip_file) != content.mCompressedSize)
{
return -1;
}
return position;
}
//==========================================================================
//
// write_central_dir
//
// Writes the central directory entry for a file.
//
//==========================================================================
int AppendCentralDirectory(FILE *zip_file, const char *filename, FCompressedBuffer &content, uint16_t date, uint16_t time, int position)
{
FZipCentralDirectoryInfo dir;
dir.Magic = ZIP_CENTRALFILE;
dir.VersionMadeBy[0] = 20;
dir.VersionMadeBy[1] = 0;
dir.VersionToExtract[0] = 20;
dir.VersionToExtract[1] = 0;
dir.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort(2) : LittleShort((uint16_t)content.mZipFlags);
dir.Method = LittleShort(content.mMethod);
dir.ModTime = time;
dir.ModDate = date;
dir.CRC32 = content.mCRC32;
dir.CompressedSize = LittleLong(content.mCompressedSize);
dir.UncompressedSize = LittleLong(content.mSize);
dir.NameLength = LittleShort((unsigned short)strlen(filename));
dir.ExtraLength = 0;
dir.CommentLength = 0;
dir.StartingDiskNumber = 0;
dir.InternalAttributes = 0;
dir.ExternalAttributes = 0;
dir.LocalHeaderOffset = LittleLong(position);
if (fwrite(&dir, sizeof(dir), 1, zip_file) != 1 ||
fwrite(filename, strlen(filename), 1, zip_file) != 1)
{
return -1;
}
return 0;
}
bool WriteZip(const char *filename, TArray<FString> &filenames, TArray<FCompressedBuffer> &content)
{
// try to determine local time
struct tm *ltime;
time_t ttime;
uint16_t mydate, mytime;
ttime = time(nullptr);
ltime = localtime(&ttime);
time_to_dos(ltime, &mydate, &mytime);
TArray<int> positions;
if (filenames.Size() != content.Size()) return false;
FILE *f = fopen(filename, "wb");
if (f != nullptr)
{
for (unsigned i = 0; i < filenames.Size(); i++)
{
int pos = AppendToZip(f, filenames[i], content[i], mydate, mytime);
if (pos == -1)
{
fclose(f);
remove(filename);
return false;
}
positions.Push(pos);
}
int dirofs = (int)ftell(f);
for (unsigned i = 0; i < filenames.Size(); i++)
{
if (AppendCentralDirectory(f, filenames[i], content[i], mydate, mytime, positions[i]) < 0)
{
fclose(f);
remove(filename);
return false;
}
}
// Write the directory terminator.
FZipEndOfCentralDirectory dirend;
dirend.Magic = ZIP_ENDOFDIR;
dirend.DiskNumber = 0;
dirend.FirstDisk = 0;
dirend.NumEntriesOnAllDisks = dirend.NumEntries = LittleShort(filenames.Size());
dirend.DirectoryOffset = dirofs;
dirend.DirectorySize = LittleLong(ftell(f) - dirofs);
dirend.ZipCommentLength = 0;
if (fwrite(&dirend, sizeof(dirend), 1, f) != 1)
{
fclose(f);
remove(filename);
return false;
}
fclose(f);
return true;
}
return false;
}

View file

@ -10,6 +10,7 @@ struct FCompressedBuffer
unsigned mCompressedSize;
int mMethod;
int mZipFlags;
unsigned mCRC32;
char *mBuffer;
bool Decompress(char *destbuffer);
@ -41,6 +42,7 @@ struct FZipLump : public FResourceLump
BYTE Method;
int CompressedSize;
int Position;
unsigned CRC32;
virtual FileReader *GetReader();
virtual int FillCache();

View file

@ -690,11 +690,12 @@ const char *FSerializer::GetOutput(unsigned *len)
FCompressedBuffer FSerializer::GetCompressedOutput()
{
if (isReading()) return{ 0,0,0,0,nullptr };
if (isReading()) return{ 0,0,0,0,0,nullptr };
FCompressedBuffer buff;
EndObject();
buff.mSize = (unsigned)w->mOutString.GetSize();
buff.mZipFlags = 0;
buff.mCRC32 = crc32(0, (const Bytef*)w->mOutString.GetString(), buff.mSize);
uint8_t *compressbuf = new uint8_t[buff.mSize+1];
@ -731,6 +732,7 @@ FCompressedBuffer FSerializer::GetCompressedOutput()
buff.mMethod = METHOD_DEFLATE;
memcpy(buff.mBuffer, compressbuf, buff.mCompressedSize);
delete[] compressbuf;
return buff;
}
error:

View file

@ -58,7 +58,7 @@ public:
{
Close();
}
bool OpenWriter();
bool OpenWriter(bool randomaccess = true);
bool OpenReader(const char *buffer, size_t length);
bool OpenReader(FCompressedBuffer *input);
void Close();

View file

@ -527,6 +527,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, OneLevel &l, OneLevel
("levelname", l.Levelname)
.EndObject();
}
return arc;
}
void STAT_Serialize(FSerializer &arc)

View file

@ -81,10 +81,6 @@ const char *GetVersionString();
// SVN revision ever got.
#define SAVEVER 4550
#define SAVEVERSTRINGIFY2(x) #x
#define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x)
#define SAVESIG "ZDOOMSAVE" SAVEVERSTRINGIFY(SAVEVER)
// This is so that derivates can use the same savegame versions without worrying about engine compatibility
#define GAMESIG "ZDOOM"
#define BASEWAD "zdoom.pk3"