/**************** * Program name : BLAKEMAP.C * Description : Blake Stone AOG mapping utility. * Version : 1.1 * * Author : David Lummis * Compuserve Id: 71534,3067 * * Last updated : Mon Apr 18, 1994 @ 00:05:24 * * Notes : Compile using large memory model. *****************/ /****************************************************************************** MAPHEAD.BS? Map header file: Contains file offsets to level headers MAPTEMP.BS? Version 1.0, 2.0 map data file. Shareware version.....: MAPHEAD.BS1, MAPTEMP.BS1 If bought episodes 1-3: MAPHEAD.BS3, MAPTEMP.BS3 (contains 1 to 3) If bought episodes 1-6: MAPHEAD.BS6, MAPTEMP.BS6 (contains 1 to 6) Note: At the time this program was written it was not know what filename (MAPTEMP or GAMEMAPS) will be used for later or commercial versions. Wolfenstein 3d used GAMEMAPS starting with v1.1, and MAPTEMP for v1.0. This program currently will check for each name starting first with MAPTEMP and then GAMEMAPS. ------------------------------------------------------------------------------ File: MAPHEAD.BS? (Blake Stone AOG - v1.0, 2.0) ================= 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 11 levels, one secret and 10 regular. All remaining offsets are FFFFFFFF or 00000000. Note: There appear to be 15 offsets listed per episode (according to reports from people with the non-shareware version). The extra offsets are apparently not part of the regular game which only has 10 levels and a secret level. ------------------------------------------------------------------------------ File: MAPTEMP.BS? (Blake Stone AOG - version 1.0, 2.0) ================= Uses compression method 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) 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 Unknown 000C int File size of Map_Block 000E int File size of Object_Block 0010 int Unknown 0012 int Horizontal map size (# of squares) 0014 int Vertical map size (# of squares) 0016 char Level name (ascii) (null terminated) 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. e) End_of_data Consists of four ascii characters: !ID! ------------------------------------------------------------------------------ Compression method 1: This method was used by Wolfenstein 3D v1.1+, and the code has been left in this program. 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. ******************************************************************************/ #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. // 1 secret level and 10 regular levels #define MAP_MAXLEVELS 11 // Maximum number of episodes allowed #define MAP_MAXEPISODES 6 // Maximum number of file offsets per level in the map temp file // Non-shareware version appears to have 15 file offsets per episode. // The offset for the first level of episode 2 is apparently the 16th in // the file. #define DATA_MAXLEVELS 15 // Maximum number of offsets that this program will read from the // maptemp file. // Indications are that the non-shareware game has 15 offsets per game // stored in the maptemp file. To play it safe, I've basically doubled // that and multiplied it by the 6 episodes, which should allow // some extra room in case there are more. #define MAP_MAXOFFSETS 180 // Filenames #define FILE_MAPEXT ".BS" #define FILE_MAPHEAD "MAPHEAD" #define FILE_0MAPTEMP "MAPTEMP" #define FILE_1MAPTEMP "GAMEMAPS" #define FILE_LEGEND "BMAP" #define FILE_LEGENDEXT ".LEG" #define FILE_COUNT "BMAP" #define FILE_COUNTEXT ".COU" #define FILE_MAP "BMAP" #define FILE_MAP2 "BMAP" #define FILE_BLAKEMAP "BLAKEMAP.CFG" // Number of different map types generated by this program #define MAP_NUMTYPES 3 // Section names allowed in the BLAKEMAP configuration file. #define SECT_MAPOFFSETS "[MAP OFFSETS]" #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 BLAKEMAP 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 511 #define NUM_GROUPS 256 #define NUM_VALUES 512 // 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 comp_method; // Compression method used (0=Wolf v1.0, 1=Wolf v1.1+) int user_comp_method; // User override of compression method via command line // Filenames char data_extension[5]; // Extension used for data files (eg ".BS1") char maphead_filename[255]; // MAPHEAD filename (map offsets) char maptemp_filename[255]; // MAPTEMP filename (map data) char blakemap_filename[255]; // Holds name of BLAKEMAP'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 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][512]; // Holds data counts by level word totals[512]; 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]; // Subscript of array holding Offsets of map data unsigned long master_offset[MAP_MAXOFFSETS]; // 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; // Unknown word map_size; // Size of map data block (before decompressing) word obj_size; // Size of object data block (before decompressing) word xxx_size; // Unknown 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 init_offsets(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[]) { unsigned long temp_offset; 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 comp_method = 0; // 0=v1.0, v2.0 user_comp_method = -1; // -1 = no user override 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; 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 # (0 to %2d, *=All)? ", MAP_MAXLEVELS-1); gets(input_buffer); if (*input_buffer=='*') { map_from = 1; map_to = MAP_MAXLEVELS; } else { map_from = atoi(input_buffer); map_from++; 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-1); else printf("Level numbers...........: %d to %d\n", map_from-1, map_to-1); } printf("\n"); /****** * Look for Blake Stone map data file. * * 1. Look for MAPTEMP. Uses compression method 0. * 2. Look for GAMEMAPS. Uses compression method 1. ******/ /* Extract extension from MAPHEAD filename */ p1 = strpbrk(maphead_filename, "."); if (p1) strcpy(data_extension, p1); else *data_extension = NULL; strcpy(maptemp_filename, FILE_0MAPTEMP); strcat(maptemp_filename, data_extension); comp_method = 0; // v1.0, v2.0 temp = fopen(maptemp_filename, "rb"); if (temp==NULL) { strcpy(maptemp_filename, FILE_1MAPTEMP); strcat(maptemp_filename, data_extension); comp_method = 1; // v1.1 temp = fopen(maptemp_filename, "rb"); if (temp==NULL) { printf("Unable to open Blake Stone AOG map data file.\n"); exit(1); } } fclose(temp); /* Allow user specified decompression method to override computer's choice */ if (user_comp_method >= 0) comp_method = user_comp_method; /* 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", blakemap_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", blakemap_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", blakemap_filename, input_line); exit(1); } // Extract "value number" work[0] = *p2++; work[1] = *p2++; work[2] = *p2++; work[3] = NULL; value_num = hextoi(work); if (value_num < 0 || value_num > 511) { printf("File %s, Line %d: Value out of range.\n", blakemap_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", blakemap_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 init_offsets(FILE *hConfig) /********** * Read map offsets info from configuration file **********/ { char *p1, *p2; int episode_num; int offset_num; int count; p2 = init_readline(hConfig); // Read first line while (feof(hConfig)==0 && *p2 != '[') { // Check for an equal sign to ensure at least 1 char after first value p1 = strpbrk(p2, "="); if (p1 == NULL) { printf("File %s, Line %d: No equal sign on line.\n", blakemap_filename, input_line); exit(1); } // Extract "episode number" while (*p2==' ') p2++; if (isdigit(*p2)) { p1 = p2; while (isdigit(*p1)) p1++; *p1 = NULL; episode_num = atoi(p2); // Extract episode number if (episode_num < 1 || episode_num > MAP_MAXEPISODES) { printf("File %s, Line %d: Episode number out of range.\n", blakemap_filename, input_line); exit(1); } } else { printf("File %s, Line %d: Non-numeric character at start of line.\n", blakemap_filename, input_line); exit(1); } // Extract "offset number" p2 = p1 + 1; while (*p2!=NULL && !isdigit(*p2)) p2++; if (*p2 != NULL) { p1 = p2; while (isdigit(*p1)) p1++; *p1 = NULL; offset_num = atoi(p2); // Extract offset number if (offset_num < 1 || offset_num > MAP_MAXOFFSETS) { printf("File %s, Line %d: Offset number out of range.\n", blakemap_filename, input_line); exit(1); } } else { printf("File %s, Line %d: No offset number found after equal sign on line.\n", blakemap_filename, input_line); exit(1); } offset_num--; // Assign values for (count=0; count < MAP_MAXLEVELS; count++) map_offset[episode_num-1][count] = offset_num+count; // map_offset subscripts 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; return; case 'C': count_flag = on_off; return; case 'D': f++; switch (*f) { case '0': user_comp_method = 0; break; case '1': user_comp_method = 1; break; default: user_comp_method = 0; break; } return; case 'F': f++; data2 = toupper(*f); if (data2) { f++; switch (data2) { case 'C': strcpy(blakemap_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); map_from++; 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; return; 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(" BLAKEMAP [