From f4ed93599cd0f3c9d08ee6a3f50558396f797c3e Mon Sep 17 00:00:00 2001 From: Lorenzo Carletti Date: Wed, 25 May 2022 01:27:27 +0200 Subject: [PATCH] Start implementing patch sets --- notes/gsc_trading_notes.txt | 4 +- useful_data/gsc/base.bin | Bin 1042 -> 1036 bytes useful_data/gsc/checks_map.bin | Bin 1042 -> 1036 bytes useful_data/gsc/mail_checks_jp.bin | Bin 488 -> 285 bytes useful_data/gsc/no_mail_section.bin | Bin 588 -> 385 bytes utilities/gsc_trading.py | 70 ++++++++++++++++++++-------- utilities/gsc_trading_data_utils.py | 55 ++++++++++++++++++---- utilities/rby_trading.py | 25 ++++++++-- 8 files changed, 121 insertions(+), 33 deletions(-) diff --git a/notes/gsc_trading_notes.txt b/notes/gsc_trading_notes.txt index 9261971..50363d8 100644 --- a/notes/gsc_trading_notes.txt +++ b/notes/gsc_trading_notes.txt @@ -38,4 +38,6 @@ Egg cycles don't count down in the trading room. There are in-game checks which make sure you always have at least one alive mon. The time capsule won't evolve any Pokémon it recieves if they hold an Everstone... The time capsule won't evolve any Pokémon it recieves which couldn't evolve in Gen 1... -The time capsule won't prevent learning new moves from Gen 2. \ No newline at end of file +The time capsule won't prevent learning new moves from Gen 2. + +Patch set = where there would be a 0xFE. It's a list covering 0xFC consecutive bytes. \ No newline at end of file diff --git a/useful_data/gsc/base.bin b/useful_data/gsc/base.bin index 4f4305b19fd740f4a59692ceaddab3f72e991034..cd443e9b58f64c3efc7d0ba6417e77cafe4c0837 100644 GIT binary patch delta 15 XcmbQl(ZjK!n`!bUrlph3nC}1pE+hs3 delta 38 YcmeC-n8dN6n~6;U3?>^eD`LTS0JDqks01*}gz5oCK diff --git a/utilities/gsc_trading.py b/utilities/gsc_trading.py index c96fe04..631b504 100644 --- a/utilities/gsc_trading.py +++ b/utilities/gsc_trading.py @@ -22,11 +22,11 @@ class GSCTradingClient: buffered_transfer = "BUF2" negotiation_transfer = "NEG2" possible_transfers = { - full_transfer: {0x412}, # Sum of special_sections_len + full_transfer: {0x412, 0x40C}, # Sum of special_sections_len - Ver 1.0 and 2.0 single_transfer: {7}, pool_transfer: {1 + 0x75 + 1, 1 + 1}, # Counter + Single Pokémon + Egg OR Counter + Fail moves_transfer: {1 + 8}, # Counter + Moves - mail_transfer : {1 + 0x24C}, # Counter + Mail + mail_transfer : {1 + 0x24C, 1 + 0x181}, # Counter + Mail - Ver 1.0 and 2.0 choice_transfer : {1 + 1 + 0x75 + 1, 1 + 1}, # Counter + Choice + Single Pokémon + Egg OR Counter + Stop accept_transfer : {1 + 1}, # Counter + Accept success_transfer : {1 + 1}, # Counter + Success @@ -369,15 +369,19 @@ class GSCTrading: start_trading_states = [[0x75, 0x75, 0x76], [{0x75}, {0}, {0xFD}]] success_values = set(range(0x70, 0x80)) possible_indexes = set(range(0x70, 0x80)) - fillers = [{}, {}, {}] + fillers = [{}, {}, {}, {}] filler_value = 0xFE00 last_filler_value = 0xFEFF max_consecutive_no_data = 0x100 next_section = 0xFD + mail_next_section = 0x20 + patch_set_base_pos = [0x13, 0] + patch_set_start_info_pos = [7, 0x11A] no_input = 0xFE no_data = 0 - special_sections_len = [0xA, 0x1BC, 0x24C] - drop_bytes_checks = [[0xA, 0x1B9, 0x1E6], [next_section, next_section, no_input], [0,0,0]] + special_sections_len = [0xA, 0x1BC, 0xC5, 0x181] + special_sections_starter = [next_section, next_section, next_section, mail_next_section] + drop_bytes_checks = [[0xA, 0x1B9, 0xC5, 0x181], [next_section, next_section, mail_next_section, no_input], [0,0,0,0]] stop_trade = 0x7F first_trade_index = 0x70 decline_trade = 0x71 @@ -465,7 +469,7 @@ class GSCTrading: self.printed_warning_drop = True def get_mail_section_id(self): - return 2 + return 3 def get_printable_index(self, index): return index+1 @@ -487,7 +491,7 @@ class GSCTrading: Reads a data section and sends it to the device. """ length = self.get_section_length(index) - next = self.next_section + next = self.special_sections_starter[index] checker = self.get_checker(index) # Prepare sanity checks stuff @@ -517,7 +521,7 @@ class GSCTrading: self.verbose_print(GSCTradingStrings.arrived_synchro_str) # Sync with the device and start the actual trade - while next == self.next_section: + while next == self.special_sections_starter[index]: next = self.swap_byte(next) # next now contains the first received byte from the device! @@ -943,8 +947,30 @@ class GSCTrading: if self.exit_or_new: self.verbose_print(GSCTradingStrings.sit_table_str) return self.send_predefined_section(self.start_trading_states, die_on_no_data=True) + + def apply_patches(self, data, patch_set, is_mail=False): + """ + Applies patch data (turns the previously read data into 0xFE) + """ + patch_sets_num = 2 + patch_sets_index = 0 + if is_mail: + patch_sets_num = 1 + patch_sets_index = 1 - def trade_starting_sequence(self, buffered, send_data = [None, None, None]): + base = self.patch_set_base_pos[patch_sets_index] + start = self.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 trade_starting_sequence(self, buffered, send_data = [None, None, None, None]): """ Handles exchanging with the device the three data sections which are needed in order to trade. @@ -957,7 +983,11 @@ class GSCTrading: # Send and get the first two sections random_data, random_data_other = self.read_section(0, send_data[0], buffered) pokemon_data, pokemon_data_other = self.read_section(1, send_data[1], buffered) + patches_data, patches_data_other = self.read_section(2, send_data[2], buffered) + self.apply_patches(pokemon_data, patches_data) + self.apply_patches(pokemon_data_other, patches_data_other) + pokemon_own = self.party_reader(pokemon_data) pokemon_other = self.party_reader(pokemon_data_other) pokemon_own_mail = pokemon_own.party_has_mail() @@ -965,21 +995,23 @@ class GSCTrading: # Trade mail data only if needed if (pokemon_own_mail and pokemon_other_mail) or buffered: - send_data[2] = self.convert_mail_data(send_data[2], True) - mail_data, mail_data_other = self.read_section(self.get_mail_section_id(), send_data[2], buffered) + send_data[3] = self.convert_mail_data(send_data[3], True) + mail_data, mail_data_other = self.read_section(self.get_mail_section_id(), send_data[3], buffered) + self.apply_patches(mail_data, mail_data, is_mail=True) mail_data = self.convert_mail_data(mail_data, False) else: - send_data[2] = self.utils_class.no_mail_section + send_data[3] = self.utils_class.no_mail_section # Get mail data if only the other client has it if pokemon_other_mail: self.verbose_print(GSCTradingStrings.mail_other_data_str) - send_data[2] = self.force_receive(self.comms.get_mail_data_only) + send_data[3] = self.force_receive(self.comms.get_mail_data_only) else: self.verbose_print(GSCTradingStrings.no_mail_other_data_str) # Exchange mail data with the device - send_data[2] = self.convert_mail_data(send_data[2], True) - mail_data, mail_data_other = self.read_section(self.get_mail_section_id(), send_data[2], True) + send_data[3] = self.convert_mail_data(send_data[3], True) + mail_data, mail_data_other = self.read_section(self.get_mail_section_id(), send_data[3], True) + self.apply_patches(mail_data, mail_data, is_mail=True) mail_data = self.convert_mail_data(mail_data, False) # Send mail data if only this client has it @@ -1000,7 +1032,7 @@ class GSCTrading: # Generate the trading data for the device # from the other player's one and use it self.verbose_print(GSCTradingStrings.recycle_data_str) - data, data_other = self.trade_starting_sequence(True, send_data=self.other_pokemon.create_trading_data(self.special_sections_len)) + data, data_other = self.trade_starting_sequence(True, send_data=self.other_pokemon.create_trading_data(self.special_sections_len, self.patch_set_base_pos, self.patch_set_start_info_pos)) self.own_pokemon = self.party_reader(data[1], data_mail=data[2]) self.other_pokemon = self.party_reader(data_other[1], data_mail=data_other[2]) return True @@ -1020,7 +1052,7 @@ class GSCTrading: else: # Generate the trading data for the device # from the other player's one and use it - data = self.other_pokemon.create_trading_data(self.special_sections_len) + data = self.other_pokemon.create_trading_data(self.special_sections_len, self.patch_set_base_pos, self.patch_set_start_info_pos) valid = True self.verbose_print(GSCTradingStrings.recycle_data_str) data, data_other = self.trade_starting_sequence(True, send_data=data) @@ -1067,7 +1099,7 @@ class GSCTrading: # Generate the trading data for the device # from the other player's one and use it self.verbose_print(GSCTradingStrings.reuse_data_str) - data, data_other = self.trade_starting_sequence(True, send_data=self.other_pokemon.create_trading_data(self.special_sections_len)) + data, data_other = self.trade_starting_sequence(True, send_data=self.other_pokemon.create_trading_data(self.special_sections_len, self.patch_set_base_pos, self.patch_set_start_info_pos)) # If only this client requires user inputs, # send its data @@ -1104,7 +1136,7 @@ class GSCTrading: self.other_pokemon = self.force_receive(self.comms.get_pool_trading_data) else: self.verbose_print(GSCTradingStrings.pool_recycle_data_str) - data, data_other = self.trade_starting_sequence(True, send_data=self.other_pokemon.create_trading_data(self.special_sections_len)) + data, data_other = self.trade_starting_sequence(True, send_data=self.other_pokemon.create_trading_data(self.special_sections_len, self.patch_set_base_pos, self.patch_set_start_info_pos)) self.own_pokemon = self.party_reader(data[1], data_mail=data[2]) # Start interacting with the trading menu diff --git a/utilities/gsc_trading_data_utils.py b/utilities/gsc_trading_data_utils.py index d73a999..e68d1b5 100644 --- a/utilities/gsc_trading_data_utils.py +++ b/utilities/gsc_trading_data_utils.py @@ -696,8 +696,8 @@ class GSCTradingData: trading_pokemon_pos = 0x15 trading_pokemon_ot_pos = 0x135 trading_pokemon_nickname_pos = 0x177 - trading_pokemon_mail_pos = 0xCB - trading_pokemon_mail_sender_pos = 0x191 + trading_pokemon_mail_pos = 0 + trading_pokemon_mail_sender_pos = 0xC6 trading_pokemon_length = 0x30 trading_name_length = 0xB @@ -854,12 +854,44 @@ class GSCTradingData: 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): + def create_patches_data(self, data, patch_set, patch_base, patch_start_info, is_mail=False): + """ + Creates patch data (turns 0xFE into a patch offset) + """ + patch_sets_num = 2 + patch_sets_index = 0 + if is_mail: + patch_sets_num = 1 + patch_sets_index = 1 + + base = patch_base[patch_sets_index] + start = patch_start_info[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: + patch_set[start+i] = 0xFF + i+=1 + + def create_trading_data(self, lengths, patch_base, patch_start_info): """ Creates the data which can be loaded to the hardware. """ data = [] - for i in range(2): + 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) @@ -873,8 +905,10 @@ class GSCTradingData: 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[2], self.trading_pokemon_mail_pos + (i * self.trading_mail_length), self.pokemon[i].mail.values) - GSCUtilsMisc.copy_to_data(data[2], self.trading_pokemon_mail_sender_pos + (i * self.trading_mail_sender_length), self.pokemon[i].mail_sender.values, self.trading_mail_sender_length) + 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.create_patches_data(data[1], data[2], patch_base, patch_start_info) + self.create_patches_data(data[3], data[3], patch_base, patch_start_info, is_mail=True) return data class GSCChecks: @@ -948,9 +982,10 @@ class GSCChecks: self = args[0] val = args[1] if self.do_sanity_checks: - return func(*args, **kwargs) - else: - return val + val = func(*args, **kwargs) + if val == 0xFE: + return 0xFF + return val return wrapper def valid_check_sanity_checks(func): @@ -989,7 +1024,7 @@ class GSCChecks: def prepare_checks_map(self, data, lengths, functions_list): raw_data_sections = GSCUtilsMisc.divide_data(data, lengths) - call_map = [[],[],[]] + 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 diff --git a/utilities/rby_trading.py b/utilities/rby_trading.py index af44cb6..f94422b 100644 --- a/utilities/rby_trading.py +++ b/utilities/rby_trading.py @@ -13,7 +13,6 @@ class RBYTradingClient(GSCTradingClient): single_transfer = "SNG1" pool_transfer = "POL1" moves_transfer = "MVS1" - mail_transfer = "MAI1" choice_transfer = "CHC1" accept_transfer = "ACP1" success_transfer = "SUC1" @@ -24,7 +23,6 @@ class RBYTradingClient(GSCTradingClient): single_transfer: {7}, pool_transfer: {1 + 0x42, 1 + 1}, # Counter + Single Pokémon OR Counter + Fail moves_transfer: {1 + 1 + 8}, # Counter + Species + Moves - mail_transfer : {1 + 0xC5}, # Counter + Mail choice_transfer : {1 + 1 + 0x42, 1 + 1}, # Counter + Choice + Single Pokémon OR Counter + Stop accept_transfer : {1 + 1}, # Counter + Accept success_transfer : {1 + 1}, # Counter + Success @@ -83,6 +81,8 @@ class RBYTrading(GSCTrading): next_section = 0xFD no_input = 0xFE drop_bytes_checks = [[0xA, 0x19F, 0xC5], [next_section, next_section, no_input], [0,0,0]] + patch_set_base_pos = [0x11, 0] + patch_set_start_info_pos = [7, 0x11A] stop_trade = 0x6F first_trade_index = 0x60 decline_trade = 0x61 @@ -103,4 +103,23 @@ class RBYTrading(GSCTrading): def get_checks(self, menu): return RBYChecks(self.special_sections_len, menu.do_sanity_checks) - \ No newline at end of file + + def trade_starting_sequence(self, buffered, send_data = [None, None, None, None]): + """ + Handles exchanging with the device the three data sections which + are needed in order to trade. + Optimizes the synchronous mail_data exchange by executing it only + if necessary and in the way which requires less packet transfers. + Returns the player's data and the other player's data. + """ + # Prepare checks + self.checks.reset_species_item_list() + # Send and get the sections + random_data, random_data_other = self.read_section(0, send_data[0], buffered) + pokemon_data, pokemon_data_other = self.read_section(1, send_data[1], buffered) + patches_data, patches_data_other = self.read_section(2, send_data[2], buffered) + + self.apply_patches(pokemon_data, patches_data) + self.apply_patches(pokemon_data_other, patches_data_other) + + return [random_data, pokemon_data, []], [random_data_other, pokemon_data_other, []]