328 lines
7.8 KiB
C
328 lines
7.8 KiB
C
// 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 <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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
|