mirror of
https://github.com/Virtual-World-RE/NeoGF.git
synced 2024-11-15 04:25:34 +01:00
Update gcmtool.py
This commit is contained in:
parent
9081661808
commit
432c23a341
|
@ -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,7 +469,10 @@ 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}")
|
||||||
config.set("bi2.bin", "SimulatedMemorySize", f"0x{self.__bi2bin.simulated_memory_size():x}")
|
config.set("bi2.bin", "SimulatedMemorySize", f"0x{self.__bi2bin.simulated_memory_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,25 +595,43 @@ 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)
|
||||||
if debug_monitor_size > 0xffffffff or debug_monitor_size & 31:
|
if debug_monitor_size > 0xffffffff or debug_monitor_size & 31:
|
||||||
|
@ -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
|
||||||
self.__load_conf(sys_path)
|
if not skip_conf:
|
||||||
|
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"
|
||||||
|
|
||||||
dol_offset = align_top(Gcm.APPLOADER_OFFSET + (sys_path / "apploader.img").stat().st_size, align)
|
self.__bootbin = BootBin((sys_path / "boot.bin").read_bytes())
|
||||||
logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.DOLOFFSET_OFFSET:x} with new dol offset (0x{dol_offset:x})")
|
self.__bi2bin = Bi2Bin((sys_path / "bi2.bin").read_bytes())
|
||||||
self.__bootbin = BootBin(bytearray((sys_path / "boot.bin").read_bytes()))
|
self.__apploaderimg = ApploaderImg((sys_path / "apploader.img").read_bytes())
|
||||||
self.__bootbin.set_dol_offset(dol_offset)
|
|
||||||
|
|
||||||
fstbin_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})")
|
|
||||||
self.__bootbin.set_fst_offset(fstbin_offset)
|
|
||||||
|
|
||||||
fst_tree = FstTree(root_path, fstbin_offset, align=align)
|
|
||||||
|
|
||||||
# Sorting paths approach original fst sort, but in original fst specials chars are after and not before chars
|
(
|
||||||
|
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)
|
||||||
|
logging.info(f"Patching sys/boot.bin offset 0x{BootBin.DOLOFFSET_OFFSET:x} with new dol offset (0x{dol_offset:x}).")
|
||||||
|
self.__bootbin.set_dol_offset(dol_offset)
|
||||||
|
|
||||||
|
dol_end_offset = align_top(dol_offset + (sys_path / "boot.dol").stat().st_size, align)
|
||||||
|
# Default = FST after dol
|
||||||
|
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_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.
|
||||||
|
# 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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user