Improve reliability of synchronous trades

This commit is contained in:
Lorenzooone 2023-07-16 19:36:09 +02:00
parent c0b2eda976
commit 65e08438b6
15 changed files with 557 additions and 144 deletions

View File

@ -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):
'''

View File

@ -0,0 +1 @@


View File

@ -1,2 +1,2 @@

!&(*,.029:<=>?@ABCDEFGHINQVXZ\^`bijlmnopqrstuvwxy~<7E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ウЖ<EFBFBD>蔚減湿逝品麺力佰厶壞嶐慵渝肆裲鉋鴒燁
 !&(*,.029:<=>?@ABCDEFGHINPQVXZ\^`bijlmnopqrstuvwxy~<7E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ウЖ<EFBFBD>葦狂瑳樟舵別洋椀冫嘖孛忤掵珀肆裲鉋鴒燁

View File

@ -1,2 +1,2 @@

 "$&
 "$&

View File

@ -0,0 +1 @@


View File

@ -0,0 +1 @@


View File

@ -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, 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]
@ -702,16 +904,32 @@ class GSCTrading:
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.
@ -854,6 +1125,21 @@ class GSCTrading:
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):
"""
Reset the trade data...
@ -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

View File

@ -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

View File

@ -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]

View File

@ -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)):

View File

@ -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)))

View File

@ -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)

View File

@ -6,20 +6,24 @@ 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):

View File

@ -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"):

View File

@ -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