Add multibooting ability

This commit is contained in:
Lorenzo Carletti 2023-03-14 22:49:16 +01:00
parent 29949c8296
commit 7c11747bef
13 changed files with 258 additions and 35 deletions

2
.gitignore vendored
View File

@ -140,5 +140,7 @@ pool_mons3.bin
#HxD backup files #HxD backup files
*.bak *.bak
*.gba
Procfile Procfile
requirements.txt requirements.txt

View File

@ -66,16 +66,16 @@ def transfer_func(p, menu):
if menu.gen == 2: if menu.gen == 2:
if menu.japanese: if menu.japanese:
trade_c = GSCTradingJP(p.sendByte, p.receiveByte, p.connection, menu, kill_function) trade_c = GSCTradingJP(p.sendByte, p.receiveByte, p.connection, menu, kill_function, True)
else: else:
trade_c = GSCTrading(p.sendByte, p.receiveByte, p.connection, menu, kill_function) trade_c = GSCTrading(p.sendByte, p.receiveByte, p.connection, menu, kill_function, True)
elif menu.gen == 3: elif menu.gen == 3:
trade_c = RSESPTrading(p.sendByte, p.receiveByte, p.connection, menu, kill_function) trade_c = RSESPTrading(p.sendByte, p.receiveByte, p.connection, menu, kill_function, True)
elif menu.gen == 1: elif menu.gen == 1:
if menu.japanese: if menu.japanese:
trade_c = RBYTradingJP(p.sendByte, p.receiveByte, p.connection, menu, kill_function) trade_c = RBYTradingJP(p.sendByte, p.receiveByte, p.connection, menu, kill_function, True)
else: else:
trade_c = RBYTrading(p.sendByte, p.receiveByte, p.connection, menu, kill_function) trade_c = RBYTrading(p.sendByte, p.receiveByte, p.connection, menu, kill_function, True)
if menu.gen != 3: if menu.gen != 3:
if menu.trade_type == GSCTradingStrings.two_player_trade_str: if menu.trade_type == GSCTradingStrings.two_player_trade_str:

142
multiboot.py Normal file
View File

