// arbitrary terminology i made up on the spot: // "entry" = code as exposed to user, in ASCII. // "code" = code as indices into lut, used internally. #include #include #include #include typedef uint32_t u32; typedef uint64_t u64; #define lament(...) fprintf(stderr, __VA_ARGS__) #define sizeofel(a) (sizeof(*(a))) #define LEN(a) (sizeof(a) / sizeofel(a)) const char *lut[] = { // characters within the same string are considered the same. // that means the number of choices for a single character // goes down from 36 to 28. "N", "P", "Q", "R", "T", "W", "X", "Y", "17I", "2Z", "5S", "8B", "0OD", "UV", "3", "4", "6", "9", "A", "C", "E", "F", "G", "H", "J", "K", "L", "M", }; enum {lutlen = LEN(lut)}; // should be 28. const u32 bases[] = { 64, 11, 21, 2, 21, 8, 6, // dummy value. }; enum { MT_CHOICE = -1, }; typedef enum { MT_YOSHI, MT_PEACH, MT_MARIO, MT_BOWSER, MT_BOO, MT_DK, MT_BABY_MARIO, MT_TOAD, MT_WALUIGI, MT_WARIO, MT_LUIGI, MT_DAISY, MT_BIRDO, MT_SHYGUY, MT_DK_JR, MT_PARATROOPA, } character_t; typedef enum { MT_SINGLES_GAME, MT_SINGLES_TIME_3, MT_SINGLES_TIME_5, MT_SINGLES_BALL_1, MT_SINGLES_BALL_3, MT_SINGLES_POINT_3, MT_SINGLES_POINT_5, MT_DOUBLES_GAME_1, MT_DOUBLES_TIME_3, MT_DOUBLES_BALL_1, MT_DOUBLES_POINT_3, } gamemode_t; typedef enum { MT_HARD, MT_CLAY, MT_GRASS, MT_COMPOSITION, MT_OPEN, } court_t; typedef struct { char code[9]; // data[0]: cup name // data[1]: gamemode // data[2]: player character (-1 for player's choice) // data[3]: unknown (boolean) // data[4]: opponent character (-1 for player's choice) // data[5]: unknown // data[6]: court (-1 for player's choice) u32 data[7]; } code_t; code_t translate(const char *entry) { code_t code = {0}; for (int i = 0; i < 9; i++) { char e = entry[i]; if (e == '\0') return code; for (int j = 0; j < lutlen; j++) { const char *c = lut[j]; while (*c != '\0') { if (e == *c) { code.code[i] = j; goto nextchar; } c++; } } nextchar: ; } return code; } int check_ranges(const code_t *code) { const u32 choice = (u32)MT_CHOICE; const u32 *data = code->data; return (data[0] < bases[0]) && (data[1] < bases[1]) && (data[2] + 1 < bases[2] - 4) && (data[3] < bases[3]) && (data[4] + 1 < bases[4] - 4) && (data[5] < bases[5]) && (data[6] + 1 < bases[6]) && (data[2] == choice || data[2] != data[4]) && (data[1] < 7 || (data[2] != choice && data[4] != choice)); } int decode_data(code_t *code) { u32 part0 = 0, part1 = 0; for (int i = 5; i >= 0; i--) part0 = part0 * lutlen + code->code[i]; for (int i = 8; i >= 6; i--) part1 = part1 * lutlen + code->code[i]; u32 checksum = (part0 + 0x2AE0) % (lutlen * lutlen * lutlen); #if !NDEBUG lament("merged data: %08X\n", part0); lament("expected checksum: %08X\n", part1); lament("actual checksum: %08X\n", checksum); #endif u32 leftover = part0 ^ 0x02AAAAAA; code->data[0] = leftover % bases[0]; leftover /= bases[0]; code->data[1] = leftover % bases[1]; leftover /= bases[1]; code->data[2] = leftover % bases[2]; leftover /= bases[2]; code->data[3] = leftover % bases[3]; leftover /= bases[3]; code->data[4] = leftover % bases[4]; leftover /= bases[4]; code->data[5] = leftover % bases[5]; leftover /= bases[5]; code->data[6] = leftover; code->data[2]--; code->data[4]--; code->data[6]--; #if !NDEBUG lament("stored data:"); for (int i = 0; i < 7; i++) lament(" %i", code->data[i]); lament("\n"); #endif if (checksum != part1) return 0; if (!check_ranges(code)) return 0; return 1; } int encode_data(code_t *code) { if (!check_ranges(code)) return 0; u32 combined = 0; combined = code->data[6] + 1; combined = combined * bases[5] + code->data[5]; combined = combined * bases[4] + code->data[4] + 1; combined = combined * bases[3] + code->data[3]; combined = combined * bases[2] + code->data[2] + 1; combined = combined * bases[1] + code->data[1]; combined = combined * bases[0] + code->data[0]; combined = combined ^ 0x02AAAAAA; u32 checksum = (combined + 0x2AE0) % (lutlen * lutlen * lutlen); #if !NDEBUG lament("merged data: %08X\n", combined); lament("checksum: %08X\n", checksum); #endif u32 part0 = combined, part1 = checksum; for (int i = 0; i <= 5; i++) { code->code[i] = part0 % lutlen; part0 /= lutlen; } for (int i = 6; i <= 8; i++) { code->code[i] = part1 % lutlen; part1 /= lutlen; } return 1; } int validate_entry(const char *entry) { code_t code = translate(entry); #if !NDEBUG lament("character mapping:"); for (int i = 0; i < 9; i++) { lament(" %c->%i,", entry[i], code.code[i]); } lament("\n"); #endif return decode_data(&code); } static void print_code(const code_t *code) { for (int j = 0; j < 9; j++) { int index = code->code[j]; printf("%c", lut[index][0]); } printf("\n"); } static u64 prng_state = 1; static u32 prng() { prng_state = 3935559000370003845 * prng_state + 1; return prng_state ^ (prng_state >> 32); } int bruteforce(int count) { /* with a fixed seed, this is useful for testing. the first 10 results should be: 8RPEHR8R4 MNXKWQMNE CE922RCER MQ1KMQMQG 2N9GGR2NR 25L53R250 15R09R159 9R9LMQ9RR L312MQL3G P93M6QP9N */ int found = 0; code_t code = {0}; while (found < count) { for (int j = 0; j < 9; j++) { code.code[j] = prng() % lutlen; } if (decode_data(&code)) { found++; print_code(&code); } } return 1; } #ifndef TENNIS_NO_MAIN int main(int argc, char **argv) { if (argc == 1) { // self-test with codes known before this program was written. #define TEST(entry) if (!validate_entry(entry)) return -1 TEST("48HWOR482"); TEST("5G3LTQ5GN"); TEST("A3W5KQA3C"); TEST("ARM6JQARU"); TEST("E880MPE8K"); TEST("E8ULJRE8M"); TEST("EPJEGREP5"); TEST("GH4KNQGHP"); TEST("H6L3MPH60"); TEST("HH4KNQHHP"); TEST("J6M9PQJ6U"); TEST("JEP8YQJE4"); TEST("LA98JRLAR"); TEST("LQM1MPLQU"); TEST("LTHWYQLT2"); TEST("M1C2YQM1W"); TEST("MM55MQMMJ"); TEST("N24K8QN2P"); TEST("OF9XFQOFR"); TEST("P4K6GRP48"); TEST("TE6WARTEQ"); TEST("TQJEGRTQ5"); TEST("UOUFMPUOM"); TEST("V2UFMPUZM"); TEST("W2HEGRW22"); TEST("WQJEGRWQ5"); TEST("WRWQARWRC"); TEST("YQJEGRYQ5"); #undef TEST return 0; } code_t code = {0}; int code_i = 0; int invalid_count = 0; for (int i = 1; i < argc; i++) { const char *arg = argv[i]; if (strlen(arg) == 9) { //if (validate_entry(arg)) printf("%s\n", arg); invalid_count += !validate_entry(arg); } else if (!strcmp(arg, "bruteforce")) { invalid_count += !bruteforce(10); } else { u32 datum = strtoul(arg, NULL, 0); code.data[code_i] = datum; code_i++; if (code_i >= 7) { code_i = 0; if (encode_data(&code)) { print_code(&code); } else { invalid_count++; } } } } return invalid_count; } #endif