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