Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
SavedGame.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "SavedGame.h"
21 
22 #include "gui/GUIManager.h"
25 #include "ps/CLogger.h"
26 #include "ps/Filesystem.h"
29 
30 static const int SAVED_GAME_VERSION_MAJOR = 1; // increment on incompatible changes to the format
31 static const int SAVED_GAME_VERSION_MINOR = 0; // increment on compatible changes to the format
32 // TODO: we ought to check version numbers when loading files
33 
34 
35 Status SavedGames::SavePrefix(const std::wstring& prefix, const std::wstring& description, CSimulation2& simulation, CGUIManager* gui, int playerID)
36 {
37  // Determine the filename to save under
38  const VfsPath basenameFormat(L"saves/" + prefix + L"-%04d");
39  const VfsPath filenameFormat = basenameFormat.ChangeExtension(L".0adsave");
40  VfsPath filename;
41 
42  // Don't make this a static global like NextNumberedFilename expects, because
43  // that wouldn't work when 'prefix' changes, and because it's not thread-safe
44  size_t nextSaveNumber = 0;
45  vfs::NextNumberedFilename(g_VFS, filenameFormat, nextSaveNumber, filename);
46 
47  return Save(filename.Filename().string(), description, simulation, gui, playerID);
48 }
49 
50 Status SavedGames::Save(const std::wstring& name, const std::wstring& description, CSimulation2& simulation, CGUIManager* gui, int playerID)
51 {
52  // Determine the filename to save under
53  const VfsPath basenameFormat(L"saves/" + name);
54  const VfsPath filename = basenameFormat.ChangeExtension(L".0adsave");
55 
56  // ArchiveWriter_Zip can only write to OsPaths, not VfsPaths,
57  // but we'd like to handle saved games via VFS.
58  // To avoid potential confusion from writing with non-VFS then
59  // reading the same file with VFS, we'll just write to a temporary
60  // non-VFS path and then load and save again via VFS,
61  // which is kind of a hack.
62 
63  OsPath tempSaveFileRealPath;
64  WARN_RETURN_STATUS_IF_ERR(g_VFS->GetDirectoryRealPath("cache/", tempSaveFileRealPath));
65  tempSaveFileRealPath = tempSaveFileRealPath / "temp.0adsave";
66 
67  time_t now = time(NULL);
68 
69  // Construct the serialized state to be saved
70 
71  std::stringstream simStateStream;
72  if (!simulation.SerializeState(simStateStream))
74 
75  CScriptValRooted metadata;
76  simulation.GetScriptInterface().Eval("({})", metadata);
77  simulation.GetScriptInterface().SetProperty(metadata.get(), "version_major", SAVED_GAME_VERSION_MAJOR);
78  simulation.GetScriptInterface().SetProperty(metadata.get(), "version_minor", SAVED_GAME_VERSION_MINOR);
79  simulation.GetScriptInterface().SetProperty(metadata.get(), "time", (double)now);
80  simulation.GetScriptInterface().SetProperty(metadata.get(), "player", playerID);
81  simulation.GetScriptInterface().SetProperty(metadata.get(), "initAttributes", simulation.GetInitAttributes());
82  if (gui)
83  {
85  simulation.GetScriptInterface().SetProperty(metadata.get(), "gui", guiMetadata);
86  }
87  simulation.GetScriptInterface().SetProperty(metadata.get(), "description", description);
88 
89  std::string metadataString = simulation.GetScriptInterface().StringifyJSON(metadata.get(), true);
90 
91  // Write the saved game as zip file containing the various components
92  PIArchiveWriter archiveWriter = CreateArchiveWriter_Zip(tempSaveFileRealPath, false);
93  if (!archiveWriter)
95 
96  WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)metadataString.c_str(), metadataString.length(), now, "metadata.json"));
97  WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)simStateStream.str().c_str(), simStateStream.str().length(), now, "simulation.dat"));
98  archiveWriter.reset(); // close the file
99 
100  WriteBuffer buffer;
101  CFileInfo tempSaveFile;
102  WARN_RETURN_STATUS_IF_ERR(GetFileInfo(tempSaveFileRealPath, &tempSaveFile));
103  buffer.Reserve(tempSaveFile.Size());
104  WARN_RETURN_STATUS_IF_ERR(io::Load(tempSaveFileRealPath, buffer.Data().get(), buffer.Size()));
105  WARN_RETURN_STATUS_IF_ERR(g_VFS->CreateFile(filename, buffer.Data(), buffer.Size()));
106 
107  OsPath realPath;
108  WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));
109  LOGMESSAGERENDER(L"Saved game to %ls\n", realPath.string().c_str());
110 
111  return INFO::OK;
112 }
113 
114 /**
115  * Helper class for retrieving data from saved game archives
116  */
118 {
120 public:
121  CGameLoader(ScriptInterface& scriptInterface, CScriptValRooted* metadata, std::string* savedState) :
122  m_ScriptInterface(scriptInterface), m_Metadata(metadata), m_SavedState(savedState)
123  {
124  }
125 
126  static void ReadEntryCallback(const VfsPath& pathname, const CFileInfo& fileInfo, PIArchiveFile archiveFile, uintptr_t cbData)
127  {
128  ((CGameLoader*)cbData)->ReadEntry(pathname, fileInfo, archiveFile);
129  }
130 
131  void ReadEntry(const VfsPath& pathname, const CFileInfo& fileInfo, PIArchiveFile archiveFile)
132  {
133  if (pathname == L"metadata.json" && m_Metadata)
134  {
135  std::string buffer;
136  buffer.resize(fileInfo.Size());
137  WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)buffer.data()), buffer.size()));
139  }
140  else if (pathname == L"simulation.dat" && m_SavedState)
141  {
142  m_SavedState->resize(fileInfo.Size());
143  WARN_IF_ERR(archiveFile->Load("", DummySharedPtr((u8*)m_SavedState->data()), m_SavedState->size()));
144  }
145  }
146 
149  std::string* m_SavedState;
150 };
151 
152 Status SavedGames::Load(const std::wstring& name, ScriptInterface& scriptInterface, CScriptValRooted& metadata, std::string& savedState)
153 {
154  // Determine the filename to load
155  const VfsPath basename(L"saves/" + name);
156  const VfsPath filename = basename.ChangeExtension(L".0adsave");
157 
158  // Don't crash just because file isn't found, this can happen if the file is deleted from the OS
159  if (!VfsFileExists(filename))
160  return ERR::FILE_NOT_FOUND;
161 
162  OsPath realPath;
163  WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));
164 
165  PIArchiveReader archiveReader = CreateArchiveReader_Zip(realPath);
166  if (!archiveReader)
168 
169  CGameLoader loader(scriptInterface, &metadata, &savedState);
170  WARN_RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader));
171 
172  return INFO::OK;
173 }
174 
175 std::vector<CScriptValRooted> SavedGames::GetSavedGames(ScriptInterface& scriptInterface)
176 {
177  TIMER(L"GetSavedGames");
178 
179  std::vector<CScriptValRooted> games;
180 
181  Status err;
182 
183  VfsPaths pathnames;
184  err = vfs::GetPathnames(g_VFS, "saves/", L"*.0adsave", pathnames);
185  WARN_IF_ERR(err);
186 
187  for (size_t i = 0; i < pathnames.size(); ++i)
188  {
189  OsPath realPath;
190  err = g_VFS->GetRealPath(pathnames[i], realPath);
191  if (err < 0)
192  {
193  DEBUG_WARN_ERR(err);
194  continue; // skip this file
195  }
196 
197  PIArchiveReader archiveReader = CreateArchiveReader_Zip(realPath);
198  if (!archiveReader)
199  {
200  // Triggered by e.g. the file being open in another program
201  LOGWARNING(L"Failed to read saved game '%ls'", realPath.string().c_str());
202  continue; // skip this file
203  }
204 
205  CScriptValRooted metadata;
206  CGameLoader loader(scriptInterface, &metadata, NULL);
207  err = archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader);
208  if (err < 0)
209  {
210  DEBUG_WARN_ERR(err);
211  continue; // skip this file
212  }
213 
214  CScriptValRooted game;
215  scriptInterface.Eval("({})", game);
216  scriptInterface.SetProperty(game.get(), "id", pathnames[i].Basename());
217  scriptInterface.SetProperty(game.get(), "metadata", metadata);
218  games.push_back(game);
219  }
220 
221  return games;
222 }
223 
224 bool SavedGames::DeleteSavedGame(const std::wstring& name)
225 {
226  const VfsPath basename(L"saves/" + name);
227  const VfsPath filename = basename.ChangeExtension(L".0adsave");
228  OsPath realpath;
229 
230  // Make sure it exists in VFS and find its real path
231  if (!VfsFileExists(filename) || g_VFS->GetRealPath(filename, realpath) != INFO::OK)
232  return false; // Error
233 
234  // Remove from VFS
235  if (g_VFS->RemoveFile(filename) != INFO::OK)
236  return false; // Error
237 
238  // Delete actual file
239  if (wunlink(realpath) != 0)
240  return false; // Error
241 
242  // Successfully deleted file
243  return true;
244 }
PIArchiveWriter CreateArchiveWriter_Zip(const OsPath &archivePathname, bool noDeflate)
NONCOPYABLE(CGameLoader)
#define u8
Definition: types.h:39
std::vector< CScriptValRooted > GetSavedGames(ScriptInterface &scriptInterface)
Get list of saved games for GUI script usage.
Definition: SavedGame.cpp:175
CScriptValRooted * m_Metadata
Definition: SavedGame.cpp:148
#define WARN_IF_ERR(expression)
Definition: status.h:265
size_t Size() const
Definition: write_buffer.h:42
bool DeleteSavedGame(const std::wstring &name)
Permanently deletes the saved game archive with the given name.
Definition: SavedGame.cpp:224
Path Filename() const
Definition: path.h:158
const Status OK
Definition: status.h:386
void NextNumberedFilename(const PIVFS &fs, const VfsPath &pathnameFormat, size_t &nextNumber, VfsPath &nextPathname)
Determine the next available pathname with a given format.
Definition: vfs_util.cpp:95
Status Save(const std::wstring &name, const std::wstring &description, CSimulation2 &simulation, CGUIManager *gui, int playerID)
Create new saved game archive with given name and simulation data.
Definition: SavedGame.cpp:50
void Reserve(size_t size)
static CStr prefix
Definition: DllLoader.cpp:47
const jsval & get() const
Returns the current value.
Definition: ScriptVal.h:38
A trivial wrapper around a jsval.
Definition: ScriptVal.h:29
static void ReadEntryCallback(const VfsPath &pathname, const CFileInfo &fileInfo, PIArchiveFile archiveFile, uintptr_t cbData)
Definition: SavedGame.cpp:126
PIArchiveReader CreateArchiveReader_Zip(const OsPath &archivePathname)
static const int SAVED_GAME_VERSION_MAJOR
Definition: SavedGame.cpp:30
std::string StringifyJSON(jsval obj, bool indent=true)
Stringify to a JSON string, UTF-8 encoded.
shared_ptr< IArchiveWriter > PIArchiveWriter
Definition: archive.h:107
CScriptValRooted ParseJSON(const std::string &string_utf8)
Parse a UTF-8-encoded JSON string.
Public API for simulation system.
Definition: Simulation2.h:46
std::string * m_SavedState
Definition: SavedGame.cpp:149
Status SavePrefix(const std::wstring &prefix, const std::wstring &description, CSimulation2 &simulation, CGUIManager *gui, int playerID)
Create new saved game archive with given prefix and simulation data.
Definition: SavedGame.cpp:35
CScriptVal GetSavedGameData()
Calls the current page&#39;s script function getSavedGameData() and returns the result.
Definition: GUIManager.cpp:187
#define LOGWARNING
Definition: CLogger.h:34
void ReadEntry(const VfsPath &pathname, const CFileInfo &fileInfo, PIArchiveFile archiveFile)
Definition: SavedGame.cpp:131
#define WARN_RETURN_STATUS_IF_ERR(expression)
Definition: status.h:287
shared_ptr< IArchiveReader > PIArchiveReader
Definition: archive.h:62
Helper class for retrieving data from saved game archives.
Definition: SavedGame.cpp:117
LIB_API int wunlink(const OsPath &pathname)
Definition: path.h:75
shared_ptr< u8 > Data() const
Definition: write_buffer.h:37
const String & string() const
Definition: path.h:123
Contains functions for managing saved game archives.
off_t Size() const
Definition: file_system.h:58
ScriptInterface & GetScriptInterface() const
bool Eval(const char *code)
static Status Load(const OsPath &pathname, void *buf, size_t size, const Parameters &p=Parameters(), const CompletedHook &completedHook=CompletedHook(), const IssueHook &issueHook=IssueHook())
Definition: io.h:337
i64 Status
Error handling system.
Definition: status.h:171
static const int SAVED_GAME_VERSION_MINOR
Definition: SavedGame.cpp:31
shared_ptr< T > DummySharedPtr(T *ptr)
Definition: shared_ptr.h:38
bool SerializeState(std::ostream &stream)
jsval CloneValueFromOtherContext(ScriptInterface &otherContext, jsval val)
Construct a new value (usable in this ScriptInterface&#39;s context) by cloning a value from a different ...
ScriptInterface & GetScriptInterface()
Definition: GUIManager.h:52
const Status FILE_NOT_FOUND
Definition: file.h:36
#define TIMER(description)
Measures the time taken to execute code up until end of the current scope; displays it via debug_prin...
Definition: timer.h:108
#define DEBUG_WARN_ERR(status)
display the error dialog with text corresponding to the given error code.
Definition: debug.h:331
CGameLoader(ScriptInterface &scriptInterface, CScriptValRooted *metadata, std::string *savedState)
Definition: SavedGame.cpp:121
shared_ptr< IArchiveFile > PIArchiveFile
Definition: archive.h:48
Path ChangeExtension(Path extension) const
Definition: path.h:185
bool VfsFileExists(const VfsPath &pathname)
Definition: Filesystem.cpp:34
Status Load(const std::wstring &name, ScriptInterface &scriptInterface, CScriptValRooted &metadata, std::string &savedState)
Load saved game archive with the given name.
Definition: SavedGame.cpp:152
#define LOGMESSAGERENDER
Definition: CLogger.h:33
#define WARN_RETURN(status)
Definition: status.h:255
std::vector< VfsPath > VfsPaths
Definition: vfs_path.h:42
jsval get() const
Returns the current value (or JSVAL_VOID if uninitialised).
Definition: ScriptVal.cpp:45
const Status FAIL
Definition: status.h:406
Abstraction around a SpiderMonkey JSContext.
External interface to the GUI system.
Definition: GUIManager.h:45
bool SetProperty(jsval obj, const char *name, const T &value, bool constant=false, bool enumerate=true)
Set the named property on the given object.
PIVFS g_VFS
Definition: Filesystem.cpp:30
CScriptValRooted GetInitAttributes()
Get the data passed to SetInitAttributes.
Status GetPathnames(const PIVFS &fs, const VfsPath &path, const wchar_t *filter, VfsPaths &pathnames)
Definition: vfs_util.cpp:40
ScriptInterface & m_ScriptInterface
Definition: SavedGame.cpp:147
Status GetFileInfo(const OsPath &pathname, CFileInfo *pPtrInfo)
Definition: file_system.cpp:65