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