/**************** * Program name : WOLFMAP.C * Description : Wolfenstein 3d mapping utility. * Version : 1.2 * * Author : David Lummis * Compuserve Id: 70042,73 * * Last updated : Wed Aug 12, 1992 @ 00:05:21 *****************/ /****************************************************************************** MAPHEAD.WL? Map header file: Contains file offsets to level headers MAPTEMP.WL? Version 1.0 map data file. GAMEMAPS.WL? Version 1.1+ map data file Shareware version.....: MAPHEAD.WL1, MAPTEMP.WL1 If bought episodes 1-3: MAPHEAD.WL3, GAMEMAPS.WL3 (contains 1 to 3) If bought episodes 1-6: MAPHEAD.WL6, GAMEMAPS.WL6 (contains 1 to 6) ------------------------------------------------------------------------------ File: MAPHEAD.WL? (Wolfenstein 3D - all versions) ================= File Structure (all values shown in hex): A) Repeat_code File Data Sample Offset Type Data Description ------ ---- ------ ------------------- 0000 int CD AB This is the 2 byte integer used in the map file to indicate data compression. B) Level_header_offsets For every level in each episode there is a 4 byte (ie. long) absolute file offset which points into the map data file at a particular Level_header. Each episode consists of 10 levels. The file seems to allow for 100 four-byte offsets (ie. 100 levels, or 10 episodes). All remaining offsets are 00000000 (v1.1) or FFFFFFFF (v1.0). ------------------------------------------------------------------------------ File: MAPTEMP.WL? (Wolfenstein 3D - version 1.0) ================= File Structure (all values shown in hex): A) File_header File Data Offset Type Sample Data Description ------ ---- ----------- ------------------------------------------- 0000 char "TED5v1.0" Ascii string (8 bytes) (no null terminator) 0008 A0 ??? 0009 FF ??? 000A CA ??? B) Level_data (contains data for all ten levels) Level structure (one per level) a) Level_header Level Header Data Offset Type Description ------ ---- ------------------------------- 0000 long File offset of Map_Block 0004 long File offset of Object_Block 0008 long File offset of Unknown_Block 000C int File size of Map_Block 000E int File size of Object_Block 0010 int File size of Unknown_Block 0012 int Horizontal map size (# of squares) 0014 int Vertical map size (# of squares) 0016 char Level name (ascii) (null terminated) 0026 char "!ID!" (4 bytes) b) Map_block c) Object_block d) Unknown_block Each block consists of a 4 byte header followed by data. Block Data Offset Type Description ------ ---- -------------------------------------------- 0000 int # of data bytes after decompresssing. 0002 int Data bytes. Values are stored as 2 byte integers (low byte first). If value == ABCD (ie. the integer we found at the beginning of the MAPHEAD file) then: - the next integer is # of repetitions - the next integer is value to repeat If value is not ABCD then store the value as is. ------------------------------------------------------------------------------ File: GAMEMAPS.WL? (Wolfenstein 3D - version 1.1+) ================== Changes since v1.0: - Level_header has moved so that it now follows the Unknown_block - must now make an extra pass through the raw data in order to decode special A7 and A8 bytes. - Byte at absolute offset 8 has changed (purpose ???). File Structure (all values shown in hex): A) File_header File Data Offset Type Sample Data Description ------ ---- ----------- ------------------------------------------- 0000 char "TED5v1.0" Ascii string (8 bytes) (no null terminator) 0008 C8 ??? 0009 FF ??? 000A CA ??? B) Level_data (contains data for all ten levels) Level structure (one per level) a) Map_block b) Object_block c) Unknown_block Each block consists of a 4 byte header followed by data. Block Data Offset Type Description ------ ---- -------------------------------------------- 0000 int # of data bytes after decoding (count includes the two bytes at Block Offset 0002). 0002 int # of data bytes after decompresssing. 0004 int Data bytes. Values are stored as 2 byte integers (low byte first). To decode the data you must make two passes at it. Pass1 (decoding): ----- If high byte of value is A7 then: If low byte is 00 then: - high byte of value is A7 - low byte of value is the next byte Else - the low byte is a count (ie. # of integers we want to reuse). - the next byte is the # of integers we want to move back in order to reuse data we have already decoded. If high byte of value is A8 then: If low byte is 00 then: - high byte of value is A8 - low byte of value is the next byte Else - the low byte is a count (ie. # of integers we want to reuse). - the next integer (2 bytes) is the # of integers we want to skip over, starting at the beginning of the buffer being used to hold data we have already decoded (the first integer in the buffer being the # of bytes after decompressing). If high byte is not A7 or A8, then store the value as is. Pass2 (decompressing): ----- If value == ABCD (ie. the integer we found at the beginning of the MAPHEAD file) then: - the next integer is # of repetitions - the next integer is value to repeat If value is not ABCD then store the value as is. d) Level_header Level Header Data Offset Type Description ------ ---- ------------------------------- 0000 long File offset of Map_Block 0004 long File offset of Object_Block 0008 long File offset of Unknown_Block 000C int File size of Map_Block 000E int File size of Object_Block 0010 int File size of Unknown_Block 0012 int Horizontal map size (# of squares) 0014 int Vertical map size (# of squares) 0016 char Level name (ascii) (null terminated) 0026 char "!ID!" (4 bytes) ******************************************************************************/ #include #include #include #include #include #define byte unsigned char #define word unsigned int // Maximum number of levels allowed for in each game's map file. #define MAP_MAXLEVELS 10 // Maximum number of episodes allowed #define MAP_MAXEPISODES 6 // Filenames #define FILE_MAPEXT ".WL" #define FILE_MAPHEAD "MAPHEAD" #define FILE_0MAPTEMP "MAPTEMP" #define FILE_1MAPTEMP "GAMEMAPS" #define FILE_LEGEND "WMAP" #define FILE_LEGENDEXT ".LEG" #define FILE_COUNT "WMAP" #define FILE_COUNTEXT ".COU" #define FILE_MAP "WMAP" #define FILE_MAP2 "WMAP" #define FILE_WOLFMAP "WOLFMAP.CFG" // Number of different map types generated by this program #define MAP_NUMTYPES 3 // Section names allowed in the WOLFMAP configuration file. #define SECT_MAPGROUPS "[MAP GROUPS]" #define SECT_MAPVALUES "[MAP VALUES]" #define SECT_OBJGROUPS "[OBJECT GROUPS]" #define SECT_OBJVALUES "[OBJECT VALUES]" // Minimum lengths of GROUP and VALUE lines in the WOLFMAP configuration file. #define LEN_GROUPLINE 11 #define LEN_VALUELINE 6 // Max length of group descriptions #define LEN_GDESCRIPTION 35 // Minimum and maximum GROUP and VALUE numbers. // Total number of GROUPS and VALUES allowed. #define MIN_GROUP 0 #define MIN_VALUE 0 #define MAX_GROUP 255 #define MAX_VALUE 255 #define NUM_GROUPS 256 #define NUM_VALUES 256 // total # of map and object groups #define NUM_GROUPS2 512 // Map group types #define MT_OTHER 0 #define MT_WALL 1 #define MT_DOOR 2 #define MT_FLOOR 3 // Object group types #define OT_OTHER 0 #define OT_TRIVIAL 1 #define OT_NONTRIVIAL 2 #define OT_ENEMY 3 // Maximum X and Y values allowed for maps. // Maximum size (in bytes) needed to hold fully uncompressed map data. // Maximum size (in words) needed to hold fully uncompressed map data. #define MAX_X 64 #define MAX_Y 64 #define MAX_BYTES 8192 #define MAX_WORDS 4096 char nullstr[1]; struct find_t diskinfo; int episode_num; // Game number (1 to 6) int num_episodes; // Number of episodes found in MAPHEAD file. int wolf_ver; // Wolfenstein version number (0=1.0, 1=1.1+) // Filenames char data_extension[5]; // Extension used for data files (eg ".WL1") char maphead1_filename[255]; // 1st MAPHEAD filename (episodes 1 to 3) (map offsets) char maphead2_filename[255]; // 2nd MAPHEAD filename (episodes 4 to 6) (map offsets) char maptemp_filename[255]; // MAPTEMP filename (map data) char wolfmap_filename[255]; // Holds name of WOLFMAP's configuration file char count_filename[255]; // Name of file to contain data counts. char legend_filename[255]; // Name of file to contain map symbol legend. char map_name[255]; char obj_name[255]; char comb_name[255]; int ep11_flag; // =1 if we found episode 1-1 file int ep13_flag; // =1 if we found episode 1-3 file int ep46_flag; // =1 if we found episode 4-6 file int ep16_flag; // =1 if we found episode 1-6 file struct group_rec // Configuration file GROUP record layout { byte character; // Character to use when creating maps byte character2a; // Character to use when creating maps (/2 option) byte character2b; // Character to use when creating maps (/2 option) int type; // Group type number char legend; // Y = print on legend page char *description; // Pointer to description }; struct value_rec // Configuration file VALUE record layout { int group; // Group number }; struct group_rec map_groups[NUM_GROUPS]; struct value_rec map_values[NUM_VALUES]; struct group_rec obj_groups[NUM_GROUPS]; struct value_rec obj_values[NUM_VALUES]; struct group_rec *sortgroups[NUM_GROUPS2]; char input_buffer[255]; // Used when reading configuration file and user input int input_line; // Current line number in configuration file. long input_pos; // Used to hold current file offset in config file. word code_repeat; // Holds repeat code found at start of MAPHEAD file word counts[MAP_MAXLEVELS][256]; // Holds data counts by level word totals[256]; int help_flag; int item_mode; int count_flag; int enemy_mode; int pause_flag; int num_lines; int map_type; // Type of map to generate. int double_width; // 1=Use 2 characters to represent a map/object value int map_from; // Starting level number int map_to; // Ending level number char map_title[255]; // Title for current map int hex_scale_x; // 1=print hexadecimal X scale on map int hex_scale_y; // 1=print hexadecimal Y scale on map unsigned long map_offset[MAP_MAXEPISODES][MAP_MAXLEVELS]; // Offsets of map data in maptemp file word map_data[MAX_WORDS]; // Fully uncompressed map data word obj_data[MAX_WORDS]; // Fully uncompressed object data byte buf1_data[MAX_BYTES]; // Holds data read directly from file word buf2_data[MAX_WORDS]; // Holds decoded data struct level_header { long map_off; // Offset of map data block long obj_off; // Offset of object data block long xxx_off; // Offset of unknown data block word map_size; // Size of map data block (before decompressing) word obj_size; // Size of object data block (before decompressing) word xxx_size; // Size of unknown data block (before decompressing) word xsize; // # of horizontal squares word ysize; // # of vertical squares char name[16]; // Map name }; struct level_header lh; /**** * Function prototypes ****/ void read_config(); void init_section(FILE *hConfig, char *p1); char *init_readline(FILE *handle); void init_groups(struct group_rec *group, FILE *hConfig); void init_values(struct value_rec *value, FILE *hConfig); void set_options(char *f); void one_more_line(); void pause(char *p); void display_help(); int make_map(int level, char *mapfile, char *objfile, char *combfile); int make_map2(int which, int level, char *outfile, word map_info[]); int read_map(int level); void read_bytes(FILE *in_stream, word map_info[], word data_size); int seek_file(FILE *stream, long offset); void print_xscale(FILE *out_stream, word xsize, int map_type); void print_bytes(int which, FILE *out_stream, word map_info[]); int count_data(); void print_counts(FILE *out_stream); void count_level(int level, word map_info[]); int hextoi(char *hex); void print_legend(); void vSort_List(struct group_rec *apsList[], int iLength); int read_hdr_info(FILE *stream, int max_episodes); void read_header_files(); /*--------------------------------------------------------------------------*/ main(int argc, char *argv[]) { char *p1; int i, count; FILE *temp; int eplow, ephi; nullstr[0] = NULL; /****** * Initialize defaults ******/ episode_num = 0; // Episode number (0 unless user uses /G option) num_episodes= 1; // # of episodes wolf_ver = 0; // 0=v1.0 help_flag = 0; // 0=No help item_mode = 2; // 2=Show non-trivial items only count_flag = 0; // 0=No count file enemy_mode = 1; // 1=Enemies by skill level pause_flag = 1; // 1=Pause during help screen. num_lines = 0; // Line counter for pause option map_type = 0; // Normal map double_width = 0; // 0=use a single char on map for each map/object value map_from = 0; // From level (0 unless user uses /L option) map_to = 0; // To level (0 unless user uses /L option) hex_scale_x = 1; // 1=print hexadecimal X scale on map hex_scale_y = 1; // 1=print hexadecimal Y scale on map ep11_flag = 0; ep13_flag = 0; ep46_flag = 0; ep16_flag = 0; for (i=0; i 1) { for(count=1; count 1 && episode_num==0) { while (episode_num == 0) { printf("Episode number (%d to %d)? ", eplow, ephi); gets(input_buffer); episode_num = atoi(input_buffer); if (episode_num==0) exit(0); if (episode_num < eplow || episode_num > ephi || episode_num > MAP_MAXEPISODES) episode_num = 0; } } else { if (episode_num < eplow || episode_num > ephi || episode_num > MAP_MAXEPISODES) episode_num = 1; printf("Episode number..........: %d\n", episode_num); } if (map_from==0 || map_to==0) { while (map_from == 0) { printf("Level # (1 to %2d, *=All)? ", MAP_MAXLEVELS); gets(input_buffer); if (*input_buffer=='*') { map_from = 1; map_to = MAP_MAXLEVELS; } else { map_from = atoi(input_buffer); if (map_from==0) exit(0); if (map_from >= 1 && map_from <= MAP_MAXLEVELS) map_to = map_from; else map_from = 0; } } } else { if (map_from==map_to) printf("Level number............: %d\n", map_from); else printf("Level numbers...........: %d to %d\n", map_from, map_to); } printf("\n"); /****** * Determine which version of Wolfenstein we are dealing with. * * 1. Look for MAPTEMP. * If found, then it is version 1.0 * 2. Look for GAMEMAPS * If found, then it is version 1.1 or higher. ******/ /* Extract extension from appropriate MAPHEAD filename */ p1 = strpbrk(episode_num <= 3 ? maphead1_filename : maphead2_filename, "."); if (p1) strcpy(data_extension, p1); else *data_extension = NULL; strcpy(maptemp_filename, FILE_0MAPTEMP); strcat(maptemp_filename, data_extension); wolf_ver = 0; // v1.0 temp = fopen(maptemp_filename, "rb"); if (temp==NULL) { strcpy(maptemp_filename, FILE_1MAPTEMP); strcat(maptemp_filename, data_extension); wolf_ver = 1; // v1.1 temp = fopen(maptemp_filename, "rb"); if (temp==NULL) { printf("Unable to open Wolfenstein 3D map data file.\n"); exit(1); } } fclose(temp); /* The count-file filename */ sprintf(count_filename, "%s%d%s", FILE_COUNT, episode_num, FILE_COUNTEXT); /****** * Now we generate the maps ******/ for (i=map_from-1; i 255) { printf("File %s, Line %d: Group number out of range.\n", wolfmap_filename, input_line); exit(1); } p2++; // Skip over space // Extract "character" character = *p2++; p2++; // Skip over space // Extract "double width character 1" character2a = *p2++; // Extract "double width character 2" character2b = *p2++; p2++; // Skip over space // Extract "type" work[0] = *p2++; work[1] = NULL; type = atoi(work); if (type < 0 || type > 255) { printf("File %s, Line %d: Type number out of range.\n", wolfmap_filename, input_line); exit(1); } p2++; // Skip over space // Extract "print on legend?" flag (Y=print) legend = toupper(*p2); if (legend != 'Y') legend = 'N'; p2++; /* Rest of line may be missing, so test char at p2 before using */ if (*p2) p2++; // Skip over space /* Allocate memory for description */ desc = malloc(LEN_GDESCRIPTION+1); if (desc==NULL) { printf("Unable to allocate memory for group description.\n"); exit(1); } if (*p2) { strncpy(desc, p2, LEN_GDESCRIPTION); *(desc+LEN_GDESCRIPTION)=NULL; // Make sure null terminated. } else *desc = NULL; // Assign values group[group_num].character = character; group[group_num].character2a = character2a; group[group_num].character2b = character2b; group[group_num].type = type; group[group_num].legend = legend; group[group_num].description = desc; p2 = init_readline(hConfig); // Read next line } if (*p2 == '[') { fseek(hConfig, input_pos, SEEK_SET); // Go back to the "[" line input_line--; } return; } /*---------------------------------------------------------------------------*/ void init_values(struct value_rec *value, FILE *hConfig) /********** * Read value info from configuration file **********/ { char work[4]; char *p2; int value_num; int group_num; work[3] = NULL; p2 = init_readline(hConfig); // Read first line while (feof(hConfig)==0 && *p2 != '[') { // Make sure line is long enough if (strlen(p2) < LEN_VALUELINE) { printf("File %s, Line %d: Not enough characters on line.\n", wolfmap_filename, input_line); exit(1); } // Extract "value number" work[0] = *p2++; work[1] = *p2++; work[2] = NULL; value_num = hextoi(work); if (value_num < 0 || value_num > 255) { printf("File %s, Line %d: Value out of range.\n", wolfmap_filename, input_line); exit(1); } p2++; // Skip over space // Extract "group number" work[0] = *p2++; work[1] = *p2++; work[2] = *p2++; group_num = atoi(work); if (group_num < 0 || group_num > 255) { printf("File %s, Line %d: Group number out of range.\n", wolfmap_filename, input_line); exit(1); } // Assign values value[value_num].group = group_num; p2 = init_readline(hConfig); // Read next line } if (*p2 == '[') { fseek(hConfig, input_pos, SEEK_SET); // Go back to the "[" line input_line--; } return; } /*---------------------------------------------------------------------------*/ void set_options(char *f) { int data, data2, on_off; char *p; byte ch; while( *f != NULL) { data = toupper(*f); on_off = 1; data2 = NULL; if (data) { /* Check next char for + or -. (quick check for allowing on/off switches) */ data2 = *(f+1); switch(data2) { case '+': on_off = 1; break; case '-': on_off = 0; break; default: on_off = 1; break; } } switch (data) { case '?': case 'H': help_flag = 1; /* Display help screen */ pause_flag = on_off; return; case '2': double_width = on_off; break; case 'C': count_flag = on_off; break; case 'F': f++; data2 = toupper(*f); if (data2) { f++; switch (data2) { case 'C': strcpy(wolfmap_filename, f); break; } } return; case 'X': f++; if (*f=='-') hex_scale_x = 0; else hex_scale_x = 1; return; case 'Y': f++; if (*f=='-') hex_scale_y = 0; else hex_scale_y = 1; return; case 'L': f++; if (*f == '*') { map_from = 1; map_to = MAP_MAXLEVELS; } else { map_from = atoi(f); if (map_from < 1) map_from = 1; if (map_from > MAP_MAXLEVELS) map_from = MAP_MAXLEVELS; map_to = map_from; } return; case 'T': f++; map_type = atoi(f); if (map_type < 0) map_type = 0; if (map_type > MAP_NUMTYPES) map_type = MAP_NUMTYPES; return; case 'G': f++; episode_num = atoi(f); if (episode_num < 1 || episode_num > MAP_MAXEPISODES) episode_num = 1; case 'E': f++; /* This applies to symbolic maps only */ switch (*f) { case '-': /* suppress enemies */ enemy_mode = 0; break; } return; case 'O': f++; switch (*f) { case '-': /* suppress all items */ item_mode = 0; break; case '1': /* display all items */ item_mode = 1; break; case '2': /* display only non trivial items */ item_mode = 2; break; } return; } f++; } return; } /*---------------------------------------------------------------------------*/ void one_more_line() { num_lines++; if (pause_flag && num_lines >= 23) { printf("Strike a key when ready . . . "); getch(); printf("%c %c", 13, 13); num_lines = 0; } return; } /*---------------------------------------------------------------------------*/ void pause(char *p) { puts(p); one_more_line(); } /*---------------------------------------------------------------------------*/ void display_help() { pause("Syntax:"); pause(" "); pause(" WOLFMAP [