/* * WADWHAT.C - print the contents of a DOOM WAD file * * by Randall R. Spangler * * Version 1.1, released 8/28/94 * */ #include #include #include #include #include #include struct s_thing { /* Thing data */ short int x, y; /* Coordinates */ short int facing; /* Facing, in degrees */ short int type; /* Type of thing */ short int attr; /* Thing attributes */ }; typedef struct s_thing THING; /***************************************************************************/ char contents[10][9] = /* Valid sub-entry names for a level */ {"THINGS", "LINEDEFS", "SIDEDEFS", "VERTEXES", "SEGS", "SSECTORS", "NODES", "SECTORS", "REJECT", "BLOCKMAP"}; char itemline[] = "\t%-20s %5ld %5ld %5ld %5ld + %5d %5d %5d %5d\n"; /* Line of text for an * item */ /***************************************************************************/ int isbrief = 0; /* If non-zero, is the skill level we're * printing brief stats on */ int battr = 0; /* Matching attribute byte for brief printout */ int battrarry[5] = {0x01, 0x01, 0x02, 0x04, 0x04}; /* Attribute bytes vs. * skill level */ FILE *savefile; /* WAD file whose things we're looking at */ long savefilepos; /* Save position in WAD file (where we were * before we started looking at THINGS, * LINEDEFS, or SECTORS) */ long thoff = 0, thlen = 0; /* Position and length of thing data */ long ldoff = 0, ldlen = 0; /* Position and length of linedef * data */ long seoff = 0, selen = 0; /* Position and length of sector data */ long rjlen = 0; /* Length of reject data */ THING *th; /* Thing data for a level */ int numth = 0; /* Number of things */ char buf[256]; /***************************************************************************/ long countth(int type, int attr) { /* Returns the number of things of the specified type with at least one * matching attribute bit. If the multi-player bit in is on, * matches either single or multi-player; else matches single-player * only. =(-1) matches any skill or multi-player. */ THING *t = th; /* Current thing */ int askill = attr & 0x07; /* Skill bits of attribute */ int amp = attr & 0x10; /* Multi-player bits of attribute */ long n = 0; /* Number of matches */ int i; for (i = 0; i < numth; i++, t++) { /* Search all things */ if (t->type != type) continue; /* Wrong object */ if (attr < 0) n++; /* Match all objects of this type */ else if (t->attr & askill) { if (amp) n++; /* Want single or multi */ else if (!(t->attr & 0x10)) n++; /* Want single only */ } } return n; /* Return match count */ } /***************************************************************************/ long *countdiff(int type, long *dest) { /* Counts the number of things of the specified type at each difficulty * level. dest[0]=L12, dest[1]=L3, dest[2]=L45, dest[3]=multiplayer * only. Returns a pointer to the destination array, or NULL if error. */ THING *t = th; int i; for (i = 0; i < 6; i++) dest[i] = 0; /* Clear destination array */ for (i = 0; i < numth; i++) { /* Search all things */ if (t->type == type) { /* Matched type */ if (t->attr & 0x00010) { /* Multi-player only */ if (t->attr & 0x0001) dest[3]++; if (t->attr & 0x0002) dest[4]++; if (t->attr & 0x0004) dest[5]++; } else { /* Single-player only */ if (t->attr & 0x0001) dest[0]++; if (t->attr & 0x0002) dest[1]++; if (t->attr & 0x0004) dest[2]++; } } t++; /* Advance to next thing */ } for (i = 0; i < 3; i++) dest[i + 3] += dest[i]; /* Single-player stuff is present in * multi-player too */ return dest; /* Return match count */ } /***************************************************************************/ long *addcountweighted(int type, long *dest, int weight) { /* Similar to countdiff(), but adds times the counts vs. * difficulty to the destination array instead of just setting it to the * match counts. Returns NULL if error. */ long nd[6]; int i; countdiff(type, nd); /* Count the matches */ for (i = 0; i < 6; i++) dest[i] += weight * nd[i]; /* Add a weighted result */ return dest; } /***************************************************************************/ /***************************************************************************/ int loadthings(FILE * f, long offs, long len) { /* Loads a map's THINGS data at offset , length . Returns * zero if error. */ savefile = f; /* Save WAD file handle */ savefilepos = ftell(f); /* Save position in WAD file */ fseek(f, offs, SEEK_SET); th = (THING *) malloc(len); if (!th) { fprintf(stderr, "Not enough memory to hold THINGS\n"); return 0; } fread(th, 1, len, f); numth = len / 10; return 1; /* Success */ } int freethings(void) { /* Frees a map's THINGS data loaded by loadthings(). Returns zero if * error. */ free(th); numth = 0; fseek(savefile, savefilepos, SEEK_SET); return 1; /* Success */ } /***************************************************************************/ void printitem(char *desc, long *arry, int double1) { /* Prints an item's presence at all skill levels. If double1, doubles * the numbers in columns 1 and 5. */ int i; for (i = 0; i < 6; i++) if (arry[i]) break; if (i == 6) return; /* Item is not present at any skill * level */ printf("\t%-20.20s", desc); /* Print description */ for (i = 0; i < 6; i++) { if (i == 3) printf(" |"); if (i == 0 || i == 3) printf(" %5ld", (double1 ? 2 * arry[i] : arry[i])); printf(" %5ld", arry[i]); } printf("\n"); } /***************************************************************************/ void prtth(int type, char *desc) { /* Prints the description and number of matching things at each * difficulty level. */ long nd[6] = {0, 0, 0, 0, 0, 0}; /* Numbers at each difficulty * level */ countdiff(type, nd); printitem(desc, nd, 0); } /***************************************************************************/ /* If there is at least one thing with id , print the description of the * thing and the number present at each skill level. */ #define PRINTITEM(desc,arry) printitem(desc,arry,0) #define PRINTITEM2(desc,arry) printitem(desc,arry,1) /* Reset an array */ #define RESET(arry) for(i=0; i<6; i++) arry[i]=0 int countthings(void) { /* Counts the things in a level's THINGS data. Returns zero if error. */ long nc[6] = {0, 0, 0, 0, 0, 0}; /* Number of things counted * at each skill level */ long wdam[6] = {0, 0, 0, 0, 0, 0}; /* Total weapon damage at * each skill level */ float dratio[6] = {0, 0, 0, 0, 0, 0}; /* Damage ratio for * difficulty */ int n, i; /** Player starts **/ printf(" Play modes:\n"); if (countth(1, -1)) /* Find player 1 start */ printf("\tSingle player\n"); n = 0; for (i = 1; i <= 4; i++) { /* Find player 1-4 starts */ if (countth(i, -1)) n++; } if (n > 1) printf("\tCooperative (%d player)\n", n); n = countth(11, -1); /* Find deathmatch starts */ if (n) printf("\tDeathmatch (%d starts)\n", n); /** Monsters **/ printf(" Bosses:\n"); prtth(3003, "Baron"); prtth(16, "Cyberdemon"); prtth(7, "Spiderdemon"); printf(" Monsters:\n"); prtth(3004, "Trooper"); prtth(9, "Sergeant"); prtth(3001, "Imp"); prtth(3002, "Demon"); prtth(58, "Spectre"); prtth(3006, "Lost soul"); prtth(3005, "Cacodemon"); printf(" Weapons:\n"); prtth(2001, "Shotgun"); prtth(2002, "Chaingun"); prtth(2003, "Rocket launcher"); prtth(2004, "Plasma gun"); prtth(2006, "BFG-9000"); prtth(2005, "Chainsaw"); printf(" Equipment:\n"); prtth(8, "Backpack"); prtth(2022, "Invulnerability"); prtth(2023, "Berserk"); prtth(2024, "Invisibility"); prtth(2025, "Radiation suit"); prtth(2026, "Computer map"); prtth(2045, "Lite amp goggles"); printf(" Expendibles:\n"); RESET(nc); addcountweighted(2002, nc, 20); /* Chainguns */ addcountweighted(2007, nc, 10); /* Clips */ addcountweighted(2048, nc, 50); /* Boxes of ammo */ addcountweighted(8, nc, 10); /* Backpacks */ addcountweighted(3004, nc, 5); /* Troopers */ PRINTITEM2("Bullets", nc); for (i = 0; i < 6; i++) wdam[i] += nc[i]; /* Accumulate damage */ RESET(nc); addcountweighted(2001, nc, 8); /* Shotguns */ addcountweighted(2008, nc, 4); /* Shells */ addcountweighted(2049, nc, 20); /* Boxes of shells */ addcountweighted(8, nc, 4); /* Backpacks */ addcountweighted(9, nc, 4); /* Sergeants */ PRINTITEM2("Shells", nc); for (i = 0; i < 6; i++) wdam[i] += 7 * nc[i]; /* Accumulate damage */ RESET(nc); addcountweighted(2003, nc, 2); /* Rocket launchers */ addcountweighted(2010, nc, 1); /* Rockets */ addcountweighted(2046, nc, 5); /* Boxes of rockets */ addcountweighted(8, nc, 1); /* Backpacks */ PRINTITEM2("Rockets", nc); for (i = 0; i < 6; i++) wdam[i] += 20 * nc[i]; /* Accumulate damage */ RESET(nc); addcountweighted(2004, nc, 40); /* Plasma guns */ addcountweighted(2006, nc, 40); /* BFG-9000's */ addcountweighted(17, nc, 20); /* Cell packs */ addcountweighted(2047, nc, 100); /* Cell charges */ addcountweighted(8, nc, 20); /* Backpacks */ PRINTITEM2("Cells", nc); for (i = 0; i < 6; i++) wdam[i] += 2 * nc[i]; /* Accumulate damage */ RESET(nc); addcountweighted(2018, nc, 100); /* Armor */ addcountweighted(2019, nc, 200); /* Super armor */ addcountweighted(2015, nc, 1); /* Armor bonuses */ PRINTITEM2("Armor points", nc); RESET(nc); addcountweighted(2011, nc, 10); /* Stimpacks */ addcountweighted(2012, nc, 25); /* Medikits */ addcountweighted(2013, nc, 100); /* Soul spheres */ addcountweighted(2023, nc, 100); /* Berserk strength */ addcountweighted(2014, nc, 1); /* Health bonuses */ PRINTITEM2("Health points", nc); prtth(2035, "Barrels"); /* Calculate difficulty based on damage we can do vs. damage required to * kill all the monsters */ printf(" Difficulty:\n"); RESET(nc); addcountweighted(3004, nc, 2); /* Troopers */ addcountweighted(9, nc, 3); /* Sergeants */ addcountweighted(3001, nc, 6); /* Imps */ addcountweighted(3002, nc, 15); /* Demons */ addcountweighted(58, nc, 15); /* Spectres */ addcountweighted(3006, nc, 10); /* Lost souls */ addcountweighted(3005, nc, 40); /* Cacodemons */ addcountweighted(3003, nc, 100); /* Barons */ addcountweighted(16, nc, 400); /* Cyberdemons */ addcountweighted(7, nc, 300); /* Spiderdemons */ PRINTITEM("Total monster hp", nc); PRINTITEM2("Max ammo damage", wdam); for (i = 0; i < 6; i++) { if (!wdam[i]) continue; dratio[i] = (wdam[i] ? (float) nc[i] / (float) wdam[i] : 0); /* Prevent * divide-by-zero */ } printf("\t%-20s %0.3f %0.3f %0.3f %0.3f", "RATIO", 0.5 * dratio[0], dratio[0], dratio[1], dratio[2]); printf(" | %0.3f %0.3f %0.3f %0.3f\n", 0.5 * dratio[3], dratio[3], dratio[4], dratio[5]); /*** Return success ***/ return 1; } /***************************************************************************/ /* Item count */ #define COB(item) countth(item,battr) /* Print a character if the item is present */ #define PRB0(item,char) printf("%c",(COB(item)?(char):'.')) /* Print item count, one digit */ #define PRB1(item) {n=COB(item);printf((n>9?"+ ":"%d "),n);} /* Print item count, two digits */ #define PRB2(item) {n=COB(item);printf((n>99?"++ ":"%2d "),n);} int countbriefly(void) { /* Counts the things in a level, with one-line output format. Returns * zero if error. */ long wdam = 0; /* Total weapon damage */ long mhp = 0; /* Total monster hit points */ float dratio; /* Damage ratio */ long l; int n, i; /*** Player starts ***/ n = 0; for (i = 1; i <= 4; i++) { /* Find player 1-4 starts */ if (countth(i, -1)) n++; } printf(" %d %2ld ", n, countth(0x11, -1)); /* Find deathmatch * starts */ /*** Monsters ***/ /* Bosses */ PRB2(3003); PRB1(16); PRB1(7); /* Monsters */ printf(" "); PRB2(3004); PRB2(9); PRB2(3001); PRB2(3002); PRB2(58); PRB2(3006); PRB2(3005); /* Weapons */ putchar(' '); PRB0(2005, '1'); putchar('2'); PRB0(2001, '3'); PRB0(2002, '4'); PRB0(2003, '5'); PRB0(2004, '6'); PRB0(2006, '7'); /* Equipment */ printf(" "); PRB0(8, 'B'); PRB0(2022, 'V'); PRB0(2023, 'S'); PRB0(2024, 'I'); PRB0(2025, 'R'); PRB0(2026, 'A'); PRB0(2045, 'L'); /*** Calculate damage ratio ***/ /** Ammo from all sources **/ l = 20 * COB(2002) + 10 * COB(2007) + 50 * COB(2048) + 10 * COB(8) + 5 * COB(3004); wdam += l; /* Bullets */ l = 8 * COB(2001) + 4 * COB(2008) + 20 * COB(2049) + 4 * COB(8) + 4 * COB(9); wdam += 7 * l; /* Shells */ l = 2 * COB(2003) + COB(2010) + 5 * COB(2046) + COB(8); wdam += 20 * l; /* Rockets */ l = 40 * COB(2004) + 40 * COB(2006) + 20 * COB(17) + 100 * COB(2047) + 20 * COB(8); wdam += 2 * l; /* Cell packs */ /** Monster hit points **/ mhp = 2 * COB(3004) + 3 * COB(9) + 6 * COB(3001) + 15 * COB(3002) + 15 * COB(58) + 10 * COB(3006) + 40 * COB(3005) + 100 * COB(3003) + 400 * COB(16) + 300 * COB(7); dratio = (wdam ? (float) mhp / (float) wdam : 0); /* Make sure we don't * divide by zero */ if (isbrief == 1) dratio *= 0.5; /* Twice as much ammo in easiest * level */ printf(" %0.3f\n", dratio); /*** Return success ***/ return 1; } /***************************************************************************/ /***************************************************************************/ int countlinedefs(FILE * f, long offs, long len) { /* Counts the interesting linedefs in a map's LINEDEFS data. Returns * zero if error. */ long savepos = ftell(f); /* Save position in WAD file */ long nlines = len / 14; /* Total number of linedefs */ long ntrig = 0; /* Number of linedefs which do something */ int ibuf[7]; long l; /*** Seek to the LINEDEFS data and loop through the linedefs ***/ fseek(f, offs, SEEK_SET); for (l = 0; l < nlines; l++) { fread(ibuf, sizeof(int), 7, f); /* Read a linedef */ if (ibuf[3]) ntrig++; } /*** Print information ***/ printf("\tTriggers / linedefs %4ld / %4ld\n", ntrig, nlines); /*** Seek to original file position and return success ***/ fseek(f, savepos, SEEK_SET); return 1; /* Success */ } /***************************************************************************/ int countsectors(FILE * f, long offs, long len) { /* Counts the interesting sectors in a map's SECTORS data. Returns zero * if error. */ long savepos = ftell(f); /* Save position in WAD file */ long nsec = len / 26;/* Total number of sectors */ long bright = 0; /* Average brightness */ long nnuke = 0; /* Sectors with nukeage */ long nsecret = 0; /* Secret sectors */ int ibuf[13]; long l; /*** Seek to the SECTORS data and loop through the sectors ***/ fseek(f, offs, SEEK_SET); for (l = 0; l < nsec; l++) { fread(ibuf, sizeof(int), 13, f);/* Read a sector */ bright += ibuf[10]; /* Accumulate brightness */ switch (ibuf[11]) { /* Check for specials */ case 4: /* -20%, blinking */ case 5: /* -10% */ case 7: /* -5% */ case 11: /* -20%, end of level */ case 16: /* -20% */ nnuke++; break; case 9: /* Secret */ nsecret++; break; } } /*** Print information ***/ printf("\tAverage brightness %4ld (0=dark, 255=bright)\n", bright / nsec); printf("\tSecrets %4ld\n", nsecret); printf("\tNukeage / sectors %4ld / %4ld\n", nnuke, nsec); /*** Seek to original file position and return success ***/ fseek(f, savepos, SEEK_SET); return 1; /* Success */ } /***************************************************************************/ /***************************************************************************/ int PrintAllStuff(FILE * f) { /* Prints all information for a map. Returns zero if error. */ /*** Print things ***/ if (thlen) { if (loadthings(f, thoff, thlen)) { /* Load things */ if (isbrief) countbriefly(); /* Print brief counts */ else countthings(); /* Print verbose counts */ freethings(); /* Free array, restore original file * position */ } } /*** If brief info, return now (no extended info) ***/ if (isbrief) return 1; /*** Print other info ***/ printf(" Other info:\n"); if (selen) countsectors(f, seoff, selen); if (ldlen) countlinedefs(f, ldoff, ldlen); printf("\tReject resource %s\n", (rjlen > 0 ? "YES" : " NO")); /*** Return success ***/ return 1; } /***************************************************************************/ int handlepwad(char *fname) { /* Handles a PWAD file. Returns zero if error. */ int ispwad = 0; /* Are we a PWAD? (or an IWAD) */ long ndirent; /* Number of entries in WAD directory */ long diroffs; /* Offset of directory in WAD file */ long eoffs, elen; /* Offset and length of a directory entry */ char ename[9] = "entrynam"; /* Name of the entry */ int episode = 0, mission = 0; /* Current episode and * mission */ FILE *f; int i, j; /*** Open the PWAD file ***/ f = fopen(fname, "rb"); if (!f) { fprintf(stderr, "Can't open file %s\n", fname); return 0; } /*** Read the header ***/ fread(buf, sizeof(char), 4, f); if (!strncmp(buf, "IWAD", 4)) { ispwad = 0; } else if (!strncmp(buf, "PWAD", 4)) { ispwad = 1; } else { printf("%s is not a DOOM WAD file\n", fname); fclose(f); return 0; } if (!isbrief) { /* Start a new filename in the output */ printf("==============================================================================\n"); printf("%cWAD FILE %s:\n", (ispwad ? 'P' : 'I'), fname); } fread(&ndirent, sizeof(long), 1, f);/* Number of entries in WAD dir */ fread(&diroffs, sizeof(long), 1, f);/* Offset of directory in WAD */ /*** Print the WAD directory ***/ fseek(f, diroffs, SEEK_SET); /* Go to the directory */ for (i = 0; i < ndirent; i++) { /** Read entry **/ fread(&eoffs, sizeof(long), 1, f); /* Offset of entry's data */ fread(&elen, sizeof(long), 1, f); /* Length of entry's data */ fread(&ename, sizeof(char), 8, f); /* Name of entry */ /** If entry is part of a level, print it **/ for (j = 0; j < 10; j++) { if (!strcmp(ename, contents[j])) break; /* Matched valid contents for a level */ } switch (j) { case 10: /* Not level contents */ if (!mission) break; /** Look at all the stuff we found **/ PrintAllStuff(f); /** Reset status **/ thlen = ldlen = selen = rjlen = 0; episode = mission = 0; /* No longer in a level */ break; case 0: /* THINGS */ thoff = eoffs; thlen = elen; break; case 1: /* LINEDEFS */ ldoff = eoffs; ldlen = elen; break; case 7: /* SECTORS */ seoff = eoffs; selen = elen; break; case 8: /* REJECT */ rjlen = elen; break; } /** Keep track of which mission we're looking at **/ if (ename[0] == 'E' && isdigit(ename[1]) && ename[2] == 'M' && isdigit(ename[3]) && ename[4] == '\0') { episode = ename[1] - '0'; mission = ename[3] - '0'; if (isbrief) { /* One-line output */ printf("%-12.12s E%dM%d", fname, episode, mission); } else { /* Verbose output */ printf("------------------------------------------------------------------------------\n"); printf("EPISODE %d MISSION %d S1 S2 S3 S45 | M1 M2 M3 M45\n", episode, mission); printf("------------------------------------------------------------------------------\n"); } } } /*** Look at all the stuff we found from the last map, if any ***/ if (mission) PrintAllStuff(f); thlen = ldlen = selen = rjlen = 0; /*** Close the file and return success ***/ fclose(f); return 1; } /***************************************************************************/ /***************************************************************************/ int main(int argc, char *argv[]) { struct ffblk ff; int i; /*** Make sure we've been given a filename ***/ printf("WADWHAT 1.1 by Randall R. Spangler (rspangle@micro.caltech.edu)\n"); if (argc < 2) { printf("Prints the contents of a WAD file or files.\n"); printf("Usage:\n\twadwhat [-Bn[M]] file1 [file2 ...]\n"); printf("\n\t\t-Bn\tbrief contents at skill level n\n"); printf("\t\t-BnM\tbrief contents at skill level n, multiplayer\n"); return 1; } /*** Match all wildcards ***/ for (i = 1; i < argc; i++) { /** See if we're an option **/ if (!strnicmp(argv[i], "-B", 2)) { /* Print briefly */ isbrief = atoi(argv[i] + 2);/* Extract skill level */ printf("File Map Play Bosses Monsters Weapons Equip RATIO\n"); printf("-------------------c-de--ba-c-s--tr-se-im-de-sp-lo-ca--cpscrpb--bvsiral-------\n"); battr = battrarry[isbrief - 1]; /* Matching attributes for * brief printout */ if (toupper(argv[i][3]) == 'M') battr |= 0x10; /* Add in multi-player stuff */ continue; } /** Make sure at least one matching file exists **/ if (findfirst(argv[i], &ff, FA_ARCH)) { /* No match for wildcard */ fprintf(stderr, "Can't find file matching %s\n", argv[i]); continue; } /** Handle all the matching files **/ do { handlepwad(ff.ff_name); } while (!findnext(&ff)); } /*** Return success ***/ return 0; } /***************************************************************************/ /***************************************************************************/