Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <assert.h>
- #include <stdint.h>
- #include <stdio.h>
- #define random __stdlib_random
- #include <stdlib.h>
- #undef random
- #include <string.h>
- #define lenof(array) ((int)(sizeof(array) / sizeof(*array)))
- #define UNUSED __attribute__((unused))
- //#define DEBUG
- #define VDEBUG
- #ifdef DEBUG
- #define dprintf printf
- #ifdef VDEBUG
- #define ddprintf printf
- #else
- #define dprintf(...) /*nothing*/
- #endif
- #else
- #define dprintf(...) /*nothing*/
- #define ddprintf(...) /*nothing*/
- #endif
- /*************************************************************************/
- /******************************* Constants *******************************/
- /*************************************************************************/
- #define SAVE_SIZE 0x313
- static const struct {uint8_t code; char text;} textmap[] = {
- {0x00, '\0'},
- {0x0B, 'a'},
- {0x0C, 'b'},
- {0x0D, 'c'},
- {0x0E, 'd'},
- {0x0F, 'e'},
- {0x10, 'f'},
- {0x11, 'g'},
- {0x12, 'h'},
- {0x13, 'i'},
- {0x14, 'j'},
- {0x15, 'k'},
- {0x16, 'l'},
- {0x17, 'm'},
- {0x18, 'n'},
- {0x19, 'o'},
- {0x1A, 'p'},
- {0x1B, 'q'},
- {0x1C, 'r'},
- {0x1D, 's'},
- {0x1E, 't'},
- {0x1F, 'u'},
- {0x20, 'v'},
- {0x21, 'w'},
- {0x22, 'x'},
- {0x23, 'y'},
- {0x24, 'z'},
- {0x25, 'A'},
- {0x26, 'B'},
- {0x27, 'C'},
- {0x28, 'D'},
- {0x29, 'E'},
- {0x2A, 'F'},
- {0x2B, 'G'},
- {0x2C, 'H'},
- {0x2D, 'I'},
- {0x2E, 'J'},
- {0x2F, 'K'},
- {0x30, 'L'},
- {0x31, 'M'},
- {0x32, 'N'},
- {0x33, 'O'},
- {0x34, 'P'},
- {0x35, 'Q'},
- {0x36, 'R'},
- {0x37, 'S'},
- {0x38, 'T'},
- {0x39, 'U'},
- {0x3A, 'V'},
- {0x3B, 'W'},
- {0x3C, 'X'},
- {0x3D, 'Y'},
- {0x3E, 'Z'},
- {0x50, ' '},
- {0x68, '\''},
- {0x6A, ','},
- {0x6B, '-'},
- {0x6C, '.'},
- {0x6D, '('},
- {0x6E, ')'},
- {0x6F, '?'},
- {0x70, '!'},
- };
- static uint8_t char_to_code[256];
- static const char * const initial_names[3][4] = {
- {"Brindar", "Ragnar", "Adan", "Glennard"},
- {"Theron", "Elucidus", "Harley", "Mathias"},
- {"Sartris", "Petrus", "Hiram", "Viron"},
- };
- static const uint8_t initial_class[3] = {4, 1, 2};
- static const struct {
- uint16_t offset;
- uint8_t class_stats[8];
- uint8_t initial_stats[5];
- } class_stats[5] = {
- {0x0C, {7, 1, 3, 0, 7, 4,15, 3}, {7, 1, 3, 0, 9}},
- {0x18, {5, 4, 4, 0, 2, 1, 8, 2}, {5, 4, 4, 0, 2}},
- {0x3C, {7, 2, 3, 0, 7, 4, 5, 4}, {7, 2, 3, 0, 7}},
- {0x24, {6, 8, 7, 0, 1, 5, 4, 1}, {6, 9, 8, 0, 1}},
- {0x30, {4, 2, 2, 0, 2, 3, 1, 8}, {4, 2, 3, 0, 2}},
- };
- static const uint16_t rate_table[] = {
- 0x0010, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000};
- static const struct {uint8_t base, range;} count_table[] = {
- {8, 1}, {4, 2}, {2, 2}, {3, 5}};
- static const struct {
- uint8_t chance[13];
- uint8_t add_group_chance[2];
- uint8_t count_index[5];
- } chance_table[] = {
- {{28,56,70,84,112,132,146,160,174,188,216,227,241}, { 0, 0}, {2,1,3,2,2}},
- {{20,40,55,70,100,120,140,150,165,175,215,226,241}, {64,128}, {1,0,3,2,1}},
- {{18,50,63,68,103,113,132,157,176,191,221,226,241}, {64, 16}, {0,1,3,2,2}},
- {{10,20,38,48,118,118,128,138,156,166,236,246,255}, {64, 0}, {3,0,1,2,1}},
- };
- UNUSED static const char * const preemptive_names[] = {
- "Normal", "Preempt+", "Back+", "None"};
- typedef struct EncounterInfo {
- uint8_t rate_index;
- uint8_t chance_index;
- uint8_t preemptive_index;
- uint8_t enemies[14];
- } EncounterInfo;
- // 108 = Metal Babble
- static const EncounterInfo rimuldar_encounters = {
- 3, 2, 0, {116, 83, 105, 255, 116, 108, 116, 113, 116, 108, 116, 255, 255, 255}};
- /*************************************************************************/
- /****************** Random number generator and helpers ******************/
- /*************************************************************************/
- static int seed = 0x3D27;
- static int counter = 1;
- static int multi_counter = 0;
- /*-----------------------------------------------------------------------*/
- static int random(void)
- {
- for (int i = 0; i < 2; i++) {
- for (int y = 8; y > 0; y--) {
- int a = (seed >> 15) ^ 1;
- seed = (seed << 1) & 0xFFFF;
- if (a) {
- seed ^= 0x1021;
- }
- }
- }
- counter = (counter + 1) & 0xFF;
- return (seed + counter) & 0xFF;
- }
- /*-----------------------------------------------------------------------*/
- /* Returns values from 0 through range-1 inclusive. */
- static int random_range(int range)
- {
- int mask = 0;
- for (int temp = range; temp != 0; temp >>= 1) {
- mask = (mask << 1) | 1;
- }
- for (;;) {
- const int value = random() & mask;
- if (value < range) {
- return value;
- }
- }
- }
- /*-----------------------------------------------------------------------*/
- static int multi_random(void)
- {
- for (int i = 0; i < multi_counter + 1; i++) {
- random();
- }
- return random();
- }
- /*-----------------------------------------------------------------------*/
- static int multi_random_range(int range)
- {
- return (multi_random() * range) >> 8;
- }
- /*-----------------------------------------------------------------------*/
- static int choose_encounter(const EncounterInfo *info, int base_rate,
- int overworld, int day,
- int enemies_ret[4], int counts_ret[4])
- {
- int rate = rate_table[info->rate_index];
- if (rate < 0x100) {
- if (random() >= rate) {
- return 0;
- }
- rate = 0x100;
- }
- rate = (rate >> 8) * base_rate;
- if (rate > 100) {
- rate = 100;
- }
- if (random() >= rate) {
- return 0;
- }
- int group, enemy;
- for (int try = 100; ; try--) {
- if (try == 1) {
- return 0;
- }
- const int encount_selector = random();
- int i;
- for (i = 0; i < 13; i++) {
- if (encount_selector < chance_table[info->chance_index].chance[i]) {
- break;
- }
- }
- group = i;
- enemy = info->enemies[i];
- if (overworld) {
- if (day) {
- if (group == 4 || group == 10 || group == 13) {
- continue;
- }
- } else {
- if (group == 0 || group == 6) {
- continue;
- }
- }
- }
- if (enemy != 255) {
- break;
- }
- }
- for (int i = 0; i < 4; i++) {
- enemies_ret[i] = 255;
- counts_ret[i] = 0;
- }
- if (group < 5) {
- enemies_ret[0] = enemy;
- int seen[6] = {0,0,0,0,0,0};
- seen[group] = 1;
- int encounter_5_group = -1;
- int added_groups = 0;
- for (int try = 20; try > 1; try--) {
- const int extra_group = random_range(6);
- if (overworld) {
- if ((day && extra_group == 4) || (!day && extra_group == 0)) {
- continue;
- }
- }
- if (seen[extra_group] || info->enemies[extra_group] == 255) {
- continue;
- }
- enemies_ret[1 + added_groups] = info->enemies[extra_group];
- seen[extra_group] = 1;
- if (extra_group == 5) {
- encounter_5_group = 1 + added_groups;
- }
- const int more_groups_chance =
- chance_table[info->chance_index].add_group_chance[added_groups];
- added_groups++;
- if (random() >= more_groups_chance) {
- break;
- }
- if (added_groups == 2) {
- added_groups = 3; // Replicate off-by-one bug.
- break;
- }
- }
- for (int i = 0; i < 1 + added_groups; i++) {
- counts_ret[i] = 1;
- }
- int try = 0;
- while (random_range(3) != 0) {
- if (++try > 65536) {
- dprintf("Detected deadlock in encounter generation\n");
- break;
- }
- const int inc_group = random_range(1 + added_groups);
- if (inc_group != encounter_5_group) {
- counts_ret[inc_group]++;
- }
- }
- } else if (group == 5) {
- enemies_ret[0] = enemy;
- counts_ret[0] = 1;
- for (int try = 20; try > 1; try--) {
- const int extra_group = random_range(5);
- if (overworld) {
- if ((day && extra_group == 4) || (!day && extra_group == 0)) {
- continue;
- }
- }
- if (info->enemies[extra_group] == 255) {
- continue;
- }
- enemies_ret[1] = info->enemies[extra_group];
- counts_ret[1] = 8;
- break;
- }
- } else if (group < 11) {
- const int range_index =
- chance_table[info->chance_index].count_index[group - 6];
- enemies_ret[0] = enemy;
- counts_ret[0] = (count_table[range_index].base
- + random_range(count_table[range_index].range));
- } else if (group == 11) {
- enemies_ret[0] = enemy;
- counts_ret[0] = 1;
- } else { // 12 or 13
- assert(!"not implemented");
- }
- ddprintf("encounter:");
- for (int i = 0; i < 4; i++) {
- if (enemies_ret[i] != 255) {
- ddprintf(" %dx%d", enemies_ret[i], counts_ret[i]);
- }
- }
- ddprintf("\n");
- return 1;
- }
- /*************************************************************************/
- /********************** Save file utility routines ***********************/
- /*************************************************************************/
- static int checksum(const uint8_t *data, int size)
- {
- int sum = 0x3A3A;
- for (int i = 0; i < size; i++) {
- int byte = data[i];
- for (int y = 8; y > 0; y--) {
- int a = (sum >> 8) ^ byte;
- sum = (sum << 1) & 0xFFFF;
- byte = (byte << 1) & 0xFF;
- if (a & 0x80) {
- sum ^= 0x1021;
- }
- }
- }
- return sum;
- }
- /*-----------------------------------------------------------------------*/
- static void set_name(uint8_t *save, int slot, const char *name)
- {
- for (int i = 0; name[i]; i++) {
- const unsigned char ch = name[i];
- const int code = char_to_code[ch];
- if (!code) {
- fprintf(stderr, "No code for character 0x%02X '%c'\n", ch, ch);
- exit(1);
- }
- save[0x114 + slot*8 + i] = code;
- }
- }
- /*-----------------------------------------------------------------------*/
- static void add_character(uint8_t *save, int is_initial, int slot,
- const char *name, int class, int sex)
- {
- int filled_slots = save[0x237] | save[0x238]<<8;
- filled_slots |= 1 << slot;
- save[0x237] = filled_slots & 0xFF;
- save[0x238] = filled_slots >> 8;
- set_name(save, slot, name);
- save[0x0 + slot] = 1; // Level
- save[0x48 + slot] = class | sex<<3;
- save[0xB4 + slot*2] = 0x80; // Condition (1st byte)
- save[0xB5 + slot*2] = 0x80; // Condition (2st byte)
- save[0xF0 + slot*3] = 1; // Return flag for Aliahan
- for (int i = 0; i < 5; i++) {
- const int offset = class_stats[i].offset;
- const int stat = is_initial ? class_stats[i].initial_stats[class]
- : class_stats[i].class_stats[class];
- save[offset + slot] = stat;
- }
- if (is_initial) {
- for (int i = 4; i >= 0; i--) {
- const int offset = class_stats[i].offset;
- save[offset + slot] += random() & 1;
- }
- } else {
- int inc_count = multi_random_range(5) + 2;
- for (int i = 0; i < inc_count; i++) {
- const int index = multi_random_range(5);
- const int offset = class_stats[index].offset;
- save[offset + slot] += 1;
- }
- }
- const int vitality = save[0x3C + slot];
- const int hp = (vitality*3/2) + 5;
- save[0x54 + slot*2] = hp & 0xFF;
- save[0x55 + slot*2] = hp >> 8;
- save[0x6C + slot*2] = hp & 0xFF;
- save[0x6D + slot*2] = hp >> 8;
- if (class < 4) {
- const int intelligence = save[0x24 + slot];
- const int mp = intelligence;
- save[0x84 + slot*2] = mp & 0xFF;
- save[0x85 + slot*2] = mp >> 8;
- save[0x9C + slot*2] = mp & 0xFF;
- save[0x9D + slot*2] = mp >> 8;
- }
- const int armor = class==2 ? 0xB0 : class==4 ? 0xA2 : 0xA0;
- const int weapon = class==1 ? 0x80 : 0x81;
- save[0x174 + slot*8] = weapon;
- save[0x175 + slot*8] = armor;
- // Replicate the off-by-one error.
- memset(&save[0x176 + slot*8], 0xFF, 7);
- // Initial spells.
- if (class == 1) {
- save[0x1D4 + slot*8] = 1;
- } else if (class == 2) {
- save[0x1D8 + slot*8] = 2;
- save[0x1DB + slot*8] = 1;
- }
- }
- /*-----------------------------------------------------------------------*/
- static void gen_save(uint8_t *save, int file, const char *name, int sex,
- int speed)
- {
- memset(save, 0, SAVE_SIZE);
- memset(&save[0x249], 0xFF, 128);
- save[0x312] = file;
- add_character(save, 1, 0, name, 0, sex);
- seed = checksum(save, SAVE_SIZE);
- // Fix hero's equipment (done after the checksum).
- save[0x174] = 0x82;
- save[0x175] = 0xA2;
- for (int i = 0; i < 3; i++) {
- const int name_index = random() & 3;
- const int slot = i + 1;
- const char *name = initial_names[i][name_index];
- const int class = initial_class[i];
- add_character(save, 1, slot, name, class, 0);
- seed = checksum(save, SAVE_SIZE);
- }
- memset(&save[0x23A], 0xFF, 3);
- save[0x311] = speed;
- }
- /*************************************************************************/
- /************************** State test routines **************************/
- /*************************************************************************/
- int test_battle(int num_enemies, const char **info_ret)
- {
- enum {ATTACK, PARRY, HEAL, FIREBAL, FLEE};
- static const int party_actions[4][3] = {{ATTACK, PARRY},
- {ATTACK, HEAL, PARRY},
- {ATTACK, HEAL, PARRY},
- {ATTACK, PARRY}};
- static const int enemy_actions[] = {FLEE, ATTACK, FLEE, FIREBAL,
- FLEE, FLEE, FIREBAL, FLEE};
- UNUSED int enemy_hp[3];
- for (int i = 0; i < num_enemies; i++) {
- enemy_hp[i] = 6 - multi_random_range(2);
- }
- for (int i = 0; i < num_enemies; i++) {
- (void) multi_random(); // Initial focus target
- }
- if (random() < 8) {
- dprintf("BAD: back attack");
- return 0;
- }
- if (random() >= 8) {
- dprintf("BAD: not preemptive attack");
- return 0;
- }
- const int agility[4] = {5, 5, 5, 6};
- int turn_speed[4];
- for (int i = 0; i < 4; i++) {
- const int agility_4 = agility[i] / 4;
- turn_speed[i] = agility_4 + multi_random_range(agility[i] - agility_4);
- }
- for (int i = 4; i < 12; i++) {
- (void) multi_random(); // Enemy turn order
- }
- int turn_order[4];
- for (int i = 0; i < 4; i++) {
- int best = 0;
- for (int j = 1; j < 4; j++) {
- if (turn_speed[j] >= turn_speed[best]) {
- best = j;
- }
- }
- turn_order[i] = best;
- turn_speed[best] = -1;
- }
- multi_counter = random() & 15;
- for (int i = 0; i < num_enemies; i++) {
- (void) multi_random(); // Enemy action
- (void) multi_random(); // Enemy target
- }
- int best_kills = 0;
- int best_act[4];
- int best_multi;
- int saved_seed = seed;
- int saved_counter = counter;
- int saved_multi = multi_counter;
- for (int try = 0; try < 2*3*3*2; try++) {
- seed = saved_seed;
- counter = saved_counter;
- multi_counter = saved_multi;
- int act[4];
- int temp = try;
- act[3] = party_actions[3][temp%2];
- temp /= 2;
- act[2] = party_actions[2][temp%3];
- temp /= 3;
- act[1] = party_actions[1][temp%3];
- temp /= 3;
- act[0] = party_actions[0][temp%2];
- int kills = 0;
- for (int i = 0; i < 4 && kills < num_enemies; i++) {
- const int ch = turn_order[i];
- if (act[ch] == ATTACK) {
- for (int enemy = 0; enemy < num_enemies - kills; enemy++) {
- int damage = multi_random() & 1;
- const int intelligence = 0; // don't care
- damage += (damage * multi_random_range(255-intelligence) / 4) >> 8;
- }
- const int target = num_enemies - kills - 1;
- int is_kill = 0;
- if (ch == 3) {
- is_kill = (multi_random() < 32);
- } else {
- const int is_crit = (multi_random() < 4);
- if (is_crit) {
- static const int attack[3] = {128, 10, 11};
- const int damage = attack[ch] * (54 + multi_random_range(11)) / 64 + 1;
- // Don't worry about multiple hits on a target for now
- is_kill = (damage >= enemy_hp[target]);
- }
- }
- if (is_kill) {
- ddprintf("try %d: char %d kills\n", try, ch);
- }
- kills += is_kill;
- } else if (act[ch] == HEAL) {
- (void) multi_random();
- }
- }
- if (kills == 0) {
- continue;
- }
- if (kills < num_enemies) {
- // Simulate the second turn to get the final battle RNG seed
- // and make sure all remaining enemies flee
- for (int i = 0; i < 12; i++) {
- (void) multi_random(); // Turn order
- }
- multi_counter = random() & 15;
- int ok = 1;
- for (int i = 0; i < num_enemies - kills; i++) {
- const int r = multi_random();
- const int enemy_action = enemy_actions[r/32];
- (void) multi_random(); // Enemy target
- if (enemy_action != FLEE) {
- ok = 0;
- break;
- }
- }
- if (!ok) {
- continue;
- }
- }
- if (kills > best_kills) {
- best_kills = kills;
- memcpy(best_act, act, sizeof(act));
- best_multi = multi_counter;
- }
- }
- if (best_kills < 2) {
- dprintf("BAD: not enough kills");
- return 0;
- }
- const char * const act_names[] =
- {[ATTACK] = "Attack", [HEAL] = "Heal", [PARRY] = "Parry"};
- static char buf[100];
- snprintf(buf, sizeof(buf), "Seed:$%X Kills:%d/%d Hr:%s P2:%s P1:%s Wz:%s",
- best_multi, best_kills, num_enemies,
- act_names[best_act[0]], act_names[best_act[1]],
- act_names[best_act[2]], act_names[best_act[3]]);
- *info_ret = buf;
- return 1;
- }
- /*-----------------------------------------------------------------------*/
- int test_seed(int initial_seed, const char **info_ret)
- {
- dprintf("Seed: $%04X\n", initial_seed);
- // File load -> Rimuldar
- seed = initial_seed;
- counter = 1;
- multi_counter = 0xD;
- random();
- random();
- // Check up to 256 steps ahead for the desired encounter
- const int start_seed = seed;
- const int start_counter = counter;
- for (int steps = 1; steps <= 256; steps++) {
- seed = start_seed;
- counter = start_counter;
- for (int i = 0; i < steps - 1; i++) {
- (void) random();
- }
- int groups[4], counts[4];
- if (!choose_encounter(&rimuldar_encounters, 10, 1, 0, groups, counts)) {
- continue;
- }
- if (groups[0] != 108 || counts[0] < 2
- || groups[1] != 255 || groups[2] != 255 || groups[3] != 255) {
- continue;
- }
- ddprintf("steps=%d: battle entry %04X %02X\n", steps, seed, counter);
- if (test_battle(counts[0], info_ret)) {
- dprintf("ok (%d steps, %s)\n", steps, *info_ret);
- return steps;
- }
- }
- dprintf("BAD (no encounter found)");
- return 0;
- }
- /*-----------------------------------------------------------------------*/
- int test_file(const char *name, int sex, int speed, int target_seed)
- {
- const int file = 2;
- dprintf("Hr: %s/%s Speed: %d ", name, sex ? "F" : "M", speed + 1);
- fflush(stdout);
- uint8_t save[SAVE_SIZE];
- seed = 0x3D27;
- counter = 1;
- gen_save(save, file, name, sex, speed);
- seed = checksum(save, SAVE_SIZE);
- ddprintf("Checksum: %04X\n", seed);
- assert(counter == 0x18);
- return seed == target_seed;
- }
- /*************************************************************************/
- /************************** Program entry point **************************/
- /*************************************************************************/
- int main(int argc, char **argv)
- {
- for (int i = 0; i < lenof(textmap); i++) {
- if (textmap[i].text) {
- char_to_code[(unsigned char)textmap[i].text] = textmap[i].code;
- }
- }
- int seeds[4][2];
- int num_seeds = 0;
- for (int s = 0; s < 0x10000; s++) {
- const char *info;
- const int steps = test_seed(s, &info);
- if (steps) {
- printf("Seed: $%04X (%d steps, %s)\n", s, steps, info);
- if (num_seeds == lenof(seeds)) {
- int worst = 0;
- for (int i = 1; i < lenof(seeds); i++) {
- if (seeds[i][1] > seeds[worst][1]) {
- worst = i;
- }
- }
- if (steps < seeds[worst][1]) {
- seeds[worst][0] = s;
- seeds[worst][1] = steps;
- }
- } else {
- seeds[num_seeds][0] = s;
- seeds[num_seeds][1] = steps;
- num_seeds++;
- }
- #ifdef DEBUG
- return 0;
- #endif
- }
- }
- if (!num_seeds) {
- fprintf(stderr, "Error: No usable seed found\n");
- return 1;
- }
- char name[8+1];
- memset(name, 0, sizeof(name));
- for (int i3 = 0; i3 < lenof(textmap); i3++) {
- if (textmap[i3].text == ' ') continue;
- name[3] = textmap[i3].text;
- for (int i2 = 0; i2 < lenof(textmap); i2++) {
- if (!i2 && i3) continue;
- if (textmap[i2].text == ' ') continue;
- name[2] = textmap[i2].text;
- for (int i1 = 0; i1 < lenof(textmap); i1++) {
- if (!i1 && i2) continue;
- if (textmap[i1].text == ' ') continue;
- name[1] = textmap[i1].text;
- fprintf(stderr, "Hr: a%s\r", &name[1]);
- for (int i0 = 1; i0 < lenof(textmap); i0++) {
- if (textmap[i0].text == ' ') continue;
- name[0] = textmap[i0].text;
- for (int sex = 0; sex < 2; sex++) {
- for (int speed = 0; speed < 8; speed++) {
- for (int si = 0; si < num_seeds; si++) {
- if (test_file(name, sex, speed, seeds[si][0])) {
- printf("Hr: %s/%s Speed: %d Seed: $%04X Steps: %d\n",
- name, sex ? "F" : "M", speed + 1,
- seeds[si][0], seeds[si][1]);
- }
- #ifdef DEBUG
- return 0;
- #endif
- }
- }
- }
- }
- }
- }
- }
- return 0;
- }
- /*************************************************************************/
- /*************************************************************************/
Add Comment
Please, Sign In to add comment