/* * Scouter: A scouting oracle for Madden NFL 2003-2004 (PC). * by George Greer * * Version 1: May 10th, 2003 * Version 2: March 24th, 2004 * * License: public domain with no warranty. * If it breaks, you get to keep both pieces. */ const int draft_team = 1015; #include #include #include #include #include #include /* Generic dump buffers. */ unsigned long data[1024]; char text[4096]; const char *me; struct field_data { char *name; int data_bits; int offset; int unknown; }; 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", }; int num_print_position = sizeof(print_position) / sizeof(*print_position); /*int play_print_fields_2003[] = { 1, 0, 87, 97, 4, 69, 21, 20, 68, 70, 32, 14, 29, 65, 62, 41, 36, 60, 9, 37, 38, 67, 13, 11, 35, 25, 99 };*/ 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", }; 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", }; 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); 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, int version, int print_team) { int field_count, record_length, record_count; int cur_field, cur_rec; struct field_data *fields; 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.\r\n", me); exit(1); } /* 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] = -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].unknown = 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++) { 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; /* 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 (tgid_field_index < 0) { fprintf(stderr, "%s: could not find team field. (TGID)\r\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 to field.\r\n", me, play_print_fields[cur_field]); exit(1); } /* Tab-delimited heading. */ for (cur_field = 0; cur_field < num_print_fields; cur_field++) fprintf(output, "%s,", play_fields[cur_field]); fputs("\r\n", output); /* Tabbed data. */ for (cur_rec = 0; cur_rec < record_count; cur_rec++) { fread(data, record_length, 1, db); /* Draft Team check */ if (resolve_number(&fields[tgid_field_index], data) != 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->data_bits > 32 && f->data_bits % 8 == 0) strcpy(text, (char *)data + f->offset / 8); else { unsigned long bitdata = resolve_number(f, data); if (strcmp(f->name, "PPOS") == 0) { /* Position */ if (bitdata > num_print_position) { fprintf(stderr, "%s: position '%lu' out of range. (%d)\r\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); } fprintf(output, "%s,", text); } fputs("\r\n", output); } for (cur_field = 0; cur_field < field_count; cur_field++) free(fields[cur_field].name); free(play_print_fields_index); free(fields); } unsigned long resolve_number(struct field_data *f, long *data) { unsigned long bitdata; int bucket = f->offset / 32; int oshift = f->offset % 32; int data_bits = f->data_bits + oshift > 32 ? 32 - oshift : f->data_bits; int mask = (1 << data_bits) - 1; bitdata = (data[bucket] >> oshift) & mask; if (oshift + f->data_bits > 32) { mask = (1 << (f->data_bits - 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; int version, main_header_size, print_team; assert(num_name_fields == num_print_fields); me = argv[0]; if (argc < 2) { fprintf(stderr, "Syntax: %s yourfranchise.fra\r\n", me); return 1; } else if (!(db = fopen(argv[1], "rb"))) { fprintf(stderr, "%s: cannot open '%s' to read: %s\r\n", me, argv[1], strerror(errno)); return 1; } print_team = argc >= 3 ? atol(argv[2]) : draft_team; if (print_team != draft_team) printf("%s: printing team #%d.\n", me, print_team); strcpy(output_file, argv[1]); strcat(output_file, ".csv"); if (!(output = fopen(output_file, "w"))) { fprintf(stderr, "%s: cannot open '%s' to write: %s\r\n", me, output_file, strerror(errno)); return 1; } /* Header reading. */ fread(data, sizeof(long), 1, db); if (data[0] == 0x07004244) { version = 2003; } else if (data[0] == 0x08004244) { version = 2004; } else { fprintf(stderr, "%s: %s: don't recognize database ID: %#08lx\r\n", me, output_file, data[0]); return 1; } printf("%s: %s: Detected Madden NFL %d data file.\r\n", me, argv[1], version); main_header_size = version == 2003 ? 4 : 5; fread(data, sizeof(long), main_header_size, db); table_count = data[3]; /* 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 (strcmp(text, "PLAY") != 0) continue; seekpos = data[0] + main_header_size * sizeof(long) + table_count * (sizeof(char) * 4 + sizeof(long)); seekpos += version == 2003 ? 4 : 8; fseek(db, seekpos, SEEK_SET); table_print(db, output, version, print_team); return 0; } fprintf(stderr, "%s: didn't find player table! (Huh?)\r\n", me); return 1; }