Last active
June 4, 2018 18:14
-
-
Save notwa/8303fb034d097c69a13aa69913341137 to your computer and use it in GitHub Desktop.
Mario Tennis (N64) Ring Tournament code generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment