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
*.bak
*.gba
Procfile
requirements.txt

View File

@ -66,16 +66,16 @@ def transfer_func(p, menu):
if menu.gen == 2:
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:
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:
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:
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:
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.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.own_id = GSCUtilsMisc.inc_byte(self.own_id)
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]
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 None
@ -385,9 +390,14 @@ class PoolTradeServer:
if success:
if index == (self.num_successes[self.gen]-1):
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]
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))
else:
self.can_continue = False

View File

@ -5,6 +5,7 @@ import sys
import traceback
import time
import os
import multiboot
from utilities.gsc_trading import GSCTrading
from utilities.gsc_trading_jp import GSCTradingJP
from utilities.rby_trading import RBYTrading
@ -16,7 +17,9 @@ from utilities.gsc_trading_strings import GSCTradingStrings
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.handle_menu()
@ -27,19 +30,41 @@ def transfer_func(sender, receiver):
connection = ProxyConnectionRunner(menu, kill_function)
elif menu.trade_type == GSCTradingStrings.pool_trade_str:
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.japanese:
trade_c = GSCTradingJP(sender, receiver, connection, menu, kill_function)
trade_c = GSCTradingJP(sender, receiver, connection, menu, kill_function, pre_sleep)
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:
trade_c = RSESPTrading(sender, receiver, connection, menu, kill_function)
trade_c = RSESPTrading(sender, receiver, connection, menu, kill_function, pre_sleep)
elif menu.gen == 1:
if menu.japanese:
trade_c = RBYTradingJP(sender, receiver, connection, menu, kill_function)
trade_c = RBYTradingJP(sender, receiver, connection, menu, kill_function, pre_sleep)
else:
trade_c = RBYTrading(sender, receiver, connection, menu, kill_function)
trade_c = RBYTrading(sender, receiver, connection, menu, kill_function, pre_sleep)
connection.start()
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'))
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):
recv = int.from_bytes(epIn.read(epIn.wMaxPacketSize, 100), byteorder='big')
return recv
def receiveByte_raw(num_bytes):
return epIn.read(epIn.wMaxPacketSize, 100)
# Code dependant on this connection method
def sendByte_win(byte_to_send, num_bytes):
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):
recv = int.from_bytes(p.read(size=num_bytes), byteorder='big')
return recv
def receiveByte_raw_win(num_bytes):
return p.read(size=num_bytes)
def kill_function():
os.kill(os.getpid(), signal.SIGINT)
@ -94,6 +146,8 @@ try:
sender = sendByte
receiver = receiveByte
list_sender = sendList
raw_receiver = receiveByte_raw
if dev is None:
if(os.name == "nt"):
@ -104,7 +158,9 @@ try:
exit_gracefully()
#p.baudrate = 115200
sender = sendByte_win
receiver = receiveByte_win
receiver = receiveByte_win
list_sender = sendList_win
raw_receiver = receiveByte_raw_win
else:
reattach = False
if(os.name != "nt"):
@ -157,7 +213,7 @@ try:
#print("control transfer out...")
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()
except:

View File

@ -414,7 +414,7 @@ class GSCTrading:
decline_trade = 0x71
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.receiveByte = receiving_func
self.checks = self.get_checks(menu)
@ -423,6 +423,7 @@ class GSCTrading:
self.kill_function = kill_function
self.extremely_verbose = False
self.utils_class = self.get_and_init_utils_class()
self.pre_sleep = pre_sleep
def get_and_init_utils_class(self):
GSCUtils()
@ -664,7 +665,8 @@ class GSCTrading:
Swaps a byte with the device. First send, and then receives.
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)
recv = self.receiveByte(self.num_bytes_per_transfer)
if self.extremely_verbose:

View File

@ -154,8 +154,8 @@ class GSCTradingJP(GSCTrading):
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]]
def __init__(self, sending_func, receiving_func, connection, menu, kill_function):
super(GSCTradingJP, self).__init__(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, pre_sleep)
self.jp_mail_converter = GSCJPMailConverter(self.checks)
def get_mail_section_id(self):

View File

@ -24,6 +24,7 @@ class GSCTradingMenu:
self.max_level = args.max_level
self.egg = args.egg
self.is_emulator = is_emulator
self.multiboot = False
if is_emulator:
self.emulator = [args.emulator_host, args.emulator_port]
self.do_sanity_checks = args.do_sanity_checks
@ -36,7 +37,8 @@ class GSCTradingMenu:
"1": self.start_gen1_trading,
"2": self.start_gen2_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 = {
"0": self.start_2p_trading,
@ -92,13 +94,16 @@ class GSCTradingMenu:
def handle_menu(self):
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()
ret_val = False
while not ret_val:
GSCTradingStrings.top_menu_print()
GSCTradingStrings.choice_print()
ret_val = self.top_menu_handlers.get(input(), self.top_menu_handlers["0"])()
if not self.multiboot:
ret_val = False
while not ret_val:
GSCTradingStrings.top_menu_print()
GSCTradingStrings.choice_print()
ret_val = self.top_menu_handlers.get(input(), self.top_menu_handlers["0"])()
else:
if self.trade_type == GSCTradingStrings.two_player_trade_str:
self.start_2p_trading()
@ -133,6 +138,11 @@ class GSCTradingMenu:
self.gen = 3
return True
def start_multiboot_gen3(self):
self.gen = 3
self.multiboot = True
return True
def handle_options(self):
ret_val = False
while not ret_val:
@ -201,7 +211,7 @@ class GSCTradingMenu:
# Parse program's arguments
parser = ArgumentParser()
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,
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,

View File

@ -89,7 +89,8 @@ class GSCTradingStrings:
"1) Red/Blue/Yellow\n"
"2) 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"
"1) Start 2-Player trade (Default)\n"

View File

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

View File

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

View File

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