/* * Scouter: A scouting oracle for Madden NFL 2003-2004 (PC). * by George Greer * * Version 2.2: October 3rd, 2004 * - "properly" handle integers over 32 bits long. * - option to print all fields * - option to skip filtering by team * - option to print to screen instead of comma-separated value file * - option to list tables * Version 2.1: May 27th, 2004 * - fixed problem with names without NUL. * Version 2: March 24th, 2004 * - Madden 2004 support * Version 1: May 10th, 2003 * - Madden 2003 support * * License: public domain with no warranty. * If it breaks, you get to keep both pieces. */ /* * options: * bit 0 (1) print all fields * bit 1 (2) don't filter by team * bit 2 (4) generate human-readable on standard output * bit 3 (8) print list of tables only */ const unsigned int draft_team = 1015; #include #include #include #include #include #include #include #include /* Generic dump buffers. */ unsigned long data[1024]; char text[4096]; const char *me; struct field_data { char *name; unsigned int data_bits; unsigned int offset; unsigned int type; }; struct field_extra { unsigned int width; const char *align; }; const char *print_position[] = { "QB", "HB", "FB", "WR", "TE", "LT", "LG", "C", "RG", "RT", "LE", "RE", "DT", "LOLB", "MLB", "ROLB", "CB", "FS", "SS", "K", "P", }; unsigned int num_print_position = sizeof(print_position) / sizeof(*print_position); const char *play_print_fields[] = { "PLNA", "PFNA", "PPOS", "PHGT", "PWGT", "POVR", "PAGE", "PSPD", "PSTR", "PAWR", "PAGI", "PACC", "PCTH", "PCAR", "PJMP", "PBTK", "PTAK", "PTHP", "PTHA", "PPBK", "PRBK", "PKPR", "PKAC", "PSTA", "PINJ", "PTGH", "PKRT", }; unsigned int num_print_fields = sizeof(play_print_fields) / sizeof(int); const char *play_fields[] = { "Last Name", "First Name", "POS", "HGT", "WGT", "OVR", "Age", "SPD", "STR", "AWR", "AGI", "ACC", "CTH", "CAR", "JMP", "BTK", "TAK", "THP", "THA", "PBK", "RBK", "KPR", "KAC", "STA", "INJ", "TGH", "KRT", }; unsigned int num_name_fields = sizeof(play_fields) / sizeof(char *); /* Field name -> field array index mapping. */ int *play_print_fields_index; /* function prototypes */ unsigned long resolve_number(struct field_data *f, long *data, unsigned char bitstart); int field_sorter(const void *a, const void *b) { struct field_data *c = (struct field_data *)a; struct field_data *d = (struct field_data *)b; return c->offset - d->offset; } void table_print(FILE *db, FILE *output, unsigned int version, unsigned int print_team, unsigned long options) { unsigned int field_count, record_length, record_count, cur_field, cur_rec; struct field_data *fields; struct field_extra *fieldx = NULL; int tgid_field_index = -1; /* Header */ switch (version) { case 2003: fread(data, sizeof(long), 6, db); record_length = data[1]; record_count = (data[3] & 0xffff0000) >> 16; field_count = data[5] & 0xff; break; case 2004: fread(data, sizeof(long), 9, db); record_length = data[1]; record_count = (data[4] & 0xffff0000) >> 16; field_count = data[6] & 0xff; break; default: fprintf(stderr, "%s: impossible; version not found.\n", me); exit(1); } if (options & 1) /* "print all fields" */ num_print_fields = field_count; /* Allocate space for printing->database field mapping. */ play_print_fields_index = calloc(num_print_fields, sizeof(int)); if (!play_print_fields) { fprintf(stderr, "%s: error allocating field mapping: %s", me, strerror(errno)); exit(1); } for (cur_field = 0; cur_field < num_print_fields; cur_field++) play_print_fields_index[cur_field] = options & 1 ? cur_field : -1; /* Field names */ fields = calloc(field_count, sizeof(struct field_data)); for (cur_field = 0; cur_field < field_count; cur_field++) { fread(data, sizeof(long), 4, db); memcpy(text, data + 2, 4); text[4] = '\0'; fields[cur_field].type = data[0]; fields[cur_field].offset = data[1]; fields[cur_field].name = strdup(text); fields[cur_field].data_bits = data[3]; } qsort(fields, field_count, sizeof(struct field_data), field_sorter); /* Now the dynamic field lookups. */ for (cur_field = 0; cur_field < field_count; cur_field++) { unsigned int printed_no; /* Locate the TGID field so we can skip non-draft players. */ if (tgid_field_index < 0 && strcmp(fields[cur_field].name, "TGID") == 0) tgid_field_index = cur_field; /* When printing all fields, the mapping is always the same. */ if (options & 1) continue; /* If this is a printed field, resolve the name to a number. */ for (printed_no = 0; printed_no < num_print_fields; printed_no++) if (strcmp(fields[cur_field].name, play_print_fields[printed_no]) == 0) { play_print_fields_index[printed_no] = cur_field; break; } } /* Make sure there is a TGID field available. */ if ((options & 2) == 0 && tgid_field_index < 0) { fprintf(stderr, "%s: could not find team field. (TGID)\n", me); exit(1); } /* Also check to make sure the other fields were translated properly. */ for (cur_field = 0; cur_field < num_print_fields; cur_field++) if (play_print_fields_index[cur_field] < 0) { fprintf(stderr, "%s: could not resolve %s (%u of %u printed, %u total) to field.\n", me, play_print_fields[cur_field], cur_field, num_print_fields, field_count); exit(1); } /* Pre-calculate field widths if printing to screen. */ if (options & 4) { fieldx = calloc(field_count, sizeof(struct field_extra)); for (cur_field = 0; cur_field < num_print_fields; cur_field++) { struct field_data *f = &fields[play_print_fields_index[cur_field]]; fieldx[cur_field].align = ""; if (f->type == 0) { fieldx[cur_field].width = f->data_bits / 8; if (strcmp(f->name, "PFNA") == 0) fieldx[cur_field].align = "-"; else if (strcmp(f->name, "PLNA") == 0) fieldx[cur_field].align = "-"; } else if (f->data_bits == 32) fieldx[cur_field].width = 10; else if (f->data_bits > 32) fieldx[cur_field].width = (f->data_bits) / 4 + 2; else if (strcmp(f->name, "PPOS") == 0) { fieldx[cur_field].width = 8; fieldx[cur_field].align = "-"; } else if (strcmp(f->name, "PHGT") == 0) fieldx[cur_field].width = 5; else if (strcmp(f->name, "PWGT") == 0) fieldx[cur_field].width = 5; else /* This calculates log with base 10 instead of e. */ fieldx[cur_field].width = (unsigned int)(log((1 << f->data_bits) - 1) / log(10) + 1); if (fieldx[cur_field].width < strlen(f->name)) fieldx[cur_field].width = strlen(f->name); } } /* Tab-delimited heading. */ if (options & 4) { for (cur_field = 0; cur_field < num_print_fields; cur_field++) if (options & 1) fprintf(output, "%*s ", fieldx[cur_field].width, fields[play_print_fields_index[cur_field]].name); else fprintf(output, "%*s ", fieldx[cur_field].width, play_fields[cur_field]); } else for (cur_field = 0; cur_field < num_print_fields; cur_field++) fprintf(output, "%s,", (options & 1) ? fields[cur_field].name : play_fields[cur_field]); fputs("\n", output); /* Tabbed data. */ for (cur_rec = 0; cur_rec < record_count; cur_rec++) { fread(data, record_length, 1, db); /* Draft Team check */ if ((options & 2) == 0 && resolve_number(&fields[tgid_field_index], data, 0) != print_team) continue; for (cur_field = 0; cur_field < num_print_fields; cur_field++) { struct field_data *f = &fields[play_print_fields_index[cur_field]]; if (f->type == 0) { if (f->data_bits % 8 != 0) { fprintf(stderr, "field %s: expected text field but not multiple of 8 bits. type=%u bits=%u\n", f->name, f->type, f->data_bits); exit(EXIT_FAILURE); } memcpy(text, (char *)data + f->offset / 8, f->data_bits / 8); } else if (f->data_bits > 32) { unsigned char i; strcpy(text, "0x"); for (i = 0; i < f->data_bits; i += 32) { if (f->data_bits - i >= 32) sprintf(text + strlen(text), "%08lx", resolve_number(f, data, i)); else if (f->data_bits - i >= 16) sprintf(text + strlen(text), "%04lx", resolve_number(f, data, i)); else if (f->data_bits - i >= 8) sprintf(text + strlen(text), "%02lx", resolve_number(f, data, i)); } } else { unsigned long bitdata = resolve_number(f, data, 0); if (strcmp(f->name, "PPOS") == 0) { /* Position */ if (bitdata > num_print_position) { fprintf(stderr, "%s: position '%lu' out of range. (%d)\n", me, bitdata, num_print_position); exit(1); } sprintf(text, "%02lu: %s", bitdata, print_position[bitdata]); } else if (strcmp(f->name, "PHGT") == 0) /* Height */ sprintf(text, "%ld'%02ld\"", bitdata / 12, bitdata % 12); else if (strcmp(f->name, "PWGT") == 0) /* Weight */ sprintf(text, "%lu", bitdata + 160); else sprintf(text, "%lu", bitdata); } if (options & 4) { char format[256]; sprintf(format, "%%%s*s ", fieldx[cur_field].align); fprintf(output, format, fieldx[cur_field].width, text); } else fprintf(output, "%s,", text); } fputs("\n", output); } for (cur_field = 0; cur_field < field_count; cur_field++) free(fields[cur_field].name); free(play_print_fields_index); free(fields); if (fieldx) free(fieldx); } unsigned long resolve_number(struct field_data *f, long *data, unsigned char bitstart) { unsigned long bitdata; unsigned long startoffset = f->offset + bitstart; unsigned long stoplen = f->data_bits - bitstart; int bucket = startoffset / 32; int oshift = startoffset % 32; int data_bits = stoplen + oshift > 32 ? 32 - oshift : stoplen; int mask = data_bits == 32 ? 0xffffffff : (1 << data_bits) - 1; bitdata = (data[bucket] >> oshift) & mask; //printf("bs=%u bd=%lu db=%lu os=%u mask=%x sto=%lu stl=%lu\n", bitstart, bitdata, data[bucket], oshift, mask, startoffset, stoplen); if (oshift && oshift + stoplen > 32) { mask = data_bits == 32 ? 0xfffffff : (1 << (stoplen - data_bits)) - 1; oshift = 32 - oshift; bucket++; bitdata |= (data[bucket] & mask) << oshift; } return bitdata; } int main(int argc, char *argv[]) { FILE *db, *output; char output_file[4096]; size_t seekpos; unsigned int cur_table, table_count, version, main_header_size, print_team; const char *version_string, *print_table = NULL; unsigned long options = 0; assert(num_name_fields == num_print_fields); me = argv[0]; if (argc < 2) { fprintf(stderr, "Syntax: %s yourfranchise.fra [team|table] [options]\n", me); return 1; } else if (!(db = fopen(argv[1], "rb"))) { fprintf(stderr, "%s: cannot open '%s' to read: %s\n", me, argv[1], strerror(errno)); return 1; } if (argc >= 4) options = atol(argv[3]); if (options & 8) options |= 4; if (argc >= 3) { if (isdigit(argv[2][0])) print_team = atol(argv[2]); else { print_table = argv[2]; print_team = 0; options |= 3; } } else print_team = draft_team; if (options & 8) printf("%s: printing list of tables.\n", me); else if (!print_table) { if (options & 2) printf("%s: printing ALL teams.\n", me); else if (print_team != draft_team) printf("%s: printing team #%d. (options=%lu)\n", me, print_team, options); } else printf("%s: printing table '%s' (options=%lu)\n", me, print_table, options); if (options & 4) output = stdout; else { strcpy(output_file, argv[1]); strcat(output_file, ".csv"); if (!(output = fopen(output_file, "w"))) { fprintf(stderr, "%s: cannot open '%s' to write: %s\n", me, output_file, strerror(errno)); return 1; } } /* Header reading. */ fread(data, sizeof(long), 1, db); if (data[0] == 0x07004244) { version = 2003; version_string = "2003"; } else if (data[0] == 0x08004244) { version = 2004; version_string = "2004/2005"; } else { fprintf(stderr, "%s: %s: don't recognize database ID: %#08lx\n", me, output_file, data[0]); return 1; } printf("%s: %s: Detected Madden NFL %s data file.\n", me, argv[1], version_string); main_header_size = version == 2003 ? 4 : 5; fread(data, sizeof(long), main_header_size, db); table_count = data[3]; if (!print_table) print_table = "PLAY"; /* Table list */ for (cur_table = 0; cur_table < table_count; cur_table++) { fread(text, sizeof(char), 4, db); text[4] = '\0'; fread(data, sizeof(long), 1, db); if (options & 8) { printf("%s\n", text); continue; } if (strcmp(text, print_table) != 0) continue; seekpos = sizeof(long) /* version marker */ + main_header_size * sizeof(long) /* file header */ + table_count * (sizeof(char) * 4 + sizeof(long)) /* index of tables */ + (version == 2003 ? 0 : sizeof(long)) /* CRC */ + data[0]; /* table offset */ fseek(db, seekpos, SEEK_SET); table_print(db, output, version, print_team, options); return 0; } if ((options & 8) == 0) { fprintf(stderr, "%s: didn't find table: %s\n", me, print_table); return 1; } return 0; }