/* * Scrooge: A player salary oracle for Madden NFL 2004 (PC). * by George Greer * * Originally based on Scouter v2. * Version 1: May 27th, 2004 * * License: public domain with no warranty. * If it breaks, you get to keep both pieces. */ #define MAX_YEARS 7 #define MAX_TEAMS 64 #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; 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); const char *play_print_fields[] = { "TGID", "PPOS", "PLNA", "PFNA", "POVR", "PAGE", "PCYL", "PSA0", "PSB0", "PSA1", "PSB1", "PSA2", "PSB2", "PSA3", "PSB3", "PSA4", "PSB4", "PSA5", "PSB5", "PSA6", "PSB6" }; int num_print_fields = sizeof(play_print_fields) / sizeof(int); const char *play_fields[] = { "Team", "POS", "Last Name", "First Name", "OVR", "Age", "CYL", "Y0 Salary", "Y0 Bonus", "Y1 Salary", "Y1 Bonus", "Y2 Salary", "Y2 Bonus", "Y3 Salary", "Y3 Bonus", "Y4 Salary", "Y4 Bonus", "Y5 Salary", "Y5 Bonus", "Y6 Salary", "Y6 Bonus", }; 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 field_count, record_length, record_count; int cur_field, cur_rec; struct field_data *fields; int tgid_field_index = -1; unsigned long totals[MAX_TEAMS][MAX_YEARS]; int current_team = 999, t_team, t_year; /* Set all the totals to zero. */ memset(totals, 0, sizeof(totals)); /* 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("\n", output); /* Tabbed data. */ for (cur_rec = 0; cur_rec < record_count; cur_rec++) { fread(data, record_length, 1, db); /* "real" team check */ if (resolve_number(&fields[tgid_field_index], data) >= MAX_TEAMS) 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) memcpy(text, (char *)data + f->offset / 8, f->data_bits / 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 ((strncmp(f->name, "PSA", 3) == 0 || strncmp(f->name, "PSB", 3) == 0) && isdigit(f->name[3])) { totals[current_team][f->name[3] - '0'] += bitdata * 10000; if (bitdata == 0) text[0] = '\0'; else sprintf(text, "%lu", bitdata * 10000); } else if (strcmp(f->name, "TGID") == 0) { current_team = bitdata; sprintf(text, "%lu", bitdata); } else sprintf(text, "%lu", bitdata); } 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); for (t_team = 0; t_team < MAX_TEAMS; t_team++) { if (!totals[t_team][0]) continue; fprintf(output, "%u,,Team,Total,,,,", t_team); for (t_year = 0; t_year < MAX_YEARS; t_year++) fprintf(output, "%lu,,", totals[t_team][t_year]); fprintf(output, "\n"); } } 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; 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; } 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; fprintf(stderr, "%s: Madden 2003 does not have variable contracts.\r\n", me); return 1; } 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); return 0; } fprintf(stderr, "%s: didn't find player table! (Huh?)\r\n", me); return 1; }