PokemonGB_Online_Trades/utilities/gsc_trading_data_utils.py
2023-07-16 19:36:09 +02:00

1412 lines
53 KiB
Python

import math
import sys
from .gsc_trading_strings import GSCTradingStrings
class GSCUtilsLoaders:
"""
Class which contains methods used to load structures from
binary or text files.
"""
def prepare_dict(target):
lines = GSCUtilsLoaders.read_text_file(target)
dict = {}
for i in lines:
dict[i.split()[0]] = int(i.split()[1])
return dict
def prepare_exp_lists(lines):
exp_lists = [[], [], [], [], [], []]
for i in range(GSCUtils.max_level):
columns = lines[i].split()
for j in range(6):
exp_lists[j] += [int(columns[j])]
return exp_lists
def read_text_file(target):
try:
with open(target,"r", encoding="utf-8") as f:
lines=f.readlines()
except FileNotFoundError as e:
return []
return lines
def prepare_learnsets(data):
dict = {}
entries = data[0]
for i in range(entries):
pos = GSCUtilsMisc.read_short(data, 1 + (i * 2))
entry = {}
species = data[pos]
levels = data[pos+1]
pos += 2
for j in range(levels):
level = data[pos]
num_moves = data[pos+1]
moves_list = []
pos += 2
for k in range(num_moves):
moves_list += [data[pos]]
pos += 1
entry[level] = moves_list
dict[species] = entry
return dict
def text_to_bytes(target, text_conv_target):
names = GSCUtilsLoaders.read_text_file(target)
text_conv_dict = GSCUtilsLoaders.prepare_dict(text_conv_target)
text_conv_dict['\n'] = GSCUtils.end_of_line
byte_names = []
for i in range(0x100):
byte_names += [[GSCUtils.end_of_line]*GSCUtils.name_size]
for j in range(len(names[i])):
letter = names[i][j].upper()
if letter in text_conv_dict:
byte_names[i][j] = text_conv_dict[letter]
else:
print(GSCTradingStrings.unrecognized_character_str.format(letter=letter))
return byte_names
def prepare_check_list(data):
ret = [False] * 0x100
if data is not None:
for i in data:
ret[i] = True
return ret
def prepare_evolution_check_list(data):
ret = [(False, None, None)] * 0x100
if data is not None:
data_len = int(len(data)/3)
for i in range(data_len):
ret[data[i]] = (True, None, data[i + (2*data_len)])
if data[i + data_len] != 0:
ret[data[i]] = (True, data[i + data_len], data[i + (2*data_len)])
return ret
def prepare_stats(data, num_stats, num_entries):
ret = [0] * num_entries
for i in range(num_entries):
ret[i] = data[(i)*num_stats:(i+1)*num_stats]
return ret
def prepare_functions_map(data, functions):
call_map = [None] * len(data)
for i in range(len(data)):
call_map[i] = functions[data[i]]
return call_map
def load_trading_data(target, lengths):
data = None
try:
with open(target, 'rb') as newFile:
data = GSCUtilsMisc.divide_data(list(newFile.read(sum(lengths))), lengths)
except FileNotFoundError as e:
pass
return data
class GSCUtils:
"""
Class which contains generic methods and data used for
pokémon-related functions.
"""
base_folder = "useful_data/gsc/"
evolution_ids_path = "evolution_ids.bin"
mail_ids_path = "ids_mail.bin"
no_mail_path = "no_mail_section.bin"
base_random_path = "base_random_section.bin"
base_stats_path = "stats.bin"
text_conv_path = "text_conv.txt"
pokemon_names_path = "pokemon_names.txt"
moves_pp_list_path = "moves_pp_list.bin"
learnset_evos_path = "learnset_evos.bin"
exp_groups_path = "pokemon_exp_groups.bin"
exp_lists_path = "pokemon_exp.txt"
egg_nick_path = "egg_nick.bin"
everstone_id = 0x70
egg_id = 0xFD
end_of_line = 0x50
num_entries = 0x100
name_size = 0xB
hp_stat_id = 0
stat_id_base_conv_table = [0,1,2,5,3,4]
stat_id_iv_conv_table = [0,0,1,2,3,3]
stat_id_exp_conv_table = [0,1,2,3,4,4]
patch_set_base_pos = [0x13, 0xC6, 0]
patch_set_start_info_pos = [7, 0x11A, 0xFC]
egg_value = 0x38
min_level = 2
max_level = 100
num_stats = 6
evolution_ids = None
mail_ids = None
base_stats = None
pokemon_names = None
no_mail_section = None
moves_pp_list = None
learnsets = None
exp_groups = None
exp_lists = None
egg_nick = None
def __init__(self):
curr_class = type(self)
curr_class.evolution_ids = GSCUtilsLoaders.prepare_evolution_check_list(GSCUtilsMisc.read_data(self.get_path(curr_class.evolution_ids_path)))
curr_class.mail_ids = GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(curr_class.mail_ids_path)))
curr_class.no_mail_section = GSCUtilsMisc.read_data(self.get_path(curr_class.no_mail_path))
curr_class.base_random_section = GSCUtilsMisc.read_data(self.get_path(curr_class.base_random_path))
curr_class.base_stats = GSCUtilsLoaders.prepare_stats(GSCUtilsMisc.read_data(self.get_path(curr_class.base_stats_path)), curr_class.num_stats, curr_class.num_entries)
curr_class.pokemon_names = GSCUtilsLoaders.text_to_bytes(self.get_path(curr_class.pokemon_names_path), self.get_path(curr_class.text_conv_path))
curr_class.moves_pp_list = GSCUtilsMisc.read_data(self.get_path(curr_class.moves_pp_list_path))
curr_class.learnsets = GSCUtilsLoaders.prepare_learnsets(GSCUtilsMisc.read_data(self.get_path(curr_class.learnset_evos_path)))
curr_class.exp_groups = GSCUtilsMisc.read_data(self.get_path(curr_class.exp_groups_path))
curr_class.exp_lists = GSCUtilsLoaders.prepare_exp_lists(GSCUtilsLoaders.read_text_file(self.get_path(curr_class.exp_lists_path)))
curr_class.egg_nick = GSCUtilsMisc.read_data(self.get_path(curr_class.egg_nick_path))
def get_path(self, target):
return self.base_folder + target
def get_level_exp(species, exp, utils_class):
start = utils_class.min_level
end = utils_class.max_level
if exp < utils_class.get_exp_level(species, start + 1, utils_class):
return start
if exp >= utils_class.get_exp_level(species, end, utils_class):
return end
end_search = False
while not end_search:
check_level = math.floor((start+end)/2)
level_exp = utils_class.get_exp_level(species, check_level, utils_class)
next_level_exp = utils_class.get_exp_level(species, check_level + 1, utils_class)
if exp < level_exp:
end = check_level
elif exp > next_level_exp:
start = check_level
elif exp == next_level_exp:
return check_level + 1
else:
return check_level
return utils_class.max_level
def get_exp_level(species, level, utils_class):
return utils_class.exp_lists[utils_class.exp_groups[species]][level-1]
def final_stat_calc_step(stat_id, level, utils_class):
if stat_id != utils_class.hp_stat_id:
return 5
return level + 10
def get_iv(iv, stat_id, utils_class):
if stat_id != utils_class.hp_stat_id:
return iv[utils_class.stat_id_iv_conv_table[stat_id]]
return ((iv[0]&1)<<3) | ((iv[1]&1)<<2) | ((iv[2]&1)<<1) | (iv[3]&1)
def get_exp(stat_exp, stat_id, utils_class):
return stat_exp[utils_class.stat_id_exp_conv_table[stat_id]]
def get_base_stat(species, stat_id, utils_class):
return utils_class.base_stats[species][utils_class.stat_id_base_conv_table[stat_id]]
def get_stat_exp_contribution(stat_exp):
val = math.ceil(math.sqrt(stat_exp))
if val >= 0x100:
val = 0xFF
return math.floor(val / 4)
def stat_calculation(stat_id, species, ivs, stat_exp, level, utils_class, do_exp=True):
inter_value = (utils_class.get_base_stat(species, stat_id, utils_class) + utils_class.get_iv(ivs, stat_id, utils_class)) * 2
if do_exp:
inter_value += utils_class.get_stat_exp_contribution(utils_class.get_exp(stat_exp, stat_id, utils_class))
inter_value = math.floor((inter_value*level)/100)
return inter_value + utils_class.final_stat_calc_step(stat_id, level, utils_class)
def is_item_mail(item):
return GSCUtilsMisc.check_normal_list(GSCUtils.mail_ids, item)
def is_evolving(species, item):
if species >= 0x100 or species < 0:
return False
evo_info = GSCUtils.evolution_ids[species]
if evo_info[0]:
if item != GSCUtils.everstone_id:
if (evo_info[1] is None) or (item == evo_info[1]):
return True
return False
def get_evolution(species, item):
if not GSCUtils.is_evolving(species, item):
return None
return GSCUtils.evolution_ids[species][2]
def get_evolution_item(species):
return GSCUtils.evolution_ids[species][1]
def get_patch_set_num_index(is_mail, is_japanese):
patch_sets_num = 2
patch_sets_index = 0
if is_mail:
patch_sets_num = 1
patch_sets_index = 1
if is_japanese:
patch_sets_index = 2
return patch_sets_num, patch_sets_index
def apply_patches(data, patch_set, utils_class, is_mail=False, is_japanese=False):
"""
Applies patch data (turns the previously read data into 0xFE)
"""
patch_sets_num, patch_sets_index = utils_class.get_patch_set_num_index(is_mail, is_japanese)
base = utils_class.patch_set_base_pos[patch_sets_index]
start = utils_class.patch_set_start_info_pos[patch_sets_index]
i = 0
while (patch_sets_num > 0) and ((start+i) < len(patch_set)):
read_pos = patch_set[start+i]
i += 1
if read_pos == 0xFF:
patch_sets_num -= 1
base += 0xFC
elif read_pos > 0 and (read_pos+base) < len(data):
data[read_pos+base-1] = 0xFE
def create_patches_data(data, patch_set, utils_class, is_mail=False, is_japanese=False):
"""
Creates patch data (turns 0xFE into a patch offset)
"""
patch_sets_num, patch_sets_index = utils_class.get_patch_set_num_index(is_mail, is_japanese)
base = utils_class.patch_set_base_pos[patch_sets_index]
start = utils_class.patch_set_start_info_pos[patch_sets_index]
i = 0
j = 0
while (patch_sets_num > 0) and ((start+i) < len(patch_set)) and ((base+j) < len(data)):
read_data = data[base+j]
if read_data == 0xFE:
data[base+j] = 0xFF
patch_set[start+i] = j+1
i+=1
j += 1
if j == 0xFC:
base += 0xFC
j = 0
patch_set[start+i] = 0xFF
i+=1
patch_sets_num -= 1
if j != 0:
if (start+i) >= len(patch_set):
i = len(patch_set)-start-1
patch_set[start+i] = 0xFF
i+=1
def single_mon_from_data(checks, data):
ret = None
# Prepare sanity checks stuff
checks.reset_species_item_list()
checks.set_single_team_size()
checks.prepare_text_buffer()
checks.prepare_patch_sets_buffer()
checks.prepare_species_buffer()
checker = checks.single_pokemon_checks_map
if len(data) > len(checker):
# Handle getting/sending eggs. That requires one extra byte
is_egg = False
if data[len(checker)] == GSCUtils.egg_value:
is_egg = True
# Prepare for further checks
if is_egg:
checks.species_cleaner(GSCUtils.egg_id)
else:
checks.species_cleaner(data[0])
checks.prepare_species_buffer()
# Applies the checks to the received data.
# If the sanity checks are off, this will be a simple copy
purified_data = checks.apply_checks_to_data(checker, data)
# Prepares the pokémon data. For both the cleaned one and
# the raw one
raw = GSCTradingPokémonInfo.set_data(data)
mon = GSCTradingPokémonInfo.set_data(purified_data)
# If the sanity checks are on, has the pokémon changed
# too much from the cleaning?
if not mon.has_changed_significantly(raw):
ret = [mon, is_egg]
return ret
def single_mon_to_data(mon, is_egg):
egg_val = 0
if is_egg:
egg_val = GSCUtils.egg_value
return mon.get_data() + [egg_val]
class GSCUtilsMisc:
"""
Class which contains generic methods and data used for
general functions.
"""
def read_data(target):
data = None
try:
with open(target, 'rb') as newFile:
tmpdata = list(newFile.read())
data = tmpdata
except FileNotFoundError as e:
pass
return data
def write_data(target, data):
try:
with open(target, 'wb') as newFile:
newFile.write(bytearray(data))
except IOError as e:
print(GSCTradingStrings.io_error_str.format(e.errno, e.strerror))
except: #handle other exceptions such as attribute errors
print(GSCTradingStrings.unknown_error_str, sys.exc_info()[0])
def read_nybbles(val):
return [(val&0xF0) >> 4, val & 0xF]
def read_short_le(data, pos):
return ((data[pos]) + (data[pos+1]<<8))
def read_int_le(data, pos):
return (data[pos]) + (data[pos+1]<<8) + (data[pos+2]<<16) + (data[pos+3]<<24)
def read_short(data, pos):
return ((data[pos] << 8) + (data[pos+1]))
def write_short(data, pos, short_data):
data[pos] = (short_data >> 8) & 0xFF
data[pos+1] = short_data & 0xFF
def write_int_le(data, pos, int_data):
data[pos] = int_data & 0xFF
data[pos+1] = (int_data >> 8) & 0xFF
data[pos+2] = (int_data >> 16) & 0xFF
data[pos+3] = (int_data >> 24) & 0xFF
def write_short_le(data, pos, short_data):
data[pos] = short_data & 0xFF
data[pos+1] = (short_data >> 8) & 0xFF
def to_n_bytes_le(in_data, n_bytes):
data = [0] * n_bytes
for i in range(n_bytes):
data[i] = (in_data>>(8*i)) & 0xFF
return data
def from_n_bytes_le(data, n_bytes):
out_data = 0
for i in range(n_bytes):
out_data |= data[i]<<(8*i)
return out_data
def inc_byte(val):
val += 1
if val >= 256:
val = 0
return val
def copy_to_data(data, pos, values, length=None):
if length is None:
length = len(values)
data[pos:pos+length] = values[:length]
def check_normal_list(checking_list, value):
if value >= 0x100 or value < 0:
return False
return checking_list[value]
def divide_data(data, lengths):
div_data = []
total_lengths = GSCUtilsMisc.calc_divide_lengths(lengths)
for i in range(len(lengths)):
div_data += [data[total_lengths[i]:total_lengths[i+1]]]
return div_data
def calc_divide_lengths(lengths):
total_lengths = []
curr_len = 0
for i in range(len(lengths)):
total_lengths += [curr_len]
curr_len += lengths[i]
total_lengths += [curr_len]
return total_lengths
def default_if_none(data, default_data):
if data is not None:
return data
return default_data
def verbose_print(to_print, verbose, end='\n'):
if verbose:
print(to_print, end=end)
class GSCTradingText:
"""
Class which contains a text entry from the trading data.
"""
def __init__(self, data, start, length=0xB, data_start=0):
self.values = data[start:start+length]
self.start_at = data_start
self.utils_class = self.get_utils_class()
def get_utils_class(self):
return GSCUtils
def values_equal(self, other):
"""
:param other: Bytes, to be compared to the ones from its own values.
"""
for i in range(len(self.values)):
if self.values[i] == self.utils_class.end_of_line and (i >= len(other) or other[i] == self.utils_class.end_of_line):
return True
if i >= len(other):
return False
if self.values[i] != other[i]:
return False
if len(other) == len(self.values) or other[len(self.values)] == self.utils_class.end_of_line:
return True
return False
class GSCTradingPartyInfo:
"""
Class which contains information about the party size and species
from the trading data.
"""
max_party_mons = 6
def __init__(self, data, start):
self.total = data[start]
if self.total <= 0 or self.total > 6:
self.total = 1
self.actual_mons = data[start + 1:start + 1 + self.max_party_mons]
def get_id(self, pos):
if pos >= self.get_total() or pos >= self.max_party_mons:
return None
return self.actual_mons[pos]
def set_id(self, pos, val):
if pos < self.get_total() and pos < self.max_party_mons:
self.actual_mons[pos] = val
def get_total(self):
return self.total
class GSCTradingPokémonInfo:
"""
Class which contains information about the pokémon from the trading data.
"""
pokemon_data_len = 0x30
ot_name_len = 0xB
nickname_len = 0xB
mail_len = 0x21
sender_len = 0xE
species_pos = 0
item_pos = 1
moves_pos = 2
pps_pos = 0x17
level_pos = 0x1F
exp_pos = 8
curr_hp_pos = 0x22
stats_pos = 0x24
evs_pos = 0xB
ivs_pos = 0x15
egg_cycles_pos = 0x1B
status_pos = 0x20
no_moves_equality_ranges = [range(0,2), range(6,0x17), range(0x1B, pokemon_data_len)]
all_lengths = [pokemon_data_len, ot_name_len, nickname_len, mail_len, sender_len]
def __init__(self, data, start, length=pokemon_data_len):
self.values = data[start:start+length]
self.mail = None
self.mail_sender = None
self._precalced_lengths = GSCUtilsMisc.calc_divide_lengths(self.all_lengths)
self.utils_class = self.get_utils_class()
self.text_class = self.get_text_class()
def get_utils_class(self):
return GSCUtils
def get_text_class(self):
return GSCTradingText
def add_ot_name(self, data, start):
self.ot_name = self.text_class(data, start, length=self.ot_name_len)
def add_nickname(self, data, start):
self.nickname = self.text_class(data, start, length=self.nickname_len)
def add_mail(self, data, start):
self.mail = self.text_class(data, start, length=self.mail_len)
def add_mail_sender(self, data, start):
self.mail_sender = self.text_class(data, start, length=self.sender_len, data_start=0)
def is_nicknamed(self):
return not self.nickname.values_equal(self.utils_class.pokemon_names[self.get_species()])
def set_default_nickname(self):
self.add_nickname(self.utils_class.pokemon_names[self.get_species()], 0)
def set_egg_nickname(self):
self.add_nickname(self.utils_class.egg_nick, 0)
def get_species(self):
return self.values[self.species_pos]
def learnable_moves(self):
"""
Returns the moves the pokémon could learn at its current level.
"""
if self.get_species() in self.utils_class.learnsets.keys():
if self.get_level() in self.utils_class.learnsets[self.get_species()].keys():
return self.utils_class.learnsets[self.get_species()][self.get_level()]
return None
def set_species(self, data):
self.values[self.species_pos] = data & 0xFF
def get_item(self):
return self.values[self.item_pos]
def set_item(self, data=0):
self.values[self.item_pos] = data & 0xFF
def has_move_index(self, move, start=0):
for i in range(start,4):
if self.get_move(i) == move:
return i
return 4
def has_move(self, move):
if self.has_move_index(move) == 4:
return False
return True
def free_move_slots(self):
free_slots = []
for i in range(4):
if self.get_move(i) == GSCChecks.free_value_moves:
free_slots += [i]
return free_slots
def get_move(self, pos):
return self.values[self.moves_pos + pos]
def set_move(self, pos, val, max_pp=True):
self.values[self.moves_pos + pos] = val
if max_pp:
self.set_pp(pos, self.utils_class.moves_pp_list[val])
def set_hatching_cycles(self, val=1):
self.values[self.egg_cycles_pos] = val
def get_hatching_cycles(self):
return self.values[self.egg_cycles_pos]
def set_pp(self, pos, val):
self.values[self.pps_pos + pos] = val
def get_pp(self, pos):
return self.values[self.pps_pos + pos]
def get_level(self):
return self.values[self.level_pos]
def set_level(self, val):
self.values[self.level_pos] = val
self.set_exp(self.utils_class.get_exp_level(self.get_species(), val, self.utils_class))
self.update_stats()
def set_exp(self, val):
self.values[self.exp_pos] = (val >> 0x10) & 0xFF
self.values[self.exp_pos+1] = (val >> 8) & 0xFF
self.values[self.exp_pos+2] = (val) & 0xFF
def update_stats(self):
"""
Updates the stats after they're changed (from evolving/changing level).
"""
old_max_hps = self.get_max_hp()
old_current_hps = self.get_curr_hp()
for i in range(self.utils_class.num_stats):
GSCUtilsMisc.write_short(self.values, self.stats_pos + (i * 2), self.utils_class.stat_calculation(i, self.get_species(), self.get_ivs(), self.get_stat_exp(), self.get_level(), self.utils_class))
new_max_hps = self.get_max_hp()
old_current_hps += new_max_hps-old_max_hps
GSCUtilsMisc.write_short(self.values, self.curr_hp_pos, min(max(0, old_current_hps), new_max_hps))
def get_stat_exp(self):
ret = [0,0,0,0,0]
for i in range(5):
ret[i] = GSCUtilsMisc.read_short(self.values, self.evs_pos + (i * 2))
return ret
def get_ivs(self):
ret = [0,0,0,0]
calc_val = [GSCUtilsMisc.read_nybbles(self.values[self.ivs_pos]), GSCUtilsMisc.read_nybbles(self.values[self.ivs_pos+1])]
for i in range(4):
ret[i] = calc_val[i>>1][i&1]
return ret
def get_curr_hp(self):
return GSCUtilsMisc.read_short(self.values, self.curr_hp_pos)
def heal(self):
GSCUtilsMisc.write_short(self.values, self.curr_hp_pos, self.get_max_hp())
self.values[self.status_pos] = 0
def faint(self):
GSCUtilsMisc.write_short(self.values, self.curr_hp_pos, 0)
self.values[self.status_pos] = 0
def get_max_hp(self):
return GSCUtilsMisc.read_short(self.values, self.stats_pos)
def has_mail(self):
return self.utils_class.is_item_mail(self.get_item())
def is_equal(self, other, weak=False):
ranges = self.no_moves_equality_ranges
for i in ranges:
for j in i:
if self.values[j] != other.values[j]:
return False
if not self.are_moves_and_pp_same(other):
return False
if not weak:
if not (self.ot_name.values_equal(other.ot_name.values) and self.nickname.values_equal(other.nickname.values)):
return False
if self.has_mail():
if not (self.mail.values_equal(other.mail.values) and self.mail_sender.values_equal(other.mail_sender.values)):
return False
return True
def get_same_moves(self):
"""
Returns for each index the list of indexes with the same move.
"""
pos = []
for i in range(4):
inner_pos = []
for j in range(4):
if self.get_move(i) == self.get_move(j):
inner_pos += [i]
pos += [inner_pos]
return pos
def are_moves_and_pp_same(self, other):
corresponding_indexes = self.are_moves_same(other)
if corresponding_indexes is None:
return False
possible_positions = self.get_same_moves()
found_pos = []
for i in range(4):
found = False
for j in possible_positions[i]:
if not found and corresponding_indexes[j] not in found_pos and self.get_pp(i) == other.get_pp(corresponding_indexes[j]):
found_pos += [corresponding_indexes[j]]
found = True
if not found:
return False
return True
def are_moves_same(self, other):
"""
Returns None if the moves are different.
If they're the same, it returns the index in which they're found.
"""
pos = []
for i in range(4):
proper_found = False
start_index = 0
while not proper_found:
index = other.has_move_index(self.get_move(i), start=start_index)
if index == 4:
return None
elif index in pos:
start_index = index + 1
else:
pos += [index]
proper_found = True
return pos
def has_changed_significantly(self, raw):
"""
Returns whether a pokémon has changed too much due to the sanity
checks cleaning it.
"""
if self.get_species() != raw.get_species():
return True
if self.are_moves_same(raw) is None:
return True
if self.get_level() != raw.get_level():
return True
return False
def get_data(self):
"""
Returns all the data used to represent this pokemon.
"""
sources = [self, self.ot_name, self.nickname]
mail_sources = [self.mail, self.mail_sender]
data = [0] * self._precalced_lengths[len(sources)+len(mail_sources)]
for i in range(len(sources)):
GSCUtilsMisc.copy_to_data(data, self._precalced_lengths[i], sources[i].values)
if self.has_mail():
for i in range(len(mail_sources)):
GSCUtilsMisc.copy_to_data(data, self._precalced_lengths[i+len(sources)], mail_sources[i].values)
return data
def set_data(data):
"""
Creates an entry from the given data.
"""
mon = GSCTradingPokémonInfo(data, 0)
mon.add_ot_name(data, mon._precalced_lengths[1])
mon.add_nickname(data, mon._precalced_lengths[2])
if mon.has_mail():
mon.add_mail(data, mon._precalced_lengths[3])
mon.add_mail_sender(data, mon._precalced_lengths[4])
return mon
class GSCTradingData:
"""
Class which contains all the informations about a trader's party.
"""
trader_name_pos = 0
trading_party_info_pos = 0xB
trading_party_final_pos = 0x12
trader_info_pos = 0x13
trading_pokemon_pos = 0x15
trading_pokemon_ot_pos = 0x135
trading_pokemon_nickname_pos = 0x177
trading_pokemon_mail_pos = 0
trading_pokemon_mail_sender_pos = 0xC6
trading_pokemon_length = 0x30
trading_name_length = 0xB
trading_mail_length = 0x21
trading_mail_sender_length = 0xE
def __init__(self, data_pokemon, data_mail=None, do_full=True):
self.utils_class = self.get_utils_class()
self.trader = self.text_generator(data_pokemon, self.trader_name_pos, length=self.trading_name_length)
self.party_info = self.party_generator(data_pokemon, self.trading_party_info_pos)
self.trader_info = self.trainer_info_generator(data_pokemon, self.trader_info_pos)
self.pokemon = []
if do_full:
for i in range(self.get_party_size()):
self.pokemon += [self.mon_generator(data_pokemon, self.trading_pokemon_pos + i * self.trading_pokemon_length)]
self.pokemon[i].add_ot_name(data_pokemon, self.trading_pokemon_ot_pos + i * self.trading_name_length)
self.pokemon[i].add_nickname(data_pokemon, self.trading_pokemon_nickname_pos + i * self.trading_name_length)
if data_mail is not None and self.pokemon[i].has_mail():
self.pokemon[i].add_mail(data_mail, self.trading_pokemon_mail_pos + i * self.trading_mail_length)
self.pokemon[i].add_mail_sender(data_mail, self.trading_pokemon_mail_sender_pos + i * self.trading_mail_sender_length)
def mon_generator(self, data, pos):
return self.mon_generator_class()(data, pos)
def mon_generator_class(self):
return GSCTradingPokémonInfo
def text_generator(self, data, pos, length=trading_name_length):
return GSCTradingText(data, pos, length=length)
def party_generator(self, data, pos):
return GSCTradingPartyInfo(data, pos)
def trainer_info_generator(self, data, pos):
return GSCUtilsMisc.read_short(data, pos)
def get_utils_class(self):
return GSCUtils
def check_pos_validity(func):
def wrapper(*args, **kwargs):
self = args[0]
pos = args[1]
if pos < 0 or pos >= self.get_party_size():
print(GSCTradingStrings.index_error_str)
return False
return func(*args, **kwargs)
return wrapper
def get_party_size(self):
return self.party_info.get_total()
def get_last_mon_index(self):
return self.get_party_size()-1
def search_for_mon(self, mon, is_egg):
"""
Returns None if a provided pokémon is not in the party.
Otherwise, it returns their index.
"""
for i in range(self.get_party_size()):
if mon.is_equal(self.pokemon[i]) and (self.is_mon_egg(i) == is_egg):
return i
for i in range(self.get_party_size()):
if mon.is_equal(self.pokemon[i], weak=True) and (self.is_mon_egg(i) == is_egg):
return i
return None
@check_pos_validity
def mon_has_mail(self, pos):
return self.pokemon[pos].has_mail()
@check_pos_validity
def is_mon_egg(self, pos):
return self.party_info.get_id(pos) == self.utils_class.egg_id
def party_has_mail(self):
mail_owned = False
for i in range(self.get_party_size()):
mail_owned |= self.mon_has_mail(i)
return mail_owned
def evolution_procedure(self, pos, evolution):
"""
Procedure which actually evolves the Pokémon in the data.
"""
if not self.pokemon[pos].is_nicknamed():
self.pokemon[pos].add_nickname(self.utils_class.pokemon_names[evolution], 0)
self.pokemon[pos].set_species(evolution)
self.party_info.set_id(pos, self.pokemon[pos].get_species())
self.pokemon[pos].update_stats()
@check_pos_validity
def requires_input(self, pos, special_mons_set):
"""
Handles detemining if the mon requires special care
"""
result = GSCUtilsMisc.default_if_none(self.evolve_mon(pos), False)
if not result:
result = self.is_special_mon(pos, special_mons_set)
return result
@check_pos_validity
def evolve_mon(self, pos):
"""
Handles evolving a pokémon in the party.
Returns None if it won't evolve.
Returns True if it evolved and player input is required.
Returns False if it evolved and no player input is required.
"""
evolution = self.utils_class.get_evolution(self.pokemon[pos].get_species(), self.pokemon[pos].get_item())
if evolution is None or self.is_mon_egg(pos):
return None
evo_item = self.utils_class.get_evolution_item(self.pokemon[pos].get_species())
if evo_item is not None:
self.pokemon[pos].set_item()
self.evolution_procedure(pos, evolution)
curr_learning = self.pokemon[pos].learnable_moves()
if curr_learning is not None:
for i in range(len(curr_learning)):
if not self.pokemon[pos].has_move(curr_learning[i]):
free_slots = self.pokemon[pos].free_move_slots()
if len(free_slots) > 0:
self.pokemon[pos].set_move(free_slots[0], curr_learning[i])
else:
return True
return False
@check_pos_validity
def is_special_mon(self, pos, special_mons_set):
"""
Handles special pokémon which may learn new moves.
Returns True if it is a special mon.
Returns False if not.
"""
return self.pokemon[pos].get_species() in special_mons_set
def get_traded_mons(self, other):
"""
Gets which pokémon were traded.
"""
return [self.pokemon[self.get_last_mon_index()].get_species(), other.pokemon[other.get_last_mon_index()].get_species()]
def trade_mon(self, other, own_index, other_index, checks):
"""
Trades a pokémon between two parties.
"""
# Prepare checks
checks.reset_species_item_list()
checks.set_single_team_size()
checks.prepare_text_buffer()
checks.prepare_patch_sets_buffer()
checks.prepare_species_buffer()
# Apply checks
own_id = checks.species_cleaner(self.party_info.get_id(own_index))
checks.prepare_species_buffer()
own = self.mon_generator_class().set_data(checks.apply_checks_to_data(checks.single_pokemon_checks_map, self.pokemon[own_index].get_data()))
# Actually trade the Pokémon
self.reorder_party(own_index)
other.reorder_party(other_index)
self.pokemon[self.get_last_mon_index()] = other.pokemon[other.get_last_mon_index()]
other.pokemon[other.get_last_mon_index()] = own
checks.curr_species_pos = self.get_last_mon_index()
checks.team_size = self.get_party_size()
self.party_info.set_id(self.get_last_mon_index(), other.party_info.get_id(other.get_last_mon_index()))
other.party_info.set_id(other.get_last_mon_index(), own_id)
@check_pos_validity
def reorder_party(self, traded_pos):
"""
Moves a pokémon at the end of the party.
"""
pa_info = self.party_info.get_id(traded_pos)
po_data = self.pokemon[traded_pos]
for i in range(traded_pos+1,self.get_party_size()):
self.party_info.set_id(i-1, self.party_info.get_id(i))
self.pokemon[i-1] = self.pokemon[i]
self.party_info.set_id(self.get_last_mon_index(), pa_info)
self.pokemon[self.get_last_mon_index()] = po_data
def create_trading_data(self, lengths):
"""
Creates the data which can be loaded to the hardware.
"""
data = []
for i in range(3):
data += [lengths[i]*[0]]
data += [self.utils_class.no_mail_section[:len(self.utils_class.no_mail_section)]]
GSCUtilsMisc.copy_to_data(data[1], self.trader_name_pos, self.trader.values, self.trading_name_length)
data[1][self.trading_party_info_pos] = self.get_party_size()
GSCUtilsMisc.copy_to_data(data[1], self.trading_party_info_pos + 1, self.party_info.actual_mons)
data[1][self.trading_party_final_pos] = 0xFF
if self.trader_info is not None:
GSCUtilsMisc.write_short(data[1], self.trader_info_pos, self.trader_info)
for i in range(self.get_party_size()):
GSCUtilsMisc.copy_to_data(data[1], self.trading_pokemon_pos + (i * self.trading_pokemon_length), self.pokemon[i].values)
GSCUtilsMisc.copy_to_data(data[1], self.trading_pokemon_ot_pos + (i * self.trading_name_length), self.pokemon[i].ot_name.values, self.trading_name_length)
GSCUtilsMisc.copy_to_data(data[1], self.trading_pokemon_nickname_pos + (i * self.trading_name_length), self.pokemon[i].nickname.values, self.trading_name_length)
if self.pokemon[i].mail is not None:
GSCUtilsMisc.copy_to_data(data[3], self.trading_pokemon_mail_pos + (i * self.trading_mail_length), self.pokemon[i].mail.values)
GSCUtilsMisc.copy_to_data(data[3], self.trading_pokemon_mail_sender_pos + (i * self.trading_mail_sender_length), self.pokemon[i].mail_sender.values, self.trading_mail_sender_length)
self.utils_class.create_patches_data(data[1], data[2], self.utils_class)
self.utils_class.create_patches_data(data[3], data[3], self.utils_class, is_mail=True)
return data
class GSCChecks:
"""
Class which handles sanity checks and cleaning of the received data.
checks_map and single_pokemon_checks_map are its product used to apply
the checks.
"""
base_folder = "useful_data/gsc/"
bad_ids_items_path = "bad_ids_items.bin"
bad_ids_moves_path = "bad_ids_moves.bin"
bad_ids_pokemon_path = "bad_ids_pokemon.bin"
bad_ids_text_path = "bad_ids_text.bin"
checks_map_path = "checks_map.bin"
single_pokemon_checks_map_path = "single_pokemon_checks_map.bin"
moves_checks_map_path = "moves_checks_map.bin"
pokemon_patch_set_0_path = "pokemon_patch_set_0.bin"
pokemon_patch_set_1_path = "pokemon_patch_set_1.bin"
mail_patch_set_path = "mail_patch_set.bin"
japanese_mail_patch_set_path = "japanese_mail_patch_set.bin"
curr_exp_pos_masks = [0, 0xFF0000, 0xFFFF00]
free_value_species = 0xFF
empty_value_species = 0
free_value_moves = 0
no_conversion_patch = 0
end_of_patch = 0xFF
patch_set_cover = 0xFC
tackle_id = 0x21
rattata_id = 0x13
question_mark = 0xE6
newline = 0x4E
def __init__(self, section_sizes, do_sanity_checks):
self.utils_class = self.get_utils_class()
self.do_sanity_checks = do_sanity_checks
self.bad_ids_items = GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.bad_ids_items_path)))
self.bad_ids_moves = GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.bad_ids_moves_path)))
self.bad_ids_pokemon = GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.bad_ids_pokemon_path)))
self.bad_ids_text = GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.bad_ids_text_path)))
self.pokemon_patch_sets = [GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.pokemon_patch_set_0_path))), GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.pokemon_patch_set_1_path)))]
self.mail_patch_set = [GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.mail_patch_set_path)))]
self.japanese_mail_patch_set = [GSCUtilsLoaders.prepare_check_list(GSCUtilsMisc.read_data(self.get_path(self.mail_patch_set_path)))]
self.check_functions = [
self.clean_nothing,
self.clean_text,
self.clean_team_size,
self.clean_species,
self.clean_move,
self.clean_item,
self.clean_level,
self.check_hp,
self.clean_text_final,
self.load_stat_exp,
self.load_stat_iv,
self.check_stat,
self.clean_species_sp,
self.clean_pp,
self.clean_experience,
self.clean_egg_cycles_friendship,
self.clean_type,
self.clean_text_newline,
self.clean_text_final_no_end,
self.clean_species_force_terminate,
self.clean_mail_species,
self.clean_mail_item,
self.clean_mail_same_species,
self.clean_pokemon_patch_set,
self.clean_mail_patch_set,
self.clean_japanese_mail_patch_set
]
self.checks_map = self.prepare_checks_map(GSCUtilsMisc.read_data(self.get_path(self.checks_map_path)), section_sizes, self.check_functions)
self.single_pokemon_checks_map = GSCUtilsLoaders.prepare_functions_map(GSCUtilsMisc.read_data(self.get_path(self.single_pokemon_checks_map_path)), self.check_functions)
self.moves_checks_map = GSCUtilsLoaders.prepare_functions_map(GSCUtilsMisc.read_data(self.get_path(self.moves_checks_map_path)), self.check_functions)
self.species_cleaner = self.clean_species_sp
def get_path(self, target):
return self.base_folder + target
def get_utils_class(self):
return GSCUtils
def clean_check_sanity_checks(func):
def wrapper(*args, **kwargs):
self = args[0]
val = args[1]
if self.do_sanity_checks:
val = func(*args, **kwargs)
return val
return wrapper
def valid_check_sanity_checks(func):
def wrapper(*args, **kwargs):
self = args[0]
if self.do_sanity_checks:
return func(*args, **kwargs)
else:
return True
return wrapper
def apply_checks_to_data(self, checker, data):
new_data = list(data)
for j in range(len(checker)):
new_data[j] = checker[j](data[j])
return new_data
def prepare_text_buffer(self):
self.curr_text = []
def reset_species_item_list(self):
self.species_list = []
self.species_list_size = 0
self.item_list = []
def set_single_team_size(self):
self.team_size = 1
def add_to_species_list(self, species):
if (species != self.free_value_species) and (species != self.empty_value_species):
self.species_list_size += 1
self.species_list += [species]
def prepare_species_buffer(self):
self.curr_species_pos = 0
def prepare_patch_sets_buffer(self):
self.curr_patch_set = 0
def prepare_checks_map(self, data, lengths, functions_list):
raw_data_sections = GSCUtilsMisc.divide_data(data, lengths)
call_map = [[],[],[],[]]
for i in range(len(raw_data_sections)):
call_map[i] = GSCUtilsLoaders.prepare_functions_map(raw_data_sections[i], functions_list)
return call_map
@clean_check_sanity_checks
def clean_nothing(self, val):
return val
@clean_check_sanity_checks
def clean_level(self, level):
self.level = self.utils_class.get_level_exp(self.curr_species, self.exp, self.utils_class)
return self.level
def exp_range_calculations(self):
return [self.utils_class.get_exp_level(self.curr_species, self.utils_class.min_level, self.utils_class), self.utils_class.get_exp_level(self.curr_species, self.utils_class.max_level, self.utils_class)]
@clean_check_sanity_checks
def clean_experience(self, val):
if self.curr_exp_pos == 0:
self.exp_range = self.exp_range_calculations()
if val >= 0x80:
self.negative_exp = True
if self.negative_exp:
val = 0
curr_read_val = val << (8 * (2 - self.curr_exp_pos))
exp_mask = self.curr_exp_pos_masks[self.curr_exp_pos]
self.exp = self.check_range(self.exp_range, (self.exp & exp_mask) | curr_read_val)
val = (self.exp >> (8 * (2 - self.curr_exp_pos))) & 0xFF
self.curr_exp_pos += 1
return val
@clean_check_sanity_checks
def clean_team_size(self, team_size):
self.team_size = self.clean_value(team_size, self.is_team_size_valid, 1)
return self.team_size
@clean_check_sanity_checks
def clean_item(self, item):
cleaned_item = self.clean_value(item, self.is_item_valid, 0)
self.item_list += [cleaned_item]
return cleaned_item
@clean_check_sanity_checks
def clean_pp(self, pp):
current_pp = pp & 0x3F
pp_ups = (pp >> 6) & 3
max_base_pp = self.utils_class.moves_pp_list[self.moves[self.curr_pp]]
pp_increment = math.floor(max_base_pp/5)
if max_base_pp == 40:
pp_increment -= 1
max_pp = max_base_pp + (pp_increment * pp_ups)
final_pp = pp
if current_pp > max_pp:
final_pp = (pp_ups << 6) | max_pp
self.curr_pp += 1
return final_pp
@clean_check_sanity_checks
def clean_move(self, move):
if move == GSCChecks.free_value_moves and self.curr_move > 0:
self.moves[self.curr_move] = GSCChecks.free_value_moves
self.curr_move += 1
return move
final_move = self.clean_value(move, self.is_move_valid, self.tackle_id)
self.moves[self.curr_move] = final_move
self.curr_move += 1
return final_move
@clean_check_sanity_checks
def clean_species(self, species):
self.curr_species = self.clean_value(species, self.is_species_valid, self.rattata_id)
self.curr_species_pos += 1
self.curr_stat_id = 0
self.iv = [0,0,0,0]
self.stat_exp = [0,0,0,0,0]
self.moves = [0,0,0,0]
self.curr_move = 0
self.curr_pp = 0
self.exp = 0
self.negative_exp = False
self.curr_exp_pos = 0
self.curr_hp = 0
self.curr_pos = 0
self.curr_iv_pos = 0
self.curr_exp_id = 0
self.curr_stat_exp_pos = 0
return self.curr_species
@clean_check_sanity_checks
def clean_species_sp(self, species):
if species == self.free_value_species or self.species_list_size >= self.team_size:
self.add_to_species_list(self.free_value_species)
self.curr_species_pos += 1
return self.free_value_species
found_species = self.clean_value(species, self.is_species_valid, self.rattata_id)
if species == self.utils_class.egg_id:
found_species = species
self.add_to_species_list(found_species)
self.curr_species_pos += 1
return found_species
@clean_check_sanity_checks
def clean_species_force_terminate(self, species):
self.prepare_species_buffer()
return self.free_value_species
@clean_check_sanity_checks
def load_stat_exp(self, val):
calc_val = val << (8 * self.curr_stat_exp_pos)
if self.curr_stat_exp_pos == 0:
self.stat_exp[self.curr_exp_id] = calc_val
self.curr_stat_exp_pos += 1
else:
self.stat_exp[self.curr_exp_id] |= calc_val
self.curr_stat_exp_pos = 0
self.curr_exp_id += 1
return val
@clean_check_sanity_checks
def load_stat_iv(self, val):
calc_val = GSCUtilsMisc.read_nybbles(val)
self.iv[self.curr_iv_pos*2] = calc_val[0]
self.iv[(self.curr_iv_pos*2) + 1] = calc_val[1]
self.curr_iv_pos += 1
return val
@clean_check_sanity_checks
def clean_mail_species(self, species):
self.curr_species_pos += 1
return self.species_list[self.curr_species_pos-1]
@clean_check_sanity_checks
def clean_mail_same_species(self, species):
return self.species_list[self.curr_species_pos-1]
@clean_check_sanity_checks
def clean_mail_item(self, item):
return self.item_list[self.curr_species_pos-1]
@clean_check_sanity_checks
def clean_text(self, char):
char_val = self.clean_value(char, self.is_char_valid, self.question_mark)
self.curr_text += [char_val]
# Possibility to put bad words filters here
return char_val
@clean_check_sanity_checks
def clean_text_final(self, char):
char_val = self.utils_class.end_of_line
self.curr_text += [char_val]
# Possibility to put bad words filters here
self.prepare_text_buffer()
return char_val
@clean_check_sanity_checks
def clean_text_newline(self, char):
char_val = self.newline
self.curr_text += [char_val]
# Possibility to put bad words filters here
return char_val
@clean_check_sanity_checks
def clean_text_final_no_end(self, char):
char_val = self.clean_value(char, self.is_char_valid, self.question_mark)
self.curr_text += [char_val]
# Possibility to put bad words filters here
self.prepare_text_buffer()
return char_val
@clean_check_sanity_checks
def clean_type(self, typing):
return typing
def check_range(self, stat_range, curr_stat):
if curr_stat > stat_range[1]:
curr_stat = stat_range[1]
if curr_stat < stat_range[0]:
curr_stat = stat_range[0]
return curr_stat
@clean_check_sanity_checks
def check_stat(self, val, zero_min=False, zero_max=False):
if self.curr_pos == 0:
self.stat = 0
min_stat = self.utils_class.stat_calculation(self.curr_stat_id, self.curr_species, self.iv, self.stat_exp, self.level, self.utils_class, do_exp=False)
max_stat = self.utils_class.stat_calculation(self.curr_stat_id, self.curr_species, self.iv, self.stat_exp, self.level, self.utils_class)
if zero_min or zero_max:
min_stat = 0
if zero_max:
max_stat = 0
self.stat_range = [min_stat, max_stat]
curr_read_val = val << (8 * (1 - (self.curr_pos & 1)))
self.stat = self.check_range(self.stat_range, (self.stat & 0xFF00) | curr_read_val)
val = (self.stat >> (8 * (1 - (self.curr_pos & 1)))) & 0xFF
self.curr_pos += 1
if self.curr_pos >= 2:
self.curr_stat_id += 1
self.curr_pos = 0
return val
def is_egg(self):
if self.species_list[self.curr_species_pos-1] == self.utils_class.egg_id:
return True
return False
@clean_check_sanity_checks
def check_hp(self, val):
start_zero = False
max_zero = False
if self.curr_hp == 0:
start_zero = True
max_zero = self.is_egg()
val = self.check_stat(val, zero_min=start_zero, zero_max=max_zero)
if self.curr_pos == 0:
if self.curr_hp == 0:
self.hps = [0,0]
self.curr_stat_id -= 1
self.hps[self.curr_hp] = self.stat
self.curr_hp += 1
if self.curr_hp == 2:
if self.hps[0] > self.hps[1]:
#Can put a warning for bad data
pass
return val
@clean_check_sanity_checks
def clean_egg_cycles_friendship(self, cycles_friendship):
return cycles_friendship
def check_patch_set(self, val, patch_sets):
if self.curr_patch_set >= len(patch_sets):
return self.no_conversion_patch
if val == self.end_of_patch:
self.curr_patch_set += 1
return val
if GSCUtilsMisc.check_normal_list(patch_sets[self.curr_patch_set], val):
return val
return self.no_conversion_patch
@clean_check_sanity_checks
def clean_pokemon_patch_set(self, val):
return self.check_patch_set(val, self.pokemon_patch_sets)
@clean_check_sanity_checks
def clean_mail_patch_set(self, val):
return self.check_patch_set(val, self.mail_patch_set)
@clean_check_sanity_checks
def clean_japanese_mail_patch_set(self, val):
return self.check_patch_set(val, self.japanese_mail_patch_set)
def clean_value(self, value, checker, default_value):
if checker(value):
return value
return default_value
@valid_check_sanity_checks
def is_team_size_valid(self, team_size):
if team_size <= 0 or team_size > 6:
return False
return True
@valid_check_sanity_checks
def is_item_valid(self, item):
return not GSCUtilsMisc.check_normal_list(self.bad_ids_items, item)
@valid_check_sanity_checks
def is_move_valid(self, move):
return not GSCUtilsMisc.check_normal_list(self.bad_ids_moves, move)
@valid_check_sanity_checks
def is_species_valid(self, species):
return not GSCUtilsMisc.check_normal_list(self.bad_ids_pokemon, species)
@valid_check_sanity_checks
def is_char_valid(self, char):
return not GSCUtilsMisc.check_normal_list(self.bad_ids_text, char)