@ -0,0 +1,142 @@
import os
import time
def get_configure_list(us_between_transfer, bytes_for_transfer):
config_base = [0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF]
config_base += [us_between_transfer & 0xFF, (us_between_transfer >> 8) & 0xFF, (us_between_transfer >> 16) & 0xFF, bytes_for_transfer & 0xFF]
return config_base
def read_all(receiver, debug=False):
output = 0
prev_len = 0
while True:
try:
data = receiver(0x40)
output <<= (8*prev_len)
output |= int.from_bytes(data, byteorder='big')
prev_len = len(data)
except:
break
if debug:
print("0x%02x " % output)
return output
def multiboot(receiver, sender, list_sender, path):
content = 0
print("Preparing data...")
content = bytearray(open(path, 'rb').read())
fsize = os.path.getsize(path)
# Padding to avoid errors
content = content.ljust(fsize + 64, b'\0')
if fsize > 0x3FF40:
print("File size error, max " + 0x3FF40 + " bytes")
exit()
fsize += 0xF
fsize &= ~0xF
sending_data = [0]*((fsize-0xC0)>>2)
complete_sending_data = [0]*(fsize-0xC0)
crcC = 0xC387
for i in range(0xC0, fsize, 4):
dat = int(content[i])
dat |= int(content[i + 1]) << 8
dat |= int(content[i + 2]) << 16
dat |= int(content[i + 3]) << 24
tmp = dat
for b in range(32):
bit = (crcC ^ tmp) & 1
if bit == 0:
crcC = (crcC >> 1) ^ 0
else:
crcC = (crcC >> 1) ^ 0xc37b
tmp >>= 1
dat = dat ^ (0xFE000000 - i) ^ 0x43202F2F
sending_data[(i-0xC0)>>2] = dat & 0xFFFFFFFF
print("Data preloaded...")
read_all(receiver)
config_base = get_configure_list(36, 4)
list_sender(config_base, chunk_size = len(config_base))
val = read_all(receiver)
recv = 0
while True:
sender(0x6202, 4)
recv = read_all(receiver)
if (recv >> 16) == 0x7202:
break
print("Lets do this thing!")
sender(0x6102, 4)
for i in range(96):
out = (int(content[(i*2)])) + (int(content[(i*2)+1]) << 8)
sender(out, 4)
sender(0x6200, 4)
sender(0x6200, 4)
sender(0x63D1, 4)
#Clear buffer
read_all(receiver)
sender(0x63D1, 4)
token = read_all(receiver)
if (token >> 24) != 0x73:
print("Failed handshake!")
return
else:
print("Handshake successful!")
crcA = (token >> 16) & 0xFF
seed = 0xFFFF00D1 | (crcA << 8)
crcA = (crcA + 0xF) & 0xFF
sender((0x6400 | crcA), 4)
read_all(receiver)
sender((fsize - 0x190) // 4, 4)
token = read_all(receiver)
crcB = (token >> 16) & 0xFF
print(fsize)
print("Sending data!")
for i in range(len(sending_data)):
seed = (seed * 0x6F646573 + 1) & 0xFFFFFFFF
complete_sending_data[(i*4)] = ((sending_data[i] ^ seed)>>24) & 0xFF
complete_sending_data[(i*4)+1] = ((sending_data[i] ^ seed)>>16) & 0xFF
complete_sending_data[(i*4)+2] = ((sending_data[i] ^ seed)>>8) & 0xFF
complete_sending_data[(i*4)+3] = ((sending_data[i] ^ seed)>>0) & 0xFF
time_transfer = time.time()
list_sender(complete_sending_data, chunk_size = 0x40)
time_transfer = time.time()-time_transfer
print(time_transfer)
print("Data sent")
tmp = 0xFFFF0000 | (crcB << 8) | crcA
for b in range(32):
bit = (crcC ^ tmp) & 1
if bit == 0:
crcC = (crcC >> 1) ^ 0
else:
crcC = (crcC >> 1) ^ 0xc37b
tmp >>= 1
sender(0x0065, 4)
while True:
sender(0x0065, 4)
recv = read_all(receiver)
if (recv >> 16) == 0x0075:
break
sender(0x0066, 4)
sender(crcC & 0xFFFF, 4)
print("DONE!")

Binary file not shown.

View File

@ -271,9 +271,14 @@ class PoolTradeServer:
self.last_success = self.received_success[0] self.last_success = self.received_success[0]
self.own_id = GSCUtilsMisc.inc_byte(self.own_id) self.own_id = GSCUtilsMisc.inc_byte(self.own_id)
self.clear_pool = True self.clear_pool = True
if not self.mon_index in in_use_mons[self.gen]:
in_use_mons[self.gen].add(self.mon_index)
mons[self.gen][self.mon_index] = self.received_mon[1] mons[self.gen][self.mon_index] = self.received_mon[1]
ServerUtils.save_mons(self.gen) ServerUtils.save_mons(self.gen)
in_use_mons[self.gen].remove(self.mon_index) try:
in_use_mons[self.gen].remove(self.mon_index)
except:
pass
return self.hll.prepare_send_data(self.trading_client_class.success_transfer, [self.own_id] + [PoolTradeServer.success_value[self.gen]]) return self.hll.prepare_send_data(self.trading_client_class.success_transfer, [self.own_id] + [PoolTradeServer.success_value[self.gen]])
return None return None
@ -385,9 +390,14 @@ class PoolTradeServer:
if success: if success:
if index == (self.num_successes[self.gen]-1): if index == (self.num_successes[self.gen]-1):
self.clear_pool = True self.clear_pool = True
if not self.mon_index in in_use_mons[self.gen]:
in_use_mons[self.gen].add(self.mon_index)
mons[self.gen][self.mon_index] = self.received_mon[1] mons[self.gen][self.mon_index] = self.received_mon[1]
ServerUtils.save_mons(self.gen) ServerUtils.save_mons(self.gen)
in_use_mons[self.gen].remove(self.mon_index) try:
in_use_mons[self.gen].remove(self.mon_index)
except:
pass
return self.hll.prepare_send_data(self.trading_client_class.success_transfer[index], [self.own_id] + GSCUtilsMisc.to_n_bytes_le(PoolTradeServer.success_value[self.gen][index] | self.expected_gen3_success_value(index, self.mon[0], self.received_mon[1][0]), 3)) return self.hll.prepare_send_data(self.trading_client_class.success_transfer[index], [self.own_id] + GSCUtilsMisc.to_n_bytes_le(PoolTradeServer.success_value[self.gen][index] | self.expected_gen3_success_value(index, self.mon[0], self.received_mon[1][0]), 3))
else: else:
self.can_continue = False self.can_continue = False

View File

@ -5,6 +5,7 @@ import sys
import traceback import traceback
import time import time
import os import os
import multiboot
from utilities.gsc_trading import GSCTrading from utilities.gsc_trading import GSCTrading
from utilities.gsc_trading_jp import GSCTradingJP from utilities.gsc_trading_jp import GSCTradingJP
from utilities.rby_trading import RBYTrading from utilities.rby_trading import RBYTrading
@ -16,7 +17,9 @@ from utilities.gsc_trading_strings import GSCTradingStrings
dev = None dev = None
def transfer_func(sender, receiver): path = "gen3_to_genx_mb.gba"
def transfer_func(sender, receiver, list_sender, raw_receiver):
menu = GSCTradingMenu(kill_function) menu = GSCTradingMenu(kill_function)
menu.handle_menu() menu.handle_menu()
@ -27,19 +30,41 @@ def transfer_func(sender, receiver):
connection = ProxyConnectionRunner(menu, kill_function) connection = ProxyConnectionRunner(menu, kill_function)
elif menu.trade_type == GSCTradingStrings.pool_trade_str: elif menu.trade_type == GSCTradingStrings.pool_trade_str:
connection = PoolTradeRunner(menu, kill_function) connection = PoolTradeRunner(menu, kill_function)
if menu.multiboot:
menu.gen = 3
if menu.gen == 3:
config_base = multiboot.get_configure_list(1000, 4)
else:
config_base = multiboot.get_configure_list(20000, 1)
multiboot.read_all(raw_receiver)
list_sender(config_base, chunk_size=len(config_base))
ret = multiboot.read_all(raw_receiver)
if(menu.gen == 3) and (ret != 1):
print("Non-reconfigurable firmware found!\nIt's best if you update to the one available at:\nhttps://github.com/Lorenzooone/gb-link-firmware-reconfigurable/releases")
pre_sleep = False
if ret == 1:
pre_sleep = True
if menu.multiboot:
multiboot.multiboot(raw_receiver, sender, list_sender, path)
return
if menu.gen == 2: if menu.gen == 2:
if menu.japanese: if menu.japanese:
trade_c = GSCTradingJP(sender, receiver, connection, menu, kill_function) trade_c = GSCTradingJP(sender, receiver, connection, menu, kill_function, pre_sleep)
else: else:
trade_c = GSCTrading(sender, receiver, connection, menu, kill_function) trade_c = GSCTrading(sender, receiver, connection, menu, kill_function, pre_sleep)
elif menu.gen == 3: elif menu.gen == 3:
trade_c = RSESPTrading(sender, receiver, connection, menu, kill_function) trade_c = RSESPTrading(sender, receiver, connection, menu, kill_function, pre_sleep)
elif menu.gen == 1: elif menu.gen == 1:
if menu.japanese: if menu.japanese:
trade_c = RBYTradingJP(sender, receiver, connection, menu, kill_function) trade_c = RBYTradingJP(sender, receiver, connection, menu, kill_function, pre_sleep)
else: else:
trade_c = RBYTrading(sender, receiver, connection, menu, kill_function) trade_c = RBYTrading(sender, receiver, connection, menu, kill_function, pre_sleep)
connection.start() connection.start()
if menu.trade_type == GSCTradingStrings.two_player_trade_str: if menu.trade_type == GSCTradingStrings.two_player_trade_str:
@ -52,18 +77,45 @@ def sendByte(byte_to_send, num_bytes):
epOut.write(byte_to_send.to_bytes(num_bytes, byteorder='big')) epOut.write(byte_to_send.to_bytes(num_bytes, byteorder='big'))
return return
# Code dependant on this connection method
def sendList(data, chunk_size=8):
num_iters = int(len(data)/chunk_size)
for i in range(num_iters):
epOut.write(data[i*chunk_size:(i+1)*chunk_size])
#print(num_iters*chunk_size)
#print(len(data))
if (num_iters*chunk_size) != len(data):
epOut.write(data[num_iters*chunk_size:])
def receiveByte(num_bytes): def receiveByte(num_bytes):
recv = int.from_bytes(epIn.read(epIn.wMaxPacketSize, 100), byteorder='big') recv = int.from_bytes(epIn.read(epIn.wMaxPacketSize, 100), byteorder='big')
return recv return recv
def receiveByte_raw(num_bytes):
return epIn.read(epIn.wMaxPacketSize, 100)
# Code dependant on this connection method # Code dependant on this connection method
def sendByte_win(byte_to_send, num_bytes): def sendByte_win(byte_to_send, num_bytes):
p.write(byte_to_send.to_bytes(num_bytes, byteorder='big')) p.write(byte_to_send.to_bytes(num_bytes, byteorder='big'))
# Code dependant on this connection method
def sendList_win(data, chunk_size=8):
num_iters = int(len(data)/chunk_size)
for i in range(num_iters):
p.write(data[i*chunk_size:(i+1)*chunk_size])
#print(num_iters*chunk_size)
#print(len(data))
if (num_iters*chunk_size) != len(data):
p.write(data[num_iters*chunk_size:])
def receiveByte_win(num_bytes): def receiveByte_win(num_bytes):
recv = int.from_bytes(p.read(size=num_bytes), byteorder='big') recv = int.from_bytes(p.read(size=num_bytes), byteorder='big')
return recv return recv
def receiveByte_raw_win(num_bytes):
return p.read(size=num_bytes)
def kill_function(): def kill_function():
os.kill(os.getpid(), signal.SIGINT) os.kill(os.getpid(), signal.SIGINT)
@ -94,6 +146,8 @@ try:
sender = sendByte sender = sendByte
receiver = receiveByte receiver = receiveByte
list_sender = sendList
raw_receiver = receiveByte_raw
if dev is None: if dev is None:
if(os.name == "nt"): if(os.name == "nt"):
@ -104,7 +158,9 @@ try:
exit_gracefully() exit_gracefully()
#p.baudrate = 115200 #p.baudrate = 115200
sender = sendByte_win sender = sendByte_win
receiver = receiveByte_win receiver = receiveByte_win
list_sender = sendList_win
raw_receiver = receiveByte_raw_win
else: else:
reattach = False reattach = False
if(os.name != "nt"): if(os.name != "nt"):
@ -157,7 +213,7 @@ try:
#print("control transfer out...") #print("control transfer out...")
dev.ctrl_transfer(bmRequestType = 1, bRequest = 0x22, wIndex = 2, wValue = 0x01) dev.ctrl_transfer(bmRequestType = 1, bRequest = 0x22, wIndex = 2, wValue = 0x01)
transfer_func(sender, receiver) transfer_func(sender, receiver, list_sender, raw_receiver)
exit_gracefully() exit_gracefully()
except: except:

View File

@ -414,7 +414,7 @@ class GSCTrading:
decline_trade = 0x71 decline_trade = 0x71
accept_trade = 0x72 accept_trade = 0x72
def __init__(self, sending_func, receiving_func, connection, menu, kill_function): def __init__(self, sending_func, receiving_func, connection, menu, kill_function, pre_sleep):
self.sendByte = sending_func self.sendByte = sending_func
self.receiveByte = receiving_func self.receiveByte = receiving_func
self.checks = self.get_checks(menu) self.checks = self.get_checks(menu)
@ -423,6 +423,7 @@ class GSCTrading:
self.kill_function = kill_function self.kill_function = kill_function
self.extremely_verbose = False self.extremely_verbose = False
self.utils_class = self.get_and_init_utils_class() self.utils_class = self.get_and_init_utils_class()
self.pre_sleep = pre_sleep
def get_and_init_utils_class(self): def get_and_init_utils_class(self):
GSCUtils() GSCUtils()
@ -664,7 +665,8 @@ class GSCTrading:
Swaps a byte with the device. First send, and then receives. Swaps a byte with the device. First send, and then receives.
It's a high level abstraction which emulates how real hardware works. It's a high level abstraction which emulates how real hardware works.
""" """
self.sleep_func() if not self.pre_sleep:
self.sleep_func()
self.sendByte(send_data, self.num_bytes_per_transfer) self.sendByte(send_data, self.num_bytes_per_transfer)
recv = self.receiveByte(self.num_bytes_per_transfer) recv = self.receiveByte(self.num_bytes_per_transfer)
if self.extremely_verbose: if self.extremely_verbose:

View File

@ -154,8 +154,8 @@ class GSCTradingJP(GSCTrading):
special_sections_sync = [True, True, True, False, False] special_sections_sync = [True, True, True, False, False]
drop_bytes_checks = [[0xA, 0x1B9, 0xC5, 0x181, 0x11D], [next_section, next_section, mail_next_section, no_input, no_input], [0,4,0,0,0]] drop_bytes_checks = [[0xA, 0x1B9, 0xC5, 0x181, 0x11D], [next_section, next_section, mail_next_section, no_input, no_input], [0,4,0,0,0]]
def __init__(self, sending_func, receiving_func, connection, menu, kill_function): def __init__(self, sending_func, receiving_func, connection, menu, kill_function, pre_sleep):
super(GSCTradingJP, self).__init__(sending_func, receiving_func, connection, menu, kill_function) super(GSCTradingJP, self).__init__(sending_func, receiving_func, connection, menu, kill_function, pre_sleep)
self.jp_mail_converter = GSCJPMailConverter(self.checks) self.jp_mail_converter = GSCJPMailConverter(self.checks)
def get_mail_section_id(self): def get_mail_section_id(self):

View File

@ -24,6 +24,7 @@ class GSCTradingMenu:
self.max_level = args.max_level self.max_level = args.max_level
self.egg = args.egg self.egg = args.egg
self.is_emulator = is_emulator self.is_emulator = is_emulator
self.multiboot = False
if is_emulator: if is_emulator:
self.emulator = [args.emulator_host, args.emulator_port] self.emulator = [args.emulator_host, args.emulator_port]
self.do_sanity_checks = args.do_sanity_checks self.do_sanity_checks = args.do_sanity_checks
@ -36,7 +37,8 @@ class GSCTradingMenu:
"1": self.start_gen1_trading, "1": self.start_gen1_trading,
"2": self.start_gen2_trading, "2": self.start_gen2_trading,
"3": self.start_gen1_trading, "3": self.start_gen1_trading,
"4": self.start_gen3_trading "4": self.start_gen3_trading,
"m": self.start_multiboot_gen3
} }
self.top_menu_handlers = { self.top_menu_handlers = {
"0": self.start_2p_trading, "0": self.start_2p_trading,
@ -92,13 +94,16 @@ class GSCTradingMenu:
def handle_menu(self): def handle_menu(self):
GSCTradingStrings.version_print() GSCTradingStrings.version_print()
if self.trade_type is None or ((self.trade_type != GSCTradingStrings.two_player_trade_str) and (self.trade_type != GSCTradingStrings.pool_trade_str)): 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)):
self.handle_game_selector() self.handle_game_selector()
ret_val = False if not self.multiboot:
while not ret_val: ret_val = False
GSCTradingStrings.top_menu_print() while not ret_val:
GSCTradingStrings.choice_print() GSCTradingStrings.top_menu_print()
ret_val = self.top_menu_handlers.get(input(), self.top_menu_handlers["0"])() GSCTradingStrings.choice_print()
ret_val = self.top_menu_handlers.get(input(), self.top_menu_handlers["0"])()
else: else:
if self.trade_type == GSCTradingStrings.two_player_trade_str: if self.trade_type == GSCTradingStrings.two_player_trade_str:
self.start_2p_trading() self.start_2p_trading()
@ -133,6 +138,11 @@ class GSCTradingMenu:
self.gen = 3 self.gen = 3
return True return True
def start_multiboot_gen3(self):
self.gen = 3
self.multiboot = True
return True
def handle_options(self): def handle_options(self):
ret_val = False ret_val = False
while not ret_val: while not ret_val:
@ -201,7 +211,7 @@ class GSCTradingMenu:
# Parse program's arguments # Parse program's arguments
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument("-g", "--generation", dest="gen_number", default = None, parser.add_argument("-g", "--generation", dest="gen_number", default = None,
help="generation (1 = RBY/Timecapsule, 2 = GSC, 4 = RSE Special)", type=int) help="generation (1 = RBY/Timecapsule, 2 = GSC, 3 = RSE Special)", type=int)
parser.add_argument("-t", "--trade_type", dest="trade_type", default = None, parser.add_argument("-t", "--trade_type", dest="trade_type", default = None,
help="trade type (" + GSCTradingStrings.two_player_trade_str + " = 2-Player Trade, " + GSCTradingStrings.pool_trade_str + " = Pool Trade)") help="trade type (" + GSCTradingStrings.two_player_trade_str + " = 2-Player Trade, " + GSCTradingStrings.pool_trade_str + " = Pool Trade)")
parser.add_argument("-r", "--room", dest="room", default = None, parser.add_argument("-r", "--room", dest="room", default = None,

View File

@ -89,7 +89,8 @@ class GSCTradingStrings:
"1) Red/Blue/Yellow\n" "1) Red/Blue/Yellow\n"
"2) Gold/Silver/Crystal\n" "2) Gold/Silver/Crystal\n"
"3) Timecapsule in Gold/Silver/Crystal\n" "3) Timecapsule in Gold/Silver/Crystal\n"
"4) Special Ruby/Sapphire/Emerald/Fire Red/Leaf Green" "4) Special Ruby/Sapphire/Emerald/Fire Red/Leaf Green\n"
"m) Multiboot Special Ruby/Sapphire/Emerald/Fire Red/Leaf Green"
) )
top_level_menu_str = ("\n=============== Top level Menu ===============\n" top_level_menu_str = ("\n=============== Top level Menu ===============\n"
"1) Start 2-Player trade (Default)\n" "1) Start 2-Player trade (Default)\n"

View File

@ -94,8 +94,8 @@ class RBYTrading(GSCTrading):
decline_trade = 0x61 decline_trade = 0x61
accept_trade = 0x62 accept_trade = 0x62
def __init__(self, sending_func, receiving_func, connection, menu, kill_function): def __init__(self, sending_func, receiving_func, connection, menu, kill_function, pre_sleep):
super(RBYTrading, self).__init__(sending_func, receiving_func, connection, menu, kill_function) super(RBYTrading, self).__init__(sending_func, receiving_func, connection, menu, kill_function, pre_sleep)
def get_and_init_utils_class(self): def get_and_init_utils_class(self):
RBYUtils() RBYUtils()

View File

@ -22,6 +22,6 @@ class RBYTradingJP(RBYTrading):
0x121 + (single_text_len * 11): [5, end_of_line] 0x121 + (single_text_len * 11): [5, end_of_line]
}, {}] }, {}]
def __init__(self, sending_func, receiving_func, connection, menu, kill_function): 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) super(RBYTradingJP, self).__init__(sending_func, receiving_func, connection, menu, kill_function, pre_sleep)

View File

@ -163,8 +163,8 @@ class RSESPTrading(GSCTrading):
decline_trade_value = [decline_trade[0]<<16, decline_trade[1]<<16] decline_trade_value = [decline_trade[0]<<16, decline_trade[1]<<16]
no_input = 0 no_input = 0
def __init__(self, sending_func, receiving_func, connection, menu, kill_function): def __init__(self, sending_func, receiving_func, connection, menu, kill_function, pre_sleep):
super(RSESPTrading, self).__init__(sending_func, receiving_func, connection, menu, kill_function) super(RSESPTrading, self).__init__(sending_func, receiving_func, connection, menu, kill_function, pre_sleep)
def get_and_init_utils_class(self): def get_and_init_utils_class(self):
RSESPUtils() RSESPUtils()