1 //
2 // Copyright(C) 2005-2014 Simon Howard
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // DESCRIPTION:
15 // Handles merging of PWADs, similar to deutex's -merge option
16 //
17 // Ideally this should work exactly the same as in deutex, but trying to
18 // read the deutex source code made my brain hurt.
19 //
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <ctype.h>
25 
26 #include "doomtype.h"
27 #include "i_swap.h" // [crispy] LONG()
28 #include "i_system.h"
29 #include "m_misc.h"
30 #include "w_merge.h"
31 #include "w_wad.h"
32 #include "z_zone.h"
33 
34 typedef enum
35 {
36     SECTION_NORMAL,
37     SECTION_FLATS,
38     SECTION_SPRITES,
39 } section_t;
40 
41 typedef struct
42 {
43     lumpinfo_t **lumps;
44     int numlumps;
45 } searchlist_t;
46 
47 typedef struct
48 {
49     char sprname[4];
50     char frame;
51     lumpinfo_t *angle_lumps[8];
52 } sprite_frame_t;
53 
54 static searchlist_t iwad;
55 static searchlist_t iwad_sprites;
56 static searchlist_t pwad;
57 
58 static searchlist_t iwad_flats;
59 static searchlist_t pwad_sprites;
60 static searchlist_t pwad_flats;
61 
62 // lumps with these sprites must be replaced in the IWAD
63 static sprite_frame_t *sprite_frames;
64 static int num_sprite_frames;
65 static int sprite_frames_alloced;
66 
67 // Search in a list to find a lump with a particular name
68 // Linear search (slow!)
69 //
70 // Returns -1 if not found
71 
FindInList(searchlist_t * list,const char * name)72 static int FindInList(searchlist_t *list, const char *name)
73 {
74     int i;
75 
76     for (i=0; i<list->numlumps; ++i)
77     {
78         if (!strncasecmp(list->lumps[i]->name, name, 8))
79             return i;
80     }
81 
82     return -1;
83 }
84 
SetupList(searchlist_t * list,searchlist_t * src_list,const char * startname,const char * endname,const char * startname2,const char * endname2)85 static boolean SetupList(searchlist_t *list, searchlist_t *src_list,
86                          const char *startname, const char *endname,
87                          const char *startname2, const char *endname2)
88 {
89     int startlump, endlump;
90 
91     list->numlumps = 0;
92     startlump = FindInList(src_list, startname);
93 
94     if (startname2 != NULL && startlump < 0)
95     {
96         startlump = FindInList(src_list, startname2);
97     }
98 
99     if (startlump >= 0)
100     {
101         endlump = FindInList(src_list, endname);
102 
103         if (endname2 != NULL && endlump < 0)
104         {
105             endlump = FindInList(src_list, endname2);
106         }
107 
108         if (endlump > startlump)
109         {
110             list->lumps = src_list->lumps + startlump + 1;
111             list->numlumps = endlump - startlump - 1;
112             return true;
113         }
114     }
115 
116     return false;
117 }
118 
119 // Sets up the sprite/flat search lists
120 
SetupLists(void)121 static void SetupLists(void)
122 {
123     // IWAD
124 
125     if (!SetupList(&iwad_flats, &iwad, "F_START", "F_END", NULL, NULL))
126     {
127         I_Error("Flats section not found in IWAD");
128     }
129 
130     if (!SetupList(&iwad_sprites, &iwad, "S_START", "S_END", NULL, NULL))
131 
132     {
133         I_Error("Sprites section not found in IWAD");
134     }
135 
136     // PWAD
137 
138     SetupList(&pwad_flats, &pwad, "F_START", "F_END", "FF_START", "FF_END");
139     SetupList(&pwad_sprites, &pwad, "S_START", "S_END", "SS_START", "SS_END");
140 }
141 
142 // Initialize the replace list
143 
InitSpriteList(void)144 static void InitSpriteList(void)
145 {
146     if (sprite_frames == NULL)
147     {
148         sprite_frames_alloced = 128;
149         sprite_frames = Z_Malloc(sizeof(*sprite_frames) * sprite_frames_alloced,
150                                  PU_STATIC, NULL);
151     }
152 
153     num_sprite_frames = 0;
154 }
155 
ValidSpriteLumpName(char * name)156 static boolean ValidSpriteLumpName(char *name)
157 {
158     if (name[0] == '\0' || name[1] == '\0'
159      || name[2] == '\0' || name[3] == '\0')
160     {
161         return false;
162     }
163 
164     // First frame:
165 
166     if (name[4] == '\0' || !isdigit(name[5]))
167     {
168         return false;
169     }
170 
171     // Second frame (optional):
172 
173     if (name[6] != '\0' && !isdigit(name[7]))
174     {
175         return false;
176     }
177 
178     return true;
179 }
180 
181 // Find a sprite frame
182 
FindSpriteFrame(char * name,int frame)183 static sprite_frame_t *FindSpriteFrame(char *name, int frame)
184 {
185     sprite_frame_t *result;
186     int i;
187 
188     // Search the list and try to find the frame
189 
190     for (i=0; i<num_sprite_frames; ++i)
191     {
192         sprite_frame_t *cur = &sprite_frames[i];
193 
194         if (!strncasecmp(cur->sprname, name, 4) && cur->frame == frame)
195         {
196             return cur;
197         }
198     }
199 
200     // Not found in list; Need to add to the list
201 
202     // Grow list?
203 
204     if (num_sprite_frames >= sprite_frames_alloced)
205     {
206         sprite_frame_t *newframes;
207 
208         newframes = Z_Malloc(sprite_frames_alloced * 2 * sizeof(*sprite_frames),
209                              PU_STATIC, NULL);
210         memcpy(newframes, sprite_frames,
211                sprite_frames_alloced * sizeof(*sprite_frames));
212         Z_Free(sprite_frames);
213         sprite_frames_alloced *= 2;
214         sprite_frames = newframes;
215     }
216 
217     // Add to end of list
218 
219     result = &sprite_frames[num_sprite_frames];
220     memcpy(result->sprname, name, 4);
221     result->frame = frame;
222 
223     for (i=0; i<8; ++i)
224         result->angle_lumps[i] = NULL;
225 
226     ++num_sprite_frames;
227 
228     return result;
229 }
230 
231 // Check if sprite lump is needed in the new wad
232 
SpriteLumpNeeded(lumpinfo_t * lump)233 static boolean SpriteLumpNeeded(lumpinfo_t *lump)
234 {
235     sprite_frame_t *sprite;
236     int angle_num;
237     int i;
238 
239     if (!ValidSpriteLumpName(lump->name))
240     {
241         return true;
242     }
243 
244     // check the first frame
245 
246     sprite = FindSpriteFrame(lump->name, lump->name[4]);
247     angle_num = lump->name[5] - '0';
248 
249     if (angle_num == 0)
250     {
251         // must check all frames
252 
253         for (i=0; i<8; ++i)
254         {
255             if (sprite->angle_lumps[i] == lump)
256                 return true;
257         }
258     }
259     else
260     {
261         // check if this lump is being used for this frame
262 
263         if (sprite->angle_lumps[angle_num - 1] == lump)
264             return true;
265     }
266 
267     // second frame if any
268 
269     // no second frame?
270     if (lump->name[6] == '\0')
271         return false;
272 
273     sprite = FindSpriteFrame(lump->name, lump->name[6]);
274     angle_num = lump->name[7] - '0';
275 
276     if (angle_num == 0)
277     {
278         // must check all frames
279 
280         for (i=0; i<8; ++i)
281         {
282             if (sprite->angle_lumps[i] == lump)
283                 return true;
284         }
285     }
286     else
287     {
288         // check if this lump is being used for this frame
289 
290         if (sprite->angle_lumps[angle_num - 1] == lump)
291             return true;
292     }
293 
294     return false;
295 }
296 
AddSpriteLump(lumpinfo_t * lump)297 static void AddSpriteLump(lumpinfo_t *lump)
298 {
299     sprite_frame_t *sprite;
300     int angle_num;
301     int i;
302 
303     if (!ValidSpriteLumpName(lump->name))
304     {
305         return;
306     }
307 
308     // first angle
309 
310     sprite = FindSpriteFrame(lump->name, lump->name[4]);
311     angle_num = lump->name[5] - '0';
312 
313     if (angle_num == 0)
314     {
315         for (i=0; i<8; ++i)
316             sprite->angle_lumps[i] = lump;
317     }
318     else
319     {
320         sprite->angle_lumps[angle_num - 1] = lump;
321     }
322 
323     // second angle
324 
325     // no second angle?
326 
327     if (lump->name[6] == '\0')
328         return;
329 
330     sprite = FindSpriteFrame(lump->name, lump->name[6]);
331     angle_num = lump->name[7] - '0';
332 
333     if (angle_num == 0)
334     {
335         for (i=0; i<8; ++i)
336             sprite->angle_lumps[i] = lump;
337     }
338     else
339     {
340         sprite->angle_lumps[angle_num - 1] = lump;
341     }
342 }
343 
344 // Generate the list.  Run at the start, before merging
345 
GenerateSpriteList(void)346 static void GenerateSpriteList(void)
347 {
348     int i;
349 
350     InitSpriteList();
351 
352     // Add all sprites from the IWAD
353 
354     for (i=0; i<iwad_sprites.numlumps; ++i)
355     {
356         AddSpriteLump(iwad_sprites.lumps[i]);
357     }
358 
359     // Add all sprites from the PWAD
360     // (replaces IWAD sprites)
361 
362     for (i=0; i<pwad_sprites.numlumps; ++i)
363     {
364         AddSpriteLump(pwad_sprites.lumps[i]);
365     }
366 }
367 
368 // Perform the merge.
369 //
370 // The merge code creates a new lumpinfo list, adding entries from the
371 // IWAD first followed by the PWAD.
372 //
373 // For the IWAD:
374 //  * Flats are added.  If a flat with the same name is in the PWAD,
375 //    it is ignored(deleted).  At the end of the section, all flats in the
376 //    PWAD are inserted.  This is consistent with the behavior of
377 //    deutex/deusf.
378 //  * Sprites are added.  The "replace list" is generated before the merge
379 //    from the list of sprites in the PWAD.  Any sprites in the IWAD found
380 //    to match the replace list are removed.  At the end of the section,
381 //    the sprites from the PWAD are inserted.
382 //
383 // For the PWAD:
384 //  * All Sprites and Flats are ignored, with the assumption they have
385 //    already been merged into the IWAD's sections.
386 
DoMerge(void)387 static void DoMerge(void)
388 {
389     section_t current_section;
390     lumpinfo_t **newlumps;
391     int num_newlumps;
392     int lumpindex;
393     int i, n;
394 
395     // Can't ever have more lumps than we already have
396     newlumps = calloc(numlumps, sizeof(lumpinfo_t *));
397     num_newlumps = 0;
398 
399     // Add IWAD lumps
400     current_section = SECTION_NORMAL;
401 
402     for (i=0; i<iwad.numlumps; ++i)
403     {
404         lumpinfo_t *lump = iwad.lumps[i];
405 
406         switch (current_section)
407         {
408             case SECTION_NORMAL:
409                 if (!strncasecmp(lump->name, "F_START", 8))
410                 {
411                     current_section = SECTION_FLATS;
412                 }
413                 else if (!strncasecmp(lump->name, "S_START", 8))
414                 {
415                     current_section = SECTION_SPRITES;
416                 }
417 
418                 newlumps[num_newlumps++] = lump;
419 
420                 break;
421 
422             case SECTION_FLATS:
423 
424                 // Have we reached the end of the section?
425 
426                 if (!strncasecmp(lump->name, "F_END", 8))
427                 {
428                     // Add all new flats from the PWAD to the end
429                     // of the section
430 
431                     for (n=0; n<pwad_flats.numlumps; ++n)
432                     {
433                         newlumps[num_newlumps++] = pwad_flats.lumps[n];
434                     }
435 
436                     newlumps[num_newlumps++] = lump;
437 
438                     // back to normal reading
439                     current_section = SECTION_NORMAL;
440                 }
441                 else
442                 {
443                     // If there is a flat in the PWAD with the same name,
444                     // do not add it now.  All PWAD flats are added to the
445                     // end of the section. Otherwise, if it is only in the
446                     // IWAD, add it now
447 
448                     lumpindex = FindInList(&pwad_flats, lump->name);
449 
450                     if (lumpindex < 0)
451                     {
452                         newlumps[num_newlumps++] = lump;
453                     }
454                 }
455 
456                 break;
457 
458             case SECTION_SPRITES:
459 
460                 // Have we reached the end of the section?
461 
462                 if (!strncasecmp(lump->name, "S_END", 8))
463                 {
464                     // add all the PWAD sprites
465 
466                     for (n=0; n<pwad_sprites.numlumps; ++n)
467                     {
468                         if (SpriteLumpNeeded(pwad_sprites.lumps[n]))
469                         {
470                             newlumps[num_newlumps++] = pwad_sprites.lumps[n];
471                         }
472                     }
473 
474                     // copy the ending
475                     newlumps[num_newlumps++] = lump;
476 
477                     // back to normal reading
478                     current_section = SECTION_NORMAL;
479                 }
480                 else
481                 {
482                     // Is this lump holding a sprite to be replaced in the
483                     // PWAD? If so, wait until the end to add it.
484 
485                     if (SpriteLumpNeeded(lump))
486                     {
487                         newlumps[num_newlumps++] = lump;
488                     }
489                 }
490 
491                 break;
492         }
493     }
494 
495     // Add PWAD lumps
496     current_section = SECTION_NORMAL;
497 
498     for (i=0; i<pwad.numlumps; ++i)
499     {
500         lumpinfo_t *lump = pwad.lumps[i];
501 
502         switch (current_section)
503         {
504             case SECTION_NORMAL:
505                 if (!strncasecmp(lump->name, "F_START", 8)
506                  || !strncasecmp(lump->name, "FF_START", 8))
507                 {
508                     current_section = SECTION_FLATS;
509                 }
510                 else if (!strncasecmp(lump->name, "S_START", 8)
511                       || !strncasecmp(lump->name, "SS_START", 8))
512                 {
513                     current_section = SECTION_SPRITES;
514                 }
515                 else
516                 {
517                     // Don't include the headers of sections
518 
519                     newlumps[num_newlumps++] = lump;
520                 }
521                 break;
522 
523             case SECTION_FLATS:
524 
525                 // PWAD flats are ignored (already merged)
526 
527                 if (!strncasecmp(lump->name, "FF_END", 8)
528                  || !strncasecmp(lump->name, "F_END", 8))
529                 {
530                     // end of section
531                     current_section = SECTION_NORMAL;
532                 }
533                 break;
534 
535             case SECTION_SPRITES:
536 
537                 // PWAD sprites are ignored (already merged)
538 
539                 if (!strncasecmp(lump->name, "SS_END", 8)
540                  || !strncasecmp(lump->name, "S_END", 8))
541                 {
542                     // end of section
543                     current_section = SECTION_NORMAL;
544                 }
545                 break;
546         }
547     }
548 
549     // Switch to the new lumpinfo, and free the old one
550 
551     free(lumpinfo);
552     lumpinfo = newlumps;
553     numlumps = num_newlumps;
554 }
555 
W_PrintDirectory(void)556 void W_PrintDirectory(void)
557 {
558     unsigned int i, n;
559 
560     // debug
561     for (i=0; i<numlumps; ++i)
562     {
563         for (n=0; n<8 && lumpinfo[i]->name[n] != '\0'; ++n)
564             putchar(lumpinfo[i]->name[n]);
565         putchar('\n');
566     }
567 }
568 
569 // Merge in a file by name
570 
W_MergeFile(const char * filename)571 void W_MergeFile(const char *filename)
572 {
573     int old_numlumps;
574 
575     old_numlumps = numlumps;
576 
577     // Load PWAD
578 
579     if (W_AddFile(filename) == NULL)
580         return;
581 
582     // IWAD is at the start, PWAD was appended to the end
583 
584     iwad.lumps = lumpinfo;
585     iwad.numlumps = old_numlumps;
586 
587     pwad.lumps = lumpinfo + old_numlumps;
588     pwad.numlumps = numlumps - old_numlumps;
589 
590     // Setup sprite/flat lists
591 
592     SetupLists();
593 
594     // Generate list of sprites to be replaced by the PWAD
595 
596     GenerateSpriteList();
597 
598     // Perform the merge
599 
600     DoMerge();
601 }
602 
603 // Replace lumps in the given list with lumps from the PWAD
604 
W_NWTAddLumps(searchlist_t * list)605 static void W_NWTAddLumps(searchlist_t *list)
606 {
607     int i;
608 
609     // Go through the IWAD list given, replacing lumps with lumps of
610     // the same name from the PWAD
611     for (i=0; i<list->numlumps; ++i)
612     {
613         int index;
614 
615         index = FindInList(&pwad, list->lumps[i]->name);
616 
617         if (index > 0)
618         {
619             memcpy(list->lumps[i], pwad.lumps[index],
620                    sizeof(lumpinfo_t));
621         }
622     }
623 }
624 
625 // Merge sprites and flats in the way NWT does with its -af and -as
626 // command-line options.
627 
W_NWTMergeFile(const char * filename,int flags)628 void W_NWTMergeFile(const char *filename, int flags)
629 {
630     int old_numlumps;
631 
632     old_numlumps = numlumps;
633 
634     // Load PWAD
635 
636     if (W_AddFile(filename) == NULL)
637         return;
638 
639     // IWAD is at the start, PWAD was appended to the end
640 
641     iwad.lumps = lumpinfo;
642     iwad.numlumps = old_numlumps;
643 
644     pwad.lumps = lumpinfo + old_numlumps;
645     pwad.numlumps = numlumps - old_numlumps;
646 
647     // Setup sprite/flat lists
648 
649     SetupLists();
650 
651     // Merge in flats?
652 
653     if (flags & W_NWT_MERGE_FLATS)
654     {
655         W_NWTAddLumps(&iwad_flats);
656     }
657 
658     // Sprites?
659 
660     if (flags & W_NWT_MERGE_SPRITES)
661     {
662         W_NWTAddLumps(&iwad_sprites);
663     }
664 
665     // Discard the PWAD
666 
667     numlumps = old_numlumps;
668 }
669 
670 // Simulates the NWT -merge command line parameter.  What this does is load
671 // a PWAD, then search the IWAD sprites, removing any sprite lumps that also
672 // exist in the PWAD.
673 
W_NWTDashMerge(const char * filename)674 void W_NWTDashMerge(const char *filename)
675 {
676     wad_file_t *wad_file;
677     int old_numlumps;
678     int i;
679 
680     old_numlumps = numlumps;
681 
682     // Load PWAD
683 
684     wad_file = W_AddFile(filename);
685 
686     if (wad_file == NULL)
687     {
688         return;
689     }
690 
691     // IWAD is at the start, PWAD was appended to the end
692 
693     iwad.lumps = lumpinfo;
694     iwad.numlumps = old_numlumps;
695 
696     pwad.lumps = lumpinfo + old_numlumps;
697     pwad.numlumps = numlumps - old_numlumps;
698 
699     // Setup sprite/flat lists
700 
701     SetupLists();
702 
703     // Search through the IWAD sprites list.
704 
705     for (i=0; i<iwad_sprites.numlumps; ++i)
706     {
707         if (FindInList(&pwad, iwad_sprites.lumps[i]->name) >= 0)
708         {
709             // Replace this entry with an empty string.  This is what
710             // nwt -merge does.
711 
712             M_StringCopy(iwad_sprites.lumps[i]->name, "", 8);
713         }
714     }
715 
716     // Discard PWAD
717     // The PWAD must now be added in again with -file.
718 
719     numlumps = old_numlumps;
720 
721     W_CloseFile(wad_file);
722 }
723 
724 // [crispy] dump merged WAD data into a new IWAD file
W_MergeDump(const char * file)725 int W_MergeDump (const char *file)
726 {
727     FILE *fp = NULL;
728     char *lump_p = NULL;
729     uint32_t i, dir_p;
730 
731     // [crispy] WAD directory structure
732     typedef struct {
733 	uint32_t pos;
734 	uint32_t size;
735 	char name[8];
736     } directory_t;
737     directory_t *dir = NULL;
738 
739     // [crispy] open file for writing
740     fp = fopen(file, "wb");
741     if (!fp)
742     {
743 	I_Error("W_MergeDump: Failed writing to file '%s'!", file);
744     }
745 
746     // [crispy] prepare directory
747     dir = calloc(numlumps, sizeof(*dir));
748     if (!dir)
749     {
750 	I_Error("W_MergeDump: Error allocating memory!");
751     }
752 
753     // [crispy] write lumps to file, starting at offset 12
754     fseek(fp, 12, SEEK_SET);
755     for (i = 0; i < numlumps; i++)
756     {
757 	dir[i].pos = LONG(ftell(fp));
758 	dir[i].size = LONG(lumpinfo[i]->size);
759 	// [crispy] lump names are zero-byte padded
760 	memset(dir[i].name, 0, 8);
761 	strncpy(dir[i].name, lumpinfo[i]->name, 8);
762 
763 	// [crispy] avoid flooding Doom's Zone Memory
764 	lump_p = I_Realloc(lump_p, lumpinfo[i]->size);
765 	W_ReadLump(i, lump_p);
766 	fwrite(lump_p, 1, lumpinfo[i]->size, fp);
767     }
768     free(lump_p);
769 
770     // [crispy] write directory
771     dir_p = LONG(ftell(fp));
772     fwrite(dir, sizeof(*dir), i, fp);
773     free(dir);
774 
775     // [crispy] write WAD header
776     fseek(fp, 0, SEEK_SET);
777     fwrite("IWAD", 1, 4, fp);
778     i = LONG(i);
779     fwrite(&i, 4, 1, fp);
780     fwrite(&dir_p, 4, 1, fp);
781 
782     fclose(fp);
783 
784     return (i);
785 }
786