Last active
May 4, 2018 04:50
-
-
Save aldelaro5/05abc560c550a18e609584b974b686dc to your computer and use it in GitHub Desktop.
Pokemon Colosseum RNG seed finder code
This file contains 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
--- | |
AlignAfterOpenBracket: 'true' | |
AllowAllParametersOfDeclarationOnNextLine: 'true' | |
AllowShortFunctionsOnASingleLine: None | |
AlwaysBreakBeforeMultilineStrings: 'true' | |
AlwaysBreakTemplateDeclarations: 'true' | |
BreakBeforeBraces: Allman | |
BreakStringLiterals: 'true' | |
FixNamespaceComments: 'true' | |
ColumnLimit: '100' | |
Cpp11BracedListStyle: 'true' | |
Language: Cpp | |
PointerAlignment: Left | |
Standard: Cpp11 | |
UseTab: Never | |
... |
This file contains 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
cmake_minimum_required(VERSION 3.5.0) | |
project(lcgTest) | |
include(FindOpenMP) | |
if(OPENMP_FOUND) | |
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") | |
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") | |
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") | |
endif() | |
set(SRCS pkmnColosseumRNG.hpp | |
seedFinder.cpp) | |
add_executable(lcgTest ${SRCS}) |
This file contains 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
#pragma once | |
#include <cstdint> | |
typedef uint8_t u8; | |
typedef int8_t s8; | |
typedef uint16_t u16; | |
typedef uint32_t u32; | |
typedef uint64_t u64; | |
typedef int64_t s64; |
This file contains 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
// This file contains the code to properly call the RNG as it behaves in Pokemon Colosseum until | |
// the sgeneration a battle now team - single battle - ultimate, this code is an implemention of the | |
// results obtained from reverse engineering the game | |
#include "commonTypes.h" | |
#include <array> | |
// The natures wanted for all Pokemon of all ultimate teams | |
static std::array<std::array<u8, 6>, 8> s_natureTeamsData = { | |
{{{0x16, 0x15, 0x0f, 0x13, 0x04, 0x04}}, | |
{{0x0b, 0x08, 0x01, 0x10, 0x10, 0x0C}}, | |
{{0x02, 0x10, 0x0f, 0x12, 0x0f, 0x03}}, | |
{{0x10, 0x13, 0x03, 0x16, 0x03, 0x18}}, | |
{{0x11, 0x10, 0x0f, 0x13, 0x05, 0x04}}, | |
{{0x0f, 0x11, 0x01, 0x03, 0x13, 0x03}}, | |
{{0x01, 0x08, 0x03, 0x01, 0x03, 0x03}}, | |
{{0x03, 0x0a, 0x0f, 0x03, 0x0f, 0x03}}}}; | |
// The genders wanted for all Pokemon of all ultimate teams | |
static std::array<std::array<u8, 6>, 8> s_genderTeamsData = {{{{0, 1, 1, 0, 0, 1}}, | |
{{2, 1, 0, 0, 1, 0}}, | |
{{0, 1, 0, 1, 0, 1}}, | |
{{2, 1, 1, 1, 0, 0}}, | |
{{0, 0, 0, 0, 0, 1}}, | |
{{2, 1, 2, 0, 2, 1}}, | |
{{2, 0, 0, 1, 1, 0}}, | |
{{1, 0, 1, 0, 1, 0}}}}; | |
// The genders wanted for dummy Pokemon of dummy teams | |
static std::array<std::array<u8, 6>, 4> s_genderDummyTeamsData = { | |
{{{1, 0, 1, 0, 1, 0}}, {{1, 0, 1, 0, 1, 0}}, {{1, 0, 1, 0, 1, 0}}, {{1, 0, 1, 0, 1, 0}}}}; | |
// The gender ratios of all Pokemon of all ultimate teams | |
static std::array<std::array<u8, 6>, 8> s_genderRatioTeamsData = { | |
{{{0x1f, 0x7f, 0x7f, 0x7f, 0xbf, 0x7f}}, | |
{{0xff, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f}}, | |
{{0x1f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f}}, | |
{{0xff, 0xbf, 0x7f, 0x7f, 0x1f, 0x7f}}, | |
{{0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x7f}}, | |
{{0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f}}, | |
{{0xff, 0x1f, 0x3f, 0x7f, 0x7f, 0x3f}}, | |
{{0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f}}}}; | |
// The gender ratios of all Pokemon of all dummy teams | |
static std::array<std::array<u8, 6>, 4> s_genderRatioDummyTeamsData = { | |
{{{0x3f, 0x3f, 0x1f, 0x1f, 0x1f, 0x1f}}, | |
{{0x1f, 0x7f, 0x1f, 0x7f, 0x7f, 0x7f}}, | |
{{0xbf, 0x1f, 0x7f, 0x7f, 0x7f, 0x7f}}, | |
{{0x7f, 0x7f, 0xff, 0x7f, 0x7f, 0x7f}}}}; | |
// Pokemon Colosseum LCG, in practice, only the last 16 bits are used | |
inline u32 LCG(u32& seed, u16* counter = nullptr) | |
{ | |
seed = seed * 0x343fd + 0x269EC3; | |
if (counter != nullptr) | |
(*counter)++; | |
return seed; | |
} | |
// Alogorithm that returns (base^exp) mod 2^32 in the complexity O(log n) | |
inline u32 modpow32(u32 base, u32 exp) | |
{ | |
u32 result = 1; | |
while (exp > 0) | |
{ | |
if (exp & 1) | |
result = result * base; | |
base = base * base; | |
exp >>= 1; | |
} | |
return result; | |
} | |
// Algorithm that returns the nth term of the LCG seqeunce starting from seed in the complexity | |
// O(log n), inspired from this thread: | |
// https://stackoverflow.com/questions/1522825/calculating-sum-of-geometric-series-mod-m | |
inline u32 LCGn(u32 seed, u32 n, u16* counter = nullptr) | |
{ | |
u32 ex = n - 1; | |
u32 q = 0x343fd; | |
u32 factor = 1; | |
u32 sum = 0; | |
while (ex > 0) | |
{ | |
if (!(ex & 1)) | |
{ | |
sum = sum + (factor * modpow32(q, ex)); | |
ex--; | |
} | |
factor *= (1 + q); | |
q *= q; | |
ex /= 2; | |
} | |
seed = (seed * modpow32(0x343fd, n)) + (sum + factor) * 0x269EC3; | |
if (counter != nullptr) | |
*counter += n; | |
return seed; | |
} | |
// Algorithm to count the number of leading zeros, pasted from https://stackoverflow.com/a/23862121 | |
inline int clz(u32 x) | |
{ | |
static const char debruijn32[32] = {0, 31, 9, 30, 3, 8, 13, 29, 2, 5, 7, 21, 12, 24, 28, 19, | |
1, 10, 4, 14, 6, 22, 25, 20, 11, 15, 23, 26, 16, 27, 17, 18}; | |
x |= x >> 1; | |
x |= x >> 2; | |
x |= x >> 4; | |
x |= x >> 8; | |
x |= x >> 16; | |
x++; | |
return debruijn32[x * 0x076be629 >> 27]; | |
} | |
// This does all the LCG calls for what seems to be a personality ID generation that will only get | |
// accepted if it meets some criterias | |
inline void rollLCGGeneratePokemonProperties(u32& seed, u32 hBaseId, u32 lBaseId, u32 pokemonId, | |
u16* counter = nullptr, s8 wantedGender = -1, | |
u32 genderRatio = 257, s8 wantedNature = -1) | |
{ | |
bool goodId = false; | |
while (!goodId) | |
{ | |
// A personality ID is generated as candidate, high then low 16 bits | |
u32 hId = LCG(seed, counter) >> 16; | |
u32 lId = LCG(seed, counter) >> 16; | |
u32 id = (hId << 16) | (lId); | |
// If we want a gender AND the gender of the pokemon is uncertain | |
if (wantedGender != -1 && !(genderRatio == 0xff || genderRatio == 0xfe || genderRatio == 0x00)) | |
{ | |
if (wantedGender == 2) | |
{ | |
u8 pokemonIdGender = genderRatio > (pokemonId & 0xff) ? 1 : 0; | |
u8 idGender = genderRatio > (id & 0xff) ? 1 : 0; | |
if (pokemonIdGender != idGender) | |
continue; | |
} | |
else | |
{ | |
u8 idGender = genderRatio > (id & 0xff) ? 1 : 0; | |
if (idGender != wantedGender) | |
continue; | |
} | |
} | |
// Reroll until we have the correct nature in the perosnality ID | |
if (wantedNature != -1 && id % 25 != wantedNature) | |
continue; | |
// No idea why it does this but that's what it does! | |
u32 x = hBaseId ^ lBaseId; | |
x ^= hId; | |
x ^= lId; | |
x ^= 8; | |
if (clz(x) != 28) | |
goodId = true; | |
} | |
} | |
// Does all the LCG calls performed before getting to the battle menu | |
inline u32 rollLCGToPokemonCompanyLogo(u32 seed, u16* counter = nullptr) | |
{ | |
// The game generates 500 numbers of 32 bit in a row, this is 2 LCG call per number which makes | |
// 1000 calls | |
seed = LCGn(seed, 1000, counter); | |
// A personality ID is generated as candidate, low then high 16 bits | |
u32 lBaseId = LCG(seed, counter) >> 16; | |
u32 hBaseId = LCG(seed, counter) >> 16; | |
for (int i = 0; i < 2; i++) | |
{ | |
// A personality ID is generated as candidate, high then low 16 bits | |
u32 hPokemonId = LCG(seed, counter) >> 16; | |
u32 lPokemonId = LCG(seed, counter) >> 16; | |
u32 pokemonId = (hPokemonId << 16) | (lPokemonId); | |
// These calls seems to generate some IV and a 50/50, they don't actually matter for the rest of | |
// the calls | |
LCG(seed, counter); | |
LCG(seed, counter); | |
LCG(seed, counter); | |
rollLCGGeneratePokemonProperties(seed, hBaseId, lBaseId, pokemonId, counter, 0, 0x1f); | |
} | |
// These calls don't matter | |
LCG(seed, counter); | |
LCG(seed, counter); | |
return seed; | |
} | |
// Does all the LCG calls before getting to the battle now menu | |
inline u32 rollLCGToBattleNowMenu(u32 seed, u16* counter = nullptr) | |
{ | |
seed = LCGn(seed, 120, counter); | |
for (int i = 0; i < 4; i++) | |
{ | |
// A personality ID is generated as candidate, low then high 16 bits | |
u32 lBaseId = LCG(seed, counter) >> 16; | |
u32 hBaseId = LCG(seed, counter) >> 16; | |
for (int j = 0; j < 7; j++) | |
{ | |
// For some reasons, the last call of all the 24 call to the perosnality ID generation is | |
// different | |
if (j == 6 && i != 3) | |
continue; | |
// A personality ID is generated as candidate, high then low 16 bits | |
u32 hPokemonId = LCG(seed, counter) >> 16; | |
u32 lPokemonId = LCG(seed, counter) >> 16; | |
u32 pokemonId = (hPokemonId << 16) | (lPokemonId); | |
// These calls seems to generate some IV and a 50/50, they don't actually matter for the rest | |
// of the calls | |
LCG(seed, counter); | |
LCG(seed, counter); | |
LCG(seed, counter); | |
if (j == 6) | |
{ | |
rollLCGGeneratePokemonProperties(seed, hBaseId, lBaseId, pokemonId, counter); | |
} | |
else | |
{ | |
rollLCGGeneratePokemonProperties(seed, hBaseId, lBaseId, pokemonId, counter, | |
s_genderDummyTeamsData[i][j], | |
s_genderRatioDummyTeamsData[i][j]); | |
} | |
} | |
} | |
return seed; | |
} | |
// Does all the LCG calls when generating a battle in battle now - single - ultimate, returns | |
// whether or not the starting seed passed respect the criteria sent | |
inline bool rollLCGBattleNowGeneration(u32& seed, int teamIndex, int trainerStrShownIndex) | |
{ | |
int enemyTeamIndex = (LCG(seed) >> 16) & 7; | |
int playerTeamIndex = -1; | |
// The game generates a player team index as long as it is not the same as the enemy one | |
do | |
{ | |
playerTeamIndex = (LCG(seed) >> 16) & 7; | |
} while (enemyTeamIndex == playerTeamIndex); | |
if (playerTeamIndex != teamIndex) | |
return false; | |
// A personality ID is generated as candidate, low then high 16 bits | |
u32 lBaseId = LCG(seed) >> 16; | |
u32 hBaseId = LCG(seed) >> 16; | |
// For each enemy pokemon | |
for (int i = 0; i < 6; i++) | |
{ | |
// A personality ID is generated as candidate, high then low 16 bits | |
u32 hPokemonId = LCG(seed) >> 16; | |
u32 lPokemonId = LCG(seed) >> 16; | |
u32 pokemonId = (hPokemonId << 16) | (lPokemonId); | |
// These calls seems to generate some IV and a 50/50, they don't actually matter for the rest | |
// of the calls | |
LCG(seed); | |
LCG(seed); | |
LCG(seed); | |
rollLCGGeneratePokemonProperties( | |
seed, hBaseId, lBaseId, pokemonId, nullptr, s_genderTeamsData[enemyTeamIndex][i], | |
s_genderRatioTeamsData[enemyTeamIndex][i], s_natureTeamsData[enemyTeamIndex][i]); | |
} | |
if ((LCG(seed) >> 16) % 3 != trainerStrShownIndex) | |
return false; | |
// A personality ID is generated as candidate, low then high 16 bits | |
lBaseId = LCG(seed) >> 16; | |
hBaseId = LCG(seed) >> 16; | |
// For each player pokemon | |
for (int i = 0; i < 6; i++) | |
{ | |
// A personality ID is generated as candidate, high then low 16 bits | |
u32 hPokemonId = LCG(seed) >> 16; | |
u32 lPokemonId = LCG(seed) >> 16; | |
u32 pokemonId = (hPokemonId << 16) | (lPokemonId); | |
// These calls seems to generate some IV and a 50/50, they don't actually matter for the rest | |
// of the calls | |
LCG(seed); | |
LCG(seed); | |
LCG(seed); | |
rollLCGGeneratePokemonProperties( | |
seed, hBaseId, lBaseId, pokemonId, nullptr, s_genderTeamsData[playerTeamIndex][i], | |
s_genderRatioTeamsData[playerTeamIndex][i], s_natureTeamsData[playerTeamIndex][i]); | |
} | |
return true; | |
} |
This file contains 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
// This file contains the algorithms and the command line interface to find the current seed in | |
// Pokemon Colosseum through a wizard like interface | |
#include "pkmnColosseumRNG.hpp" | |
#include <algorithm> | |
#include <chrono> | |
#include <cstring> | |
#include <fstream> | |
#include <iostream> | |
#include <sstream> | |
#include <string> | |
#include <thread> | |
#include <vector> | |
struct seedRange | |
{ | |
s64 min = 0; | |
s64 max = 0; | |
}; | |
// The GameCube RTC does 40500000 ticks per second while the Wii one is exactly 1.5 faster | |
static const int c_nbrTickPerSecondGc = 40500000; | |
static const int c_nbrTickPerSecondWii = static_cast<int>(c_nbrTickPerSecondGc * 1.5); | |
// This number was obtained by TASing in the GameCube IPL to set the RTC to 01/01/2001 - 00:00:00 | |
// and then booting the game to check what seed was used to initialise it, it is therefore believed | |
// to be the minimum possible to get if one were to set the RTC and boot the game as fast as | |
// possible | |
static const u32 c_minimalRtcTicksToBootGc = 0x0ca1a81c; | |
// This number was determined arbirtrarilly as Dolphin had some issues that prevented from testing | |
// this more rigurously, it would be the minimum seed possible to get if one were to set the RTC and | |
// boot the game as fast as possible on Wii, most results done with real or emualted wiimote were in | |
// the 0x2b000000 - 0x2d000000 range | |
static const u32 c_minimalRtcTicksToBootWii = 0x2a000000; | |
void getUserInputSeedFinder(int& playerBattleTeamIndex, int& trainerStrIndex) | |
{ | |
std::cout << "Please enter the number that matches the name of your generated trainer:\n" | |
"> 0) WES\n" | |
"> 1) SETH\n" | |
"> 2) THOMAS\n" | |
<< std::endl; | |
std::cout << "Enter the number and press enter: "; | |
std::cin >> trainerStrIndex; | |
std::cin.ignore(); | |
std::cout << std::endl; | |
std::cout << "Please enter the number that matches the leader of your generated team:\n" | |
"> 0) BLAZIKEN\n" | |
"> 1) ENTEI\n" | |
"> 2) SWANPERT\n" | |
"> 3) RAIKOU\n" | |
"> 4) MEGANIUM\n" | |
"> 5) SUICUNE\n" | |
"> 6) METAGROSS\n" | |
"> 7) HERACROSS\n" | |
<< std::endl; | |
std::cout << "Enter the number and press enter: "; | |
std::cin >> playerBattleTeamIndex; | |
std::cin.ignore(); | |
std::cout << std::endl; | |
} | |
void getUserInputPreparation(bool& useWii, int& rtcErrorMarginSeconds) | |
{ | |
std::cout << "Please enter the number that matches the platform you will be using:\n" | |
"> 0) Nintendo GameCube\n" | |
"> 1) Nintendo Wii\n" | |
<< std::endl; | |
std::cout << "Enter the number and press enter: "; | |
std::cin >> useWii; | |
std::cin.ignore(); | |
std::cout << std::endl; | |
std::cout << "Please enter the number of seconds of the margin of error of the RTC. This\n" | |
"coresponds to the maximum time in seconds you can take between frame perfect\n" | |
"and the actuall time you take to boot the game after setting the RTC to\n" | |
"00:00:00 - 01/01/2000. A lower amount makes the procedure faster, but you\n" | |
"run more into the risk of not booting the game fast enough.\n" | |
"Entering 0 will turn off the reduced range using the RTC (VERY SLOW,\n" | |
"for testing only)\n" | |
"In doubt, use the recommended setting.\n" | |
<< std::endl; | |
std::cout << "Enter the number and press enter (recommended: 3): "; | |
std::cin >> rtcErrorMarginSeconds; | |
std::cin.ignore(); | |
std::cout << std::endl; | |
} | |
seedRange getRangeForSettings(bool useWii, int rtcErrorMarginSeconds) | |
{ | |
seedRange range; | |
int ticksPerSecond = useWii ? c_nbrTickPerSecondWii : c_nbrTickPerSecondGc; | |
if (rtcErrorMarginSeconds == 0) | |
{ | |
range.min = 0; | |
range.max = 0x100000000; | |
} | |
else | |
{ | |
range.min = useWii ? c_minimalRtcTicksToBootWii : c_minimalRtcTicksToBootGc; | |
range.max = range.min + ticksPerSecond * rtcErrorMarginSeconds; | |
} | |
return range; | |
} | |
std::string getPrecalcFilenameForSettings(bool useWii, int rtcErrorMarginSeconds) | |
{ | |
std::stringstream ss; | |
ss << (useWii ? "Wii" : "GC"); | |
ss << '-'; | |
ss << rtcErrorMarginSeconds; | |
ss << ".dat"; | |
return ss.str(); | |
} | |
// The idea of precalculation is to precalculate the number of LCG calls in the desired range for | |
// the calls before the pokemon company logo added to the ones when entering the battle now menu, | |
// these calls only has to be done once in the first pass meaning preloading the number of calls to | |
// a file is possible, can be reused with the sange range and it improves performance because of the | |
// LCGn function which is O(log n) complexity which is much more efficient | |
void precalculateNbrRollsBeforeTeamGeneration(bool useWii, int rtcErrorMarginSeconds) | |
{ | |
std::ofstream precalcFile(getPrecalcFilenameForSettings(useWii, rtcErrorMarginSeconds), | |
std::ios::binary | std::ios::out); | |
seedRange range = getRangeForSettings(useWii, rtcErrorMarginSeconds); | |
for (s64 i = range.min; i < range.max; i++) | |
{ | |
u32 seed = 0; | |
u16 counter = 0; | |
seed = rollLCGToPokemonCompanyLogo(static_cast<u32>(i), &counter); | |
rollLCGToBattleNowMenu(seed, &counter); | |
u16* ptrCounter = &counter; | |
precalcFile.write(reinterpret_cast<const char*>(ptrCounter), sizeof(u16)); | |
} | |
precalcFile.close(); | |
} | |
// The actuall seed finding algorithm, this algorithm scales VERY well with multithreading, having a | |
// precalculated file and a reduced ranged greatly improves the performance of this algorithm | |
void seedFinder(int teamIndex, int trainerStrIndex, std::vector<u32>& seeds, bool useWii, | |
int rtcErrorMarginSeconds, bool usePrecalc, bool firstPass) | |
{ | |
std::vector<u32> newSeeds; | |
seedRange range; | |
range.max = seeds.size(); | |
if (firstPass) | |
range = getRangeForSettings(useWii, rtcErrorMarginSeconds); | |
std::ifstream precalcFile(getPrecalcFilenameForSettings(useWii, rtcErrorMarginSeconds), | |
std::ios::binary | std::ios::in); | |
usePrecalc = usePrecalc && precalcFile.good(); | |
u16* precalc = nullptr; | |
if (usePrecalc) | |
{ | |
precalc = new u16[range.max - range.min]; | |
precalcFile.read(reinterpret_cast<char*>(precalc), (range.max - range.min) * sizeof(u16)); | |
} | |
std::cout << "Simulating " << range.max - range.min << " seeds " | |
<< (usePrecalc ? "with" : "without") << " precalculation using " | |
<< std::thread::hardware_concurrency() << " thread(s)...\n"; | |
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); | |
#pragma omp parallel for | |
for (s64 i = range.min; i < range.max; i++) | |
{ | |
u32 seed = 0; | |
if (firstPass) | |
{ | |
if (usePrecalc) | |
{ | |
u16 nbrRngCalls = 0; | |
std::memcpy(&nbrRngCalls, precalc + (i - range.min), sizeof(u16)); | |
seed = LCGn(static_cast<u32>(i), nbrRngCalls); | |
} | |
else | |
{ | |
seed = rollLCGToPokemonCompanyLogo(static_cast<u32>(i)); | |
seed = rollLCGToBattleNowMenu(seed); | |
} | |
} | |
else | |
{ | |
seed = seeds[i]; | |
} | |
if (rollLCGBattleNowGeneration(seed, teamIndex, trainerStrIndex)) | |
#pragma omp critical(addSeed) | |
newSeeds.push_back(seed); | |
} | |
std::swap(newSeeds, seeds); | |
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); | |
std::cout << "done in " << std::chrono::duration_cast<std::chrono::seconds>(end - start).count() | |
<< " seconds" << std::endl; | |
// As the number of calls differs depending on the starting seed, it may happen that some seeds | |
// may end up being the same as another one, this gets rid of the duplicates so the program can | |
// only have one unique result at the end | |
std::sort(seeds.begin(), seeds.end()); | |
auto last = std::unique(seeds.begin(), seeds.end()); | |
seeds.erase(last, seeds.end()); | |
std::cout << seeds.size() << " result(s)" << std::endl; | |
std::cout << std::endl; | |
delete[] precalc; | |
} | |
bool askForPrecalc(bool useWii, int rtcErrorMarginSeconds) | |
{ | |
std::ifstream file(getPrecalcFilenameForSettings(useWii, rtcErrorMarginSeconds)); | |
if (file.good()) | |
{ | |
std::cout << "The program has detected a precalculation file with these settings, do you\n" | |
"want to use it? y/n: "; | |
char answer = '0'; | |
std::cin >> answer; | |
std::cin.ignore(); | |
std::cout << std::endl << std::endl; | |
return (answer == 'y'); | |
} | |
else | |
{ | |
seedRange range = getRangeForSettings(useWii, rtcErrorMarginSeconds); | |
size_t fileSizeMb = (range.max - range.min) * sizeof(u16) / 1024 / 1024; | |
std::cout << "The program has not detected a precalculation file with the given settings,\n" | |
"would you like to create one? This file contains precalculated informations\n" | |
"that might take a while to generate and might be large, but once generated,\n" | |
"it will speed up the procedure significantly and can be reused in subsequent\n" | |
"seed finding procedures.\n\n"; | |
std::cout << "Estimated file size: " << fileSizeMb << "MB, create it? y/n: "; | |
char answer = '0'; | |
std::cin >> answer; | |
std::cin.ignore(); | |
if (answer == 'y') | |
{ | |
std::cout << "\nCreating the precalculation file, this might take while...\n"; | |
precalculateNbrRollsBeforeTeamGeneration(useWii, rtcErrorMarginSeconds); | |
std::cout << "Done! this file will be used in the current seed finding and it may now be\n" | |
"used with the following settings on subsequent seed finding:\n"; | |
std::cout << "> Platform: " << (useWii ? "Nintendo Wii\n" : "Nintendo GameCube\n"); | |
std::cout << "> RTC reduced range: " | |
<< (rtcErrorMarginSeconds == 0 | |
? "disabled\n" | |
: "enabled with " + std::to_string(rtcErrorMarginSeconds) + | |
" seconds of margin of errors\n"); | |
std::cout << std::endl; | |
std::cout << "Press enter to continue"; | |
std::cin.ignore(); | |
std::cout << std::endl; | |
return true; | |
} | |
std::cout << std::endl; | |
return false; | |
} | |
} | |
int main(int argc, char** argv) | |
{ | |
std::vector<u32> seeds; | |
std::cout << "This program will attempt to find your current seed in Pokemon Colosseum.\n" | |
"First, we need gather some information\n"; | |
std::cout << std::endl; | |
bool useWii = false; | |
int nbrSecondsRtcErrorMargin = -1; | |
getUserInputPreparation(useWii, nbrSecondsRtcErrorMargin); | |
bool usePrecalc = askForPrecalc(useWii, nbrSecondsRtcErrorMargin); | |
if (nbrSecondsRtcErrorMargin != 0) | |
{ | |
std::cout << "Insert your disc and boot the console then go to the calendar setting\n" | |
"(the main menu on GameCube or the Wii settings on Wii). Set the date to\n" | |
"01/01/2000 and the time to 00:00:00 (on Wii, the seconds will be set on its\n" | |
"own). AS SOON AS you set the time, boot the game as fast as possible, you can\n" | |
"waste up to the amount of seconds you set as the margin of error.\n\n"; | |
if (useWii) | |
std::cout | |
<< "Note about the Wii: make sure you see the disc spinning animation before booting\n" | |
"the game on the disc channel, to ensure it will be shown, check the Wii menu and\n" | |
"wait that the thumbnail says Nintendo GameCube, then you can go ahead and set\n" | |
"the clock.\n"; | |
else | |
std::cout | |
<< "Note about the GameCube: make sure you do not notice a small delay when\n" | |
"naviguating the cube menu to select the game disc, to ensure it won't happen,\n" | |
"keep your stick neutral after turning the cube menu to the center, then push it up\n"; | |
std::cout << std::endl; | |
} | |
std::cout | |
<< "When the game is booted, go to \"Battle now\", select \"single battle\" and choose\n" | |
"the \"ultimate\" difficulty. Answer the questions that the program will ask. This\n" | |
"has to be done several times to be certain of the seed and it may take a while\n" | |
"(especially the first time). NEVER leave the \"Battle now\" menu at ANY time\n" | |
"during this procedure and NEVER accept the battle, just back off\n" | |
"the confirmation prompt and go back to it between each passes.\n"; | |
std::cout << std::endl; | |
if (nbrSecondsRtcErrorMargin == 0) | |
{ | |
std::cout << "Operating on the full range as the RTC reduced range was disabled,\n" | |
"THIS WILL BE SLOW AND MAY TAKE SEVERAL MINUTES!\n"; | |
std::cout << std::endl; | |
} | |
std::cout << "Press enter when you are ready"; | |
std::cin.ignore(); | |
std::cout << std::endl; | |
int teamIndex = -1; | |
int trainerStrIndex = -1; | |
getUserInputSeedFinder(teamIndex, trainerStrIndex); | |
seedFinder(teamIndex, trainerStrIndex, seeds, useWii, nbrSecondsRtcErrorMargin, usePrecalc, true); | |
while (seeds.size() > 1) | |
{ | |
teamIndex = -1; | |
trainerStrIndex = -1; | |
getUserInputSeedFinder(teamIndex, trainerStrIndex); | |
seedFinder(teamIndex, trainerStrIndex, seeds, useWii, nbrSecondsRtcErrorMargin, false, false); | |
} | |
if (seeds.size() == 1) | |
std::cout << "Your seed is " << std::hex << seeds.front(); | |
else if (seeds.size() == 0) | |
std::cout << "Couldn't find the seed" << std::endl; | |
std::cout << "\nPress enter to exit"; | |
std::cin.ignore(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment