From 65e08438b6b2df8d110f93f929e85c146f9705d9 Mon Sep 17 00:00:00 2001 From: Lorenzooone Date: Sun, 16 Jul 2023 19:36:09 +0200 Subject: [PATCH] Improve reliability of synchronous trades --- serving.py | 69 +++- useful_data/gsc/base_random_section.bin | 1 + useful_data/gsc/pokemon_patch_set_0.bin | 2 +- useful_data/gsc/pokemon_patch_set_1.bin | 2 +- useful_data/rby/base_random_section.bin | 1 + useful_data/rse/base_random_section.bin | 1 + utilities/gsc_trading.py | 492 +++++++++++++++++++----- utilities/gsc_trading_data_utils.py | 9 +- utilities/gsc_trading_jp.py | 30 +- utilities/gsc_trading_menu.py | 3 +- utilities/gsc_trading_strings.py | 6 +- utilities/rby_trading.py | 27 +- utilities/rby_trading_jp.py | 32 +- utilities/rse_sp_trading.py | 4 + utilities/trading_version.py | 22 ++ 15 files changed, 557 insertions(+), 144 deletions(-) create mode 100644 useful_data/gsc/base_random_section.bin create mode 100644 useful_data/rby/base_random_section.bin create mode 100644 useful_data/rse/base_random_section.bin create mode 100644 utilities/trading_version.py diff --git a/serving.py b/serving.py index 0f4e13c..e89ca46 100644 --- a/serving.py +++ b/serving.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import datetime import asyncio import websockets import threading @@ -9,6 +10,7 @@ import boto3 import botocore from random import Random from time import sleep +from utilities.trading_version import TradingVersion from utilities.high_level_listener import HighLevelListener from utilities.gsc_trading import GSCTradingClient from utilities.rby_trading import RBYTradingClient @@ -113,6 +115,28 @@ class DataUploader(threading.Thread): except botocore.exceptions.NoCredentialsError: pass +class ServerSpecificTransfers: + def __init__(self): + self.prepare_random_data() + + def prepare_random_data(self): + rnd = Random() + rnd.seed() + self.random_data = [] + self.last_read = 0 + for i in range(10): + self.random_data += [rnd.randint(0,0xFC)] + + def handle_get_version(hll, version_transfer): + return hll.prepare_send_data(version_transfer, TradingVersion.prepare_version_data()) + + def handle_get_random(self, hll, random_transfer): + if self.last_read != 0: + if (datetime.datetime.now() - self.last_read).total_seconds() > (2 * 60): + self.prepare_random_data() + self.last_read = datetime.datetime.now() + return hll.prepare_send_data(random_transfer, self.random_data) + class PoolTradeServer: """ Class which handles the pool trading part. @@ -155,7 +179,10 @@ class PoolTradeServer: self.get_handlers = { self.trading_client_class.pool_transfer: self.handle_get_pool, self.trading_client_class.accept_transfer: self.handle_get_accepted, - self.trading_client_class.success_transfer: self.handle_get_success + self.trading_client_class.success_transfer: self.handle_get_success, + self.trading_client_class.version_client_transfer: self.handle_get_client_version, + self.trading_client_class.version_server_transfer: self.handle_get_server_version, + self.trading_client_class.random_data_transfer: self.handle_get_random_data } self.send_handlers = { self.trading_client_class.choice_transfer: self.handle_recv_mon, @@ -177,7 +204,10 @@ class PoolTradeServer: self.trading_client_class.success_transfer[3]: self.handle_get_success3_3, self.trading_client_class.success_transfer[4]: self.handle_get_success3_4, self.trading_client_class.success_transfer[5]: self.handle_get_success3_5, - self.trading_client_class.success_transfer[6]: self.handle_get_success3_6 + self.trading_client_class.success_transfer[6]: self.handle_get_success3_6, + self.trading_client_class.version_client_transfer: self.handle_get_client_version, + self.trading_client_class.version_server_transfer: self.handle_get_server_version, + self.trading_client_class.random_data_transfer: self.handle_get_random_data } self.send_handlers = { self.trading_client_class.pool_transfer_out: self.handle_recv_mon3, @@ -237,6 +267,19 @@ class PoolTradeServer: else: return self.hll.prepare_send_data(self.trading_client_class.pool_transfer, [self.own_id] + self.utils_class.single_mon_to_data(self.mon[0], self.mon[1])) + def handle_get_client_version(self): + return ServerSpecificTransfers.handle_get_version(self.hll, self.trading_client_class.version_client_transfer) + + def handle_get_server_version(self): + return ServerSpecificTransfers.handle_get_version(self.hll, self.trading_client_class.version_server_transfer) + + def handle_get_random_data(self): + """ + Maybe a bit wasteful, but whatever... + """ + i = ServerSpecificTransfers() + return i.handle_get_random(self.hll, self.trading_client_class.random_data_transfer) + def handle_get_accepted(self): """ If the proper steps have been taken, it sends whether the data @@ -547,6 +590,7 @@ class ProxyLinkServer: checks_class = RSESPChecks self.trading_client_class = RSESPTradingClient self.utils_class = RSESPUtils + self.server_data = ServerSpecificTransfers() self.other = None self.other_ws = None self.own_ws = ws @@ -559,12 +603,21 @@ class ProxyLinkServer: """ request = self.hll.is_received_valid(data) if request is not None: - if (self.other is not None) and (self.other.other == self): - await self.other_ws.send(data) - else: - self.other = None - self.other_ws = None - await self.own_ws.close() + processed = False + if request[0] == GSCTradingStrings.get_request: + if request[1] == self.trading_client_class.version_server_transfer: + await self.own_ws.send(ServerSpecificTransfers.handle_get_version(self.hll, self.trading_client_class.version_server_transfer)) + processed = True + elif request[1] == self.trading_client_class.random_data_transfer: + await self.own_ws.send(self.server_data.handle_get_random(self.hll, self.trading_client_class.random_data_transfer)) + processed = True + if not processed: + if (self.other is not None) and (self.other.other == self): + await self.other_ws.send(data) + else: + self.other = None + self.other_ws = None + await self.own_ws.close() class WebsocketServer (threading.Thread): ''' diff --git a/useful_data/gsc/base_random_section.bin b/useful_data/gsc/base_random_section.bin new file mode 100644 index 0000000..21808bb --- /dev/null +++ b/useful_data/gsc/base_random_section.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/useful_data/gsc/pokemon_patch_set_0.bin b/useful_data/gsc/pokemon_patch_set_0.bin index 8eb0004..16b9dda 100644 --- a/useful_data/gsc/pokemon_patch_set_0.bin +++ b/useful_data/gsc/pokemon_patch_set_0.bin @@ -1,2 +1,2 @@  - !&(*,.029:<=>?@ABCDEFGHINQVXZ\^`bijlmnopqrstuvwxy~ \ No newline at end of file +  !&(*,.029:<=>?@ABCDEFGHINPQVXZ\^`bijlmnopqrstuvwxy~ \ No newline at end of file diff --git a/useful_data/gsc/pokemon_patch_set_1.bin b/useful_data/gsc/pokemon_patch_set_1.bin index c7b0c80..f49be73 100644 --- a/useful_data/gsc/pokemon_patch_set_1.bin +++ b/useful_data/gsc/pokemon_patch_set_1.bin @@ -1,2 +1,2 @@  -  "$& \ No newline at end of file +  "$& \ No newline at end of file diff --git a/useful_data/rby/base_random_section.bin b/useful_data/rby/base_random_section.bin new file mode 100644 index 0000000..21808bb --- /dev/null +++ b/useful_data/rby/base_random_section.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/useful_data/rse/base_random_section.bin b/useful_data/rse/base_random_section.bin new file mode 100644 index 0000000..21808bb --- /dev/null +++ b/useful_data/rse/base_random_section.bin @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/utilities/gsc_trading.py b/utilities/gsc_trading.py index 104a3c2..9ca6de1 100644 --- a/utilities/gsc_trading.py +++ b/utilities/gsc_trading.py @@ -1,5 +1,7 @@ import time +import datetime from random import Random +from .trading_version import TradingVersion from .gsc_trading_data_utils import * from .gsc_trading_menu import GSCBufferedNegotiator from .gsc_trading_strings import GSCTradingStrings @@ -21,18 +23,24 @@ class GSCTradingClient: success_transfer = "SUC2" buffered_transfer = "BUF2" negotiation_transfer = "NEG2" + version_client_transfer = "VEC2" + version_server_transfer = "VES2" + random_data_transfer = "RAN2" need_data_transfer = "ASK2" possible_transfers = { - full_transfer: {0x412, 0x40C}, # Sum of special_sections_len - Ver 1.0 and 2.0 - single_transfer: {7}, + full_transfer: {0x412, 0x40C}, # Sum of special_sections_len - Ver 1.0, 2.0 - 4.0 + single_transfer: {7, 32}, # Ver 1.0 - 3.0 and 4.0 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 - Ver 1.0 + mail_transfer : {1 + 0x24C, 1 + 0x24B}, # Counter + Mail - Ver 1.0 - 3.0 and 4.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 buffered_transfer : {1 + 1}, # Counter + Buffered or not negotiation_transfer : {1 + 1}, # Counter + Convergence value + version_client_transfer : {6}, # Client's version value + version_server_transfer : {6}, # Server's version value + random_data_transfer : {10}, # Random values from server need_data_transfer : {1 + 1} # Counter + Whether it needs the other player's data } buffered_value = 0x85 @@ -68,6 +76,36 @@ class GSCTradingClient: """ GSCUtilsMisc.verbose_print(to_print, self.verbose, end=end) + def get_server_version(self): + """ + Handles getting the server's version. + """ + ret = self.connection.recv_data(self.version_server_transfer) + if ret is not None: + ret = TradingVersion.read_version_data(ret) + return ret + + def get_client_version(self): + """ + Handles getting the other's version. + """ + ret = self.connection.recv_data(self.version_client_transfer) + if ret is not None: + ret = TradingVersion.read_version_data(ret) + return ret + + def send_client_version(self): + """ + Handles sending my own version. + """ + self.connection.send_data(self.version_client_transfer, TradingVersion.prepare_version_data()) + + def get_random(self): + """ + Handles getting the RNG values. + """ + return self.connection.recv_data(self.random_data_transfer) + def get_success(self): """ Handles getting the success trade confirmation value. @@ -405,6 +443,11 @@ class GSCTrading: no_input = 0xFE no_input_alternative = 0xFF no_data = 0 + total_send_buf_old_bytes = 2 + bytes_per_send_buf_old_byte = 3 + total_send_buf_new_bytes = 8 + bytes_per_send_buf_new_byte = 4 + max_tolerance_bytes = 3 special_sections_len = [0xA, 0x1BC, 0xC5, 0x181] special_sections_starter = [next_section, next_section, next_section, mail_next_section] special_sections_sync = [True, True, True, False] @@ -423,6 +466,8 @@ class GSCTrading: self.kill_function = kill_function self.extremely_verbose = False self.utils_class = self.get_and_init_utils_class() + self.is_running_compat_3_mode = False + self.max_seconds_between_transfers = 0.8 self.pre_sleep = pre_sleep def get_and_init_utils_class(self): @@ -489,12 +534,19 @@ class GSCTrading: or an error depending on kill_on_byte_drops. """ if self.has_transfer_failed(byte, byte_index, section_index): - if self.menu.kill_on_byte_drops: - print(GSCTradingStrings.error_byte_dropped_str) - self.kill_function() - elif not self.printed_warning_drop: - self.verbose_print(GSCTradingStrings.warning_byte_dropped_str) - self.printed_warning_drop = True + self.act_on_bad_data() + + def act_on_bad_data(self): + """ + If any byte was dropped, either drop a warning + or an error depending on kill_on_byte_drops. + """ + if self.menu.kill_on_byte_drops: + print(GSCTradingStrings.error_byte_dropped_str) + self.kill_function() + elif not self.printed_warning_drop: + self.verbose_print(GSCTradingStrings.warning_byte_dropped_str) + self.printed_warning_drop = True def get_mail_section_id(self): return 3 @@ -522,7 +574,7 @@ class GSCTrading: return self.no_input_alternative return val - def read_section(self, index, send_data, buffered): + def read_section(self, index, send_data, buffered, last_sent, last_index): """ Reads a data section and sends it to the device. """ @@ -536,26 +588,10 @@ class GSCTrading: self.checks.prepare_species_buffer() if not buffered: - # Wait for a connection to be established if it's synchronous - send_buf = [[0xFFFF,0xFF],[0xFFFF,0xFF],[index]] - self.comms.send_trading_data(self.write_entire_data(send_buf)) - found = False - if index == 0: - self.verbose_print(GSCTradingStrings.waiting_synchro_str) - while not found: - received = self.comms.get_trading_data() - if received is not None: - recv_buf = self.read_entire_data(received) - if recv_buf[1] is not None and recv_buf[1][0] == 0xFFFF and recv_buf[2][0] == index: - found = True - elif recv_buf[1] is not None and recv_buf[1][0] == 0xFFFF: - self.verbose_print(GSCTradingStrings.incompatible_trade_str) - self.kill_function() - if not found: - self.sleep_func() - self.swap_byte(self.no_input) - if index == 0: - self.verbose_print(GSCTradingStrings.arrived_synchro_str) + if self.is_running_compat_3_mode: + self.synch_synch_section_old(index) + else: + send_buf = self.synch_synch_section_new(index, last_sent, last_index) if self.special_sections_sync[index]: next = self.no_input @@ -603,63 +639,229 @@ class GSCTrading: self.swap_byte(self.no_data) other_buf = send_data else: - # If the trade is synchronous, prepare small send buffers - self.printed_warning_drop = False - buf = [next] - other_buf = [] - send_buf = [[0,next],[0xFFFF,0xFF],[index]] - recv_data = {} - i = 0 - while i < (length + 1): - found = False - # Send the current byte (and the previous one) to the - # other client - self.comms.send_trading_data(self.write_entire_data(send_buf)) - while not found: - received = self.comms.get_trading_data() - if received is not None: - if i not in recv_data.keys(): - recv_buf = self.read_entire_data(received) - # Get all the bytes we can consecutively send to the device - recv_data = self.get_swappable_bytes(recv_buf, length, index) - if i in recv_data.keys() and (i < length): - # Clean it and send it - cleaned_byte = self.prevent_no_input(checker[i](recv_data[i])) - next_i = i+1 - # Handle fillers - if next_i in self.fillers[index].keys(): - filler_len = self.fillers[index][next_i][0] - filler_val = self.fillers[index][next_i][1] - send_buf[(next_i)&1][0] = self.filler_value + filler_len - send_buf[(next_i)&1][1] = filler_val - buf += ([filler_val] * filler_len) - for j in range(filler_len): - other_buf += [checker[next_i + j](filler_val)] - i += (filler_len - 1) - else: - next = self.swap_byte(cleaned_byte) - self.verbose_print(GSCTradingStrings.transfer_to_hardware_str.format(index=self.get_printable_index(index), completion=GSCTradingStrings.x_out_of_y_str(next_i, length)), end='') - # Fillers aren't needed anymore, but their last byte may be needed - self.remove_filler(send_buf, i) - # This will, in turn, get the next byte - # the other client needs - send_buf[(next_i)&1][0] = next_i - send_buf[(next_i)&1][1] = next - other_buf += [cleaned_byte] - # Check for "bad transfer" clues - self.check_bad_data(cleaned_byte, i, index) - self.check_bad_data(next, next_i, index) - buf += [next] - found = True - # Handle the last byte differently - elif i in recv_data.keys() and (i >= length): - found = True - if not found: - self.sleep_func() - i += 1 + if self.is_running_compat_3_mode: + buf, other_buf = self.synch_exchange_section_old(next, index, length, checker) + else: + buf, other_buf, last_sent = self.synch_exchange_section_new(next, index, length, checker, send_buf) + self.verbose_print(GSCTradingStrings.separate_section_str, end='') - return buf, other_buf + return buf, other_buf, last_sent + def synch_synch_section_old(self, index): + # Wait for a connection to be established if it's synchronous + send_buf = [[0xFFFF,0xFF],[0xFFFF,0xFF],[index]] + self.comms.send_trading_data(self.write_entire_data(send_buf)) + found = False + if index == 0: + self.verbose_print(GSCTradingStrings.waiting_synchro_str) + while not found: + received = self.comms.get_trading_data() + if received is not None: + recv_buf = self.read_entire_data(received) + if recv_buf[1] is not None and recv_buf[1][0] == 0xFFFF and recv_buf[2] == index: + found = True + elif recv_buf[1] is not None and recv_buf[1][0] == 0xFFFF: + self.verbose_print(GSCTradingStrings.incompatible_trade_str) + self.kill_function() + if not found: + self.sleep_func() + self.swap_byte(self.no_input) + if index == 0: + self.verbose_print(GSCTradingStrings.arrived_synchro_str) + + def synch_synch_section_new(self, index, last_sent, last_index): + # Wait for a connection to be established if it's synchronous + if last_sent is None: + send_buf = [] + for i in range(self.total_send_buf_new_bytes): + send_buf += [[0xFFFF,0xFF, index, False, 0]] + else: + min_index = 0 + for i in range(self.total_send_buf_new_bytes): + if(last_sent[i][0] < last_sent[min_index][0]): + min_index = i + last_sent[min_index] = last_sent[self.total_send_buf_new_bytes - 1] + last_sent[self.total_send_buf_new_bytes - 1] = [0xFFFF,0xFF, index, False, 0] + send_buf = last_sent + + self.comms.send_trading_data(self.write_entire_data_new(send_buf)) + found = False + if index == 1: + self.verbose_print(GSCTradingStrings.waiting_synchro_str) + while not found: + received = self.comms.get_trading_data() + if received is not None: + recv_buf = self.read_entire_data_new(received) + if recv_buf[self.total_send_buf_new_bytes - 1] is not None and recv_buf[self.total_send_buf_new_bytes - 1][0] == 0xFFFF: + if recv_buf[self.total_send_buf_new_bytes - 1][2] == index: + found = True + elif recv_buf[self.total_send_buf_new_bytes - 1][2] != last_index: + self.verbose_print(GSCTradingStrings.incompatible_trade_str) + self.kill_function() + if not found: + self.sleep_func() + self.swap_byte(self.no_input) + if index == 1: + self.verbose_print(GSCTradingStrings.arrived_synchro_str) + return send_buf + + def synch_exchange_section_old(self, next, index, length, checker): + # If the trade is synchronous, prepare small send buffers + self.printed_warning_drop = False + buf = [next] + other_buf = [] + send_buf = [[0,next],[0xFFFF,0xFF],[index]] + recv_data = {} + i = 0 + while i < (length + 1): + found = False + # Send the current byte (and the previous one) to the + # other client + self.comms.send_trading_data(self.write_entire_data(send_buf)) + while not found: + received = self.comms.get_trading_data() + if received is not None: + if i not in recv_data.keys(): + recv_buf = self.read_entire_data(received) + # Get all the bytes we can consecutively send to the device + recv_data = self.get_swappable_bytes(recv_buf, length, index) + if i in recv_data.keys() and (i < length): + # Clean it and send it + cleaned_byte = self.prevent_no_input(checker[i](recv_data[i])) + next_i = i+1 + # Handle fillers + if next_i in self.fillers[index].keys(): + filler_len = self.fillers[index][next_i][0] + filler_val = self.fillers[index][next_i][1] + send_buf[(next_i)&1][0] = self.filler_value + filler_len + send_buf[(next_i)&1][1] = filler_val + buf += ([filler_val] * filler_len) + for j in range(filler_len): + other_buf += [checker[next_i + j](filler_val)] + i += (filler_len - 1) + else: + next = self.swap_byte(cleaned_byte) + self.verbose_print(GSCTradingStrings.transfer_to_hardware_str.format(index=self.get_printable_index(index), completion=GSCTradingStrings.x_out_of_y_str(next_i, length)), end='') + # Fillers aren't needed anymore, but their last byte may be needed + self.remove_filler(send_buf, i) + # This will, in turn, get the next byte + # the other client needs + send_buf[(next_i)&1][0] = next_i + send_buf[(next_i)&1][1] = next + other_buf += [cleaned_byte] + # Check for "bad transfer" clues + self.check_bad_data(cleaned_byte, i, index) + self.check_bad_data(next, next_i, index) + buf += [next] + found = True + # Handle the last byte differently + elif i in recv_data.keys() and (i >= length): + found = True + if not found: + self.sleep_func() + i += 1 + return buf, other_buf + + def synch_exchange_section_new(self, next, index, length, checker, send_buf): + # If the trade is synchronous, prepare small send buffers + self.printed_warning_drop = False + buf = [next] + other_buf = [] + recv_data = {} + safety_transfer_amount = self.max_tolerance_bytes - 2 + pos_recv = 0 + i = 0 + pos_send = 0 + send_index = 0 + bytes_offset = 1 + bytes_offset_target = self.max_tolerance_bytes + send_buf[send_index] = [pos_send, next, index, False, 0] + pos_send += 1 + send_index = (send_index + 1) % self.total_send_buf_new_bytes + last_transfer_time = datetime.datetime.now() + self.comms.send_trading_data(self.write_entire_data_new(send_buf)) + while i < (length - self.max_tolerance_bytes): + received = self.comms.get_trading_data() + if received is not None: + recv_buf = self.read_entire_data_new(received) + # Get all the bytes we can consecutively send to the device + recv_data = self.get_swappable_bytes_new(recv_buf, length, index) + while pos_recv in recv_data.keys(): + if pos_recv >= length: + break + cleaned_byte = self.prevent_no_input(checker[pos_recv](recv_data[pos_recv])) + other_buf += [cleaned_byte] + pos_recv += 1 + + if pos_recv in self.fillers[index].keys(): + filler_len = self.fillers[index][pos_send][0] + filler_val = self.fillers[index][pos_send][1] + added_len = 0 + for j in range(filler_len): + if (pos_recv + j) >= length: + break + other_buf += [checker[pos_recv + j](filler_val)] + added_len += 1 + pos_recv += added_len + byte_to_console = self.no_input + schedule_console = False + time_diff = datetime.datetime.now() - last_transfer_time + if time_diff.total_seconds() >= self.max_seconds_between_transfers: + schedule_console = True + if bytes_offset < bytes_offset_target: + schedule_console = True + elif pos_recv > i: + byte_to_console = other_buf[i] + if pos_recv > (i + safety_transfer_amount): + schedule_console = True + if schedule_console: + i += 1 + if i in self.fillers[index].keys(): + filler_len = self.fillers[index][pos_send][0] + i += filler_len + + if schedule_console: + if byte_to_console == self.no_input: + bytes_offset += 1 + if bytes_offset > self.max_tolerance_bytes: + self.act_on_bad_data() + next = self.swap_byte(byte_to_console) + self.verbose_print(GSCTradingStrings.transfer_to_hardware_str.format(index=self.get_printable_index(index), completion=GSCTradingStrings.x_out_of_y_str(i, length)), end='') + last_transfer_time = datetime.datetime.now() + send_buf[send_index] = [pos_send, next, index, False, 0] + send_index = (send_index + 1) % self.total_send_buf_new_bytes + buf += [next] + pos_send += 1 + if pos_send in self.fillers[index].keys(): + filler_len = self.fillers[index][pos_send][0] + filler_val = self.fillers[index][pos_send][1] + send_buf[send_index] = [pos_send, filler_val, index, True, filler_len] + send_index = (send_index + 1) % self.total_send_buf_new_bytes + buf += ([filler_val] * filler_len) + pos_send += filler_len + self.comms.send_trading_data(self.write_entire_data_new(send_buf)) + self.sleep_func() + + while i < (length - (bytes_offset)): + byte_to_console = self.no_data + i += 1 + schedule_console = True + + if schedule_console: + next = self.swap_byte(byte_to_console) + self.verbose_print(GSCTradingStrings.transfer_to_hardware_str.format(index=self.get_printable_index(index), completion=GSCTradingStrings.x_out_of_y_str(i, length)), end='') + send_buf[send_index] = [pos_send, next, index, False, 0] + send_index = (send_index + 1) % self.total_send_buf_new_bytes + pos_send += 1 + self.comms.send_trading_data(self.write_entire_data_new(send_buf)) + self.sleep_func() + while len(buf) < length: + buf += [self.no_data] + while len(other_buf) < length: + other_buf += [self.no_data] + self.verbose_print(GSCTradingStrings.transfer_to_hardware_str.format(index=self.get_printable_index(index), completion=GSCTradingStrings.x_out_of_y_str(length, length)), end='') + return buf, other_buf, send_buf + def swap_byte(self, send_data): """ Swaps a byte with the device. First send, and then receives. @@ -688,7 +890,7 @@ class GSCTrading: Tries to read a single synchronous entry. """ if recv_buf[scanning_index] is not None: - if recv_buf[2][0] >= (index + 1): + if recv_buf[2] >= (index + 1): ret[length] = 0 else: byte_num = recv_buf[scanning_index][0] @@ -701,17 +903,33 @@ class GSCTrading: byte_num = recv_buf[previous_scanning_index][0] for j in range(total_bytes): ret[byte_num + 1 + j] = recv_buf[scanning_index][1] + + def prepare_single_entry_new(self, recv_buf, scanning_index, length, index, ret): + """ + Tries to read a single synchronous entry. + """ + if recv_buf[scanning_index] is not None: + if recv_buf[scanning_index][2] > index: + ret[length] = 0 + else: + byte_num = recv_buf[scanning_index][0] + if byte_num <= length: + if recv_buf[scanning_index][3]: + for j in range(recv_buf[scanning_index][4]): + ret[byte_num + j] = recv_buf[scanning_index][1] + else: + ret[byte_num] = recv_buf[scanning_index][1] def remove_filler(self, send_buf, curr_byte_num): """ Removes the filler from the send buffer when a new byte is read. Also prevents desyncs. """ - for i in range(2): + for i in range(self.total_send_buf_old_bytes): byte_num = send_buf[i][0] byte_val = send_buf[i][1] if byte_num > self.filler_value and byte_num <= self.last_filler_value: - for j in range(2): + for j in range(self.total_send_buf_old_bytes): send_buf[j][0] = curr_byte_num send_buf[j][1] = byte_val @@ -721,15 +939,33 @@ class GSCTrading: Tries to speedup the transfer a bit. """ ret = {} - for i in range(2): + for i in range(self.total_send_buf_old_bytes): self.prepare_single_entry(recv_buf, i, length, index, ret) return ret + def get_swappable_bytes_new(self, recv_buf, length, index): + """ + Returns the maximum amount of bytes we can swap freely. + Tries to speedup the transfer a bit. + """ + ret = {} + for i in range(self.total_send_buf_new_bytes): + self.prepare_single_entry_new(recv_buf, i, length, index, ret) + return ret + def read_entire_data(self, data): - return [self.read_sync_data(data, 0), self.read_sync_data(data, 3), [data[6]]] + final_product = [] + for i in range(self.total_send_buf_old_bytes): + final_product += [self.read_sync_data(data, i * self.bytes_per_send_buf_old_byte)] + final_product += [data[self.total_send_buf_old_bytes * self.bytes_per_send_buf_old_byte]] + return final_product def write_entire_data(self, data): - return self.write_sync_data(data[0]) + self.write_sync_data(data[1]) + data[2] + final_product = [] + for i in range(self.total_send_buf_old_bytes): + final_product += self.write_sync_data(data[i]) + final_product += data[self.total_send_buf_old_bytes] + return final_product def read_sync_data(self, data, pos): if data is not None and len(data) > 0: @@ -739,6 +975,41 @@ class GSCTrading: def write_sync_data(self, data): return [(data[0]>>8)&0xFF, data[0]&0xFF, data[1]] + def read_entire_data_new(self, data): + final_product = [] + for i in range(self.total_send_buf_new_bytes): + final_product += [self.read_sync_data_new(data, i * self.bytes_per_send_buf_new_byte)] + return final_product + + def write_entire_data_new(self, data): + final_product = [] + for i in range(self.total_send_buf_new_bytes): + final_product += self.write_sync_data_new(data[i]) + return final_product + + def read_sync_data_new(self, data, pos): + if data is not None and len(data) > 0: + ret_val = [((data[pos]&0x01)<<8) + data[pos+1], data[pos+2], data[pos+3], self.read_is_filler(data[pos]), (data[pos] >> 1) & 0x3F] + if ret_val[0] == (0xFFFF & 0x1FF): + ret_val[0] = 0xFFFF + ret_val[3] = False + ret_val[4] = 0 + return ret_val + return None + + def read_is_filler(self, value): + if (value >> 7) == 1: + return True + return False + + def write_is_filler(self, value): + if value == True: + return 1 << 7 + return 0 << 7 + + def write_sync_data_new(self, data): + return [((data[0]>>8)&0x01) | ((data[4] & 0x3F) << 1) | self.write_is_filler(data[3]), data[0]&0xFF, data[1], data[2]] + def end_trade(self): """ Forces a currently open trade menu to be closed. @@ -853,6 +1124,21 @@ class GSCTrading: received = fun() self.swap_byte(self.no_input) return received + + def attempt_receive(self, fun, max_seconds): + """ + Blocking wait for the requested data, with timeout + It also keeps the device clock running properly. + """ + received = None + start = datetime.datetime.now() + while received is None: + self.sleep_func() + received = fun() + self.swap_byte(self.no_input) + if (datetime.datetime.now() - start).total_seconds() > max_seconds: + break + return received def reset_trade(self): """ @@ -1028,10 +1314,24 @@ class GSCTrading: # Prepare checks self.checks.reset_species_item_list() # 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) + send_data[0] = self.utils_class.base_random_section + just_sent = None + self.is_running_compat_3_mode = True + self.comms.send_client_version() + server_version = self.attempt_receive(self.comms.get_server_version, 5) + if server_version is not None: + send_data[0] = self.force_receive(self.comms.get_random) + other_client_version = self.attempt_receive(self.comms.get_client_version, 5) + if other_client_version is not None: + self.is_running_compat_3_mode = False + + if self.is_running_compat_3_mode: + random_data, random_data_other, just_sent = self.read_section(0, send_data[0], buffered, just_sent, 0) + else: + random_data, random_data_other, just_sent = self.read_section(0, send_data[0], True, just_sent, 0) + pokemon_data, pokemon_data_other, just_sent = self.read_section(1, send_data[1], buffered, just_sent, 0) # Get and apply patches for the Pokémon data - patches_data, patches_data_other = self.read_section(2, send_data[2], buffered) + patches_data, patches_data_other, just_sent = self.read_section(2, send_data[2], buffered, just_sent, 1) self.utils_class.apply_patches(pokemon_data, patches_data, self.utils_class) self.utils_class.apply_patches(pokemon_data_other, patches_data_other, self.utils_class) @@ -1043,7 +1343,7 @@ class GSCTrading: # Trade mail data only if needed if (pokemon_own_mail or pokemon_other_mail) or 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) + mail_data, mail_data_other, just_sent = self.read_section(self.get_mail_section_id(), send_data[3], buffered, just_sent, 2) mail_data = self.convert_mail_data(mail_data, False) else: send_data[3] = self.utils_class.no_mail_section @@ -1051,7 +1351,7 @@ class GSCTrading: # Exchange mail data with the device 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) + mail_data, mail_data_other, just_sent = self.read_section(self.get_mail_section_id(), send_data[3], True, just_sent, 2) mail_data = self.convert_mail_data(mail_data, False) # Apply patches for the mail data diff --git a/utilities/gsc_trading_data_utils.py b/utilities/gsc_trading_data_utils.py index 7b10e78..66fdcea 100644 --- a/utilities/gsc_trading_data_utils.py +++ b/utilities/gsc_trading_data_utils.py @@ -115,6 +115,7 @@ class GSCUtils: 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" @@ -156,6 +157,7 @@ class GSCUtils: 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)) @@ -1177,9 +1179,10 @@ class GSCChecks: current_pp = pp & 0x3F pp_ups = (pp >> 6) & 3 max_base_pp = self.utils_class.moves_pp_list[self.moves[self.curr_pp]] - max_pp = max_base_pp + (math.floor(max_base_pp/5) * pp_ups) - if max_pp > 61: - max_pp = 61 + 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 diff --git a/utilities/gsc_trading_jp.py b/utilities/gsc_trading_jp.py index 837e8cc..5c66783 100644 --- a/utilities/gsc_trading_jp.py +++ b/utilities/gsc_trading_jp.py @@ -135,20 +135,24 @@ class GSCTradingJP(GSCTrading): end_of_line = 0x50 single_text_len = 0xB mail_sender_len = 0xE + end_of_player_name_pos = 6 + end_of_gsc_data_pos = 0x13B + player_name_len_diff = 5 + pokemon_name_len_diff = 5 fillers = [{}, { - 6: [5, end_of_line], - 0x13B + (single_text_len * 0): [5, end_of_line], - 0x13B + (single_text_len * 1): [5, end_of_line], - 0x13B + (single_text_len * 2): [5, end_of_line], - 0x13B + (single_text_len * 3): [5, end_of_line], - 0x13B + (single_text_len * 4): [5, end_of_line], - 0x13B + (single_text_len * 5): [5, end_of_line], - 0x13B + (single_text_len * 6): [5, end_of_line], - 0x13B + (single_text_len * 7): [5, end_of_line], - 0x13B + (single_text_len * 8): [5, end_of_line], - 0x13B + (single_text_len * 9): [5, end_of_line], - 0x13B + (single_text_len * 10): [5, end_of_line], - 0x13B + (single_text_len * 11): [5, end_of_line] + end_of_player_name_pos: [player_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 0): [player_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 1): [player_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 2): [player_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 3): [player_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 4): [player_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 5): [player_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 6): [pokemon_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 7): [pokemon_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 8): [pokemon_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 9): [pokemon_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 10): [pokemon_name_len_diff, end_of_line], + end_of_gsc_data_pos + (single_text_len * 11): [pokemon_name_len_diff, end_of_line] }, {}, {}, {}] special_sections_starter = [next_section, next_section, next_section, mail_next_section, mail_next_section] special_sections_sync = [True, True, True, False, False] diff --git a/utilities/gsc_trading_menu.py b/utilities/gsc_trading_menu.py index 235497c..00cb6f0 100644 --- a/utilities/gsc_trading_menu.py +++ b/utilities/gsc_trading_menu.py @@ -1,5 +1,6 @@ import threading from random import Random +from .trading_version import TradingVersion from .gsc_trading_strings import GSCTradingStrings from argparse import ArgumentParser @@ -93,7 +94,7 @@ class GSCTradingMenu: ret_val = ret_val() def handle_menu(self): - GSCTradingStrings.version_print() + GSCTradingStrings.version_print(TradingVersion.version_major, TradingVersion.version_minor, TradingVersion.version_build) if self.multiboot: self.start_pool_trading() elif self.trade_type is None or ((self.trade_type != GSCTradingStrings.two_player_trade_str) and (self.trade_type != GSCTradingStrings.pool_trade_str)): diff --git a/utilities/gsc_trading_strings.py b/utilities/gsc_trading_strings.py index a931aca..7f8ea74 100644 --- a/utilities/gsc_trading_strings.py +++ b/utilities/gsc_trading_strings.py @@ -4,7 +4,7 @@ class GSCTradingStrings: Class which collects all the text used by the program and methods connected to that. """ - version_str = "Version: 3.0.2" + version_str = "Version: {major}.{minor}.{build}" buffered_str = "Buffered" synchronous_str = "Synchronous" send_request = "S" @@ -144,8 +144,8 @@ class GSCTradingStrings: print(GSCTradingStrings.buffered_negotiation_str.format(other_buffered=GSCTradingStrings.get_buffered_str(not buffered))) print(GSCTradingStrings.yes_no_str, end = '') - def version_print(): - print(GSCTradingStrings.version_str) + def version_print(major, minor, build): + print(GSCTradingStrings.version_str.format(major=major, minor=minor, build=build)) def buffered_other_negotiation_print(buffered): print(GSCTradingStrings.buffered_other_negotiation_str.format(own_buffered = GSCTradingStrings.get_buffered_str(buffered))) diff --git a/utilities/rby_trading.py b/utilities/rby_trading.py index 5a3065a..ab28418 100644 --- a/utilities/rby_trading.py +++ b/utilities/rby_trading.py @@ -18,10 +18,13 @@ class RBYTradingClient(GSCTradingClient): success_transfer = "SUC1" buffered_transfer = "BUF1" negotiation_transfer = "NEG1" + version_client_transfer = "VEC1" + version_server_transfer = "VES1" + random_data_transfer = "RAN1" need_data_transfer = "ASK1" possible_transfers = { full_transfer: {0x271}, # Sum of special_sections_len - single_transfer: {7}, + single_transfer: {7, 32}, pool_transfer: {1 + 0x42, 1 + 1}, # Counter + Single Pokémon OR Counter + Fail moves_transfer: {1 + 1 + 8}, # Counter + Species + Moves choice_transfer : {1 + 1 + 0x42, 1 + 1}, # Counter + Choice + Single Pokémon OR Counter + Stop @@ -29,6 +32,9 @@ class RBYTradingClient(GSCTradingClient): success_transfer : {1 + 1}, # Counter + Success buffered_transfer : {1 + 1}, # Counter + Buffered or not negotiation_transfer : {1 + 1}, # Counter + Convergence value + version_client_transfer : {6}, # Client's version value + version_server_transfer : {6}, # Server's version value + random_data_transfer : {10}, # Random values from server need_data_transfer : {1 + 1} # Counter + Whether it needs the other player's data } @@ -127,9 +133,22 @@ class RBYTrading(GSCTrading): # 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) + send_data[0] = self.utils_class.base_random_section + just_sent = None + self.is_running_compat_3_mode = True + self.comms.send_client_version() + server_version = self.attempt_receive(self.comms.get_server_version, 5) + if server_version is not None: + send_data[0] = self.force_receive(self.comms.get_random) + other_client_version = self.attempt_receive(self.comms.get_client_version, 5) + if other_client_version is not None: + self.is_running_compat_3_mode = False + if self.is_running_compat_3_mode: + random_data, random_data_other, just_sent = self.read_section(0, send_data[0], buffered, just_sent, 0) + else: + random_data, random_data_other, just_sent = self.read_section(0, send_data[0], True, just_sent, 0) + pokemon_data, pokemon_data_other, just_sent = self.read_section(1, send_data[1], buffered, just_sent, 0) + patches_data, patches_data_other, just_sent = self.read_section(2, send_data[2], buffered, just_sent, 1) self.utils_class.apply_patches(pokemon_data, patches_data, self.utils_class) self.utils_class.apply_patches(pokemon_data_other, patches_data_other, self.utils_class) diff --git a/utilities/rby_trading_jp.py b/utilities/rby_trading_jp.py index 7750cfd..cbdb3cf 100644 --- a/utilities/rby_trading_jp.py +++ b/utilities/rby_trading_jp.py @@ -6,22 +6,26 @@ class RBYTradingJP(RBYTrading): """ end_of_line = 0x50 single_text_len = 0xB + end_of_player_name_pos = 6 + end_of_rby_data_pos = 0x121 + player_name_len_diff = 5 + pokemon_name_len_diff = 5 fillers = [{}, { - 6: [5, end_of_line], - 0x121 + (single_text_len * 0): [5, end_of_line], - 0x121 + (single_text_len * 1): [5, end_of_line], - 0x121 + (single_text_len * 2): [5, end_of_line], - 0x121 + (single_text_len * 3): [5, end_of_line], - 0x121 + (single_text_len * 4): [5, end_of_line], - 0x121 + (single_text_len * 5): [5, end_of_line], - 0x121 + (single_text_len * 6): [5, end_of_line], - 0x121 + (single_text_len * 7): [5, end_of_line], - 0x121 + (single_text_len * 8): [5, end_of_line], - 0x121 + (single_text_len * 9): [5, end_of_line], - 0x121 + (single_text_len * 10): [5, end_of_line], - 0x121 + (single_text_len * 11): [5, end_of_line] + end_of_player_name_pos: [player_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 0): [player_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 1): [player_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 2): [player_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 3): [player_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 4): [player_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 5): [player_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 6): [pokemon_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 7): [pokemon_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 8): [pokemon_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 9): [pokemon_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 10): [pokemon_name_len_diff, end_of_line], + end_of_rby_data_pos + (single_text_len * 11): [pokemon_name_len_diff, end_of_line] }, {}] def __init__(self, sending_func, receiving_func, connection, menu, kill_function, pre_sleep): super(RBYTradingJP, self).__init__(sending_func, receiving_func, connection, menu, kill_function, pre_sleep) - \ No newline at end of file + diff --git a/utilities/rse_sp_trading.py b/utilities/rse_sp_trading.py index 4558bec..6d41fc8 100644 --- a/utilities/rse_sp_trading.py +++ b/utilities/rse_sp_trading.py @@ -15,6 +15,8 @@ class RSESPTradingClient(GSCTradingClient): pool_transfer = "P3SI" pool_transfer_out = "P3SO" choice_transfer = "CH3S" + version_client_transfer = "VEC3" + version_server_transfer = "VES3" accept_transfer = ["A3S1", "A3S2"] success_transfer = ["S3S1", "S3S2", "S3S3", "S3S4", "S3S5", "S3S6", "S3S7"] possible_transfers = { @@ -31,6 +33,8 @@ class RSESPTradingClient(GSCTradingClient): success_transfer[4] : {1 + 3}, # Counter + Success success_transfer[5] : {1 + 3}, # Counter + Success success_transfer[6] : {1 + 3}, # Counter + Success + version_client_transfer : {6}, # Client's version value + version_server_transfer : {6}, # Server's version value } def __init__(self, trader, connection, verbose, stop_trade, party_reader, base_no_trade = base_folder + "base.bin", base_pool = base_folder + "base_pool.bin"): diff --git a/utilities/trading_version.py b/utilities/trading_version.py new file mode 100644 index 0000000..538198c --- /dev/null +++ b/utilities/trading_version.py @@ -0,0 +1,22 @@ +class TradingVersion: + """ + Class which contains the version and the version's helper methods. + """ + + version_major = 4 + version_minor = 0 + version_build = 0 + + def read_version_data(data): + ret = [] + for i in range(3): + ret += [data[i * 2] + (data[(i * 2) + 1] << 8)] + return ret + + def prepare_version_data(): + ret = [] + ret += [TradingVersion.version_major & 0xFF, (TradingVersion.version_major >> 8) & 0xFF] + ret += [TradingVersion.version_minor & 0xFF, (TradingVersion.version_minor >> 8) & 0xFF] + ret += [TradingVersion.version_build & 0xFF, (TradingVersion.version_build >> 8) & 0xFF] + return ret +