1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2010 EDuke32 developers and contributors
4
5 This file is part of EDuke32.
6
7 EDuke32 is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License version 2
9 as published by the Free Software Foundation.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15 See the 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 Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21 //-------------------------------------------------------------------------
22
23 #include "duke3d.h"
24 #include "premap.h"
25 #include "prlights.h"
26 #include "savegame.h"
27
28 #include "vfs.h"
29
30 static OutputFileCounter savecounter;
31
32 // For storing pointers in files.
33 // back_p==0: ptr -> "small int"
34 // back_p==1: "small int" -> ptr
35 //
36 // mode: see enum in savegame.h
G_Util_PtrToIdx(void * ptr,int32_t const count,const void * base,int32_t const mode)37 void G_Util_PtrToIdx(void *ptr, int32_t const count, const void *base, int32_t const mode)
38 {
39 intptr_t *iptr = (intptr_t *)ptr;
40 intptr_t const ibase = (intptr_t)base;
41 int32_t const onlynon0_p = mode&P2I_ONLYNON0_BIT;
42
43 // TODO: convert to proper offsets/indices for (a step towards) cross-
44 // compatibility between 32- and 64-bit systems in the netplay.
45 // REMEMBER to bump BYTEVERSION then.
46
47 // WARNING: C std doesn't say that bit pattern of NULL is necessarily 0!
48 if ((mode & P2I_BACK_BIT) == 0)
49 {
50 for (bssize_t i = 0; i < count; i++)
51 if (!onlynon0_p || iptr[i])
52 iptr[i] -= ibase;
53 }
54 else
55 {
56 for (bssize_t i = 0; i < count; i++)
57 if (!onlynon0_p || iptr[i])
58 iptr[i] += ibase;
59 }
60 }
61
G_Util_PtrToIdx2(void * ptr,int32_t const count,size_t const stride,const void * base,int32_t const mode)62 void G_Util_PtrToIdx2(void *ptr, int32_t const count, size_t const stride, const void *base, int32_t const mode)
63 {
64 uint8_t *iptr = (uint8_t *)ptr;
65 intptr_t const ibase = (intptr_t)base;
66 int32_t const onlynon0_p = mode&P2I_ONLYNON0_BIT;
67
68 if ((mode & P2I_BACK_BIT) == 0)
69 {
70 for (bssize_t i = 0; i < count; ++i)
71 {
72 if (!onlynon0_p || *(intptr_t *)iptr)
73 *(intptr_t *)iptr -= ibase;
74
75 iptr += stride;
76 }
77 }
78 else
79 {
80 for (bssize_t i = 0; i < count; ++i)
81 {
82 if (!onlynon0_p || *(intptr_t *)iptr)
83 *(intptr_t *)iptr += ibase;
84
85 iptr += stride;
86 }
87 }
88 }
89
90 // TODO: sync with TROR special interpolations? (e.g. upper floor of subway)
G_ResetInterpolations(void)91 void G_ResetInterpolations(void)
92 {
93 int32_t k, i;
94
95 g_interpolationCnt = 0;
96
97 k = headspritestat[STAT_EFFECTOR];
98 while (k >= 0)
99 {
100 switch (sprite[k].lotag)
101 {
102 case SE_31_FLOOR_RISE_FALL:
103 G_SetInterpolation(§or[sprite[k].sectnum].floorz);
104 break;
105 case SE_32_CEILING_RISE_FALL:
106 G_SetInterpolation(§or[sprite[k].sectnum].ceilingz);
107 break;
108 case SE_17_WARP_ELEVATOR:
109 case SE_25_PISTON:
110 G_SetInterpolation(§or[sprite[k].sectnum].floorz);
111 G_SetInterpolation(§or[sprite[k].sectnum].ceilingz);
112 break;
113 case SE_0_ROTATING_SECTOR:
114 case SE_5:
115 case SE_6_SUBWAY:
116 case SE_11_SWINGING_DOOR:
117 case SE_14_SUBWAY_CAR:
118 case SE_15_SLIDING_DOOR:
119 case SE_16_REACTOR:
120 case SE_26:
121 case SE_30_TWO_WAY_TRAIN:
122 Sect_SetInterpolation(sprite[k].sectnum);
123 break;
124 }
125
126 k = nextspritestat[k];
127 }
128
129 for (i=g_interpolationCnt-1; i>=0; i--) bakipos[i] = *curipos[i];
130 for (i = g_animateCnt-1; i>=0; i--)
131 G_SetInterpolation(g_animatePtr[i]);
132 }
133
134 savebrief_t g_lastautosave, g_lastusersave, g_freshload;
135 int32_t g_lastAutoSaveArbitraryID = -1;
136 bool g_saveRequested;
137 savebrief_t * g_quickload;
138
139 menusave_t * g_menusaves;
140 uint16_t g_nummenusaves;
141
142 static menusave_t * g_internalsaves;
143 static uint16_t g_numinternalsaves;
144
ReadSaveGameHeaders_CACHE1D(BUILDVFS_FIND_REC * f)145 static void ReadSaveGameHeaders_CACHE1D(BUILDVFS_FIND_REC *f)
146 {
147 savehead_t h;
148
149 for (; f != nullptr; f = f->next)
150 {
151 char const * fn = f->name;
152 buildvfs_kfd fil = kopen4loadfrommod(fn, 0);
153 if (fil == buildvfs_kfd_invalid)
154 continue;
155
156 menusave_t & msv = g_internalsaves[g_numinternalsaves];
157
158 msv.brief.isExt = 0;
159
160 int32_t k = sv_loadheader(fil, 0, &h);
161 if (k)
162 {
163 if (k < 0)
164 msv.isUnreadable = 1;
165 else
166 {
167 if (FURY)
168 {
169 char extfn[BMAX_PATH];
170 snprintf(extfn, ARRAY_SIZE(extfn), "%s.ext", fn);
171 buildvfs_kfd extfil = kopen4loadfrommod(extfn, 0);
172 if (extfil != buildvfs_kfd_invalid)
173 {
174 msv.brief.isExt = 1;
175 kclose(extfil);
176 }
177 }
178 }
179 msv.isOldVer = 1;
180 }
181 else
182 msv.isOldVer = 0;
183
184 msv.isAutoSave = h.isAutoSave();
185
186 strncpy(msv.brief.path, fn, ARRAY_SIZE(msv.brief.path));
187 ++g_numinternalsaves;
188
189 if (k >= 0 && h.savename[0] != '\0')
190 {
191 memcpy(msv.brief.name, h.savename, ARRAY_SIZE(msv.brief.name));
192 }
193 else
194 msv.isUnreadable = 1;
195
196 kclose(fil);
197 }
198 }
199
countcache1dfind(BUILDVFS_FIND_REC * f)200 static int countcache1dfind(BUILDVFS_FIND_REC *f)
201 {
202 int x = 0;
203 for (; f != nullptr; f = f->next)
204 ++x;
205 return x;
206 }
207
ReadSaveGameHeaders_Internal(void)208 static void ReadSaveGameHeaders_Internal(void)
209 {
210 static char const DefaultPath[] = "/", SavePattern[] = "*.esv";
211
212 BUILDVFS_FIND_REC *findfiles_default = klistpath(DefaultPath, SavePattern, BUILDVFS_FIND_FILE);
213
214 // potentially overallocating but programmatically simple
215 int const numfiles = countcache1dfind(findfiles_default);
216 size_t const internalsavesize = sizeof(menusave_t) * numfiles;
217
218 g_internalsaves = (menusave_t *)Xrealloc(g_internalsaves, internalsavesize);
219
220 for (int x = 0; x < numfiles; ++x)
221 g_internalsaves[x].clear();
222
223 g_numinternalsaves = 0;
224 ReadSaveGameHeaders_CACHE1D(findfiles_default);
225 klistfree(findfiles_default);
226
227 g_nummenusaves = 0;
228 for (int x = g_numinternalsaves-1; x >= 0; --x)
229 {
230 menusave_t & msv = g_internalsaves[x];
231 if (!msv.isUnreadable)
232 {
233 ++g_nummenusaves;
234 }
235 }
236 size_t const menusavesize = sizeof(menusave_t) * g_nummenusaves;
237
238 g_menusaves = (menusave_t *)Xrealloc(g_menusaves, menusavesize);
239
240 for (int x = 0; x < g_nummenusaves; ++x)
241 g_menusaves[x].clear();
242
243 for (int x = g_numinternalsaves-1, y = 0; x >= 0; --x)
244 {
245 menusave_t & msv = g_internalsaves[x];
246 if (!msv.isUnreadable)
247 {
248 g_menusaves[y++] = msv;
249 }
250 }
251
252 for (int x = g_numinternalsaves-1; x >= 0; --x)
253 {
254 char const * const path = g_internalsaves[x].brief.path;
255 int const pathlen = Bstrlen(path);
256 if (pathlen < 12)
257 continue;
258 char const * const fn = path + (pathlen-12);
259 if (fn[0] == 's' && fn[1] == 'a' && fn[2] == 'v' && fn[3] == 'e' &&
260 isdigit(fn[4]) && isdigit(fn[5]) && isdigit(fn[6]) && isdigit(fn[7]))
261 {
262 char number[5];
263 memcpy(number, fn+4, 4);
264 number[4] = '\0';
265 savecounter.count = Batoi(number)+1;
266 break;
267 }
268 }
269 }
270
ReadSaveGameHeaders(void)271 void ReadSaveGameHeaders(void)
272 {
273 ReadSaveGameHeaders_Internal();
274
275 if (!ud.autosavedeletion)
276 return;
277
278 bool didDelete = false;
279 int numautosaves = 0;
280 for (int x = 0; x < g_nummenusaves; ++x)
281 {
282 menusave_t & msv = g_menusaves[x];
283 if (!msv.isAutoSave)
284 continue;
285 if (numautosaves >= ud.maxautosaves)
286 {
287 G_DeleteSave(msv.brief);
288 didDelete = true;
289 }
290 ++numautosaves;
291 }
292
293 if (didDelete)
294 ReadSaveGameHeaders_Internal();
295 }
296
G_LoadSaveHeaderNew(char const * fn,savehead_t * saveh)297 int32_t G_LoadSaveHeaderNew(char const *fn, savehead_t *saveh)
298 {
299 buildvfs_kfd fil = kopen4loadfrommod(fn, 0);
300 if (fil == buildvfs_kfd_invalid)
301 return -1;
302
303 int32_t i = sv_loadheader(fil, 0, saveh);
304 if (i < 0)
305 goto corrupt;
306
307 int32_t screenshotofs;
308 if (kread(fil, &screenshotofs, 4) != 4)
309 goto corrupt;
310
311 walock[TILE_LOADSHOT] = CACHE1D_PERMANENT;
312 if (waloff[TILE_LOADSHOT] == 0)
313 g_cache.allocateBlock(&waloff[TILE_LOADSHOT], 320*200, &walock[TILE_LOADSHOT]);
314 tilesiz[TILE_LOADSHOT].x = 200;
315 tilesiz[TILE_LOADSHOT].y = 320;
316 if (screenshotofs)
317 {
318 if (kdfread_LZ4((char *)waloff[TILE_LOADSHOT], 320, 200, fil) != 200)
319 {
320 OSD_Printf("G_LoadSaveHeaderNew(): failed reading screenshot in \"%s\"\n", fn);
321 goto corrupt;
322 }
323
324 #if 0
325 // debug code to dump the screenshot
326 char scrbuf[BMAX_PATH];
327 if (G_ModDirSnprintf(scrbuf, sizeof(scrbuf), "%s.raw", fn) == 0)
328 {
329 buildvfs_FILE scrfil = buildvfs_fopen_write(scrbuf);
330 buildvfs_fwrite((char *)waloff[TILE_LOADSHOT], 320, 200, scrfil);
331 buildvfs_fclose(scrfil);
332 }
333 #endif
334 }
335 else
336 {
337 Bmemset((char *)waloff[TILE_LOADSHOT], 0, 320*200);
338 }
339 tileInvalidate(TILE_LOADSHOT, 0, 255);
340
341 kclose(fil);
342 return 0;
343
344 corrupt:
345 kclose(fil);
346 return 1;
347 }
348
349
350 static void sv_postudload();
351
352 // hack
353 static int different_user_map;
354
355 #include "sjson.h"
356
357 // XXX: keyboard input 'blocked' after load fail? (at least ESC?)
G_LoadPlayer(savebrief_t & sv)358 int32_t G_LoadPlayer(savebrief_t & sv)
359 {
360 if (sv.isExt)
361 {
362 int volume = -1;
363 int level = -1;
364 int skill = -1;
365
366 buildvfs_kfd const fil = kopen4loadfrommod(sv.path, 0);
367
368 if (fil != buildvfs_kfd_invalid)
369 {
370 savehead_t h;
371 int status = sv_loadheader(fil, 0, &h);
372 if (status >= 0)
373 {
374 volume = h.volnum;
375 level = h.levnum;
376 skill = h.skill;
377 }
378
379 kclose(fil);
380 }
381
382 char extfn[BMAX_PATH];
383 snprintf(extfn, ARRAY_SIZE(extfn), "%s.ext", sv.path);
384 buildvfs_kfd extfil = kopen4loadfrommod(extfn, 0);
385 if (extfil == buildvfs_kfd_invalid)
386 {
387 return -1;
388 }
389
390 int32_t len = kfilelength(extfil);
391 auto text = (char *)Xmalloc(len+1);
392 text[len] = '\0';
393
394 if (kread_and_test(extfil, text, len))
395 {
396 kclose(extfil);
397 Xfree(text);
398 return -1;
399 }
400
401 kclose(extfil);
402
403
404 sjson_context * ctx = sjson_create_context(0, 0, NULL);
405 sjson_node * root = sjson_decode(ctx, text);
406
407 Xfree(text);
408
409 if (volume == -1)
410 volume = sjson_get_int(root, "volume", volume);
411 if (level == -1)
412 level = sjson_get_int(root, "level", level);
413 if (skill == -1)
414 skill = sjson_get_int(root, "skill", skill);
415
416 if (volume == -1 || level == -1 || skill == -1)
417 {
418 sjson_destroy_context(ctx);
419 return -1;
420 }
421
422 sjson_node * players = sjson_find_member(root, "players");
423
424 int numplayers = sjson_child_count(players);
425
426 if (numplayers != ud.multimode)
427 {
428 P_DoQuote(QUOTE_SAVE_BAD_PLAYERS, g_player[myconnectindex].ps);
429
430 sjson_destroy_context(ctx);
431 return 1;
432 }
433
434
435 ud.returnvar[0] = level;
436 volume = VM_OnEventWithReturn(EVENT_VALIDATESTART, g_player[myconnectindex].ps->i, myconnectindex, volume);
437 level = ud.returnvar[0];
438
439
440 {
441 // CODEDUP from non-isExt branch, with simplifying assumptions
442
443 VM_OnEvent(EVENT_PRELOADGAME, g_player[screenpeek].ps->i, screenpeek);
444
445 ud.multimode = numplayers;
446
447 Net_WaitForServer();
448
449 FX_StopAllSounds();
450 S_ClearSoundLocks();
451
452 ud.m_volume_number = volume;
453 ud.m_level_number = level;
454 ud.m_player_skill = skill;
455
456 boardfilename[0] = '\0';
457
458 int const mapIdx = volume*MAXLEVELS + level;
459
460 if (boardfilename[0])
461 Bstrcpy(currentboardfilename, boardfilename);
462 else if (g_mapInfo[mapIdx].filename)
463 Bstrcpy(currentboardfilename, g_mapInfo[mapIdx].filename);
464
465
466 if (currentboardfilename[0])
467 {
468 // only setup art if map differs from previous
469 if (!previousboardfilename[0] || Bstrcmp(previousboardfilename, currentboardfilename))
470 {
471 artSetupMapArt(currentboardfilename);
472 Bstrcpy(previousboardfilename, currentboardfilename);
473 }
474 append_ext_UNSAFE(currentboardfilename, ".mhk");
475 engineLoadMHK(currentboardfilename);
476 }
477
478 currentboardfilename[0] = '\0';
479
480 // G_NewGame_EnterLevel();
481 }
482
483 {
484 // CODEDUP from G_NewGame
485
486 auto & p0 = *g_player[0].ps;
487
488 ready2send = 0;
489
490 ud.from_bonus = 0;
491 ud.last_level = -1;
492 ud.level_number = level;
493 ud.player_skill = skill;
494 ud.secretlevel = 0;
495 ud.skill_voice = -1;
496 ud.volume_number = volume;
497
498 g_lastAutoSaveArbitraryID = -1;
499
500 #ifdef EDUKE32_TOUCH_DEVICES
501 p0.zoom = 360;
502 #else
503 p0.zoom = 768;
504 #endif
505 p0.gm = 0;
506
507 Menu_Close(0);
508
509 Gv_ResetVars();
510 Gv_InitWeaponPointers();
511 Gv_RefreshPointers();
512 Gv_ResetSystemDefaults();
513
514 for (int i=0; i < (MAXVOLUMES*MAXLEVELS); i++)
515 G_FreeMapState(i);
516
517 if (ud.m_coop != 1)
518 p0.last_weapon = -1;
519
520 display_mirror = 0;
521 }
522
523 int p = 0;
524 for (sjson_node * player = sjson_first_child(players); player != nullptr; player = player->next)
525 {
526 playerdata_t * playerData = &g_player[p];
527 DukePlayer_t * ps = playerData->ps;
528 auto pSprite = &sprite[ps->i];
529
530 pSprite->extra = sjson_get_int(player, "extra", -1);
531 ps->max_player_health = sjson_get_int(player, "max_player_health", -1);
532
533 sjson_node * gotweapon = sjson_find_member(player, "gotweapon");
534 int w_end = min<int>(MAX_WEAPONS, sjson_child_count(gotweapon));
535 ps->gotweapon = 0;
536 for (int w = 0; w < w_end; ++w)
537 {
538 sjson_node * ele = sjson_find_element(gotweapon, w);
539 if (ele->tag == SJSON_BOOL && ele->bool_)
540 ps->gotweapon |= 1<<w;
541 }
542
543 /* bool flag_ammo_amount = */ sjson_get_int16s(ps->ammo_amount, MAX_WEAPONS, player, "ammo_amount");
544 /* bool flag_max_ammo_amount = */ sjson_get_int16s(ps->max_ammo_amount, MAX_WEAPONS, player, "max_ammo_amount");
545 /* bool flag_inv_amount = */ sjson_get_int16s(ps->inv_amount, GET_MAX, player, "inv_amount");
546
547 ps->max_shield_amount = sjson_get_int(player, "max_shield_amount", -1);
548
549 ps->curr_weapon = sjson_get_int(player, "curr_weapon", -1);
550 ps->subweapon = sjson_get_int(player, "subweapon", -1);
551 ps->inven_icon = sjson_get_int(player, "inven_icon", -1);
552
553 sjson_node * vars = sjson_find_member(player, "vars");
554
555 for (int j=0; j<g_gameVarCount; j++)
556 {
557 gamevar_t & var = aGameVars[j];
558
559 if (!(var.flags & GAMEVAR_SERIALIZE))
560 continue;
561
562 if ((var.flags & (GAMEVAR_PERPLAYER|GAMEVAR_PERACTOR)) != GAMEVAR_PERPLAYER)
563 continue;
564
565 Gv_SetVar(j, sjson_get_int(vars, var.szLabel, var.defaultValue), ps->i, p);
566 }
567
568 ++p;
569 }
570
571 {
572 sjson_node * vars = sjson_find_member(root, "vars");
573
574 for (int j=0; j<g_gameVarCount; j++)
575 {
576 gamevar_t & var = aGameVars[j];
577
578 if (!(var.flags & GAMEVAR_SERIALIZE))
579 continue;
580
581 if (var.flags & (GAMEVAR_PERPLAYER|GAMEVAR_PERACTOR))
582 continue;
583
584 Gv_SetVar(j, sjson_get_int(vars, var.szLabel, var.defaultValue));
585 }
586 }
587
588 sjson_destroy_context(ctx);
589
590
591 if (G_EnterLevel(MODE_GAME|MODE_EOL))
592 G_BackToMenu();
593
594
595 // postloadplayer(1);
596
597 // sv_postudload();
598
599
600 VM_OnEvent(EVENT_LOADGAME, g_player[screenpeek].ps->i, screenpeek);
601
602 return 0;
603 }
604
605 buildvfs_kfd const fil = kopen4loadfrommod(sv.path, 0);
606
607 if (fil == buildvfs_kfd_invalid)
608 return -1;
609
610 ready2send = 0;
611
612 savehead_t h;
613 int status = sv_loadheader(fil, 0, &h);
614
615 if (status < 0 || h.numplayers != ud.multimode)
616 {
617 if (status == -4 || status == -3 || status == 1)
618 P_DoQuote(QUOTE_SAVE_BAD_VERSION, g_player[myconnectindex].ps);
619 else if (h.numplayers != ud.multimode)
620 P_DoQuote(QUOTE_SAVE_BAD_PLAYERS, g_player[myconnectindex].ps);
621
622 kclose(fil);
623 ototalclock = totalclock;
624 ready2send = 1;
625
626 return 1;
627 }
628
629 VM_OnEvent(EVENT_PRELOADGAME, g_player[screenpeek].ps->i, screenpeek);
630
631 // some setup first
632 ud.multimode = h.numplayers;
633
634 if (numplayers > 1)
635 {
636 pub = NUMPAGES;
637 pus = NUMPAGES;
638 G_UpdateScreenArea();
639 G_DrawBackground();
640 menutext_center(100, "Loading...");
641 videoNextPage();
642 }
643
644 Net_WaitForServer();
645
646 FX_StopAllSounds();
647 S_ClearSoundLocks();
648
649 // non-"m_" fields will be loaded from svgm_udnetw
650 ud.m_volume_number = h.volnum;
651 ud.m_level_number = h.levnum;
652 ud.m_player_skill = h.skill;
653
654 EDUKE32_STATIC_ASSERT(sizeof(h.boardfn) < sizeof(boardfilename));
655 different_user_map = Bstrncmp(boardfilename, h.boardfn, sizeof(h.boardfn));
656 // NOTE: size arg is (unconventionally) that of the source, it being smaller.
657 Bstrncpyz(boardfilename, h.boardfn, sizeof(h.boardfn) /*!*/);
658
659 int const mapIdx = h.volnum*MAXLEVELS + h.levnum;
660
661 if (boardfilename[0])
662 Bstrcpy(currentboardfilename, boardfilename);
663 else if (g_mapInfo[mapIdx].filename)
664 Bstrcpy(currentboardfilename, g_mapInfo[mapIdx].filename);
665
666 if (currentboardfilename[0])
667 {
668 // only setup art if map differs from previous
669 if (!previousboardfilename[0] || Bstrcmp(previousboardfilename, currentboardfilename))
670 {
671 artSetupMapArt(currentboardfilename);
672 Bstrcpy(previousboardfilename, currentboardfilename);
673 }
674 append_ext_UNSAFE(currentboardfilename, ".mhk");
675 engineLoadMHK(currentboardfilename);
676 }
677
678 Bmemcpy(currentboardfilename, boardfilename, BMAX_PATH);
679
680 if (status == 2)
681 G_NewGame_EnterLevel();
682 else if ((status = sv_loadsnapshot(fil, 0, &h))) // read the rest...
683 {
684 // in theory, we could load into an initial dump first and trivially
685 // recover if things go wrong...
686 Bsprintf(tempbuf, "Loading save game file \"%s\" failed (code %d), cannot recover.", sv.path, status);
687 G_GameExit(tempbuf);
688 }
689
690 sv_postudload(); // ud.m_XXX = ud.XXX
691 VM_OnEvent(EVENT_LOADGAME, g_player[screenpeek].ps->i, screenpeek);
692 kclose(fil);
693
694 return 0;
695 }
696
697 ////////// TIMER SAVING/RESTORING //////////
698
699 static struct {
700 ClockTicks totalclock, totalclocklock; // engine
701 ClockTicks ototalclock, lockclock; // game
702 } g_timers;
703
G_SaveTimers(void)704 static void G_SaveTimers(void)
705 {
706 g_timers.totalclock = totalclock;
707 g_timers.totalclocklock = totalclocklock;
708 g_timers.ototalclock = ototalclock;
709 g_timers.lockclock = lockclock;
710 }
711
G_RestoreTimers(void)712 static void G_RestoreTimers(void)
713 {
714 totalclock = g_timers.totalclock;
715 totalclocklock = g_timers.totalclocklock;
716 ototalclock = g_timers.ototalclock;
717 lockclock = g_timers.lockclock;
718 }
719
720 //////////
721
G_DeleteSave(savebrief_t const & sv)722 void G_DeleteSave(savebrief_t const & sv)
723 {
724 if (!sv.isValid())
725 return;
726
727 char temp[BMAX_PATH];
728
729 if (G_ModDirSnprintf(temp, sizeof(temp), "%s", sv.path))
730 {
731 OSD_Printf("G_SavePlayer: file name \"%s\" too long\n", sv.path);
732 return;
733 }
734
735 buildvfs_unlink(temp);
736 Bstrcat(temp, ".ext");
737 buildvfs_unlink(temp);
738 }
739
G_DeleteOldSaves(void)740 void G_DeleteOldSaves(void)
741 {
742 ReadSaveGameHeaders();
743
744 for (int x = 0; x < g_numinternalsaves; ++x)
745 {
746 menusave_t const & msv = g_internalsaves[x];
747 if (msv.isOldVer || msv.isUnreadable)
748 G_DeleteSave(msv.brief);
749 }
750 }
751
G_CountOldSaves(void)752 uint16_t G_CountOldSaves(void)
753 {
754 ReadSaveGameHeaders();
755
756 int bad = 0;
757 for (int x = 0; x < g_numinternalsaves; ++x)
758 {
759 menusave_t const & msv = g_internalsaves[x];
760 if (msv.isOldVer || msv.isUnreadable)
761 ++bad;
762 }
763
764 return bad;
765 }
766
G_SavePlayer(savebrief_t & sv,bool isAutoSave)767 int32_t G_SavePlayer(savebrief_t & sv, bool isAutoSave)
768 {
769 #ifdef __ANDROID__
770 G_SavePalette();
771 #endif
772
773 G_SaveTimers();
774
775 Net_WaitForServer();
776 ready2send = 0;
777
778 char fn[BMAX_PATH];
779
780 errno = 0;
781 buildvfs_FILE fil;
782
783 if (sv.isValid())
784 {
785 if (G_ModDirSnprintf(fn, sizeof(fn), "%s", sv.path))
786 {
787 OSD_Printf("G_SavePlayer: file name \"%s\" too long\n", sv.path);
788 goto saveproblem;
789 }
790 fil = buildvfs_fopen_write(fn);
791 }
792 else
793 {
794 static char const SaveName[] = "save0000.esv";
795 int const len = G_ModDirSnprintfLite(fn, ARRAY_SIZE(fn), SaveName);
796 if (len >= ARRAY_SSIZE(fn)-1)
797 {
798 OSD_Printf("G_SavePlayer: could not form automatic save path\n");
799 goto saveproblem;
800 }
801 char * zeros = fn + (len-8);
802 fil = savecounter.opennextfile(fn, zeros);
803 savecounter.count++;
804 // don't copy the mod dir into sv.path
805 Bstrcpy(sv.path, fn + (len-(ARRAY_SIZE(SaveName)-1)));
806 }
807
808 if (!fil)
809 {
810 OSD_Printf("G_SavePlayer: failed opening \"%s\" for writing: %s\n",
811 fn, strerror(errno));
812 goto saveproblem;
813 }
814
815 sv.isExt = 0;
816
817 // temporary hack
818 ud.user_map = G_HaveUserMap();
819
820 #ifdef POLYMER
821 if (videoGetRenderMode() == REND_POLYMER)
822 polymer_resetlights();
823 #endif
824
825 VM_OnEvent(EVENT_SAVEGAME, g_player[myconnectindex].ps->i, myconnectindex);
826
827 portableBackupSave(sv.path, sv.name, ud.last_stateless_volume, ud.last_stateless_level);
828
829 // SAVE!
830 sv_saveandmakesnapshot(fil, sv.name, 0, 0, 0, 0, isAutoSave);
831
832 buildvfs_fclose(fil);
833
834 if (!g_netServer && ud.multimode < 2)
835 {
836 OSD_Printf("Saved: %s\n", fn);
837 Bstrcpy(apStrings[QUOTE_RESERVED4], "Game Saved");
838 P_DoQuote(QUOTE_RESERVED4, g_player[myconnectindex].ps);
839 }
840
841 ready2send = 1;
842 Net_WaitForServer();
843
844 G_RestoreTimers();
845
846 VM_OnEvent(EVENT_POSTSAVEGAME, g_player[myconnectindex].ps->i, myconnectindex);
847
848 return 0;
849
850 saveproblem:
851 ready2send = 1;
852 Net_WaitForServer();
853
854 G_RestoreTimers();
855
856 return -1;
857 }
858
G_LoadPlayerMaybeMulti(savebrief_t & sv)859 int32_t G_LoadPlayerMaybeMulti(savebrief_t & sv)
860 {
861 if (g_netServer || ud.multimode > 1)
862 {
863 Bstrcpy(apStrings[QUOTE_RESERVED4], "Multiplayer Loading Not Yet Supported");
864 P_DoQuote(QUOTE_RESERVED4, g_player[myconnectindex].ps);
865
866 // g_player[myconnectindex].ps->gm = MODE_GAME;
867 return 127;
868 }
869 else
870 {
871 int32_t c = G_LoadPlayer(sv);
872 if (c == 0)
873 g_player[myconnectindex].ps->gm = MODE_GAME;
874 return c;
875 }
876 }
877
G_SavePlayerMaybeMulti(savebrief_t & sv,bool isAutoSave)878 void G_SavePlayerMaybeMulti(savebrief_t & sv, bool isAutoSave)
879 {
880 CONFIG_WriteSetup(2);
881
882 if (g_netServer || ud.multimode > 1)
883 {
884 Bstrcpy(apStrings[QUOTE_RESERVED4], "Multiplayer Saving Not Yet Supported");
885 P_DoQuote(QUOTE_RESERVED4, g_player[myconnectindex].ps);
886 }
887 else
888 {
889 G_SavePlayer(sv, isAutoSave);
890 }
891 }
892
893 ////////// GENERIC SAVING/LOADING SYSTEM //////////
894
895 typedef struct dataspec_
896 {
897 uint32_t flags;
898 void * const ptr;
899 uint32_t size;
900 intptr_t cnt;
901 } dataspec_t;
902
903 typedef struct dataspec_gv_
904 {
905 uint32_t flags;
906 void * ptr;
907 uint32_t size;
908 intptr_t cnt;
909 } dataspec_gv_t;
910
911 #define SV_DEFAULTCOMPRTHRES 8
912 static uint8_t savegame_diffcompress; // 0:none, 1:Ken's LZW in cache1d.c
913 static uint8_t savegame_comprthres;
914
915
916 #define DS_DYNAMIC 1 // dereference .ptr one more time
917 #define DS_STRING 2
918 #define DS_CMP 4
919 // 8
920 #define DS_CNT(x) ((sizeof(x))<<3) // .cnt is pointer to...
921 #define DS_CNT16 16
922 #define DS_CNT32 32
923 #define DS_CNTMASK (8|DS_CNT16|DS_CNT32|64)
924 // 64
925 #define DS_LOADFN 128 // .ptr is function that is run when loading
926 #define DS_SAVEFN 256 // .ptr is function that is run when saving
927 #define DS_NOCHK 1024 // don't check for diffs (and don't write out in dump) since assumed constant throughout demo
928 #define DS_PROTECTFN 512
929 #define DS_END (0x70000000)
930
ds_getcnt(const dataspec_t * spec)931 static int32_t ds_getcnt(const dataspec_t *spec)
932 {
933 int cnt = -1;
934
935 switch (spec->flags & DS_CNTMASK)
936 {
937 case 0: cnt = spec->cnt; break;
938 case DS_CNT16: cnt = *((int16_t *)spec->cnt); break;
939 case DS_CNT32: cnt = *((int32_t *)spec->cnt); break;
940 }
941
942 return cnt;
943 }
944
ds_get(const dataspec_t * spec,void ** ptr,int32_t * cnt)945 static inline void ds_get(const dataspec_t *spec, void **ptr, int32_t *cnt)
946 {
947 *cnt = ds_getcnt(spec);
948 *ptr = (spec->flags & DS_DYNAMIC) ? *((void **)spec->ptr) : spec->ptr;
949 }
950
951 // write state to file and/or to dump
writespecdata(const dataspec_t * spec,buildvfs_FILE fil,uint8_t * dump)952 static uint8_t *writespecdata(const dataspec_t *spec, buildvfs_FILE fil, uint8_t *dump)
953 {
954 for (; spec->flags != DS_END; spec++)
955 {
956 if (spec->flags & (DS_SAVEFN|DS_LOADFN))
957 {
958 if (spec->flags & DS_SAVEFN)
959 (*(void (*)(void))spec->ptr)();
960 continue;
961 }
962
963 if (!fil && (spec->flags & (DS_NOCHK|DS_CMP|DS_STRING)))
964 continue;
965 else if (spec->flags & DS_STRING)
966 {
967 buildvfs_fwrite(spec->ptr, Bstrlen((const char *)spec->ptr), 1, fil); // not null-terminated!
968 continue;
969 }
970
971 void * ptr;
972 int32_t cnt;
973
974 ds_get(spec, &ptr, &cnt);
975
976 if (cnt < 0)
977 {
978 OSD_Printf("wsd: cnt=%d, f=0x%x.\n", cnt, spec->flags);
979 continue;
980 }
981
982 if (!ptr || !cnt)
983 continue;
984
985 if (fil)
986 {
987 if ((spec->flags & DS_CMP) || ((spec->flags & DS_CNTMASK) == 0 && spec->size * cnt <= savegame_comprthres))
988 buildvfs_fwrite(ptr, spec->size, cnt, fil);
989 else
990 dfwrite_LZ4((void *)ptr, spec->size, cnt, fil);
991 }
992
993 if (dump && (spec->flags & (DS_NOCHK|DS_CMP)) == 0)
994 {
995 Bmemcpy(dump, ptr, spec->size * cnt);
996 dump += spec->size * cnt;
997 }
998 }
999 return dump;
1000 }
1001
1002 // let havedump=dumpvar&&*dumpvar
1003 // (fil>=0 && havedump): first restore dump from file, then restore state from dump
1004 // (fil<0 && havedump): only restore state from dump
1005 // (fil>=0 && !havedump): only restore state from file
readspecdata(const dataspec_t * spec,buildvfs_kfd fil,uint8_t ** dumpvar)1006 static int32_t readspecdata(const dataspec_t *spec, buildvfs_kfd fil, uint8_t **dumpvar)
1007 {
1008 uint8_t * dump = dumpvar ? *dumpvar : NULL;
1009 auto const sptr = spec;
1010
1011 for (; spec->flags != DS_END; spec++)
1012 {
1013 if (fil == buildvfs_kfd_invalid && spec->flags & (DS_NOCHK|DS_STRING|DS_CMP)) // we're updating
1014 continue;
1015
1016 if (spec->flags & (DS_LOADFN|DS_SAVEFN))
1017 {
1018 if (spec->flags & DS_LOADFN)
1019 (*(void (*)())spec->ptr)();
1020 continue;
1021 }
1022
1023 if (spec->flags & (DS_STRING|DS_CMP)) // DS_STRING and DS_CMP is for static data only
1024 {
1025 static char cmpstrbuf[32];
1026 int const siz = (spec->flags & DS_STRING) ? Bstrlen((const char *)spec->ptr) : spec->size * spec->cnt;
1027 int const ksiz = kread(fil, cmpstrbuf, siz);
1028
1029 if (ksiz != siz || Bmemcmp(spec->ptr, cmpstrbuf, siz))
1030 {
1031 OSD_Printf("rsd: spec=%s, idx=%d:\n", (char *)sptr->ptr, (int32_t)(spec-sptr));
1032
1033 if (ksiz!=siz)
1034 OSD_Printf(" kread returned %d, expected %d.\n", ksiz, siz);
1035 else
1036 OSD_Printf(" sp->ptr and cmpstrbuf not identical!\n");
1037
1038 return -1;
1039 }
1040 continue;
1041 }
1042
1043 void * ptr;
1044 int32_t cnt;
1045
1046 ds_get(spec, &ptr, &cnt);
1047
1048 if (cnt < 0)
1049 {
1050 OSD_Printf("rsd: cnt<0... wtf?\n");
1051 return -1;
1052 }
1053
1054 if (!ptr || !cnt)
1055 continue;
1056
1057 if (fil != buildvfs_kfd_invalid)
1058 {
1059 auto const mem = (dump && (spec->flags & DS_NOCHK) == 0) ? dump : (uint8_t *)ptr;
1060 bool const comp = !((spec->flags & DS_CNTMASK) == 0 && spec->size * cnt <= savegame_comprthres);
1061 int const siz = comp ? cnt : cnt * spec->size;
1062 int const ksiz = comp ? kdfread_LZ4(mem, spec->size, siz, fil) : kread(fil, mem, siz);
1063
1064 if (ksiz != siz)
1065 {
1066 OSD_Printf("rsd: spec=%s, idx=%d, mem=%p\n", (char *)sptr->ptr, (int32_t)(spec - sptr), mem);
1067 OSD_Printf(" (%s): read %d, expected %d!\n",
1068 ((spec->flags & DS_CNTMASK) == 0 && spec->size * cnt <= savegame_comprthres) ? "uncompressed" : "compressed", ksiz, siz);
1069
1070 if (ksiz == -1)
1071 OSD_Printf(" read: %s\n", strerror(errno));
1072
1073 return -1;
1074 }
1075 }
1076
1077 if (dump && (spec->flags & DS_NOCHK) == 0)
1078 {
1079 Bmemcpy(ptr, dump, spec->size * cnt);
1080 dump += spec->size * cnt;
1081 }
1082 }
1083
1084 if (dumpvar)
1085 *dumpvar = dump;
1086
1087 return 0;
1088 }
1089
1090 #define UINT(bits) uint##bits##_t
1091 #define BYTES(bits) (bits>>3)
1092 #define VAL(bits,p) (*(UINT(bits) const *)(p))
1093 #define WVAL(bits,p) (*(UINT(bits) *)(p))
1094
docmpsd(const void * ptr,void * dump,uint32_t size,uint32_t cnt,uint8_t ** diffvar)1095 static void docmpsd(const void *ptr, void *dump, uint32_t size, uint32_t cnt, uint8_t **diffvar)
1096 {
1097 uint8_t *retdiff = *diffvar;
1098
1099 // Hail to the C preprocessor, baby!
1100 #define CPSINGLEVAL(Datbits) \
1101 if (VAL(Datbits, ptr) != VAL(Datbits, dump)) \
1102 { \
1103 WVAL(Datbits, retdiff) = WVAL(Datbits, dump) = VAL(Datbits, ptr); \
1104 *diffvar = retdiff + BYTES(Datbits); \
1105 }
1106
1107 if (cnt == 1)
1108 switch (size)
1109 {
1110 case 8: CPSINGLEVAL(64); return;
1111 case 4: CPSINGLEVAL(32); return;
1112 case 2: CPSINGLEVAL(16); return;
1113 case 1: CPSINGLEVAL(8); return;
1114 }
1115
1116 #define CPELTS(Idxbits, Datbits) \
1117 do \
1118 { \
1119 for (int i = 0; i < nelts; i++) \
1120 { \
1121 if (*p != *op) \
1122 { \
1123 *op = *p; \
1124 WVAL(Idxbits, retdiff) = i; \
1125 retdiff += BYTES(Idxbits); \
1126 WVAL(Datbits, retdiff) = *p; \
1127 retdiff += BYTES(Datbits); \
1128 } \
1129 p++; \
1130 op++; \
1131 } \
1132 WVAL(Idxbits, retdiff) = -1; \
1133 retdiff += BYTES(Idxbits); \
1134 } while (0)
1135
1136 #define CPDATA(Datbits) \
1137 do \
1138 { \
1139 auto p = (UINT(Datbits) const *)ptr; \
1140 auto op = (UINT(Datbits) *)dump; \
1141 int nelts = tabledivide32_noinline(size * cnt, BYTES(Datbits)); \
1142 if (nelts > 65536) \
1143 CPELTS(32, Datbits); \
1144 else if (nelts > 256) \
1145 CPELTS(16, Datbits); \
1146 else \
1147 CPELTS(8, Datbits); \
1148 } while (0)
1149
1150 if (size == 8)
1151 CPDATA(64);
1152 else if ((size & 3) == 0)
1153 CPDATA(32);
1154 else if ((size & 1) == 0)
1155 CPDATA(16);
1156 else
1157 CPDATA(8);
1158
1159 *diffvar = retdiff;
1160
1161 #undef CPELTS
1162 #undef CPSINGLEVAL
1163 #undef CPDATA
1164 }
1165
1166 // get the number of elements to be monitored for changes
getnumvar(const dataspec_t * spec)1167 static int32_t getnumvar(const dataspec_t *spec)
1168 {
1169 int n = 0;
1170 for (; spec->flags != DS_END; spec++)
1171 if (!(spec->flags & (DS_STRING|DS_CMP|DS_NOCHK|DS_SAVEFN|DS_LOADFN)))
1172 ++n;
1173 return n;
1174 }
1175
1176 // update dump at *dumpvar with new state and write diff to *diffvar
cmpspecdata(const dataspec_t * spec,uint8_t ** dumpvar,uint8_t ** diffvar)1177 static void cmpspecdata(const dataspec_t *spec, uint8_t **dumpvar, uint8_t **diffvar)
1178 {
1179 uint8_t * dump = *dumpvar;
1180 uint8_t * diff = *diffvar;
1181 int nbytes = (getnumvar(spec) + 7) >> 3;
1182 int const slen = Bstrlen((const char *)spec->ptr);
1183
1184 Bmemcpy(diff, spec->ptr, slen);
1185 diff += slen;
1186
1187 while (nbytes--)
1188 *(diff++) = 0; // the bitmap of indices which elements of spec have changed go here
1189
1190 int eltnum = 0;
1191
1192 for (spec++; spec->flags!=DS_END; spec++)
1193 {
1194 if ((spec->flags&(DS_NOCHK|DS_STRING|DS_CMP)))
1195 continue;
1196
1197 if (spec->flags&(DS_LOADFN|DS_SAVEFN))
1198 {
1199 if ((spec->flags&(DS_PROTECTFN))==0)
1200 (*(void (*)())spec->ptr)();
1201 continue;
1202 }
1203
1204 void * ptr;
1205 int32_t cnt;
1206
1207 ds_get(spec, &ptr, &cnt);
1208
1209 if (cnt < 0)
1210 {
1211 OSD_Printf("csd: cnt=%d, f=0x%x\n", cnt, spec->flags);
1212 continue;
1213 }
1214
1215 uint8_t * const tmptr = diff;
1216
1217 docmpsd(ptr, dump, spec->size, cnt, &diff);
1218
1219 if (diff != tmptr)
1220 (*diffvar + slen)[eltnum>>3] |= 1<<(eltnum&7);
1221
1222 dump += spec->size*cnt;
1223 eltnum++;
1224 }
1225
1226 *diffvar = diff;
1227 *dumpvar = dump;
1228 }
1229
1230 #define VALOFS(bits,p,ofs) (*(((UINT(bits) *)(p)) + (ofs)))
1231
1232 // apply diff to dump, not to state! state is restored from dump afterwards.
applydiff(const dataspec_t * spec,uint8_t ** dumpvar,uint8_t ** diffvar)1233 static int32_t applydiff(const dataspec_t *spec, uint8_t **dumpvar, uint8_t **diffvar)
1234 {
1235 uint8_t * dump = *dumpvar;
1236 uint8_t * diff = *diffvar;
1237 int const nbytes = (getnumvar(spec)+7)>>3;
1238 int const slen = Bstrlen((const char *)spec->ptr);
1239
1240 if (Bmemcmp(diff, spec->ptr, slen)) // check STRING magic (sync check)
1241 return 1;
1242
1243 diff += slen+nbytes;
1244
1245 int eltnum = -1;
1246 for (spec++; spec->flags != DS_END; spec++)
1247 {
1248 if ((spec->flags & (DS_NOCHK|DS_STRING|DS_CMP|DS_LOADFN|DS_SAVEFN)))
1249 continue;
1250
1251 int const cnt = ds_getcnt(spec);
1252 if (cnt < 0) return 1;
1253
1254 eltnum++;
1255 if (((*diffvar+slen)[eltnum>>3] & pow2char[eltnum&7]) == 0)
1256 {
1257 dump += spec->size * cnt;
1258 continue;
1259 }
1260
1261 // ----------
1262 #define CPSINGLEVAL(Datbits) \
1263 WVAL(Datbits, dump) = VAL(Datbits, diff); \
1264 diff += BYTES(Datbits); \
1265 dump += BYTES(Datbits)
1266
1267 if (cnt == 1)
1268 {
1269 switch (spec->size)
1270 {
1271 case 8: CPSINGLEVAL(64); continue;
1272 case 4: CPSINGLEVAL(32); continue;
1273 case 2: CPSINGLEVAL(16); continue;
1274 case 1: CPSINGLEVAL(8); continue;
1275 }
1276 }
1277
1278 #define CPELTS(Idxbits, Datbits) \
1279 do \
1280 { \
1281 UINT(Idxbits) idx; \
1282 goto readidx_##Idxbits##_##Datbits; \
1283 do \
1284 { \
1285 VALOFS(Datbits, dump, idx) = VAL(Datbits, diff); \
1286 diff += BYTES(Datbits); \
1287 readidx_##Idxbits##_##Datbits: \
1288 idx = VAL(Idxbits, diff); \
1289 diff += BYTES(Idxbits); \
1290 } while ((int##Idxbits##_t)idx != -1); \
1291 } while (0)
1292
1293 #define CPDATA(Datbits) \
1294 do \
1295 { \
1296 int const elts = tabledivide32_noinline(spec->size * cnt, BYTES(Datbits)); \
1297 if (elts > 65536) \
1298 CPELTS(32, Datbits); \
1299 else if (elts > 256) \
1300 CPELTS(16, Datbits); \
1301 else \
1302 CPELTS(8, Datbits); \
1303 } while (0)
1304
1305 if (spec->size == 8)
1306 CPDATA(64);
1307 else if ((spec->size & 3) == 0)
1308 CPDATA(32);
1309 else if ((spec->size & 1) == 0)
1310 CPDATA(16);
1311 else
1312 CPDATA(8);
1313 dump += spec->size * cnt;
1314 // ----------
1315
1316 #undef CPELTS
1317 #undef CPSINGLEVAL
1318 #undef CPDATA
1319 }
1320
1321 *diffvar = diff;
1322 *dumpvar = dump;
1323 return 0;
1324 }
1325
1326 #undef VAL
1327 #undef VALOFS
1328 #undef BYTES
1329 #undef UINT
1330
1331 // calculate size needed for dump
calcsz(const dataspec_t * spec)1332 static uint32_t calcsz(const dataspec_t *spec)
1333 {
1334 uint32_t dasiz = 0;
1335
1336 for (; spec->flags != DS_END; spec++)
1337 {
1338 // DS_STRINGs are used as sync checks in the diffs but not in the dump
1339 if ((spec->flags & (DS_CMP|DS_NOCHK|DS_SAVEFN|DS_LOADFN|DS_STRING)))
1340 continue;
1341
1342 int const cnt = ds_getcnt(spec);
1343
1344 if (cnt <= 0)
1345 continue;
1346
1347 dasiz += cnt * spec->size;
1348 }
1349
1350 return dasiz;
1351 }
1352
1353 #ifdef USE_OPENGL
1354 static void sv_prespriteextsave();
1355 static void sv_postspriteext();
1356 #endif
1357 static void sv_prelabelsave();
1358 static void sv_prelabelload();
1359 static void sv_preactorsave();
1360 static void sv_postactordata();
1361 static void sv_preanimateptrsave();
1362 static void sv_postanimateptr();
1363 static void sv_restsave();
1364 static void sv_restload();
1365 static void sv_preprojectilesave();
1366 static void sv_postprojectilesave();
1367 static void sv_preprojectileload();
1368 static void sv_postprojectileload();
1369
1370 static projectile_t *savegame_projectiledata;
1371 static uint8_t savegame_projectiles[(MAXTILES + 7) >> 3];
1372 static int32_t savegame_projectilecnt = 0;
1373
1374 static int32_t savegame_labelcnt;
1375 static char *savegame_labels;
1376
1377 #define SVARDATALEN \
1378 ((sizeof(g_player[0].user_name)+sizeof(g_player[0].pcolor)+sizeof(g_player[0].pteam) \
1379 +sizeof(g_player[0].frags)+sizeof(DukePlayer_t))*MAXPLAYERS)
1380
1381 static uint8_t savegame_restdata[SVARDATALEN];
1382
1383 static char svgm_udnetw_string [] = "blK:udnt";
1384 static const dataspec_t svgm_udnetw[] =
1385 {
1386 { DS_STRING, (void *)svgm_udnetw_string, 0, 1 },
1387 { 0, &ud.multimode, sizeof(ud.multimode), 1 },
1388 { 0, &g_playerSpawnCnt, sizeof(g_playerSpawnCnt), 1 },
1389 { 0, &g_playerSpawnPoints, sizeof(g_playerSpawnPoints), 1 },
1390
1391 { DS_NOCHK, &ud.volume_number, sizeof(ud.volume_number), 1 },
1392 { DS_NOCHK, &ud.level_number, sizeof(ud.level_number), 1 },
1393 { DS_NOCHK, &ud.user_map, sizeof(ud.user_map), 1 },
1394 { DS_NOCHK, &ud.player_skill, sizeof(ud.player_skill), 1 },
1395 { DS_NOCHK, &ud.music_episode, sizeof(ud.music_episode), 1 },
1396 { DS_NOCHK, &ud.music_level, sizeof(ud.music_level), 1 },
1397
1398 { DS_NOCHK, &ud.from_bonus, sizeof(ud.from_bonus), 1 },
1399 { DS_NOCHK, &ud.secretlevel, sizeof(ud.secretlevel), 1 },
1400 { DS_NOCHK, &ud.respawn_monsters, sizeof(ud.respawn_monsters), 1 },
1401 { DS_NOCHK, &ud.respawn_items, sizeof(ud.respawn_items), 1 },
1402 { DS_NOCHK, &ud.respawn_inventory, sizeof(ud.respawn_inventory), 1 },
1403 { 0, &ud.god, sizeof(ud.god), 1 },
1404 { 0, &ud.auto_run, sizeof(ud.auto_run), 1 },
1405 // { DS_NOCHK, &ud.crosshair, sizeof(ud.crosshair), 1 },
1406 { DS_NOCHK, &ud.monsters_off, sizeof(ud.monsters_off), 1 },
1407 { DS_NOCHK, &ud.last_level, sizeof(ud.last_level), 1 },
1408 { 0, &ud.eog, sizeof(ud.eog), 1 },
1409 { DS_NOCHK, &ud.coop, sizeof(ud.coop), 1 },
1410 { DS_NOCHK, &ud.marker, sizeof(ud.marker), 1 },
1411 { DS_NOCHK, &ud.ffire, sizeof(ud.ffire), 1 },
1412 { DS_NOCHK, &ud.noexits, sizeof(ud.noexits), 1 },
1413 { DS_NOCHK, &ud.playerai, sizeof(ud.playerai), 1 },
1414 { 0, &ud.pause_on, sizeof(ud.pause_on), 1 },
1415 { DS_NOCHK, ¤tboardfilename[0], BMAX_PATH, 1 },
1416 // { DS_LOADFN, (void *)&sv_postudload, 0, 1 },
1417 { 0, connectpoint2, sizeof(connectpoint2), 1 },
1418 { 0, &randomseed, sizeof(randomseed), 1 },
1419 { 0, &g_globalRandom, sizeof(g_globalRandom), 1 },
1420 // { 0, &lockclock_dummy, sizeof(lockclock), 1 },
1421 { DS_END, 0, 0, 0 }
1422 };
1423
1424 #if !defined DEBUG_MAIN_ARRAYS
1425 # define DS_MAINAR DS_DYNAMIC
1426 #else
1427 # define DS_MAINAR 0
1428 #endif
1429
1430 static char svgm_secwsp_string [] = "blK:swsp";
1431 static const dataspec_t svgm_secwsp[] =
1432 {
1433 { DS_STRING, (void *)svgm_secwsp_string, 0, 1 },
1434 { DS_NOCHK, &numwalls, sizeof(numwalls), 1 },
1435 { DS_MAINAR|DS_CNT(numwalls), &wall, sizeof(walltype), (intptr_t)&numwalls },
1436 { DS_NOCHK, &numsectors, sizeof(numsectors), 1 },
1437 { DS_MAINAR|DS_CNT(numsectors), §or, sizeof(sectortype), (intptr_t)&numsectors },
1438 { DS_MAINAR, &sprite, sizeof(spritetype), MAXSPRITES },
1439 #ifdef YAX_ENABLE
1440 { DS_NOCHK, &numyaxbunches, sizeof(numyaxbunches), 1 },
1441 # if !defined NEW_MAP_FORMAT
1442 { DS_CNT(numsectors), yax_bunchnum, sizeof(yax_bunchnum[0]), (intptr_t)&numsectors },
1443 { DS_CNT(numwalls), yax_nextwall, sizeof(yax_nextwall[0]), (intptr_t)&numwalls },
1444 # endif
1445 { DS_LOADFN|DS_PROTECTFN, (void *)&sv_postyaxload, 0, 1 },
1446 #endif
1447 { 0, &Numsprites, sizeof(Numsprites), 1 },
1448 { 0, &tailspritefree, sizeof(tailspritefree), 1 },
1449 { 0, &headspritesect[0], sizeof(headspritesect[0]), MAXSECTORS+1 },
1450 { 0, &prevspritesect[0], sizeof(prevspritesect[0]), MAXSPRITES },
1451 { 0, &nextspritesect[0], sizeof(nextspritesect[0]), MAXSPRITES },
1452 { 0, &headspritestat[0], sizeof(headspritestat[0]), MAXSTATUS+1 },
1453 { 0, &prevspritestat[0], sizeof(prevspritestat[0]), MAXSPRITES },
1454 { 0, &nextspritestat[0], sizeof(nextspritestat[0]), MAXSPRITES },
1455 #ifdef USE_OPENGL
1456 { DS_SAVEFN, (void *)&sv_prespriteextsave, 0, 1 },
1457 #endif
1458 { DS_MAINAR, &spriteext, sizeof(spriteext_t), MAXSPRITES+MAXUNIQHUDID },
1459 #ifndef NEW_MAP_FORMAT
1460 { DS_MAINAR, &wallext, sizeof(wallext_t), MAXWALLS },
1461 #endif
1462 #ifdef USE_OPENGL
1463 { DS_SAVEFN|DS_LOADFN, (void *)&sv_postspriteext, 0, 1 },
1464 #endif
1465 { DS_NOCHK, &g_cyclerCnt, sizeof(g_cyclerCnt), 1 },
1466 { DS_CNT(g_cyclerCnt), &g_cyclers[0][0], sizeof(g_cyclers[0]), (intptr_t)&g_cyclerCnt },
1467 { DS_NOCHK, &g_animWallCnt, sizeof(g_animWallCnt), 1 },
1468 { DS_CNT(g_animWallCnt), &animwall, sizeof(animwall[0]), (intptr_t)&g_animWallCnt },
1469 { DS_NOCHK, &g_mirrorCount, sizeof(g_mirrorCount), 1 },
1470 { DS_NOCHK, &g_mirrorWall[0], sizeof(g_mirrorWall[0]), ARRAY_SIZE(g_mirrorWall) },
1471 { DS_NOCHK, &g_mirrorSector[0], sizeof(g_mirrorSector[0]), ARRAY_SIZE(g_mirrorSector) },
1472 // projectiles
1473 { 0, &SpriteProjectile[0], sizeof(projectile_t), MAXSPRITES },
1474 { 0, &everyothertime, sizeof(everyothertime), 1 },
1475 { DS_END, 0, 0, 0 }
1476 };
1477
1478 static char svgm_script_string [] = "blK:scri";
1479 static const dataspec_t svgm_script[] =
1480 {
1481 { DS_STRING, (void *)svgm_script_string, 0, 1 },
1482 { DS_SAVEFN, (void *) &sv_prelabelsave, 0, 1 },
1483 { DS_NOCHK, &savegame_labelcnt, sizeof(savegame_labelcnt), 1},
1484 { DS_LOADFN, (void *) &sv_prelabelload, 0, 1 },
1485 { DS_DYNAMIC|DS_CNT(savegame_labelcnt), &savegame_labels, 1<<6, (intptr_t)&savegame_labelcnt },
1486 { DS_SAVEFN, (void *) &sv_preprojectilesave, 0, 1 },
1487 { 0, savegame_projectiles, sizeof(uint8_t), (MAXTILES + 7) >> 3 },
1488 { DS_LOADFN, (void *) &sv_preprojectileload, 0, 1 },
1489 { DS_DYNAMIC|DS_CNT(savegame_projectilecnt), &savegame_projectiledata, sizeof(projectile_t), (intptr_t)&savegame_projectilecnt },
1490 { DS_SAVEFN, (void *) &sv_postprojectilesave, 0, 1 },
1491 { DS_LOADFN, (void *) &sv_postprojectileload, 0, 1 },
1492 { DS_SAVEFN, (void *) &sv_preactorsave, 0, 1 },
1493 { 0, &actor[0], sizeof(actor_t), MAXSPRITES },
1494 { DS_SAVEFN|DS_LOADFN, (void *)&sv_postactordata, 0, 1 },
1495 { DS_END, 0, 0, 0 }
1496 };
1497
1498 static char svgm_anmisc_string [] = "blK:anms";
1499
1500 static const dataspec_t svgm_anmisc[] =
1501 {
1502 { DS_STRING, (void *)svgm_anmisc_string, 0, 1 },
1503 { 0, &g_animateCnt, sizeof(g_animateCnt), 1 },
1504 { 0, &g_animateSect[0], sizeof(g_animateSect[0]), MAXANIMATES },
1505 { 0, &g_animateGoal[0], sizeof(g_animateGoal[0]), MAXANIMATES },
1506 { 0, &g_animateVel[0], sizeof(g_animateVel[0]), MAXANIMATES },
1507 { DS_SAVEFN, (void *)&sv_preanimateptrsave, 0, 1 },
1508 { 0, &g_animatePtr[0], sizeof(g_animatePtr[0]), MAXANIMATES },
1509 { DS_SAVEFN|DS_LOADFN , (void *)&sv_postanimateptr, 0, 1 },
1510 { 0, &g_curViewscreen, sizeof(g_curViewscreen), 1 },
1511 { 0, &g_origins[0], sizeof(g_origins[0]), ARRAY_SIZE(g_origins) },
1512 { 0, &g_spriteDeleteQueuePos, sizeof(g_spriteDeleteQueuePos), 1 },
1513 { DS_NOCHK, &g_deleteQueueSize, sizeof(g_deleteQueueSize), 1 },
1514 { DS_CNT(g_deleteQueueSize), &SpriteDeletionQueue[0], sizeof(int16_t), (intptr_t)&g_deleteQueueSize },
1515 { 0, &show2dsector[0], sizeof(uint8_t), (MAXSECTORS+7)>>3 },
1516 { DS_NOCHK, &g_cloudCnt, sizeof(g_cloudCnt), 1 },
1517 { 0, &g_cloudSect[0], sizeof(g_cloudSect), 1 },
1518 { 0, &g_cloudX, sizeof(g_cloudX), 1 },
1519 { 0, &g_cloudY, sizeof(g_cloudY), 1 },
1520 { 0, &g_pskyidx, sizeof(g_pskyidx), 1 }, // DS_NOCHK?
1521 { 0, &g_earthquakeTime, sizeof(g_earthquakeTime), 1 },
1522
1523 { DS_SAVEFN, (void *)&sv_restsave, 0, 1 },
1524 { 0, savegame_restdata, 1, sizeof(savegame_restdata) }, // sz/cnt swapped for kdfread
1525 { DS_LOADFN, (void *)&sv_restload, 0, 1 },
1526
1527 { DS_END, 0, 0, 0 }
1528 };
1529
1530 static dataspec_gv_t *svgm_vars=NULL;
1531 static uint8_t *dosaveplayer2(buildvfs_FILE fil, uint8_t *mem);
1532 static int32_t doloadplayer2(buildvfs_kfd fil, uint8_t **memptr);
1533 static void postloadplayer(int32_t savegamep);
1534
1535 // SVGM snapshot system
1536 static uint32_t svsnapsiz;
1537 static uint8_t *svsnapshot;
1538 static uint8_t *svinitsnap;
1539 static uint32_t svdiffsiz;
1540 static uint8_t *svdiff;
1541
1542 #include "gamedef.h"
1543
1544 #define SV_SKIPMASK (GAMEVAR_PTR_MASK|SAVEGAMEVARSKIPMASK)
1545
1546 static char svgm_vars_string [] = "blK:vars";
1547 // setup gamevar data spec for snapshotting and diffing... gamevars must be loaded when called
sv_makevarspec()1548 static void sv_makevarspec()
1549 {
1550 int vcnt = 0;
1551
1552 for (int i = 0; i < g_gameVarCount; i++)
1553 vcnt += (aGameVars[i].flags & SV_SKIPMASK) ? 0 : 1;
1554
1555 for (int i=0; i<g_gameArrayCount; i++)
1556 vcnt += !(aGameArrays[i].flags & SAVEGAMEARRAYSKIPMASK); // SYSTEM_GAMEARRAY
1557
1558 svgm_vars = (dataspec_gv_t *)Xrealloc(svgm_vars, (vcnt + 2) * sizeof(dataspec_gv_t));
1559
1560 svgm_vars[0].flags = DS_STRING;
1561 svgm_vars[0].ptr = svgm_vars_string;
1562 svgm_vars[0].cnt = 1;
1563
1564 vcnt = 1;
1565
1566 for (int i = 0; i < g_gameVarCount; i++)
1567 {
1568 if (aGameVars[i].flags & SV_SKIPMASK)
1569 continue;
1570
1571 unsigned const per = aGameVars[i].flags & GAMEVAR_USER_MASK;
1572
1573 svgm_vars[vcnt].flags = 0;
1574 svgm_vars[vcnt].ptr = (per == 0) ? &aGameVars[i].global : aGameVars[i].pValues;
1575 svgm_vars[vcnt].size = sizeof(intptr_t);
1576 svgm_vars[vcnt].cnt = (per == 0) ? 1 : (per == GAMEVAR_PERPLAYER ? MAXPLAYERS : MAXSPRITES);
1577
1578 ++vcnt;
1579 }
1580
1581 for (int i = 0; i < g_gameArrayCount; i++)
1582 {
1583 // We must not update read-only SYSTEM_GAMEARRAY gamearrays: besides
1584 // being questionable by itself, sizeof(...) may be e.g. 4 whereas the
1585 // actual element type is int16_t (such as tilesizx[]/tilesizy[]).
1586 if (aGameArrays[i].flags & SAVEGAMEARRAYSKIPMASK)
1587 continue;
1588
1589 auto const pValues = aGameArrays[i].pValues;
1590
1591 svgm_vars[vcnt].flags = 0;
1592 svgm_vars[vcnt].ptr = pValues;
1593
1594 if (aGameArrays[i].flags & GAMEARRAY_BITMAP)
1595 {
1596 svgm_vars[vcnt].size = (aGameArrays[i].size + 7) >> 3;
1597 svgm_vars[vcnt].cnt = 1; // assumed constant throughout demo, i.e. no RESIZEARRAY
1598 }
1599 else
1600 {
1601 svgm_vars[vcnt].size = pValues == NULL ? 0 : sizeof(aGameArrays[0].pValues[0]);
1602 svgm_vars[vcnt].cnt = aGameArrays[i].size; // assumed constant throughout demo, i.e. no RESIZEARRAY
1603 }
1604
1605 ++vcnt;
1606 }
1607
1608 svgm_vars[vcnt].flags = DS_END;
1609 svgm_vars[vcnt].ptr = NULL;
1610 svgm_vars[vcnt].size = 0;
1611 svgm_vars[vcnt].cnt = 0;
1612 }
1613
sv_freemem()1614 void sv_freemem()
1615 {
1616 DO_FREE_AND_NULL(svsnapshot);
1617 DO_FREE_AND_NULL(svinitsnap);
1618 DO_FREE_AND_NULL(svdiff);
1619 }
1620
SV_AllocSnap(int32_t allocinit)1621 static void SV_AllocSnap(int32_t allocinit)
1622 {
1623 sv_freemem();
1624
1625 svsnapshot = (uint8_t *)Xmalloc(svsnapsiz);
1626 if (allocinit)
1627 svinitsnap = (uint8_t *)Xmalloc(svsnapsiz);
1628 svdiffsiz = svsnapsiz; // theoretically it's less than could be needed in the worst case, but practically it's overkill
1629 svdiff = (uint8_t *)Xmalloc(svdiffsiz);
1630 }
1631
1632 // make snapshot only if spot < 0 (demo)
sv_saveandmakesnapshot(buildvfs_FILE fil,char const * name,int8_t spot,int8_t recdiffsp,int8_t diffcompress,int8_t synccompress,bool isAutoSave)1633 int32_t sv_saveandmakesnapshot(buildvfs_FILE fil, char const *name, int8_t spot, int8_t recdiffsp, int8_t diffcompress, int8_t synccompress, bool isAutoSave)
1634 {
1635 savehead_t h;
1636
1637 // set a few savegame system globals
1638 savegame_comprthres = SV_DEFAULTCOMPRTHRES;
1639 savegame_diffcompress = diffcompress;
1640
1641 // calculate total snapshot size
1642 sv_makevarspec();
1643 svsnapsiz = calcsz((const dataspec_t *)svgm_vars);
1644 svsnapsiz += calcsz(svgm_udnetw) + calcsz(svgm_secwsp) + calcsz(svgm_script) + calcsz(svgm_anmisc);
1645
1646
1647 // create header
1648 Bmemcpy(h.headerstr, "E32SAVEGAME", 11);
1649 h.majorver = SV_MAJOR_VER;
1650 h.minorver = SV_MINOR_VER;
1651 h.ptrsize = sizeof(intptr_t);
1652
1653 if (isAutoSave)
1654 h.ptrsize |= 1u << 7u;
1655
1656 h.bytever = BYTEVERSION;
1657 h.userbytever = ud.userbytever;
1658
1659 Bstrncpyz(h.scriptname, g_scriptFileName, sizeof(h.scriptname));
1660 h.comprthres = savegame_comprthres;
1661 h.recdiffsp = recdiffsp;
1662 h.diffcompress = savegame_diffcompress;
1663 h.synccompress = synccompress;
1664
1665 h.reccnt = 0;
1666 h.snapsiz = svsnapsiz;
1667
1668 // the following is kinda redundant, but we save it here to be able to quickly fetch
1669 // it in a savegame header read
1670
1671 h.numplayers = ud.multimode;
1672 h.volnum = ud.volume_number;
1673 h.levnum = ud.level_number;
1674 h.skill = ud.player_skill;
1675
1676 Bstrncpyz(h.boardfn, currentboardfilename, sizeof(h.boardfn));
1677
1678 if (spot >= 0)
1679 {
1680 // savegame
1681 Bstrncpyz(h.savename, name, sizeof(h.savename));
1682 #ifdef __ANDROID__
1683 Bstrncpyz(h.volname, g_volumeNames[ud.volume_number], sizeof(h.volname));
1684 Bstrncpyz(h.skillname, g_skillNames[ud.player_skill], sizeof(h.skillname));
1685 #endif
1686 }
1687 else
1688 {
1689 // demo
1690
1691 const time_t t = time(NULL);
1692 struct tm * st;
1693
1694 Bstrncpyz(h.savename, "EDuke32 demo", sizeof(h.savename));
1695 if (t>=0 && (st = localtime(&t)))
1696 Bsnprintf(h.savename, sizeof(h.savename), "Demo %04d%02d%02d %s",
1697 st->tm_year+1900, st->tm_mon+1, st->tm_mday, s_buildRev);
1698 }
1699
1700
1701 // write header
1702 buildvfs_fwrite(&h, sizeof(savehead_t), 1, fil);
1703
1704 // for savegames, the file offset after the screenshot goes here;
1705 // for demos, we keep it 0 to signify that we didn't save one
1706 buildvfs_fwrite("\0\0\0\0", 4, 1, fil);
1707 if (spot >= 0 && waloff[TILE_SAVESHOT])
1708 {
1709 int32_t ofs;
1710
1711 // write the screenshot compressed
1712 dfwrite_LZ4((char *)waloff[TILE_SAVESHOT], 320, 200, fil);
1713
1714 // write the current file offset right after the header
1715 ofs = buildvfs_ftell(fil);
1716 buildvfs_fseek_abs(fil, sizeof(savehead_t));
1717 buildvfs_fwrite(&ofs, 4, 1, fil);
1718 buildvfs_fseek_abs(fil, ofs);
1719 }
1720
1721 #ifdef DEBUGGINGAIDS
1722 OSD_Printf("sv_saveandmakesnapshot: snapshot size: %d bytes.\n", svsnapsiz);
1723 #endif
1724
1725 if (spot >= 0)
1726 {
1727 // savegame
1728 dosaveplayer2(fil, NULL);
1729 }
1730 else
1731 {
1732 // demo
1733 SV_AllocSnap(0);
1734
1735 uint8_t * const p = dosaveplayer2(fil, svsnapshot);
1736
1737 if (p != svsnapshot+svsnapsiz)
1738 {
1739 OSD_Printf("sv_saveandmakesnapshot: ptr-(snapshot end)=%d!\n", (int32_t)(p - (svsnapshot + svsnapsiz)));
1740 return 1;
1741 }
1742 }
1743
1744 return 0;
1745 }
1746
1747 // if file is not an EDuke32 savegame/demo, h->headerstr will be all zeros
sv_loadheader(buildvfs_kfd fil,int32_t spot,savehead_t * h)1748 int32_t sv_loadheader(buildvfs_kfd fil, int32_t spot, savehead_t *h)
1749 {
1750 int32_t havedemo = (spot < 0);
1751
1752 if (kread(fil, h, sizeof(savehead_t)) != sizeof(savehead_t))
1753 {
1754 OSD_Printf("%s %d header corrupt.\n", havedemo ? "Demo":"Savegame", havedemo ? -spot : spot);
1755 Bmemset(h->headerstr, 0, sizeof(h->headerstr));
1756 return -1;
1757 }
1758
1759 if (Bmemcmp(h->headerstr, "E32SAVEGAME", 11))
1760 {
1761 char headerCstr[sizeof(h->headerstr) + 1];
1762 Bmemcpy(headerCstr, h->headerstr, sizeof(h->headerstr));
1763 headerCstr[sizeof(h->headerstr)] = '\0';
1764 OSD_Printf("%s %d header reads \"%s\", expected \"E32SAVEGAME\".\n",
1765 havedemo ? "Demo":"Savegame", havedemo ? -spot : spot, headerCstr);
1766 Bmemset(h->headerstr, 0, sizeof(h->headerstr));
1767 return -2;
1768 }
1769
1770 if (h->majorver != SV_MAJOR_VER || h->minorver != SV_MINOR_VER || h->bytever != BYTEVERSION || h->userbytever != ud.userbytever
1771 || Bstrncasecmp(g_scriptFileName, h->scriptname, Bstrlen(h->scriptname)))
1772 {
1773 #ifndef DEBUGGINGAIDS
1774 if (havedemo)
1775 #endif
1776 OSD_Printf("Incompatible savegame. Expected version %d.%d.%d.%d created with %s, found %d.%d.%d.%d created with %s\n", SV_MAJOR_VER, SV_MINOR_VER, BYTEVERSION,
1777 ud.userbytever, g_scriptFileName, h->majorver, h->minorver, h->bytever, h->userbytever, h->scriptname);
1778
1779 if (h->majorver == SV_MAJOR_VER && h->minorver == SV_MINOR_VER)
1780 return 1;
1781 else
1782 {
1783 Bmemset(h->headerstr, 0, sizeof(h->headerstr));
1784 return -3;
1785 }
1786 }
1787
1788 if (h->getPtrSize() != sizeof(intptr_t))
1789 {
1790 #ifndef DEBUGGINGAIDS
1791 if (havedemo)
1792 #endif
1793 OSD_Printf("File incompatible. Expected pointer size %d, found %d\n",
1794 (int32_t)sizeof(intptr_t), h->getPtrSize());
1795
1796 Bmemset(h->headerstr, 0, sizeof(h->headerstr));
1797 return -4;
1798 }
1799
1800 return 0;
1801 }
1802
sv_loadsnapshot(buildvfs_kfd fil,int32_t spot,savehead_t * h)1803 int32_t sv_loadsnapshot(buildvfs_kfd fil, int32_t spot, savehead_t *h)
1804 {
1805 uint8_t *p;
1806 int32_t i;
1807
1808 if (spot < 0)
1809 {
1810 // demo
1811 i = sv_loadheader(fil, spot, h);
1812 if (i)
1813 return i;
1814
1815 // Used to be in doloadplayer2(), now redundant for savegames since
1816 // we checked before. Multiplayer demos need still to be taken care of.
1817 if (h->numplayers != numplayers)
1818 return 9;
1819 }
1820 // else (if savegame), we just read the header and are now at offset sizeof(savehead_t)
1821
1822 #ifdef DEBUGGINGAIDS
1823 OSD_Printf("sv_loadsnapshot: snapshot size: %d bytes.\n", h->snapsiz);
1824 #endif
1825
1826 if (kread(fil, &i, 4) != 4)
1827 {
1828 OSD_Printf("sv_snapshot: couldn't read 4 bytes after header.\n");
1829 return 7;
1830 }
1831 if (i > 0)
1832 {
1833 if (klseek(fil, i, SEEK_SET) != i)
1834 {
1835 OSD_Printf("sv_snapshot: failed skipping over the screenshot.\n");
1836 return 8;
1837 }
1838 }
1839
1840 savegame_comprthres = h->comprthres;
1841
1842 if (spot >= 0)
1843 {
1844 // savegame
1845 i = doloadplayer2(fil, NULL);
1846 if (i)
1847 {
1848 OSD_Printf("sv_loadsnapshot: doloadplayer2() returned %d.\n", i);
1849 return 5;
1850 }
1851 }
1852 else
1853 {
1854 // demo
1855 savegame_diffcompress = h->diffcompress;
1856
1857 svsnapsiz = h->snapsiz;
1858
1859 SV_AllocSnap(1);
1860
1861 p = svsnapshot;
1862 i = doloadplayer2(fil, &p);
1863 if (i)
1864 {
1865 OSD_Printf("sv_loadsnapshot: doloadplayer2() returned %d.\n", i);
1866 sv_freemem();
1867 return 5;
1868 }
1869
1870 if (p != svsnapshot+svsnapsiz)
1871 {
1872 OSD_Printf("sv_loadsnapshot: internal error: p-(snapshot end)=%d!\n",
1873 (int32_t)(p-(svsnapshot+svsnapsiz)));
1874 sv_freemem();
1875 return 6;
1876 }
1877
1878 Bmemcpy(svinitsnap, svsnapshot, svsnapsiz);
1879 }
1880
1881 postloadplayer((spot >= 0));
1882
1883 return 0;
1884 }
1885
1886
sv_writediff(buildvfs_FILE fil)1887 uint32_t sv_writediff(buildvfs_FILE fil)
1888 {
1889 uint8_t *p = svsnapshot;
1890 uint8_t *d = svdiff;
1891
1892 cmpspecdata(svgm_udnetw, &p, &d);
1893 cmpspecdata(svgm_secwsp, &p, &d);
1894 cmpspecdata(svgm_script, &p, &d);
1895 cmpspecdata(svgm_anmisc, &p, &d);
1896 cmpspecdata((const dataspec_t *)svgm_vars, &p, &d);
1897
1898 if (p != svsnapshot+svsnapsiz)
1899 OSD_Printf("sv_writediff: dump+siz=%p, p=%p!\n", svsnapshot+svsnapsiz, p);
1900
1901 uint32_t const diffsiz = d - svdiff;
1902
1903 buildvfs_fwrite("dIfF",4,1,fil);
1904 buildvfs_fwrite(&diffsiz, sizeof(diffsiz), 1, fil);
1905
1906 if (savegame_diffcompress)
1907 dfwrite_LZ4(svdiff, 1, diffsiz, fil); // cnt and sz swapped
1908 else
1909 buildvfs_fwrite(svdiff, 1, diffsiz, fil);
1910
1911 return diffsiz;
1912 }
1913
sv_readdiff(buildvfs_kfd fil)1914 int32_t sv_readdiff(buildvfs_kfd fil)
1915 {
1916 int32_t diffsiz;
1917
1918 if (kread(fil, &diffsiz, sizeof(uint32_t)) != sizeof(uint32_t))
1919 return -1;
1920
1921 if (savegame_diffcompress)
1922 {
1923 if (kdfread_LZ4(svdiff, 1, diffsiz, fil) != diffsiz) // cnt and sz swapped
1924 return -2;
1925 }
1926 else
1927 {
1928 if (kread(fil, svdiff, diffsiz) != diffsiz)
1929 return -2;
1930 }
1931
1932 uint8_t *p = svsnapshot;
1933 uint8_t *d = svdiff;
1934
1935 if (applydiff(svgm_udnetw, &p, &d)) return -3;
1936 if (applydiff(svgm_secwsp, &p, &d)) return -4;
1937 if (applydiff(svgm_script, &p, &d)) return -5;
1938 if (applydiff(svgm_anmisc, &p, &d)) return -6;
1939 if (applydiff((const dataspec_t *)svgm_vars, &p, &d)) return -7;
1940
1941 int i = 0;
1942
1943 if (p!=svsnapshot+svsnapsiz)
1944 i|=1;
1945 if (d!=svdiff+diffsiz)
1946 i|=2;
1947 if (i)
1948 OSD_Printf("sv_readdiff: p=%p, svsnapshot+svsnapsiz=%p; d=%p, svdiff+diffsiz=%p",
1949 p, svsnapshot+svsnapsiz, d, svdiff+diffsiz);
1950 return i;
1951 }
1952
1953 // SVGM data description
sv_postudload()1954 static void sv_postudload()
1955 {
1956 // Bmemcpy(&boardfilename[0], ¤tboardfilename[0], BMAX_PATH); // DON'T do this in demos!
1957 #if 1
1958 ud.m_level_number = ud.level_number;
1959 ud.m_volume_number = ud.volume_number;
1960 ud.m_player_skill = ud.player_skill;
1961 ud.m_respawn_monsters = ud.respawn_monsters;
1962 ud.m_respawn_items = ud.respawn_items;
1963 ud.m_respawn_inventory = ud.respawn_inventory;
1964 ud.m_monsters_off = ud.monsters_off;
1965 ud.m_coop = ud.coop;
1966 ud.m_marker = ud.marker;
1967 ud.m_ffire = ud.ffire;
1968 ud.m_noexits = ud.noexits;
1969 #endif
1970 }
1971 //static int32_t lockclock_dummy;
1972
1973 #ifdef USE_OPENGL
sv_prespriteextsave()1974 static void sv_prespriteextsave()
1975 {
1976 for (int i=0; i<MAXSPRITES+MAXUNIQHUDID; i++)
1977 if (spriteext[i].mdanimtims)
1978 {
1979 spriteext[i].mdanimtims -= mdtims;
1980 if (spriteext[i].mdanimtims==0)
1981 spriteext[i].mdanimtims++;
1982 }
1983 }
sv_postspriteext()1984 static void sv_postspriteext()
1985 {
1986 for (int i=0; i<MAXSPRITES+MAXUNIQHUDID; i++)
1987 if (spriteext[i].mdanimtims)
1988 spriteext[i].mdanimtims += mdtims;
1989 }
1990 #endif
1991
1992 #ifdef YAX_ENABLE
sv_postyaxload(void)1993 void sv_postyaxload(void)
1994 {
1995 yax_update(numyaxbunches>0 ? 2 : 1);
1996 }
1997 #endif
1998
sv_prelabelsave(void)1999 static void sv_prelabelsave(void)
2000 {
2001 savegame_labelcnt = g_labelCnt;
2002 savegame_labels = label;
2003 }
2004
sv_prelabelload(void)2005 static void sv_prelabelload(void)
2006 {
2007 savegame_labels = (char *)Xmalloc(savegame_labelcnt << 6);
2008 }
2009
sv_findlabelindex(int32_t const val,int const type)2010 static int sv_findlabelindex(int32_t const val, int const type)
2011 {
2012 for (int i=0;i<g_labelCnt;i++)
2013 if (labelcode[i] == val && (labeltype[i] & type) != 0)
2014 return i;
2015
2016 for (int i=0;i<g_labelCnt;i++)
2017 if (labelcode[i] == val)
2018 return i;
2019
2020 return -1;
2021 }
2022
sv_checkoffset(int const scriptoffs,int const val,int const endoffs)2023 static inline int sv_checkoffset(int const scriptoffs, int const val, int const endoffs)
2024 {
2025 return ((unsigned)scriptoffs > 0 && (unsigned)scriptoffs + endoffs < (unsigned)g_scriptSize
2026 && apScript[scriptoffs - 1] == val
2027 && apScript[scriptoffs + endoffs] == CON_END);
2028 }
2029
2030 // translate anything in actor[] that holds an offset into compiled CON into a label index
sv_preactorsave(void)2031 static void sv_preactorsave(void)
2032 {
2033 for (int i = 0; i < MAXSPRITES; i++)
2034 {
2035 auto &a = actor[i];
2036
2037 if (sv_checkoffset(AC_MOVE_ID(a.t_data), CON_MOVE, 3))
2038 {
2039 int const index = sv_findlabelindex(AC_MOVE_ID(a.t_data), LABEL_MOVE | LABEL_DEFINE);
2040 Bassert(index != -1);
2041 AC_MOVE_ID(a.t_data) = index;
2042 a.flags |= SFLAG_RESERVED;
2043 }
2044
2045 if (sv_checkoffset(AC_ACTION_ID(a.t_data), CON_ACTION, ACTION_PARAM_COUNT))
2046 {
2047 int const index = sv_findlabelindex(AC_ACTION_ID(a.t_data), LABEL_ACTION);
2048 Bassert(index != -1);
2049 AC_ACTION_ID(a.t_data) = index;
2050 a.flags |= SFLAG_RESERVED2;
2051 }
2052
2053 if (sv_checkoffset(AC_AI_ID(a.t_data), CON_AI, 3))
2054 {
2055 int const index = sv_findlabelindex(AC_AI_ID(a.t_data), LABEL_AI);
2056 Bassert(index != -1);
2057 AC_AI_ID(a.t_data) = index;
2058 a.flags |= SFLAG_RESERVED3;
2059 }
2060 }
2061 }
2062
2063 // translate the script offsets back from label index to offset in the currently compiled script
sv_postactordata(void)2064 static void sv_postactordata(void)
2065 {
2066 for (int i = 0; i < MAXSPRITES; i++)
2067 {
2068 auto &a = actor[i];
2069 auto s = (uspriteptr_t)&sprite[i];
2070
2071 #ifdef POLYMER
2072 practor[i].lightptr = NULL;
2073 practor[i].lightId = -1;
2074 #endif
2075 if (a.flags & SFLAG_RESERVED)
2076 {
2077 int const index = hash_find(&h_labels, &savegame_labels[AC_MOVE_ID(a.t_data) << 6]);
2078 if (index == -1)
2079 {
2080 OSD_Printf("couldn't find savegame label %s\n", &savegame_labels[AC_MOVE_ID(a.t_data) << 6]);
2081 AC_MOVE_ID(a.t_data) = *(g_tile[s->picnum].execPtr + 2);
2082 }
2083 else
2084 AC_MOVE_ID(a.t_data) = labelcode[index];
2085 }
2086
2087 if (a.flags & SFLAG_RESERVED2)
2088 {
2089 char *str = &savegame_labels[AC_ACTION_ID(a.t_data) << 6];
2090 int const index = hash_find(&h_labels, str);
2091 if (index == -1)
2092 {
2093 OSD_Printf("couldn't find savegame label %s\n", &savegame_labels[AC_ACTION_ID(a.t_data) << 6]);
2094 if (g_tile[s->picnum].execPtr)
2095 AC_ACTION_ID(a.t_data) = *(g_tile[s->picnum].execPtr + 1);
2096 AC_ACTION_COUNT(a.t_data) = 0;
2097 AC_CURFRAME(a.t_data) = 0;
2098 }
2099 else
2100 AC_ACTION_ID(a.t_data) = labelcode[index];
2101 }
2102
2103 if (a.flags & SFLAG_RESERVED3)
2104 {
2105 int const index = hash_find(&h_labels, &savegame_labels[AC_AI_ID(a.t_data) << 6]);
2106 if (index == -1)
2107 {
2108 OSD_Printf("couldn't find savegame label %s\n", &savegame_labels[AC_AI_ID(a.t_data) << 6]);
2109 AC_AI_ID(a.t_data) = 0;
2110 }
2111 else
2112 AC_AI_ID(a.t_data) = labelcode[index];
2113 }
2114 a.flags &= ~(SFLAG_RESERVED|SFLAG_RESERVED2|SFLAG_RESERVED3);
2115 }
2116 }
2117
sv_preanimateptrsave()2118 static void sv_preanimateptrsave()
2119 {
2120 G_Util_PtrToIdx(g_animatePtr, g_animateCnt, sector, P2I_FWD);
2121 }
sv_postanimateptr()2122 static void sv_postanimateptr()
2123 {
2124 G_Util_PtrToIdx(g_animatePtr, g_animateCnt, sector, P2I_BACK);
2125 }
2126
sv_preprojectilesave()2127 static void sv_preprojectilesave()
2128 {
2129 savegame_projectilecnt = 0;
2130 Bmemset(savegame_projectiles, 0, sizeof(savegame_projectiles));
2131
2132 for (auto & i : g_tile)
2133 if (i.proj)
2134 savegame_projectilecnt++;
2135
2136 if (savegame_projectilecnt > 0)
2137 savegame_projectiledata = (projectile_t *) Xrealloc(savegame_projectiledata, sizeof(projectile_t) * savegame_projectilecnt);
2138
2139 for (int i = 0, cnt = 0; i < MAXTILES; i++)
2140 {
2141 if (g_tile[i].proj)
2142 {
2143 savegame_projectiles[i>>3] |= 1<<(i&7);
2144 Bmemcpy(&savegame_projectiledata[cnt++], g_tile[i].proj, sizeof(projectile_t));
2145 }
2146 }
2147 }
2148
sv_postprojectilesave()2149 static void sv_postprojectilesave()
2150 {
2151 DO_FREE_AND_NULL(savegame_projectiledata);
2152 }
2153
sv_preprojectileload()2154 static void sv_preprojectileload()
2155 {
2156 savegame_projectilecnt = 0;
2157
2158 for (int i = 0; i < MAXTILES; i++)
2159 {
2160 if (savegame_projectiles[i>>3] & pow2char[i&7])
2161 savegame_projectilecnt++;
2162 }
2163
2164 if (savegame_projectilecnt > 0)
2165 savegame_projectiledata = (projectile_t *) Xrealloc(savegame_projectiledata, sizeof(projectile_t) * savegame_projectilecnt);
2166 }
2167
sv_postprojectileload()2168 static void sv_postprojectileload()
2169 {
2170 for (int i = 0, cnt = 0; i < MAXTILES; i++)
2171 {
2172 if (savegame_projectiles[i>>3] & pow2char[i&7])
2173 {
2174 C_AllocProjectile(i);
2175 Bmemcpy(g_tile[i].proj, &savegame_projectiledata[cnt++], sizeof(projectile_t));
2176 }
2177 }
2178
2179 DO_FREE_AND_NULL(savegame_projectiledata);
2180 }
2181
sv_restsave()2182 static void sv_restsave()
2183 {
2184 uint8_t * mem = savegame_restdata;
2185 DukePlayer_t dummy_ps;
2186
2187 Bmemset(&dummy_ps, 0, sizeof(DukePlayer_t));
2188
2189 #define CPDAT(ptr,sz) do { Bmemcpy(mem, ptr, sz), mem+=sz ; } while (0)
2190 for (int i = 0; i < MAXPLAYERS; i++)
2191 {
2192 CPDAT(g_player[i].user_name, 32);
2193 CPDAT(&g_player[i].pcolor, sizeof(g_player[0].pcolor));
2194 CPDAT(&g_player[i].pteam, sizeof(g_player[0].pteam));
2195 CPDAT(&g_player[i].frags[0], sizeof(g_player[0].frags));
2196 CPDAT(g_player[i].ps ? g_player[i].ps : &dummy_ps, sizeof(DukePlayer_t));
2197 }
2198
2199 Bassert((savegame_restdata + SVARDATALEN) - mem == 0);
2200 #undef CPDAT
2201 }
sv_restload()2202 static void sv_restload()
2203 {
2204 uint8_t * mem = savegame_restdata;
2205 DukePlayer_t dummy_ps;
2206
2207 #define CPDAT(ptr,sz) Bmemcpy(ptr, mem, sz), mem+=sz
2208 for (int i = 0; i < MAXPLAYERS; i++)
2209 {
2210 CPDAT(g_player[i].user_name, 32);
2211 CPDAT(&g_player[i].pcolor, sizeof(g_player[0].pcolor));
2212 CPDAT(&g_player[i].pteam, sizeof(g_player[0].pteam));
2213 CPDAT(&g_player[i].frags[0], sizeof(g_player[0].frags));
2214 CPDAT(g_player[i].ps ? g_player[i].ps : &dummy_ps, sizeof(DukePlayer_t));
2215 }
2216 #undef CPDAT
2217
2218 if (g_player[myconnectindex].ps)
2219 g_player[myconnectindex].ps->auto_aim = ud.config.AutoAim;
2220 }
2221
2222 #ifdef DEBUGGINGAIDS
2223 # define PRINTSIZE(name) do { if (mem) OSD_Printf(name ": %d\n", (int32_t)(mem-tmem)); \
2224 OSD_Printf(name ": %d ms\n", timerGetTicks()-t); t=timerGetTicks(); tmem=mem; } while (0)
2225 #else
2226 # define PRINTSIZE(name) do { } while (0)
2227 #endif
2228
dosaveplayer2(buildvfs_FILE fil,uint8_t * mem)2229 static uint8_t *dosaveplayer2(buildvfs_FILE fil, uint8_t *mem)
2230 {
2231 #ifdef DEBUGGINGAIDS
2232 uint8_t *tmem = mem;
2233 int32_t t=timerGetTicks();
2234 #endif
2235 mem=writespecdata(svgm_udnetw, fil, mem); // user settings, players & net
2236 PRINTSIZE("ud");
2237 mem=writespecdata(svgm_secwsp, fil, mem); // sector, wall, sprite
2238 PRINTSIZE("sws");
2239 mem=writespecdata(svgm_script, fil, mem); // script
2240 PRINTSIZE("script");
2241 mem=writespecdata(svgm_anmisc, fil, mem); // animates, quotes & misc.
2242 PRINTSIZE("animisc");
2243
2244 Gv_WriteSave(fil); // gamevars
2245 mem=writespecdata((const dataspec_t *)svgm_vars, 0, mem);
2246 PRINTSIZE("vars");
2247
2248 return mem;
2249 }
2250
doloadplayer2(buildvfs_kfd fil,uint8_t ** memptr)2251 static int32_t doloadplayer2(buildvfs_kfd fil, uint8_t **memptr)
2252 {
2253 uint8_t *mem = memptr ? *memptr : NULL;
2254 #ifdef DEBUGGINGAIDS
2255 uint8_t *tmem=mem;
2256 int32_t t=timerGetTicks();
2257 #endif
2258 if (readspecdata(svgm_udnetw, fil, &mem)) return -2;
2259 PRINTSIZE("ud");
2260 if (readspecdata(svgm_secwsp, fil, &mem)) return -4;
2261 PRINTSIZE("sws");
2262 if (readspecdata(svgm_script, fil, &mem)) return -5;
2263 PRINTSIZE("script");
2264 if (readspecdata(svgm_anmisc, fil, &mem)) return -6;
2265 PRINTSIZE("animisc");
2266
2267 int const i = Gv_ReadSave(fil);
2268
2269 if (savegame_labels != label)
2270 DO_FREE_AND_NULL(savegame_labels);
2271
2272 if (i) return i;
2273
2274 if (mem)
2275 {
2276 int32_t i;
2277
2278 sv_makevarspec();
2279 for (i=1; svgm_vars[i].flags!=DS_END; i++)
2280 {
2281 Bmemcpy(mem, svgm_vars[i].ptr, svgm_vars[i].size*svgm_vars[i].cnt); // careful! works because there are no DS_DYNAMIC's!
2282 mem += svgm_vars[i].size*svgm_vars[i].cnt;
2283 }
2284 }
2285 PRINTSIZE("vars");
2286
2287 if (memptr)
2288 *memptr = mem;
2289 return 0;
2290 }
2291
sv_updatestate(int32_t frominit)2292 int32_t sv_updatestate(int32_t frominit)
2293 {
2294 uint8_t *p = svsnapshot, *pbeg=p;
2295
2296 if (frominit)
2297 Bmemcpy(svsnapshot, svinitsnap, svsnapsiz);
2298
2299 if (readspecdata(svgm_udnetw, buildvfs_kfd_invalid, &p)) return -2;
2300 if (readspecdata(svgm_secwsp, buildvfs_kfd_invalid, &p)) return -4;
2301 if (readspecdata(svgm_script, buildvfs_kfd_invalid, &p)) return -5;
2302 if (readspecdata(svgm_anmisc, buildvfs_kfd_invalid, &p)) return -6;
2303
2304 if (readspecdata((const dataspec_t *)svgm_vars, buildvfs_kfd_invalid, &p)) return -8;
2305
2306 if (p != pbeg+svsnapsiz)
2307 {
2308 OSD_Printf("sv_updatestate: ptr-(snapshot end)=%d\n", (int32_t)(p-(pbeg+svsnapsiz)));
2309 return -9;
2310 }
2311
2312 if (frominit)
2313 postloadplayer(0);
2314 #ifdef POLYMER
2315 if (videoGetRenderMode() == REND_POLYMER)
2316 polymer_resetlights(); // must do it after polymer_loadboard() !!!
2317 #endif
2318
2319 return 0;
2320 }
2321
postloadplayer(int32_t savegamep)2322 static void postloadplayer(int32_t savegamep)
2323 {
2324 int32_t i;
2325
2326 //1
2327 if (g_player[myconnectindex].ps->over_shoulder_on != 0)
2328 {
2329 CAMERADIST = 0;
2330 CAMERACLOCK = 0;
2331 g_player[myconnectindex].ps->over_shoulder_on = 1;
2332 }
2333
2334 //2
2335 screenpeek = myconnectindex;
2336
2337 //2.5
2338 if (savegamep)
2339 {
2340 int32_t musicIdx = (ud.music_episode*MAXLEVELS) + ud.music_level;
2341
2342 Bmemset(gotpic, 0, sizeof(gotpic));
2343 S_ClearSoundLocks();
2344 G_CacheMapData();
2345
2346 if (boardfilename[0] != 0 && ud.level_number == 7 && ud.volume_number == 0 && ud.music_level == USERMAPMUSICFAKELEVEL && ud.music_episode == USERMAPMUSICFAKEVOLUME)
2347 {
2348 char levname[BMAX_PATH];
2349 G_SetupFilenameBasedMusic(levname, boardfilename);
2350 }
2351
2352 if (g_mapInfo[musicIdx].musicfn != NULL && (musicIdx != g_musicIndex || different_user_map))
2353 {
2354 ud.music_episode = g_musicIndex / MAXLEVELS;
2355 ud.music_level = g_musicIndex % MAXLEVELS;
2356 S_PlayLevelMusicOrNothing(musicIdx);
2357 }
2358 else
2359 S_ContinueLevelMusic();
2360
2361 if (ud.config.MusicToggle)
2362 S_PauseMusic(false);
2363
2364 g_player[myconnectindex].ps->gm = MODE_GAME;
2365 ud.recstat = 0;
2366
2367 if (g_player[myconnectindex].ps->jetpack_on)
2368 A_PlaySound(DUKE_JETPACK_IDLE, g_player[myconnectindex].ps->i);
2369 }
2370
2371 //3
2372 P_UpdateScreenPal(g_player[myconnectindex].ps);
2373 g_restorePalette = -1;
2374
2375 //3.5
2376 if (savegamep)
2377 {
2378 for (SPRITES_OF(STAT_FX, i))
2379 if (sprite[i].picnum == MUSICANDSFX)
2380 {
2381 T2(i) = ud.config.SoundToggle;
2382 T1(i) = 0;
2383 }
2384
2385 G_UpdateScreenArea();
2386 FX_SetReverb(0);
2387 }
2388
2389 //4
2390 if (savegamep)
2391 {
2392 #ifndef EDUKE32_STANDALONE
2393 if (ud.lockout)
2394 {
2395 for (i=0; i<g_animWallCnt; i++)
2396 switch (DYNAMICTILEMAP(wall[animwall[i].wallnum].picnum))
2397 {
2398 case FEMPIC1__STATIC:
2399 wall[animwall[i].wallnum].picnum = BLANKSCREEN;
2400 break;
2401 case FEMPIC2__STATIC:
2402 case FEMPIC3__STATIC:
2403 wall[animwall[i].wallnum].picnum = SCREENBREAK6;
2404 break;
2405 }
2406 }
2407 #if 0
2408 else
2409 {
2410 for (i=0; i<g_numAnimWalls; i++)
2411 if (wall[animwall[i].wallnum].extra >= 0)
2412 wall[animwall[i].wallnum].picnum = wall[animwall[i].wallnum].extra;
2413 }
2414 #endif
2415 #endif
2416 }
2417
2418 //5
2419 G_ResetInterpolations();
2420
2421 //6
2422 g_showShareware = 0;
2423 if (savegamep)
2424 everyothertime = 0;
2425
2426 //7
2427 for (i=0; i<MAXPLAYERS; i++)
2428 g_player[i].playerquitflag = 1;
2429
2430 // ----------
2431
2432 //7.5
2433 if (savegamep)
2434 {
2435 ready2send = 1;
2436 G_ClearFIFO();
2437 Net_WaitForServer();
2438 }
2439
2440 //8
2441 // if (savegamep) ?
2442 G_ResetTimers(0);
2443
2444 #ifdef USE_STRUCT_TRACKERS
2445 Bmemset(sectorchanged, 0, sizeof(sectorchanged));
2446 Bmemset(spritechanged, 0, sizeof(spritechanged));
2447 Bmemset(wallchanged, 0, sizeof(wallchanged));
2448 #endif
2449
2450 #ifdef USE_OPENGL
2451 Polymost_prepare_loadboard();
2452 #endif
2453
2454 #ifdef POLYMER
2455 //9
2456 if (videoGetRenderMode() == REND_POLYMER)
2457 polymer_loadboard();
2458
2459 // this light pointer nulling needs to be outside the videoGetRenderMode check
2460 // because we might be loading the savegame using another renderer but
2461 // change to Polymer later
2462 for (i=0; i<MAXSPRITES; i++)
2463 {
2464 practor[i].lightptr = NULL;
2465 practor[i].lightId = -1;
2466 }
2467 #endif
2468 }
2469
2470 ////////// END GENERIC SAVING/LOADING SYSTEM //////////
2471