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(&sector[sprite[k].sectnum].floorz);
104             break;
105         case SE_32_CEILING_RISE_FALL:
106             G_SetInterpolation(&sector[sprite[k].sectnum].ceilingz);
107             break;
108         case SE_17_WARP_ELEVATOR:
109         case SE_25_PISTON:
110             G_SetInterpolation(&sector[sprite[k].sectnum].floorz);
111             G_SetInterpolation(&sector[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, &currentboardfilename[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), &sector, 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], &currentboardfilename[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