diff --git a/src/d_main.cpp b/src/d_main.cpp index 689603084..2dbf1273c 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -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); } diff --git a/src/farchive.cpp b/src/farchive.cpp index 3aaf1bd86..3d57e4f71 100644 --- a/src/farchive.cpp +++ b/src/farchive.cpp @@ -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) { diff --git a/src/farchive.h b/src/farchive.h index 484c1018b..ee0310898 100644 --- a/src/farchive.h +++ b/src/farchive.h @@ -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 void UserReadClass(T *&info) { diff --git a/src/g_game.cpp b/src/g_game.cpp index 46bd3f7e4..7ed832e0a 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -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 @@ -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 &filenames, TArray &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 savegame_content; + TArray 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. diff --git a/src/g_level.cpp b/src/g_level.cpp index d917794b3..2acb1335c 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -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 &filenames, TArray &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); diff --git a/src/g_level.h b/src/g_level.h index 4d730b2f2..4e86978ce 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -295,7 +295,6 @@ struct level_info_t SBYTE WallVertLight, WallHorizLight; int musicorder; FCompressedBuffer Snapshot; - DWORD snapshotVer; TArray 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 &, TArray &); +void G_WriteVisited(FSerializer &arc); void G_ClearHubInfo(); enum ESkillProperty diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index e6b9c093b..8f0e2da13 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -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; diff --git a/src/menu/loadsavemenu.cpp b/src/menu/loadsavemenu.cpp index 4ccd21db6..6333ab3bf 100644 --- a/src/menu/loadsavemenu.cpp +++ b/src/menu/loadsavemenu.cpp @@ -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) { diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 3b9b33c3f..1b9f1a057 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -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()) diff --git a/src/resourcefiles/file_zip.cpp b/src/resourcefiles/file_zip.cpp index 244577631..e2574d045 100644 --- a/src/resourcefiles/file_zip.cpp +++ b/src/resourcefiles/file_zip.cpp @@ -33,6 +33,7 @@ ** */ +#include #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 &filenames, TArray &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 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; +} diff --git a/src/resourcefiles/file_zip.h b/src/resourcefiles/file_zip.h index dc10d558d..e802b41e8 100644 --- a/src/resourcefiles/file_zip.h +++ b/src/resourcefiles/file_zip.h @@ -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(); diff --git a/src/serializer.cpp b/src/serializer.cpp index 884475413..ea18a3a58 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -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: diff --git a/src/serializer.h b/src/serializer.h index 4d910aec6..312d53393 100644 --- a/src/serializer.h +++ b/src/serializer.h @@ -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(); diff --git a/src/statistics.cpp b/src/statistics.cpp index 536d60a44..92a459e15 100644 --- a/src/statistics.cpp +++ b/src/statistics.cpp @@ -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) diff --git a/src/version.h b/src/version.h index f20bdfa5b..439a21a83 100644 --- a/src/version.h +++ b/src/version.h @@ -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"