1 /**
2 * @file pfile.cpp
3 *
4 * Implementation of the save game encoding functionality.
5 */
6 #include <string>
7
8 #include "all.h"
9 #include "paths.h"
10 #include "../3rdParty/Storm/Source/storm.h"
11 #include "../DiabloUI/diabloui.h"
12 #include "file_util.h"
13
14 DEVILUTION_BEGIN_NAMESPACE
15
16 #define PASSWORD_SPAWN_SINGLE "adslhfb1"
17 #define PASSWORD_SPAWN_MULTI "lshbkfg1"
18 #define PASSWORD_SINGLE "xrgyrkj1"
19 #define PASSWORD_MULTI "szqnlsk1"
20
21 namespace {
22
GetSavePath(DWORD save_num)23 std::string GetSavePath(DWORD save_num)
24 {
25 std::string path = GetPrefPath();
26 const char *ext = ".sv";
27 if (gbIsHellfire)
28 ext = ".hsv";
29
30 if (gbIsSpawn) {
31 if (!gbIsMultiplayer) {
32 path.append("spawn");
33 } else {
34 path.append("share_");
35 }
36 } else {
37 if (!gbIsMultiplayer) {
38 path.append("single_");
39 } else {
40 path.append("multi_");
41 }
42 }
43
44 char save_num_str[21];
45 snprintf(save_num_str, sizeof(save_num_str) / sizeof(char), "%d", save_num);
46 path.append(save_num_str);
47 path.append(ext);
48 return path;
49 }
50
51 } // namespace
52
53 /** List of character names for the character selection screen. */
54 static char hero_names[MAX_CHARACTERS][PLR_NAME_LEN];
55 BOOL gbValidSaveFile;
56
pfile_get_save_num_from_name(const char * name)57 static DWORD pfile_get_save_num_from_name(const char *name)
58 {
59 DWORD i;
60
61 for (i = 0; i < MAX_CHARACTERS; i++) {
62 if (!strcasecmp(hero_names[i], name))
63 break;
64 }
65
66 return i;
67 }
68
pfile_read_archive(HANDLE archive,const char * pszName,DWORD * pdwLen)69 static BYTE *pfile_read_archive(HANDLE archive, const char *pszName, DWORD *pdwLen)
70 {
71 DWORD nread;
72 HANDLE file;
73 BYTE *buf;
74
75 if (!SFileOpenFileEx(archive, pszName, 0, &file))
76 return NULL;
77
78 *pdwLen = SFileGetFileSize(file, NULL);
79 if (*pdwLen == 0)
80 return NULL;
81
82 buf = DiabloAllocPtr(*pdwLen);
83 if (!SFileReadFile(file, buf, *pdwLen, &nread, NULL))
84 return NULL;
85 SFileCloseFile(file);
86
87 {
88 const char *password;
89 DWORD nSize = 16;
90
91 if (gbIsSpawn) {
92 password = PASSWORD_SPAWN_SINGLE;
93 if (gbIsMultiplayer)
94 password = PASSWORD_SPAWN_MULTI;
95 } else {
96 password = PASSWORD_SINGLE;
97 if (gbIsMultiplayer)
98 password = PASSWORD_MULTI;
99 }
100
101 *pdwLen = codec_decode(buf, *pdwLen, password);
102 if (*pdwLen == 0)
103 return NULL;
104 }
105 return buf;
106 }
107
pfile_read_hero(HANDLE archive,PkPlayerStruct * pPack)108 static BOOL pfile_read_hero(HANDLE archive, PkPlayerStruct *pPack)
109 {
110 DWORD read;
111 BYTE *buf;
112
113 buf = pfile_read_archive(archive, "hero", &read);
114 if (buf == NULL)
115 return FALSE;
116
117 BOOL ret = FALSE;
118 if (read == sizeof(*pPack)) {
119 memcpy(pPack, buf, sizeof(*pPack));
120 ret = TRUE;
121 }
122
123 mem_free_dbg(buf);
124 return ret;
125 }
126
pfile_encode_hero(const PkPlayerStruct * pPack)127 static void pfile_encode_hero(const PkPlayerStruct *pPack)
128 {
129 BYTE *packed;
130 DWORD packed_len;
131 const char *password;
132
133 if (gbIsSpawn) {
134 password = PASSWORD_SPAWN_SINGLE;
135 if (gbIsMultiplayer)
136 password = PASSWORD_SPAWN_MULTI;
137 } else {
138 password = PASSWORD_SINGLE;
139 if (gbIsMultiplayer)
140 password = PASSWORD_MULTI;
141 }
142
143 packed_len = codec_get_encoded_len(sizeof(*pPack));
144 packed = (BYTE *)DiabloAllocPtr(packed_len);
145 memcpy(packed, pPack, sizeof(*pPack));
146 codec_encode(packed, sizeof(*pPack), packed_len, password);
147 mpqapi_write_file("hero", packed, packed_len);
148 mem_free_dbg(packed);
149 }
150
pfile_open_archive(DWORD save_num)151 static BOOL pfile_open_archive(DWORD save_num)
152 {
153 if (OpenMPQ(GetSavePath(save_num).c_str(), save_num))
154 return TRUE;
155
156 return FALSE;
157 }
158
pfile_flush(BOOL is_single_player,DWORD save_num)159 static void pfile_flush(BOOL is_single_player, DWORD save_num)
160 {
161 mpqapi_flush_and_close(GetSavePath(save_num).c_str(), is_single_player, save_num);
162 }
163
pfile_open_save_archive(DWORD save_num)164 static HANDLE pfile_open_save_archive(DWORD save_num)
165 {
166 HANDLE archive;
167
168 if (SFileOpenArchive(GetSavePath(save_num).c_str(), 0x7000, FS_PC, &archive))
169 return archive;
170 return NULL;
171 }
172
pfile_SFileCloseArchive(HANDLE * hsArchive)173 static void pfile_SFileCloseArchive(HANDLE *hsArchive)
174 {
175 if (*hsArchive == NULL)
176 return;
177
178 SFileCloseArchive(*hsArchive);
179 *hsArchive = NULL;
180 }
181
pfile_write_hero()182 void pfile_write_hero()
183 {
184 DWORD save_num;
185 PkPlayerStruct pkplr;
186
187 save_num = pfile_get_save_num_from_name(plr[myplr]._pName);
188 if (pfile_open_archive(save_num)) {
189 PackPlayer(&pkplr, myplr, !gbIsMultiplayer);
190 pfile_encode_hero(&pkplr);
191 if (!gbVanilla) {
192 SaveHotkeys();
193 SaveHeroItems(&plr[myplr]);
194 }
195 pfile_flush(!gbIsMultiplayer, save_num);
196 }
197 }
198
pfile_create_player_description(char * dst,DWORD len)199 BOOL pfile_create_player_description(char *dst, DWORD len)
200 {
201 char desc[128];
202 _uiheroinfo uihero;
203
204 myplr = 0;
205 pfile_read_player_from_save();
206 game_2_ui_player(plr, &uihero, gbValidSaveFile);
207 UiSetupPlayerInfo(gszHero, &uihero, GAME_ID);
208
209 if (dst != NULL && len) {
210 if (UiCreatePlayerDescription(&uihero, GAME_ID, &desc) == 0)
211 return FALSE;
212 SStrCopy(dst, desc, len);
213 }
214 return TRUE;
215 }
216
pfile_rename_hero(const char * name_1,const char * name_2)217 BOOL pfile_rename_hero(const char *name_1, const char *name_2)
218 {
219 int i;
220 DWORD save_num;
221 _uiheroinfo uihero;
222 BOOL found = FALSE;
223
224 if (pfile_get_save_num_from_name(name_2) == MAX_CHARACTERS) {
225 for (i = 0; i != MAX_PLRS; i++) {
226 if (!strcasecmp(name_1, plr[i]._pName)) {
227 found = TRUE;
228 break;
229 }
230 }
231 }
232
233 if (!found)
234 return FALSE;
235 save_num = pfile_get_save_num_from_name(name_1);
236 if (save_num == MAX_CHARACTERS)
237 return FALSE;
238
239 SStrCopy(hero_names[save_num], name_2, PLR_NAME_LEN);
240 SStrCopy(plr[i]._pName, name_2, PLR_NAME_LEN);
241 if (!strcasecmp(gszHero, name_1))
242 SStrCopy(gszHero, name_2, sizeof(gszHero));
243 game_2_ui_player(plr, &uihero, gbValidSaveFile);
244 UiSetupPlayerInfo(gszHero, &uihero, GAME_ID);
245 pfile_write_hero();
246 return TRUE;
247 }
248
pfile_flush_W()249 void pfile_flush_W()
250 {
251 pfile_flush(TRUE, pfile_get_save_num_from_name(plr[myplr]._pName));
252 }
253
game_2_ui_player(const PlayerStruct * p,_uiheroinfo * heroinfo,BOOL bHasSaveFile)254 void game_2_ui_player(const PlayerStruct *p, _uiheroinfo *heroinfo, BOOL bHasSaveFile)
255 {
256 memset(heroinfo, 0, sizeof(*heroinfo));
257 strncpy(heroinfo->name, p->_pName, sizeof(heroinfo->name) - 1);
258 heroinfo->name[sizeof(heroinfo->name) - 1] = '\0';
259 heroinfo->level = p->_pLevel;
260 heroinfo->heroclass = p->_pClass;
261 heroinfo->strength = p->_pStrength;
262 heroinfo->magic = p->_pMagic;
263 heroinfo->dexterity = p->_pDexterity;
264 heroinfo->vitality = p->_pVitality;
265 heroinfo->gold = p->_pGold;
266 heroinfo->hassaved = bHasSaveFile;
267 heroinfo->herorank = p->pDiabloKillLevel;
268 heroinfo->spawned = gbIsSpawn;
269 }
270
pfile_ui_set_hero_infos(BOOL (* ui_add_hero_info)(_uiheroinfo *))271 BOOL pfile_ui_set_hero_infos(BOOL (*ui_add_hero_info)(_uiheroinfo *))
272 {
273 DWORD i;
274
275 memset(hero_names, 0, sizeof(hero_names));
276
277 for (i = 0; i < MAX_CHARACTERS; i++) {
278 PkPlayerStruct pkplr;
279 HANDLE archive = pfile_open_save_archive(i);
280 if (archive) {
281 if (pfile_read_hero(archive, &pkplr)) {
282 _uiheroinfo uihero;
283 strcpy(hero_names[i], pkplr.pName);
284 bool hasSaveGame = pfile_archive_contains_game(archive, i);
285 if (hasSaveGame)
286 pkplr.bIsHellfire = gbIsHellfireSaveGame;
287
288 UnPackPlayer(&pkplr, 0, FALSE);
289
290 pfile_SFileCloseArchive(&archive);
291 LoadHeroItems(&plr[0]);
292 RemoveEmptyInventory(0);
293 CalcPlrInv(0, FALSE);
294
295 game_2_ui_player(plr, &uihero, hasSaveGame);
296 ui_add_hero_info(&uihero);
297 }
298 pfile_SFileCloseArchive(&archive);
299 }
300 }
301
302 return TRUE;
303 }
304
pfile_archive_contains_game(HANDLE hsArchive,DWORD save_num)305 BOOL pfile_archive_contains_game(HANDLE hsArchive, DWORD save_num)
306 {
307 if (gbIsMultiplayer)
308 return FALSE;
309
310 DWORD dwLen;
311 BYTE *gameData = pfile_read_archive(hsArchive, "game", &dwLen);
312 if (gameData == NULL)
313 return FALSE;
314
315 Uint32 hdr = LOAD_LE32(gameData);
316 mem_free_dbg(gameData);
317
318 return IsHeaderValid(hdr);
319 }
320
pfile_ui_set_class_stats(unsigned int player_class_nr,_uidefaultstats * class_stats)321 void pfile_ui_set_class_stats(unsigned int player_class_nr, _uidefaultstats *class_stats)
322 {
323 class_stats->strength = StrengthTbl[player_class_nr];
324 class_stats->magic = MagicTbl[player_class_nr];
325 class_stats->dexterity = DexterityTbl[player_class_nr];
326 class_stats->vitality = VitalityTbl[player_class_nr];
327 }
328
pfile_ui_save_create(_uiheroinfo * heroinfo)329 BOOL pfile_ui_save_create(_uiheroinfo *heroinfo)
330 {
331 DWORD save_num;
332 PkPlayerStruct pkplr;
333
334 save_num = pfile_get_save_num_from_name(heroinfo->name);
335 if (save_num >= MAX_CHARACTERS) {
336 for (save_num = 0; save_num < MAX_CHARACTERS; save_num++) {
337 if (!hero_names[save_num][0])
338 break;
339 }
340 if (save_num >= MAX_CHARACTERS)
341 return FALSE;
342 }
343 if (!pfile_open_archive(save_num))
344 return FALSE;
345 mpqapi_remove_hash_entries(pfile_get_file_name);
346 strncpy(hero_names[save_num], heroinfo->name, PLR_NAME_LEN);
347 hero_names[save_num][PLR_NAME_LEN - 1] = '\0';
348 CreatePlayer(0, heroinfo->heroclass);
349 strncpy(plr[0]._pName, heroinfo->name, PLR_NAME_LEN);
350 plr[0]._pName[PLR_NAME_LEN - 1] = '\0';
351 PackPlayer(&pkplr, 0, TRUE);
352 pfile_encode_hero(&pkplr);
353 game_2_ui_player(&plr[0], heroinfo, FALSE);
354 if (!gbVanilla) {
355 SaveHotkeys();
356 SaveHeroItems(&plr[0]);
357 }
358 pfile_flush(TRUE, save_num);
359 return TRUE;
360 }
361
pfile_get_file_name(DWORD lvl,char * dst)362 BOOL pfile_get_file_name(DWORD lvl, char *dst)
363 {
364 const char *fmt;
365
366 if (gbIsMultiplayer) {
367 if (lvl)
368 return FALSE;
369 fmt = "hero";
370 } else {
371 if (lvl < giNumberOfLevels)
372 fmt = "perml%02d";
373 else if (lvl < giNumberOfLevels * 2) {
374 lvl -= giNumberOfLevels;
375 fmt = "perms%02d";
376 } else if (lvl == giNumberOfLevels * 2)
377 fmt = "game";
378 else if (lvl == giNumberOfLevels * 2 + 1)
379 fmt = "hero";
380 else
381 return FALSE;
382 }
383 sprintf(dst, fmt, lvl);
384 return TRUE;
385 }
386
pfile_delete_save(_uiheroinfo * hero_info)387 BOOL pfile_delete_save(_uiheroinfo *hero_info)
388 {
389 DWORD save_num;
390
391 save_num = pfile_get_save_num_from_name(hero_info->name);
392 if (save_num < MAX_CHARACTERS) {
393 hero_names[save_num][0] = '\0';
394 RemoveFile(GetSavePath(save_num).c_str());
395 }
396 return TRUE;
397 }
398
pfile_read_player_from_save()399 void pfile_read_player_from_save()
400 {
401 HANDLE archive;
402 DWORD save_num;
403 PkPlayerStruct pkplr;
404
405 save_num = pfile_get_save_num_from_name(gszHero);
406 archive = pfile_open_save_archive(save_num);
407 if (archive == NULL)
408 app_fatal("Unable to open archive");
409 if (!pfile_read_hero(archive, &pkplr))
410 app_fatal("Unable to load character");
411
412 gbValidSaveFile = pfile_archive_contains_game(archive, save_num);
413 if (gbValidSaveFile)
414 pkplr.bIsHellfire = gbIsHellfireSaveGame;
415
416 pfile_SFileCloseArchive(&archive);
417
418 UnPackPlayer(&pkplr, myplr, FALSE);
419
420 LoadHeroItems(&plr[myplr]);
421 RemoveEmptyInventory(myplr);
422 CalcPlrInv(myplr, FALSE);
423 }
424
LevelFileExists()425 bool LevelFileExists()
426 {
427 char szName[MAX_PATH];
428
429 GetPermLevelNames(szName);
430
431 DWORD save_num = pfile_get_save_num_from_name(plr[myplr]._pName);
432 if (!pfile_open_archive(save_num))
433 app_fatal("Unable to read to save file archive");
434
435 bool has_file = mpqapi_has_file(szName);
436 pfile_flush(TRUE, save_num);
437 return has_file;
438 }
439
GetTempLevelNames(char * szTemp)440 void GetTempLevelNames(char *szTemp)
441 {
442 if (setlevel)
443 sprintf(szTemp, "temps%02d", setlvlnum);
444 else
445 sprintf(szTemp, "templ%02d", currlevel);
446 }
447
GetPermLevelNames(char * szPerm)448 void GetPermLevelNames(char *szPerm)
449 {
450 DWORD save_num;
451 BOOL has_file;
452
453 save_num = pfile_get_save_num_from_name(plr[myplr]._pName);
454 GetTempLevelNames(szPerm);
455 if (!pfile_open_archive(save_num))
456 app_fatal("Unable to read to save file archive");
457
458 has_file = mpqapi_has_file(szPerm);
459 pfile_flush(TRUE, save_num);
460 if (!has_file) {
461 if (setlevel)
462 sprintf(szPerm, "perms%02d", setlvlnum);
463 else
464 sprintf(szPerm, "perml%02d", currlevel);
465 }
466 }
467
GetPermSaveNames(DWORD dwIndex,char * szPerm)468 static BOOL GetPermSaveNames(DWORD dwIndex, char *szPerm)
469 {
470 const char *fmt;
471
472 if (dwIndex < giNumberOfLevels)
473 fmt = "perml%02d";
474 else if (dwIndex < giNumberOfLevels * 2) {
475 dwIndex -= giNumberOfLevels;
476 fmt = "perms%02d";
477 } else
478 return FALSE;
479
480 sprintf(szPerm, fmt, dwIndex);
481 return TRUE;
482 }
483
GetTempSaveNames(DWORD dwIndex,char * szTemp)484 static BOOL GetTempSaveNames(DWORD dwIndex, char *szTemp)
485 {
486 const char *fmt;
487
488 if (dwIndex < giNumberOfLevels)
489 fmt = "templ%02d";
490 else if (dwIndex < giNumberOfLevels * 2) {
491 dwIndex -= giNumberOfLevels;
492 fmt = "temps%02d";
493 } else
494 return FALSE;
495
496 sprintf(szTemp, fmt, dwIndex);
497 return TRUE;
498 }
499
pfile_remove_temp_files()500 void pfile_remove_temp_files()
501 {
502 if (gbIsMultiplayer)
503 return;
504
505 DWORD save_num = pfile_get_save_num_from_name(plr[myplr]._pName);
506 if (!pfile_open_archive(save_num))
507 app_fatal("Unable to write to save file archive");
508 mpqapi_remove_hash_entries(GetTempSaveNames);
509 pfile_flush(TRUE, save_num);
510 }
511
pfile_rename_temp_to_perm()512 void pfile_rename_temp_to_perm()
513 {
514 DWORD dwChar, dwIndex;
515 BOOL bResult;
516 char szTemp[MAX_PATH];
517 char szPerm[MAX_PATH];
518
519 dwChar = pfile_get_save_num_from_name(plr[myplr]._pName);
520 assert(dwChar < MAX_CHARACTERS);
521 assert(!gbIsMultiplayer);
522 if (!pfile_open_archive(dwChar))
523 app_fatal("Unable to write to save file archive");
524
525 dwIndex = 0;
526 while (GetTempSaveNames(dwIndex, szTemp)) {
527 bResult = GetPermSaveNames(dwIndex, szPerm);
528 assert(bResult);
529 dwIndex++;
530 if (mpqapi_has_file(szTemp)) {
531 if (mpqapi_has_file(szPerm))
532 mpqapi_remove_hash_entry(szPerm);
533 mpqapi_rename(szTemp, szPerm);
534 }
535 }
536 assert(!GetPermSaveNames(dwIndex, szPerm));
537 pfile_flush(TRUE, dwChar);
538 }
539
pfile_write_save_file(const char * pszName,BYTE * pbData,DWORD dwLen,DWORD qwLen)540 void pfile_write_save_file(const char *pszName, BYTE *pbData, DWORD dwLen, DWORD qwLen)
541 {
542 DWORD save_num;
543
544 save_num = pfile_get_save_num_from_name(plr[myplr]._pName);
545 {
546 const char *password;
547 if (gbIsSpawn) {
548 password = PASSWORD_SPAWN_SINGLE;
549 if (gbIsMultiplayer)
550 password = PASSWORD_SPAWN_MULTI;
551 } else {
552 password = PASSWORD_SINGLE;
553 if (gbIsMultiplayer)
554 password = PASSWORD_MULTI;
555 }
556
557 codec_encode(pbData, dwLen, qwLen, password);
558 }
559 if (!pfile_open_archive(save_num))
560 app_fatal("Unable to write to save file archive");
561 mpqapi_write_file(pszName, pbData, qwLen);
562 pfile_flush(TRUE, save_num);
563 }
564
pfile_read(const char * pszName,DWORD * pdwLen)565 BYTE *pfile_read(const char *pszName, DWORD *pdwLen)
566 {
567 DWORD save_num;
568 HANDLE archive;
569 BYTE *buf;
570
571 save_num = pfile_get_save_num_from_name(plr[myplr]._pName);
572 archive = pfile_open_save_archive(save_num);
573 if (archive == NULL)
574 return NULL;
575
576 buf = pfile_read_archive(archive, pszName, pdwLen);
577 pfile_SFileCloseArchive(&archive);
578 if (buf == NULL)
579 return NULL;
580
581 return buf;
582 }
583
pfile_update(bool force_save)584 void pfile_update(bool force_save)
585 {
586 static Uint32 save_prev_tc;
587
588 if (!gbIsMultiplayer)
589 return;
590
591 Uint32 tick = SDL_GetTicks();
592 if (!force_save && tick - save_prev_tc <= 60000)
593 return;
594
595 save_prev_tc = tick;
596 pfile_write_hero();
597 }
598
599 DEVILUTION_END_NAMESPACE
600