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