Update gcmtool.py

This commit is contained in:
tmpz23 2022-08-14 22:50:48 +02:00 committed by GitHub
parent 9081661808
commit 432c23a341
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -5,7 +5,7 @@ from pathlib import Path
import re import re
__version__ = "0.1.4" __version__ = "0.2.0"
__author__ = "rigodron, algoflash, GGLinnk" __author__ = "rigodron, algoflash, GGLinnk"
__license__ = "MIT" __license__ = "MIT"
__status__ = "developpement" __status__ = "developpement"
@ -39,15 +39,15 @@ class InvalidConfValueError(Exception): pass
class ApploaderOverflowError(Exception): pass class ApploaderOverflowError(Exception): pass
def align_top(address:int, align:int): def align_top(offset:int, align:int):
""" """
Give the upper rounded address aligned using the align value. Give the upper rounded offset aligned using the align value.
input: address = int input: offset = int
input: align = int input: align = int
return address = int return offset = int
""" """
if address % align == 0: return address if offset % align == 0: return offset
return address + align - (address % align) return offset + align - (offset % align)
class Fst: class Fst:
@ -150,6 +150,7 @@ class FstTree(Fst):
* root_path = Path (the part with folder that are out of the tree) * root_path = Path (the part with folder that are out of the tree)
* fst_offset = int (to know where is the current min offset before * fst_offset = int (to know where is the current min offset before
adding the fst and name_block length) adding the fst and name_block length)
has to be aligned
* align = int (It could change in some GCM) * align = int (It could change in some GCM)
""" """
# When we walk recursivly in a path we don't wan't to add theirs out parents so it allow to stop at the folder we choose as root # When we walk recursivly in a path we don't wan't to add theirs out parents so it allow to stop at the folder we choose as root
@ -164,7 +165,11 @@ class FstTree(Fst):
__name_block = None __name_block = None
# Used to find min file_offset when fst is at the end of the iso beginning (otherweise we can't know the first available offset) # Used to find min file_offset when fst is at the end of the iso beginning (otherweise we can't know the first available offset)
__nameblock_length = None __nameblock_length = None
def __init__(self, root_path:Path, fstbin_offset:int, align:int = 4): __user_position = None
__user_length = None
# FST high tell if fst is after dol
__is_fst_last = None
def __init__(self, root_path:Path, offset:int, is_fst_last:bool, align:int = 4):
# as said before we don't want to add parents folder that don't are used in the folder we are packing. # as said before we don't want to add parents folder that don't are used in the folder we are packing.
self.__root_path_length = len(root_path.parts) self.__root_path_length = len(root_path.parts)
self.__root_node = Folder(root_path.name, None) self.__root_node = Folder(root_path.name, None)
@ -172,7 +177,8 @@ class FstTree(Fst):
self.__name_block = b"" self.__name_block = b""
self.__fst_block = b"" self.__fst_block = b""
self.__nameblock_length = 0 self.__nameblock_length = 0
self.__current_file_offset = fstbin_offset self.__current_file_offset = offset
self.__is_fst_last = is_fst_last
def __str__(self): def __str__(self):
return self.__to_str(self.__root_node) return self.__to_str(self.__root_node)
def __to_str(self, node:Node, depth=0): def __to_str(self, node:Node, depth=0):
@ -275,9 +281,14 @@ class FstTree(Fst):
The hard part Here is that we have to know the result before The hard part Here is that we have to know the result before
knowing where we can begin to add files. knowing where we can begin to add files.
""" """
self.__current_file_offset += self.__get_fst_length() if self.__is_fst_last:
self.__current_file_offset += self.__get_fst_length() # aligned + aligned = aligned
self.__user_position = self.__current_file_offset
self.__prepare() self.__prepare()
self.__user_length = self.__current_file_offset - self.__user_position
return self.__fst_block + self.__name_block return self.__fst_block + self.__name_block
def user_position(self): return self.__user_position
def user_length(self): return self.__user_length
class BootBin: class BootBin:
@ -310,6 +321,8 @@ class BootBin:
def fst_offset(self): return int.from_bytes(self.__data[BootBin.FSTOFFSET_OFFSET:BootBin.FSTOFFSET_OFFSET+4],"big") def fst_offset(self): return int.from_bytes(self.__data[BootBin.FSTOFFSET_OFFSET:BootBin.FSTOFFSET_OFFSET+4],"big")
def fst_len(self): return int.from_bytes(self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4],"big") def fst_len(self): return int.from_bytes(self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4],"big")
def fst_max_len(self): return int.from_bytes(self.__data[BootBin.FSTMAXLEN_OFFSET:BootBin.FSTMAXLEN_OFFSET+4],"big") def fst_max_len(self): return int.from_bytes(self.__data[BootBin.FSTMAXLEN_OFFSET:BootBin.FSTMAXLEN_OFFSET+4],"big")
def user_position(self): return int.from_bytes(self.__data[0x434:0x438],"big")
def user_length(self): return int.from_bytes(self.__data[0x438:0x43c],"big")
def set_game_code(self, game_code:str): def set_game_code(self, game_code:str):
self.__data[:4] = bytes(game_code, "ascii") self.__data[:4] = bytes(game_code, "ascii")
def set_maker_code(self, maker_code:str): def set_maker_code(self, maker_code:str):
@ -330,10 +343,14 @@ class BootBin:
self.__data[BootBin.DOLOFFSET_OFFSET:BootBin.DOLOFFSET_OFFSET+4] = offset.to_bytes(4, "big") self.__data[BootBin.DOLOFFSET_OFFSET:BootBin.DOLOFFSET_OFFSET+4] = offset.to_bytes(4, "big")
def set_fst_offset(self, offset:int): def set_fst_offset(self, offset:int):
self.__data[BootBin.FSTOFFSET_OFFSET:BootBin.FSTOFFSET_OFFSET+4] = offset.to_bytes(4, "big") self.__data[BootBin.FSTOFFSET_OFFSET:BootBin.FSTOFFSET_OFFSET+4] = offset.to_bytes(4, "big")
def set_fst_len(self, size:int): def set_fst_len(self, length:int):
self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4] = size.to_bytes(4, "big") self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4] = length.to_bytes(4, "big")
def set_fst_max_len(self, size:int): def set_fst_max_len(self, length:int):
self.__data[BootBin.FSTMAXLEN_OFFSET:BootBin.FSTMAXLEN_OFFSET+4] = size.to_bytes(4, "big") self.__data[BootBin.FSTMAXLEN_OFFSET:BootBin.FSTMAXLEN_OFFSET+4] = length.to_bytes(4, "big")
def set_user_position(self, user_position:int):
self.__data[0x434:0x438] = user_position.to_bytes(4, "big")
def set_user_length(self, user_length:int):
self.__data[0x438:0x43c] = user_length.to_bytes(4, "big")
class Bi2Bin: class Bi2Bin:
@ -360,6 +377,7 @@ class Bi2Bin:
def country_code(self): return int.from_bytes(self.__data[24:28], "big") def country_code(self): return int.from_bytes(self.__data[24:28], "big")
def total_disk(self): return int.from_bytes(self.__data[28:32], "big") def total_disk(self): return int.from_bytes(self.__data[28:32], "big")
def long_file_name_support(self): return int.from_bytes(self.__data[32:36], "big") def long_file_name_support(self): return int.from_bytes(self.__data[32:36], "big")
def dol_limit(self): return int.from_bytes(self.__data[40:44], "big")
def set_debug_monitor_size(self, debug_monitor_size:int): def set_debug_monitor_size(self, debug_monitor_size:int):
self.__data[:4] = debug_monitor_size.to_bytes(4, "big") self.__data[:4] = debug_monitor_size.to_bytes(4, "big")
def set_simulated_memory_size(self, simulated_memory_size:int): def set_simulated_memory_size(self, simulated_memory_size:int):
@ -378,6 +396,8 @@ class Bi2Bin:
self.__data[28:32] = total_disk.to_bytes(4, "big") self.__data[28:32] = total_disk.to_bytes(4, "big")
def set_long_file_name_support(self, long_file_name_support:int): def set_long_file_name_support(self, long_file_name_support:int):
self.__data[32:36] = long_file_name_support.to_bytes(4, "big") self.__data[32:36] = long_file_name_support.to_bytes(4, "big")
def set_dol_limit(self, dol_limit:int):
self.__data[40:44] = dol_limit.to_bytes(4, "big")
class ApploaderImg: class ApploaderImg:
@ -431,7 +451,7 @@ class Gcm:
config = ConfigParser(allow_no_value=True) # allow_no_value to allow adding comments config = ConfigParser(allow_no_value=True) # allow_no_value to allow adding comments
config.optionxform = str # makes options case sensitive config.optionxform = str # makes options case sensitive
config.add_section("Default") config.add_section("Default")
config.set("Default", "# Documentation available here: https://github.com/Virtual-World-RE/NeoGF/tree/main/gcmtool#system_conf") config.set("Default", "# Documentation available here: https://github.com/Virtual-World-RE/NeoGF/blob/main/gcmtool/README.md#syssytemconf")
config.set("Default", "boot.bin_section", "disabled") config.set("Default", "boot.bin_section", "disabled")
config.set("Default", "bi2.bin_section", "disabled") config.set("Default", "bi2.bin_section", "disabled")
config.set("Default", "apploader.img_section", "disabled") config.set("Default", "apploader.img_section", "disabled")
@ -449,6 +469,9 @@ class Gcm:
config.set("boot.bin", "FstOffset", f"auto") config.set("boot.bin", "FstOffset", f"auto")
config.set("boot.bin", "FstLen", f"auto") config.set("boot.bin", "FstLen", f"auto")
config.set("boot.bin", "FstMaxLen", f"auto") config.set("boot.bin", "FstMaxLen", f"auto")
config.set("boot.bin", "UserPosition", f"auto")
config.set("boot.bin", "UserLength", f"auto")
config.add_section("bi2.bin") config.add_section("bi2.bin")
config.set("bi2.bin", "DebugMonitorSize", f"0x{self.__bi2bin.debug_monitor_size():x}") config.set("bi2.bin", "DebugMonitorSize", f"0x{self.__bi2bin.debug_monitor_size():x}")
@ -460,6 +483,7 @@ class Gcm:
config.set("bi2.bin", "CountryCode", str(self.__bi2bin.country_code())) # 0, 1, 2, 4 config.set("bi2.bin", "CountryCode", str(self.__bi2bin.country_code())) # 0, 1, 2, 4
config.set("bi2.bin", "TotalDisk", str(self.__bi2bin.total_disk())) # 1-99 config.set("bi2.bin", "TotalDisk", str(self.__bi2bin.total_disk())) # 1-99
config.set("bi2.bin", "LongFileNameSupport", str(self.__bi2bin.long_file_name_support())) # 0, 1 config.set("bi2.bin", "LongFileNameSupport", str(self.__bi2bin.long_file_name_support())) # 0, 1
config.set("bi2.bin", "DolLimit", f"0x{self.__bi2bin.dol_limit():x}")
config.add_section("apploader.img") config.add_section("apploader.img")
config.set("apploader.img", "Version", self.__apploaderimg.version()) config.set("apploader.img", "Version", self.__apploaderimg.version())
@ -469,7 +493,8 @@ class Gcm:
with (sys_path / "system.conf").open("w") as conf_file: with (sys_path / "system.conf").open("w") as conf_file:
config.write(conf_file) config.write(conf_file)
def __load_conf(self, sys_path:Path): logging.info("sys/sytem.conf saved.")
def __load_conf(self, sys_path:Path, get_conf_values:bool = False):
"Patch boot.bin, bi2.bin and apploader.img with the conf in sys/system.conf if Default section status is enabled." "Patch boot.bin, bi2.bin and apploader.img with the conf in sys/system.conf if Default section status is enabled."
config = ConfigParser(allow_no_value=True) # allow_no_value to allow adding comments config = ConfigParser(allow_no_value=True) # allow_no_value to allow adding comments
config.optionxform = str # makes options case sensitive config.optionxform = str # makes options case sensitive
@ -506,11 +531,14 @@ class Gcm:
("boot.bin", "FstOffset", True), ("boot.bin", "FstOffset", True),
("boot.bin", "FstLen", True), ("boot.bin", "FstLen", True),
("boot.bin", "FstMaxLen", True), ("boot.bin", "FstMaxLen", True),
("boot.bin", "UserPosition", True),
("boot.bin", "UserLength", True),
("bi2.bin", "DebugMonitorSize", False), ("bi2.bin", "DebugMonitorSize", False),
("bi2.bin", "SimulatedMemorySize", False), ("bi2.bin", "SimulatedMemorySize", False),
("bi2.bin", "ArgumentOffset", False), ("bi2.bin", "ArgumentOffset", False),
("bi2.bin", "TrackLocation", False), ("bi2.bin", "TrackLocation", False),
("bi2.bin", "TrackSize", False), ("bi2.bin", "TrackSize", False),
("bi2.bin", "DolLimit", False),
("apploader.img", "EntryPoint", False), ("apploader.img", "EntryPoint", False),
("apploader.img", "Size", False), ("apploader.img", "Size", False),
("apploader.img", "TrailerSize", False)]) ("apploader.img", "TrailerSize", False)])
@ -519,6 +547,13 @@ class Gcm:
self.__bi2bin.make_mut() self.__bi2bin.make_mut()
self.__apploaderimg.make_mut() self.__apploaderimg.make_mut()
conf_value_dol_offset = None
conf_value_fst_offset = None
conf_value_fst_len = 0
conf_value_fst_max_len = None
conf_value_user_position = None
conf_value_user_length = None
if config["Default"]["boot.bin_section"].lower() == "enabled": if config["Default"]["boot.bin_section"].lower() == "enabled":
if len(config["boot.bin"]["GameCode"]) != 4: if len(config["boot.bin"]["GameCode"]) != 4:
raise InvalidConfValueError("Error - Invalid [boot.bin][GameCode]: must be str with length = 4.") raise InvalidConfValueError("Error - Invalid [boot.bin][GameCode]: must be str with length = 4.")
@ -560,24 +595,42 @@ class Gcm:
if dol_offset > 0xffffffff: if dol_offset > 0xffffffff:
raise InvalidConfValueError("Error - Invalid [boot.bin][DolOffset]: must be auto or unsigned hex value with length < 5 bytes.") raise InvalidConfValueError("Error - Invalid [boot.bin][DolOffset]: must be auto or unsigned hex value with length < 5 bytes.")
self.__bootbin.set_dol_offset( dol_offset ) self.__bootbin.set_dol_offset( dol_offset )
conf_value_dol_offset = dol_offset
if config["boot.bin"]["FstOffset"] != "auto": if config["boot.bin"]["FstOffset"] != "auto":
fst_offset = int(config["boot.bin"]["FstOffset"], 16) fst_offset = int(config["boot.bin"]["FstOffset"], 16)
if fst_offset > 0xffffffff: if fst_offset > 0xffffffff:
raise InvalidConfValueError("Error - Invalid [boot.bin][FstOffset]: must be auto or unsigned hex value with length < 5 bytes.") raise InvalidConfValueError("Error - Invalid [boot.bin][FstOffset]: must be auto or unsigned hex value with length < 5 bytes.")
self.__bootbin.set_fst_offset( fst_offset ) self.__bootbin.set_fst_offset( fst_offset )
conf_value_fst_offset = fst_offset
if config["boot.bin"]["FstLen"] != "auto": if config["boot.bin"]["FstLen"] != "auto":
fst_len = int(config["boot.bin"]["FstLen"], 16) fst_len = int(config["boot.bin"]["FstLen"], 16)
if fst_len > 0xffffffff: if fst_len > 0xffffffff:
raise InvalidConfValueError("Error - Invalid [boot.bin][FstLen]: must be auto or unsigned hex value with length < 5 bytes.") raise InvalidConfValueError("Error - Invalid [boot.bin][FstLen]: must be auto or unsigned hex value with length < 5 bytes.")
self.__bootbin.set_fst_len( fst_len ) self.__bootbin.set_fst_len( fst_len )
conf_value_fst_len = fst_len
if config["boot.bin"]["FstMaxLen"] != "auto": if config["boot.bin"]["FstMaxLen"] != "auto":
fst_max_len = int(config["boot.bin"]["FstMaxLen"], 16) fst_max_len = int(config["boot.bin"]["FstMaxLen"], 16)
if fst_max_len > 0xffffffff: if fst_max_len > 0xffffffff:
raise InvalidConfValueError("Error - Invalid [boot.bin][FstMaxLen]: must be auto or unsigned hex value with length < 5 bytes.") raise InvalidConfValueError("Error - Invalid [boot.bin][FstMaxLen]: must be auto or unsigned hex value with length < 5 bytes.")
self.__bootbin.set_fst_max_len( fst_max_len ) self.__bootbin.set_fst_max_len( fst_max_len )
conf_value_fst_max_len = fst_max_len
if config["boot.bin"]["UserPosition"] != "auto":
user_position = int(config["boot.bin"]["UserPosition"], 16)
if user_position > 0xffffffff:
raise InvalidConfValueError("Error - Invalid [boot.bin][UserPosition]: must be auto or unsigned hex value with length < 5 bytes.")
self.__bootbin.set_user_position( user_position )
conf_value_user_position = user_position
if config["boot.bin"]["UserLength"] != "auto":
user_length = int(config["boot.bin"]["UserLength"], 16)
if user_length > 0xffffffff:
raise InvalidConfValueError("Error - Invalid [boot.bin][UserLength]: must be auto or unsigned hex value with length < 5 bytes.")
self.__bootbin.set_user_length( user_length )
conf_value_user_length = user_length
if config["Default"]["bi2.bin_section"].lower() == "enabled": if config["Default"]["bi2.bin_section"].lower() == "enabled":
debug_monitor_size = int(config["bi2.bin"]["DebugMonitorSize"], 16) debug_monitor_size = int(config["bi2.bin"]["DebugMonitorSize"], 16)
@ -622,6 +675,11 @@ class Gcm:
raise InvalidConfValueError("Error - Invalid [bi2.bin][LongFileNameSupport]: must be 0 or 1.") raise InvalidConfValueError("Error - Invalid [bi2.bin][LongFileNameSupport]: must be 0 or 1.")
self.__bi2bin.set_long_file_name_support( int(config["bi2.bin"]["LongFileNameSupport"]) ) self.__bi2bin.set_long_file_name_support( int(config["bi2.bin"]["LongFileNameSupport"]) )
dol_limit = int(config["bi2.bin"]["DolLimit"], 16)
if dol_limit > 0xffffffff:
raise InvalidConfValueError("Error - Invalid [bi2.bin][DolLimit]: must be hex value with length < 5 bytes.")
self.__bi2bin.set_dol_limit( dol_limit )
if config["Default"]["apploader.img_section"].lower() == "enabled": if config["Default"]["apploader.img_section"].lower() == "enabled":
version = config["apploader.img"]["Version"] version = config["apploader.img"]["Version"]
if len(version) > 10: if len(version) > 10:
@ -646,6 +704,18 @@ class Gcm:
(sys_path / "boot.bin").write_bytes(self.__bootbin.data()) (sys_path / "boot.bin").write_bytes(self.__bootbin.data())
(sys_path / "bi2.bin").write_bytes(self.__bi2bin.data()) (sys_path / "bi2.bin").write_bytes(self.__bi2bin.data())
(sys_path / "apploader.img").write_bytes(self.__apploaderimg.data()) (sys_path / "apploader.img").write_bytes(self.__apploaderimg.data())
logging.info("sys/sytem.conf loaded.")
if get_conf_values:
return (
conf_value_dol_offset,
conf_value_fst_offset,
conf_value_fst_len,
conf_value_fst_max_len,
conf_value_user_position,
conf_value_user_length
)
def __get_min_file_offset(self, fstbin_data:bytes): def __get_min_file_offset(self, fstbin_data:bytes):
"Get the min file offset to check if there is an overflow." "Get the min file offset to check if there is an overflow."
min_offset = None min_offset = None
@ -752,7 +822,7 @@ class Gcm:
(currentdir_path / name).write_bytes( iso_file.read(filesize) ) (currentdir_path / name).write_bytes( iso_file.read(filesize) )
logging.debug(f"{iso_path}(0x{fileoffset:x}:0x{fileoffset + filesize:x}) -> {currentdir_path / name}") logging.debug(f"{iso_path}(0x{fileoffset:x}:0x{fileoffset + filesize:x}) -> {currentdir_path / name}")
def pack(self, folder_path:Path, iso_path:Path = None, disable_ignore:bool = False): def pack(self, folder_path:Path, iso_path:Path = None, disable_ignore:bool = False, skip_conf:bool = False):
""" """
Pack takes a folder unpacked by the pack command and pack it in a GCM/iso file. Pack takes a folder unpacked by the pack command and pack it in a GCM/iso file.
input: folder_path = Path input: folder_path = Path
@ -771,8 +841,11 @@ class Gcm:
self.__bi2bin = Bi2Bin((sys_path / "bi2.bin").read_bytes()) self.__bi2bin = Bi2Bin((sys_path / "bi2.bin").read_bytes())
self.__apploaderimg = ApploaderImg((sys_path / "apploader.img").read_bytes()) self.__apploaderimg = ApploaderImg((sys_path / "apploader.img").read_bytes())
# Patch boot.bin and bi2.bin if system.conf is enabled # Patch boot.bin bi2.bin and apploader.img if system.conf is enabled
if not skip_conf:
self.__load_conf(sys_path) self.__load_conf(sys_path)
if self.__bootbin.fst_len() > self.__bootbin.fst_max_len():
raise InvalidFSTSizeError(f"Error - fst.bin max length < fst.bin length in boot.bin offset 0x{BootBin.FSTMAXLEN_OFFSET:x}:0x{BootBin.FSTMAXLEN_OFFSET+4:x}.")
logging.debug(f"{sys_path / 'boot.bin'} -> {iso_path}(0x0:0x{BootBin.LEN:x})") logging.debug(f"{sys_path / 'boot.bin'} -> {iso_path}(0x0:0x{BootBin.LEN:x})")
iso_file.write(self.__bootbin.data()) iso_file.write(self.__bootbin.data())
@ -787,7 +860,7 @@ class Gcm:
fstbin_len = self.__bootbin.fst_len() fstbin_len = self.__bootbin.fst_len()
fstbin_end_offset = fstbin_offset + fstbin_len fstbin_end_offset = fstbin_offset + fstbin_len
if (sys_path / "fst.bin").stat().st_size != fstbin_len: if (sys_path / "fst.bin").stat().st_size != fstbin_len:
raise InvalidFSTSizeError(f"Error - Invalid fst.bin size in boot.bin offset 0x{BootBin.FSTLEN_OFFSET:x}:0x{BootBin.FSTLEN_OFFSET+4:x}.") raise InvalidFSTSizeError(f"Error - Invalid fst.bin length in boot.bin offset 0x{BootBin.FSTLEN_OFFSET:x}:0x{BootBin.FSTLEN_OFFSET+4:x}.")
logging.debug(f"{sys_path / 'fst.bin'} -> {iso_path}(0x{fstbin_offset:x}:0x{fstbin_offset + fstbin_len:x})") logging.debug(f"{sys_path / 'fst.bin'} -> {iso_path}(0x{fstbin_offset:x}:0x{fstbin_offset + fstbin_len:x})")
iso_file.seek( fstbin_offset ) iso_file.seek( fstbin_offset )
fstbin_data = (sys_path / "fst.bin").read_bytes() fstbin_data = (sys_path / "fst.bin").read_bytes()
@ -803,15 +876,15 @@ class Gcm:
if not disable_ignore: if not disable_ignore:
if not Gcm.APPLOADER_OFFSET < dol_offset < dol_end_offset <= fstbin_offset and not \ if not Gcm.APPLOADER_OFFSET < dol_offset < dol_end_offset <= fstbin_offset and not \
fstbin_offset < dol_offset < dol_end_offset <= min_file_offset: fstbin_offset < dol_offset < dol_end_offset <= min_file_offset:
raise DolSizeOverflowError("Error - The dol size has been increased and overflow on next file or on FST. To solve this check the sys/system.conf file if used or use --rebuild-fst.") raise DolSizeOverflowError("Error - The dol length has been increased and overflow on next file or on FST. To solve this check the sys/system.conf file if used or use --rebuild-fst.")
if not Gcm.APPLOADER_OFFSET < fstbin_offset < fstbin_end_offset <= dol_offset and not \ if not Gcm.APPLOADER_OFFSET < fstbin_offset < fstbin_end_offset <= dol_offset and not \
dol_end_offset <= fstbin_offset < fstbin_end_offset <= min_file_offset: dol_end_offset <= fstbin_offset < fstbin_end_offset <= min_file_offset:
raise FstSizeOverflowError("Error - The FST size has been increased and overflow on next file or on FST. To solve this check the sys/system.conf file if used or use --rebuild-fst.") raise FstSizeOverflowError("Error - The FST length has been increased and overflow on next file or on dol. To solve this check the sys/system.conf file if used or use --rebuild-fst.")
if Gcm.APPLOADER_OFFSET < dol_offset < apploader_end_offset or \ if Gcm.APPLOADER_OFFSET < dol_offset < apploader_end_offset or \
Gcm.APPLOADER_OFFSET < fstbin_offset < apploader_end_offset: Gcm.APPLOADER_OFFSET < fstbin_offset < apploader_end_offset:
raise ApploaderOverflowError("Error - The apploader size has been increased and overflow on dol or on FST. To solve this check the sys/system.conf file if used or use --rebuild-fst.") raise ApploaderOverflowError("Error - The apploader length has been increased and overflow on dol or on FST. To solve this check the sys/system.conf file if used or use --rebuild-fst.")
logging.debug(f"{sys_path / 'boot.dol'} -> {iso_path}(0x{dol_offset:x}:0x{dol_end_offset:x})") logging.debug(f"{sys_path / 'boot.dol'} -> {iso_path}(0x{dol_offset:x}:0x{dol_end_offset:x})")
iso_file.seek( dol_offset ) iso_file.seek( dol_offset )
@ -862,7 +935,7 @@ class Gcm:
file_len = int.from_bytes(fstbin_data[i+8:i+12], "big") file_len = int.from_bytes(fstbin_data[i+8:i+12], "big")
if (currentdir_path / name).stat().st_size != file_len: if (currentdir_path / name).stat().st_size != file_len:
raise InvalidFSTFileSizeError(f"Error - Invalid file size: {currentdir_path / name} - use --rebuild-fst before packing files in the iso.") raise InvalidFSTFileSizeError(f"Error - Invalid file length: {currentdir_path / name} - use --rebuild-fst before packing files in the iso.")
logging.debug(f"{currentdir_path / name} -> {iso_path}(0x{file_offset:x}:0x{file_offset + file_len:x})") logging.debug(f"{currentdir_path / name} -> {iso_path}(0x{file_offset:x}:0x{file_offset + file_len:x})")
iso_file.seek(file_offset) iso_file.seek(file_offset)
iso_file.write( (currentdir_path / name).read_bytes() ) iso_file.write( (currentdir_path / name).read_bytes() )
@ -870,7 +943,7 @@ class Gcm:
FSTDirNotFoundError, FSTFileNotFoundError, InvalidConfValueError, FstSizeOverflowError, ApploaderOverflowError): FSTDirNotFoundError, FSTFileNotFoundError, InvalidConfValueError, FstSizeOverflowError, ApploaderOverflowError):
iso_path.unlink() iso_path.unlink()
raise raise
def rebuild_fst(self, folder_path:Path, align:int): def rebuild_fst(self, folder_path:Path, align:int, skip_conf:bool):
""" """
Rebuild FST generate a new file system by using all files in the root folder Rebuild FST generate a new file system by using all files in the root folder
it patch boot.bin caracteristics, apploader.img and also file system changes. it patch boot.bin caracteristics, apploader.img and also file system changes.
@ -882,18 +955,37 @@ class Gcm:
root_path = folder_path / "root" root_path = folder_path / "root"
sys_path = folder_path / "sys" sys_path = folder_path / "sys"
self.__bootbin = BootBin((sys_path / "boot.bin").read_bytes())
self.__bi2bin = Bi2Bin((sys_path / "bi2.bin").read_bytes())
self.__apploaderimg = ApploaderImg((sys_path / "apploader.img").read_bytes())
(
dol_offset,
fst_offset,
fst_len,
fst_max_len,
user_position,
user_length
) = self.__load_conf(sys_path, get_conf_values = True) if not skip_conf else (None, None, 0, None, None, None)
if dol_offset is None:
dol_offset = align_top(Gcm.APPLOADER_OFFSET + (sys_path / "apploader.img").stat().st_size, align) dol_offset = align_top(Gcm.APPLOADER_OFFSET + (sys_path / "apploader.img").stat().st_size, align)
logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.DOLOFFSET_OFFSET:x} with new dol offset (0x{dol_offset:x})") logging.info(f"Patching sys/boot.bin offset 0x{BootBin.DOLOFFSET_OFFSET:x} with new dol offset (0x{dol_offset:x}).")
self.__bootbin = BootBin(bytearray((sys_path / "boot.bin").read_bytes()))
self.__bootbin.set_dol_offset(dol_offset) self.__bootbin.set_dol_offset(dol_offset)
fstbin_offset = align_top(dol_offset + (sys_path / "boot.dol").stat().st_size, align) dol_end_offset = align_top(dol_offset + (sys_path / "boot.dol").stat().st_size, align)
logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.FSTOFFSET_OFFSET:x} with new FST offset (0x{fstbin_offset:x})") # Default = FST after dol
self.__bootbin.set_fst_offset(fstbin_offset) if fst_offset is None:
fst_offset = dol_end_offset
logging.info(f"Patching sys/boot.bin offset 0x{BootBin.FSTOFFSET_OFFSET:x} with new FST offset (0x{fst_offset:x}).")
self.__bootbin.set_fst_offset(fst_offset)
fst_tree = FstTree(root_path, fstbin_offset, align=align) fst_end_offset = fst_offset + fst_len
fst_tree = FstTree(root_path, max(dol_end_offset, fst_offset, fst_end_offset), \
is_fst_last = (dol_end_offset <= fst_offset and fst_len == 0), align=align)
# Sorting paths approach original fst sort, but in original fst specials chars are after and not before chars # Sorting paths approach original fst sort, but in original fst specials chars are after and not before chars.
# Files / Folders are sometimes put in arbitrary order.
path_list = sorted([path for path in root_path.glob('**/*')], key=lambda s:Path(str(s).upper())) path_list = sorted([path for path in root_path.glob('**/*')], key=lambda s:Path(str(s).upper()))
for path in path_list: for path in path_list:
fst_tree.add_node_by_path(path) fst_tree.add_node_by_path(path)
@ -901,14 +993,28 @@ class Gcm:
fst_path = sys_path / "fst.bin" fst_path = sys_path / "fst.bin"
logging.info(f"Writing fst in {Path('sys/fst.bin')}") logging.info(f"Writing fst in sys/fst.bin")
fst_path.write_bytes( fst_tree.generate_fst() ) fst_path.write_bytes( fst_tree.generate_fst() )
fstbin_len = fst_path.stat().st_size if fst_len == 0:
logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.FSTLEN_OFFSET:x} with new FST size (0x{fstbin_len:x})") fst_len = fst_path.stat().st_size
self.__bootbin.set_fst_len(fstbin_len) logging.info(f"Patching sys/boot.bin offset 0x{BootBin.FSTLEN_OFFSET:x} with new FST size (0x{fst_len:x}).")
logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.FSTMAXLEN_OFFSET:x} with new FST max size (0x{fstbin_len:x})") self.__bootbin.set_fst_len(fst_len)
self.__bootbin.set_fst_max_len(fstbin_len)
if fst_max_len is None and fst_len > self.__bootbin.fst_max_len():
logging.info(f"Patching sys/boot.bin offset 0x{BootBin.FSTMAXLEN_OFFSET:x} with new FST max size (0x{fst_len:x}).")
self.__bootbin.set_fst_max_len(fst_len)
if user_position is None:
# Allow fixed fst_len or dol after FST fixed by conf
user_position = max(fst_tree.user_position(), fst_offset + fst_len, dol_end_offset)
logging.info(f"Patching sys/boot.bin offset 0x434 with new user position (0x{user_position:x}).")
self.__bootbin.set_user_position(user_position)
if user_length is None:
user_length = fst_tree.user_length()
logging.info(f"Patching sys/boot.bin offset 0x438 with new user length (0x{user_length:x}).")
self.__bootbin.set_user_length(user_length)
(sys_path / "boot.bin").write_bytes(self.__bootbin.data()) (sys_path / "boot.bin").write_bytes(self.__bootbin.data())
def __get_sys_from_folder(self, folder_path:Path): def __get_sys_from_folder(self, folder_path:Path):
@ -972,7 +1078,9 @@ class Gcm:
f"DolOffset = 0x{self.__bootbin.dol_offset():x}\n" + \ f"DolOffset = 0x{self.__bootbin.dol_offset():x}\n" + \
f"FstOffset = 0x{self.__bootbin.fst_offset():x}\n" + \ f"FstOffset = 0x{self.__bootbin.fst_offset():x}\n" + \
f"FstLen = 0x{self.__bootbin.fst_len():x}\n" + \ f"FstLen = 0x{self.__bootbin.fst_len():x}\n" + \
f"FstMaxLen = 0x{self.__bootbin.fst_max_len():x}\n\n" + \ f"FstMaxLen = 0x{self.__bootbin.fst_max_len():x}\n" + \
f"UserPosition = 0x{self.__bootbin.user_position():x}\n" + \
f"UserLength = 0x{self.__bootbin.user_length():x}\n\n" + \
"[bi2.bin]\n" + \ "[bi2.bin]\n" + \
f"DebugMonitorSize = 0x{self.__bi2bin.debug_monitor_size():x}\n" + \ f"DebugMonitorSize = 0x{self.__bi2bin.debug_monitor_size():x}\n" + \
f"SimulatedMemorySize = 0x{self.__bi2bin.simulated_memory_size():x}\n" + \ f"SimulatedMemorySize = 0x{self.__bi2bin.simulated_memory_size():x}\n" + \
@ -982,7 +1090,8 @@ class Gcm:
f"TrackSize = 0x{self.__bi2bin.track_size():x}\n" + \ f"TrackSize = 0x{self.__bi2bin.track_size():x}\n" + \
f"CountryCode = {self.__bi2bin.country_code()}\n" + \ f"CountryCode = {self.__bi2bin.country_code()}\n" + \
f"TotalDisk = {self.__bi2bin.total_disk()}\n" + \ f"TotalDisk = {self.__bi2bin.total_disk()}\n" + \
f"LongFileNameSupport = {self.__bi2bin.long_file_name_support()}\n\n" + \ f"LongFileNameSupport = {self.__bi2bin.long_file_name_support()}\n" + \
f"DolLimit = 0x{self.__bi2bin.dol_limit():x}\n\n" + \
"[apploader.img]\n" + \ "[apploader.img]\n" + \
f"Version = {self.__apploaderimg.version()}\n" + \ f"Version = {self.__apploaderimg.version()}\n" + \
f"EntryPoint = 0x{self.__apploaderimg.entry_point():x}\n" + \ f"EntryPoint = 0x{self.__apploaderimg.entry_point():x}\n" + \
@ -1066,12 +1175,12 @@ class Gcm:
print(full_title + "\n".join([str(mem_obj) for mem_obj in mem_obj_list])) print(full_title + "\n".join([str(mem_obj) for mem_obj in mem_obj_list]))
def pack(p_input:Path, p_output:Path, disable_ignore): def pack(p_input:Path, p_output:Path, disable_ignore:bool, skip_conf:bool = False):
logging.info("### Pack in new GCM iso") logging.info("### Pack in new GCM iso")
if(p_output == Path(".")): if(p_output == Path(".")):
p_output = Path(p_input.with_suffix(".iso")) p_output = Path(p_input.with_suffix(".iso"))
logging.info(f"packing folder \"{p_input}\" in \"{p_output}\"") logging.info(f"Packing folder \"{p_input}\" in \"{p_output}\"")
gcm.pack(p_input, p_output, disable_ignore) gcm.pack(p_input, p_output, disable_ignore, skip_conf)
def unpack(p_input:Path, p_output:Path): def unpack(p_input:Path, p_output:Path):
@ -1079,12 +1188,12 @@ def unpack(p_input:Path, p_output:Path):
gcm.unpack(p_input, p_output) gcm.unpack(p_input, p_output)
def rebuild_fst(p_input:Path, align): def rebuild_fst(p_input:Path, align:int, skip_conf:bool = False):
logging.info("### Rebuilding FST and patching boot.bin") logging.info("### Rebuilding FST and patching boot.bin")
if args.align < 1: if args.align < 1:
raise BadAlignError("Error - Align must be > 0.") raise BadAlignError("Error - Align must be > 0.")
logging.info(f"Using alignment: {args.align}") logging.info(f"Using alignment: {args.align}")
gcm.rebuild_fst(p_input, align) gcm.rebuild_fst(p_input, align, skip_conf)
def get_argparser(): def get_argparser():
@ -1127,8 +1236,8 @@ if __name__ == '__main__':
elif args.rebuild_fst: elif args.rebuild_fst:
rebuild_fst(p_input, args.align) rebuild_fst(p_input, args.align)
elif args.rebuild_fst_pack: elif args.rebuild_fst_pack:
rebuild_fst(p_input, args.align) rebuild_fst(p_input, args.align) # rebuild fst parse and patch with conf
pack(p_input, p_output, args.disable_ignore) pack(p_input, p_output, args.disable_ignore, skip_conf = True)
elif args.unpack_rebuild_fst: elif args.unpack_rebuild_fst:
unpack(p_input, p_output) unpack(p_input, p_output) # conf isn't enabled yet
rebuild_fst(p_output, args.align) rebuild_fst(p_output, args.align, skip_conf = True)