1 /***************************************************************************
2 * Copyright (C) 2009 by Andrey Afletdinov <fheroes2@gmail.com> *
3 * *
4 * Part of the Free Heroes2 Engine: *
5 * http://sourceforge.net/projects/fheroes2 *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21 ***************************************************************************/
22
23 #include <ctime>
24
25 #include "campaign_savedata.h"
26 #include "dialog.h"
27 #include "game.h"
28 #include "game_io.h"
29 #include "game_over.h"
30 #include "game_static.h"
31 #include "logging.h"
32 #include "monster.h"
33 #include "save_format_version.h"
34 #include "settings.h"
35 #include "system.h"
36 #include "text.h"
37 #include "translations.h"
38 #include "world.h"
39 #include "zzlib.h"
40
41 namespace
42 {
43 const uint16_t SAV2ID2 = 0xFF02;
44 const uint16_t SAV2ID3 = 0xFF03;
45
46 struct HeaderSAV
47 {
48 enum
49 {
50 IS_LOYALTY = 0x4000
51 };
52
53 HeaderSAV() = delete;
54
HeaderSAV__anon88a45b230111::HeaderSAV55 explicit HeaderSAV( const int saveFileVersion )
56 : status( 0 )
57 , gameType( 0 )
58 , _saveFileVersion( saveFileVersion )
59 {}
60
HeaderSAV__anon88a45b230111::HeaderSAV61 HeaderSAV( const Maps::FileInfo & fi, const int gameType_, const int saveFileVersion )
62 : status( 0 )
63 , info( fi )
64 , gameType( gameType_ )
65 , _saveFileVersion( saveFileVersion )
66 {
67 time_t rawtime;
68 std::time( &rawtime );
69 info.localtime = rawtime;
70
71 if ( fi._version == GameVersion::PRICE_OF_LOYALTY )
72 status |= IS_LOYALTY;
73 }
74
75 uint16_t status;
76 Maps::FileInfo info;
77 int gameType;
78 const int _saveFileVersion;
79 };
80
operator <<(StreamBase & msg,const HeaderSAV & hdr)81 StreamBase & operator<<( StreamBase & msg, const HeaderSAV & hdr )
82 {
83 return msg << hdr.status << hdr.info << hdr.gameType;
84 }
85
operator >>(StreamBase & msg,HeaderSAV & hdr)86 StreamBase & operator>>( StreamBase & msg, HeaderSAV & hdr )
87 {
88 return msg >> hdr.status >> hdr.info >> hdr.gameType;
89 }
90 }
91
AutoSave()92 bool Game::AutoSave()
93 {
94 return Game::Save( System::ConcatePath( GetSaveDir(), "AUTOSAVE" + GetSaveFileExtension() ) );
95 }
96
Save(const std::string & fn)97 bool Game::Save( const std::string & fn )
98 {
99 DEBUG_LOG( DBG_GAME, DBG_INFO, fn );
100 const bool autosave = ( System::GetBasename( fn ) == "AUTOSAVE" + GetSaveFileExtension() );
101 const Settings & conf = Settings::Get();
102
103 StreamFile fs;
104 fs.setbigendian( true );
105
106 if ( !fs.open( fn, "wb" ) ) {
107 DEBUG_LOG( DBG_GAME, DBG_WARN, fn << ", error open" );
108 return false;
109 }
110
111 u16 loadver = GetLoadVersion();
112 if ( !autosave )
113 Game::SetLastSavename( fn );
114
115 // raw info content
116 fs << static_cast<uint8_t>( SAV2ID3 >> 8 ) << static_cast<uint8_t>( SAV2ID3 & 0xFF ) << std::to_string( loadver ) << loadver
117 << HeaderSAV( conf.CurrentFileInfo(), conf.GameType(), CURRENT_FORMAT_VERSION );
118 fs.close();
119
120 ZStreamFile fz;
121 fz.setbigendian( true );
122
123 // zip game data content
124 fz << loadver << World::Get() << Settings::Get() << GameOver::Result::Get();
125
126 if ( conf.isCampaignGameType() )
127 fz << Campaign::CampaignSaveData::Get();
128
129 fz << SAV2ID3; // eof marker
130
131 return !fz.fail() && fz.write( fn, true );
132 }
133
Load(const std::string & fn)134 fheroes2::GameMode Game::Load( const std::string & fn )
135 {
136 DEBUG_LOG( DBG_GAME, DBG_INFO, fn );
137
138 StreamFile fs;
139 fs.setbigendian( true );
140
141 if ( !fs.open( fn, "rb" ) ) {
142 DEBUG_LOG( DBG_GAME, DBG_WARN, fn << ", error open" );
143 return fheroes2::GameMode::CANCEL;
144 }
145
146 Game::ShowMapLoadingText();
147
148 char major;
149 char minor;
150 fs >> major >> minor;
151 const u16 savid = ( static_cast<u16>( major ) << 8 ) | static_cast<u16>( minor );
152
153 // check version sav file
154 if ( savid != SAV2ID2 && savid != SAV2ID3 ) {
155 DEBUG_LOG( DBG_GAME, DBG_WARN, fn << ", incorrect SAV2ID" );
156 return fheroes2::GameMode::CANCEL;
157 }
158
159 std::string strver;
160 u16 binver = 0;
161
162 // read raw info
163 fs >> strver >> binver;
164
165 // hide: unsupported version
166 if ( binver > CURRENT_FORMAT_VERSION || binver < LAST_SUPPORTED_FORMAT_VERSION )
167 return fheroes2::GameMode::CANCEL;
168
169 int fileGameType = Game::TYPE_STANDARD;
170 HeaderSAV header( binver );
171 fs >> header;
172 fileGameType = header.gameType;
173
174 size_t offset = fs.tell();
175 fs.close();
176
177 Settings & conf = Settings::Get();
178 if ( ( conf.GameType() & fileGameType ) == 0 ) {
179 Dialog::Message( _( "Warning" ), _( "Invalid file game type. Please ensure that you are running the latest type of save files." ), Font::BIG, Dialog::OK );
180 return fheroes2::GameMode::CANCEL;
181 }
182
183 ZStreamFile fz;
184 fz.setbigendian( true );
185
186 if ( !fz.read( fn, offset ) ) {
187 DEBUG_LOG( DBG_GAME, DBG_WARN, ", uncompress: error" );
188 return fheroes2::GameMode::CANCEL;
189 }
190
191 if ( ( header.status & HeaderSAV::IS_LOYALTY ) && !conf.isPriceOfLoyaltySupported() ) {
192 Dialog::Message( _( "Warning" ), _( "This file is saved in the \"The Price of Loyalty\" version.\nSome items may be unavailable." ), Font::BIG, Dialog::OK );
193 }
194
195 fz >> binver;
196
197 // check version: false
198 if ( binver > CURRENT_FORMAT_VERSION || binver < LAST_SUPPORTED_FORMAT_VERSION ) {
199 std::string errorMessage( _( "Usupported save format: " ) );
200 errorMessage += std::to_string( binver );
201 errorMessage += ".\n";
202 errorMessage += _( "Current game version: " );
203 errorMessage += std::to_string( CURRENT_FORMAT_VERSION );
204 errorMessage += ".\n";
205 errorMessage += _( "Last supported version: " );
206 errorMessage += std::to_string( LAST_SUPPORTED_FORMAT_VERSION );
207 errorMessage += ".\n";
208
209 Dialog::Message( _( "Error" ), errorMessage, Font::BIG, Dialog::OK );
210 return fheroes2::GameMode::CANCEL;
211 }
212
213 DEBUG_LOG( DBG_GAME, DBG_TRACE, "load version: " << binver );
214 SetLoadVersion( binver );
215
216 fz >> World::Get() >> conf >> GameOver::Result::Get();
217
218 // Settings should contain the full path to the current map file, if this map is available
219 conf.SetMapsFile( Settings::GetLastFile( "maps", System::GetBasename( conf.MapsFile() ) ) );
220
221 if ( !conf.loadedFileLanguage().empty() && conf.loadedFileLanguage() != "en" && conf.loadedFileLanguage() != conf.getGameLanguage() ) {
222 std::string warningMessage( _( "This saved game is localized to '" ) );
223 warningMessage.append( conf.loadedFileLanguage() );
224 warningMessage.append( _( "' language, but the current language of the game is '" ) );
225 warningMessage.append( conf.getGameLanguage() );
226 warningMessage += "'.";
227 Dialog::Message( _( "Warning" ), warningMessage, Font::BIG, Dialog::OK );
228 }
229
230 fheroes2::GameMode returnValue = fheroes2::GameMode::START_GAME;
231
232 if ( conf.isCampaignGameType() ) {
233 Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get();
234 fz >> saveData;
235
236 if ( !saveData.isStarting() && saveData.getCurrentScenarioID() == saveData.getLastCompletedScenarioID() ) {
237 // This is the end of the current scenario. We should show next scenario selection.
238 returnValue = fheroes2::GameMode::COMPLETE_CAMPAIGN_SCENARIO_FROM_LOAD_FILE;
239 }
240 }
241
242 uint16_t end_check = 0;
243 fz >> end_check;
244
245 if ( fz.fail() || ( end_check != SAV2ID2 && end_check != SAV2ID3 ) ) {
246 DEBUG_LOG( DBG_GAME, DBG_WARN, "invalid load file: " << fn );
247 return fheroes2::GameMode::CANCEL;
248 }
249
250 SetLoadVersion( CURRENT_FORMAT_VERSION );
251
252 Game::SetLastSavename( fn );
253 conf.SetGameType( conf.GameType() | Game::TYPE_LOADFILE );
254
255 if ( returnValue != fheroes2::GameMode::START_GAME ) {
256 return returnValue;
257 }
258
259 // rescan path passability for all heroes, for this we need actual info about players from Settings
260 World::Get().RescanAllHeroesPathPassable();
261
262 return returnValue;
263 }
264
LoadSAV2FileInfo(const std::string & fn,Maps::FileInfo & finfo)265 bool Game::LoadSAV2FileInfo( const std::string & fn, Maps::FileInfo & finfo )
266 {
267 DEBUG_LOG( DBG_GAME, DBG_INFO, fn );
268
269 StreamFile fs;
270 fs.setbigendian( true );
271
272 if ( !fs.open( fn, "rb" ) ) {
273 DEBUG_LOG( DBG_GAME, DBG_WARN, fn << ", error open" );
274 return false;
275 }
276
277 char major;
278 char minor;
279 fs >> major >> minor;
280 const u16 savid = ( static_cast<u16>( major ) << 8 ) | static_cast<u16>( minor );
281
282 // check version sav file
283 if ( savid != SAV2ID2 && savid != SAV2ID3 ) {
284 DEBUG_LOG( DBG_GAME, DBG_WARN, fn << ", incorrect SAV2ID" );
285 return false;
286 }
287
288 std::string strver;
289 u16 binver = 0;
290
291 // read raw info
292 fs >> strver >> binver;
293
294 // hide: unsupported version
295 if ( binver > CURRENT_FORMAT_VERSION || binver < LAST_SUPPORTED_FORMAT_VERSION )
296 return false;
297
298 int fileGameType = Game::TYPE_STANDARD;
299 HeaderSAV header( binver );
300 fs >> header;
301 fileGameType = header.gameType;
302
303 if ( ( Settings::Get().GameType() & fileGameType ) == 0 )
304 return false;
305
306 finfo = header.info;
307 finfo.file = fn;
308
309 return true;
310 }
311
GetSaveDir()312 std::string Game::GetSaveDir()
313 {
314 return System::ConcatePath( System::ConcatePath( System::GetDataDirectory( "fheroes2" ), "files" ), "save" );
315 }
316
GetSaveFileExtension()317 std::string Game::GetSaveFileExtension()
318 {
319 return GetSaveFileExtension( Settings::Get().GameType() );
320 }
321
GetSaveFileExtension(const int gameType)322 std::string Game::GetSaveFileExtension( const int gameType )
323 {
324 if ( gameType & Game::TYPE_STANDARD )
325 return ".sav";
326 else if ( gameType & Game::TYPE_CAMPAIGN )
327 return ".savc";
328 else if ( gameType & Game::TYPE_HOTSEAT )
329 return ".savh";
330
331 return ".savm";
332 }
333
SaveCompletedCampaignScenario()334 bool Game::SaveCompletedCampaignScenario()
335 {
336 const std::string & name = Settings::Get().CurrentFileInfo().name;
337
338 std::string base = name.empty() ? "newgame" : name;
339 std::replace_if( base.begin(), base.end(), ::isspace, '_' );
340
341 return Save( System::ConcatePath( Game::GetSaveDir(), base ) + "_Complete" + Game::GetSaveFileExtension() );
342 }
343