1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2007 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27 
28 #include "machine/serialization.h"
29 
30 // include headers that implement a archive in simple text format
31 #include <boost/archive/text_oarchive.hpp>
32 #include <boost/archive/text_iarchive.hpp>
33 #include <boost/serialization/split_free.hpp>
34 #include <boost/serialization/vector.hpp>
35 #include <boost/serialization/set.hpp>
36 #include <boost/serialization/map.hpp>
37 #include <boost/filesystem/path.hpp>
38 #include <boost/filesystem/fstream.hpp>
39 #include <boost/filesystem/operations.hpp>
40 #include <boost/iostreams/filtering_stream.hpp>
41 #include <boost/iostreams/filter/zlib.hpp>
42 #include <fstream>
43 #include <sstream>
44 #include <iostream>
45 
46 #include "libreallive/intmemref.h"
47 #include "machine/memory.h"
48 #include "machine/rlmachine.h"
49 #include "systems/base/event_system.h"
50 #include "systems/base/graphics_system.h"
51 #include "systems/base/sound_system.h"
52 #include "systems/base/system.h"
53 #include "systems/base/text_system.h"
54 #include "utilities/dynamic_bitset_serialize.h"
55 #include "utilities/exception.h"
56 #include "utilities/gettext.h"
57 
58 namespace fs = boost::filesystem;
59 
60 namespace Serialization {
61 
62 // - Was at 2 was most of rlvm's lifetime.
63 // - Was changed to 3 during the 0.7 release because boost 1.35 had a serious
64 //   bug in its implementation of vectors of primitive types which made
65 //   archives not-backwards (or forwards) compatible. Thankfully, the save
66 //   games themselves don't use that feature.
67 const int CURRENT_GLOBAL_VERSION = 3;
68 
buildGlobalMemoryFilename(RLMachine & machine)69 fs::path buildGlobalMemoryFilename(RLMachine& machine) {
70   return machine.system().GameSaveDirectory() / "global.sav.gz";
71 }
72 
saveGlobalMemory(RLMachine & machine)73 void saveGlobalMemory(RLMachine& machine) {
74   fs::path home = buildGlobalMemoryFilename(machine);
75   fs::ofstream file(home, std::ios::binary);
76   if (!file) {
77     throw rlvm::Exception(_("Could not open global memory file."));
78   }
79 
80   saveGlobalMemoryTo(file, machine);
81 }
82 
saveGlobalMemoryTo(std::ostream & oss,RLMachine & machine)83 void saveGlobalMemoryTo(std::ostream& oss, RLMachine& machine) {
84   boost::iostreams::filtering_stream<boost::iostreams::output> filtered_output;
85   filtered_output.push(boost::iostreams::zlib_compressor());
86   filtered_output.push(oss);
87 
88   boost::archive::text_oarchive oa(filtered_output);
89   System& sys = machine.system();
90 
91   oa << CURRENT_GLOBAL_VERSION
92      << const_cast<const GlobalMemory&>(machine.memory().global())
93      << const_cast<const SystemGlobals&>(sys.globals())
94      << const_cast<const GraphicsSystemGlobals&>(sys.graphics().globals())
95      << const_cast<const EventSystemGlobals&>(sys.event().globals())
96      << const_cast<const TextSystemGlobals&>(sys.text().globals())
97      << const_cast<const SoundSystemGlobals&>(sys.sound().globals());
98 }
99 
loadGlobalMemory(RLMachine & machine)100 void loadGlobalMemory(RLMachine& machine) {
101   fs::path home = buildGlobalMemoryFilename(machine);
102   fs::ifstream file(home, std::ios::binary);
103 
104   // If we were able to open the file for reading, load it. Don't
105   // complain if we're unable to, since this may be the first run on
106   // this certain game and it may not exist yet.
107   if (file) {
108     try {
109       loadGlobalMemoryFrom(file, machine);
110     }
111     catch (...) {
112       // Swallow ALL exceptions during file reading. If loading the global
113       // memory file fails in any way, something is EXTREMELY wrong. Either
114       // we're trying to read an incompatible old version's files or the global
115       // data is corrupted. Either way, we can't safely do ANYTHING with this
116       // game's entire save data so move it out of the way.
117       fs::path save_dir = machine.system().GameSaveDirectory();
118       fs::path dest_save_dir = save_dir.parent_path() /
119                                (save_dir.filename() / ".old_corrupted_data");
120 
121       if (fs::exists(dest_save_dir))
122         fs::remove_all(dest_save_dir);
123       fs::rename(save_dir, dest_save_dir);
124 
125       std::cerr << "WARNING: Unable to read saved global memory file. Moving "
126                 << save_dir << " to " << dest_save_dir << std::endl;
127     }
128   }
129 }
130 
loadGlobalMemoryFrom(std::istream & iss,RLMachine & machine)131 void loadGlobalMemoryFrom(std::istream& iss, RLMachine& machine) {
132   boost::iostreams::filtering_stream<boost::iostreams::input> filtered_input;
133   filtered_input.push(boost::iostreams::zlib_decompressor());
134   filtered_input.push(iss);
135 
136   boost::archive::text_iarchive ia(filtered_input);
137   System& sys = machine.system();
138   int version;
139   ia >> version;
140 
141   // Load global memory.
142   ia >> machine.memory().global();
143 
144   // When Karmic Koala came out, support for all boost earlier than 1.36 was
145   // dropped. For years, I had used boost 1.35 on Ubuntu. It turns out that
146   // boost 1.35 had a serious bug in it, where it wouldn't save vectors of
147   // primitive data types correctly. These global data files no longer load
148   // correctly.
149   //
150   // After flirting with moving to Google protobuf (can't; doesn't handle
151   // complex object graphs like GraphicsObject and its copy-on-write stuff),
152   // and then trying to fix the problem in a forked copy of the serialization
153   // headers which was unsuccessful, I'm just saying to hell with the user's
154   // settings. Most people don't change these values and save games and global
155   // memory still work (per above.)
156   if (version == CURRENT_GLOBAL_VERSION) {
157     ia >> sys.globals() >> sys.graphics().globals() >> sys.event().globals() >>
158         sys.text().globals() >> sys.sound().globals();
159 
160     // Restore options which may have System specific implementations. (This
161     // will probably expand as more of RealLive is implemented).
162     sys.sound().RestoreFromGlobals();
163   }
164 }
165 
166 }  // namespace Serialization
167