mirror of
				https://github.com/Virtual-World-RE/NeoGF.git
				synced 2025-10-31 17:30:30 +01:00 
			
		
		
		
	system.conf with collision detection improvments
This commit is contained in:
		| @@ -1,9 +1,11 @@ | |||||||
| #!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||||
| from pathlib import Path | from configparser import ConfigParser | ||||||
| import logging | import logging | ||||||
|  | from pathlib import Path | ||||||
|  | import re | ||||||
|  |  | ||||||
|  |  | ||||||
| __version__ = "0.1.3" | __version__ = "0.1.4" | ||||||
| __author__ = "rigodron, algoflash, GGLinnk" | __author__ = "rigodron, algoflash, GGLinnk" | ||||||
| __license__ = "MIT" | __license__ = "MIT" | ||||||
| __status__ = "developpement" | __status__ = "developpement" | ||||||
| @@ -19,6 +21,8 @@ class InvalidPackIsoError(Exception): pass | |||||||
| class InvalidFSTSizeError(Exception): pass | class InvalidFSTSizeError(Exception): pass | ||||||
| # raised during pack when boot.dol size overflow on first file or on FST | # raised during pack when boot.dol size overflow on first file or on FST | ||||||
| class DolSizeOverflowError(Exception): pass | class DolSizeOverflowError(Exception): pass | ||||||
|  | # raised during pack when fst.bin size overflow on first file or on dol | ||||||
|  | class FstSizeOverflowError(Exception): pass | ||||||
| # raised during pack when FST folder entry has an invalid nextdir id value; this happen when file and folder has been added/removed | # raised during pack when FST folder entry has an invalid nextdir id value; this happen when file and folder has been added/removed | ||||||
| class InvalidRootFileFolderCountError(Exception): pass | class InvalidRootFileFolderCountError(Exception): pass | ||||||
| # raised during pack when FST file entry has a different value than the file being packed; this happen when a file has been edited changing it's size | # raised during pack when FST file entry has a different value than the file being packed; this happen when a file has been edited changing it's size | ||||||
| @@ -29,6 +33,10 @@ class FSTDirNotFoundError(Exception): pass | |||||||
| class FSTFileNotFoundError(Exception): pass | class FSTFileNotFoundError(Exception): pass | ||||||
| # raised when using an invalid align | # raised when using an invalid align | ||||||
| class BadAlignError(Exception): pass | class BadAlignError(Exception): pass | ||||||
|  | # raised when a system conf entry has an invalid format | ||||||
|  | class InvalidConfValueError(Exception): pass | ||||||
|  | # raised when apploader overflow on dol or fst | ||||||
|  | class ApploaderOverflowError(Exception): pass | ||||||
|  |  | ||||||
|  |  | ||||||
| def align_top(address:int, align:int): | def align_top(address:int, align:int): | ||||||
| @@ -156,7 +164,7 @@ 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, fst_offset:int, align:int = 4): |     def __init__(self, root_path:Path, fstbin_offset:int, 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) | ||||||
| @@ -164,7 +172,7 @@ 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 = fst_offset |         self.__current_file_offset = fstbin_offset | ||||||
|     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): | ||||||
| @@ -245,12 +253,11 @@ class FstTree(Fst): | |||||||
|         """ |         """ | ||||||
|         Add a path with each folder as Folder class and the File as a leaf. |         Add a path with each folder as Folder class and the File as a leaf. | ||||||
|         We take care to set parent and childs for folder and retrieve necessary |         We take care to set parent and childs for folder and retrieve necessary | ||||||
|         informations: |         informations from the node_path input: | ||||||
|         * name |         * name | ||||||
|         * size |         * size | ||||||
|         * parent id & parent->child |         * parent id & parent->child | ||||||
|         input: |         input: path = Path (folder / file) | ||||||
|         * path = Path (folder / file) |  | ||||||
|         """ |         """ | ||||||
|         parent = self.__root_node |         parent = self.__root_node | ||||||
|         node = None |         node = None | ||||||
| @@ -266,7 +273,7 @@ class FstTree(Fst): | |||||||
|         """ |         """ | ||||||
|         Generate the FST. |         Generate the 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() |         self.__current_file_offset += self.__get_fst_length() | ||||||
|         self.__prepare() |         self.__prepare() | ||||||
| @@ -275,41 +282,118 @@ class FstTree(Fst): | |||||||
|  |  | ||||||
| class BootBin: | class BootBin: | ||||||
|     """ |     """ | ||||||
|     BootBin group all operations related to the boot.bin system file |     BootBin describe the Disk Header "boot.bin" file at the beginning of  | ||||||
|     using this class avoid errors and it's easier to use it elsewhere |     the GCM/iso. It groups all operations related to the boot.bin system  | ||||||
|     this groupment add meaning to hex values but we can also patch it. |     file extracted in sys/boot.bin. Using this class avoid errors on offsets | ||||||
|  |     and makes it easier to get or set values. | ||||||
|     Constructor: |     Constructor: | ||||||
|     * datas = bytes or bytearray if edit is needed of the boot.bin |     * datas = bytes or bytearray if edit of the boot.bin is needed. | ||||||
|     """ |     """ | ||||||
|     LEN = 0x440 |     LEN = 0x440 | ||||||
|     DOLOFFSET_OFFSET = 0x420 |     DOLOFFSET_OFFSET = 0x420 | ||||||
|     FSTOFFSET_OFFSET = 0x424 |     FSTOFFSET_OFFSET = 0x424 | ||||||
|     FSTLEN_OFFSET = 0x428 |     FSTLEN_OFFSET = 0x428 | ||||||
|     MAXFSTLEN_OFFSET = 0x42c |     FSTMAXLEN_OFFSET = 0x42c | ||||||
|     __data = None |     __data = None | ||||||
|     def __init__(self, data:bytes): |     def __init__(self, data:bytes): self.__data = data | ||||||
|         self.__data = bytearray(data) |     def data(self):               return self.__data | ||||||
|     def data(self): return self.__data |     def make_mut(self):           self.__data = bytearray(self.__data) | ||||||
|     def dvd_magic(self): |     def game_code(self):          return self.__data[:4].decode("ascii") | ||||||
|         return self.__data[0x1c:0x20] |     def maker_code(self):         return self.__data[4:6].decode("ascii") | ||||||
|     def fstbin_offset(self): |     def disk_number(self):        return int.from_bytes(self.__data[6:7], 'big') | ||||||
|         return int.from_bytes(self.__data[BootBin.FSTOFFSET_OFFSET:BootBin.FSTOFFSET_OFFSET+4],"big", signed=False) |     def game_version(self):       return int.from_bytes(self.__data[7:8], 'big') | ||||||
|     def fstbin_len(self): |     def audio_streaming(self):    return int.from_bytes(self.__data[8:9], 'big') | ||||||
|         return int.from_bytes(self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4],"big", signed=False) |     def stream_buffer_size(self): return int.from_bytes(self.__data[9:0xa], 'big') | ||||||
|     def dol_offset(self): |     def dvd_magic(self):          return self.__data[0x1c:0x20] | ||||||
|         return int.from_bytes(self.__data[BootBin.DOLOFFSET_OFFSET:BootBin.DOLOFFSET_OFFSET+4],"big", signed=False) |     def game_name(self):          return self.__data[0x20:0x60].split(b"\x00")[0].decode("utf-8") | ||||||
|     def game_code(self): |     def dol_offset(self):         return int.from_bytes(self.__data[BootBin.DOLOFFSET_OFFSET:BootBin.DOLOFFSET_OFFSET+4],"big") | ||||||
|         return self.__data[:4].decode('utf-8') |     def fst_offset(self):         return int.from_bytes(self.__data[BootBin.FSTOFFSET_OFFSET:BootBin.FSTOFFSET_OFFSET+4],"big") | ||||||
|     def disc_number(self): |     def fst_len(self):            return int.from_bytes(self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4],"big") | ||||||
|         return int.from_bytes(self.__data[6:7], 'big', signed=False) |     def fst_max_len(self):        return int.from_bytes(self.__data[BootBin.FSTMAXLEN_OFFSET:BootBin.FSTMAXLEN_OFFSET+4],"big") | ||||||
|  |     def set_game_code(self, game_code:str): | ||||||
|  |         self.__data[:4] = bytes(game_code, "ascii") | ||||||
|  |     def set_maker_code(self, maker_code:str): | ||||||
|  |         self.__data[4:6] = bytes(maker_code, "ascii") | ||||||
|  |     def set_disk_number(self, disk_number:int): | ||||||
|  |         self.__data[6:7] = disk_number.to_bytes(1, "big") | ||||||
|  |     def set_game_version(self, game_version:int): | ||||||
|  |         self.__data[7:8] = game_version.to_bytes(1, "big") | ||||||
|  |     def set_audio_streaming(self, audio_streaming:int): | ||||||
|  |         self.__data[8:9] = audio_streaming.to_bytes(1, "big") | ||||||
|  |     def set_stream_buffer_size(self, stream_buffer_size:int): | ||||||
|  |         self.__data[9:0xa] = stream_buffer_size.to_bytes(1, "big") | ||||||
|  |     def set_dvd_magic(self, dvd_magic:int): | ||||||
|  |         self.__data[0x1c:0x20] = dvd_magic.to_bytes(4, "big") | ||||||
|  |     def set_game_name(self, game_name:int): | ||||||
|  |         self.__data[0x20:0x60] = bytes(game_name, "utf-8").ljust(0x40, b"\x00") | ||||||
|     def set_dol_offset(self, offset:int): |     def set_dol_offset(self, offset:int): | ||||||
|         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, size:int): | ||||||
|         self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4] = size.to_bytes(4, "big") |         self.__data[BootBin.FSTLEN_OFFSET:BootBin.FSTLEN_OFFSET+4] = size.to_bytes(4, "big") | ||||||
|     def set_max_fst_len(self, size:int): |     def set_fst_max_len(self, size:int): | ||||||
|         self.__data[BootBin.MAXFSTLEN_OFFSET:BootBin.MAXFSTLEN_OFFSET+4] = size.to_bytes(4, "big") |         self.__data[BootBin.FSTMAXLEN_OFFSET:BootBin.FSTMAXLEN_OFFSET+4] = size.to_bytes(4, "big") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Bi2Bin: | ||||||
|  |     """ | ||||||
|  |     Bi2Bin describe the Disk Header Information "bi2.bin" file at the  | ||||||
|  |     beginning of the GCM/iso after boot.bin. It groups all operations | ||||||
|  |     related to the bi2.bin system file extracted in sys/bi2.bin. Using  | ||||||
|  |     this class avoid errors on offsets and makes it easier to get or set  | ||||||
|  |     values. | ||||||
|  |     Constructor: | ||||||
|  |     * datas = bytes or bytearray if edit of the bi2.bin is needed. | ||||||
|  |     """ | ||||||
|  |     LEN = 0x2000 | ||||||
|  |     __data = None | ||||||
|  |     def __init__(self, data:bytes): self.__data = data | ||||||
|  |     def data(self):                 return self.__data | ||||||
|  |     def make_mut(self):             self.__data = bytearray(self.__data) | ||||||
|  |     def debug_monitor_size(self):     return int.from_bytes(self.__data[:4], "big") | ||||||
|  |     def simulated_memory_size(self):  return int.from_bytes(self.__data[4:8], "big") | ||||||
|  |     def argument_offset(self):        return int.from_bytes(self.__data[8:12], "big") | ||||||
|  |     def debug_flag(self):             return int.from_bytes(self.__data[12:16], "big") | ||||||
|  |     def track_location(self):         return int.from_bytes(self.__data[16:20], "big") | ||||||
|  |     def track_size(self):             return int.from_bytes(self.__data[20:24], "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 long_file_name_support(self): return int.from_bytes(self.__data[32:36], "big") | ||||||
|  |     def set_debug_monitor_size(self, debug_monitor_size:int): | ||||||
|  |         self.__data[:4] = debug_monitor_size.to_bytes(4, "big") | ||||||
|  |     def set_simulated_memory_size(self, simulated_memory_size:int): | ||||||
|  |         self.__data[4:8] = simulated_memory_size.to_bytes(4, "big") | ||||||
|  |     def set_argument_offset(self, argument_offset:int): | ||||||
|  |         self.__data[8:12] = argument_offset.to_bytes(4, "big") | ||||||
|  |     def set_debug_flag(self, debug_flag:int): | ||||||
|  |         self.__data[12:16] = debug_flag.to_bytes(4, "big") | ||||||
|  |     def set_track_location(self, track_location:int): | ||||||
|  |         self.__data[16:20] = track_location.to_bytes(4, "big") | ||||||
|  |     def set_track_size(self, track_size:int): | ||||||
|  |         self.__data[20:24] = track_size.to_bytes(4, "big") | ||||||
|  |     def set_country_code(self, country_code:int): | ||||||
|  |         self.__data[24:28] = country_code.to_bytes(4, "big") | ||||||
|  |     def set_total_disk(self, total_disk:int): | ||||||
|  |         self.__data[28:32] = total_disk.to_bytes(4, "big") | ||||||
|  |     def set_long_file_name_support(self, long_file_name_support:int): | ||||||
|  |         self.__data[32:36] = long_file_name_support.to_bytes(4, "big") | ||||||
|  |          | ||||||
|  |  | ||||||
|  | class ApploaderImg: | ||||||
|  |     __data = None | ||||||
|  |     def __init__(self, data:bytes): self.__data = data | ||||||
|  |     def data(self):         return self.__data | ||||||
|  |     def len(self):          return len(self.__data) | ||||||
|  |     def make_mut(self):     self.__data = bytearray(self.__data) | ||||||
|  |     def version(self):      return self.__data[:0x10].split(b"\x00")[0].decode("ascii") | ||||||
|  |     def entry_point(self):  return int.from_bytes(self.__data[0x10:0x14], "big") | ||||||
|  |     def size(self):         return int.from_bytes(self.__data[0x14:0x18], "big") | ||||||
|  |     def trailer_size(self): return int.from_bytes(self.__data[0x18:0x1c], "big") | ||||||
|  |     def set_version(self, version:int):           self.__data[:0x10]     = bytes(version, "ascii").ljust(0x10, b"\x00") | ||||||
|  |     def set_entry_point(self, entry_point:int):   self.__data[0x10:0x14] = entry_point.to_bytes(4, "big") | ||||||
|  |     def set_size(self, size:int):                 self.__data[0x14:0x18] = size.to_bytes(4, "big") | ||||||
|  |     def set_trailer_size(self, trailer_size:int): self.__data[0x18:0x1c] = trailer_size.to_bytes(4, "big") | ||||||
|  |  | ||||||
|  |  | ||||||
| class Dol: | class Dol: | ||||||
| @@ -324,7 +408,7 @@ class Dol: | |||||||
|         """ |         """ | ||||||
|         dol_len = Dol.HEADER_LEN |         dol_len = Dol.HEADER_LEN | ||||||
|         for i in range(18): |         for i in range(18): | ||||||
|             dol_len += int.from_bytes(dolheader_data[Dol.HEADER_SECTIONLENTABLE_OFFSET+i*4:Dol.HEADER_SECTIONLENTABLE_OFFSET+(i+1)*4], "big", signed=False) |             dol_len += int.from_bytes(dolheader_data[Dol.HEADER_SECTIONLENTABLE_OFFSET+i*4:Dol.HEADER_SECTIONLENTABLE_OFFSET+(i+1)*4], "big") | ||||||
|         return dol_len |         return dol_len | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -333,20 +417,244 @@ class Gcm: | |||||||
|     Gcm handle all operations needed by the command parser. |     Gcm handle all operations needed by the command parser. | ||||||
|     File format informations: https://sudonull.com/post/68549-Gamecube-file-system-device |     File format informations: https://sudonull.com/post/68549-Gamecube-file-system-device | ||||||
|     """ |     """ | ||||||
|     BI2BIN_LEN = 0x2000 |  | ||||||
|     APPLOADER_HEADER_LEN = 0x20 |     APPLOADER_HEADER_LEN = 0x20 | ||||||
|     APPLOADER_OFFSET = 0x2440 |     APPLOADER_OFFSET = 0x2440 | ||||||
|     APPLOADERSIZE_OFFSET = 0x2454 |     APPLOADERLEN_OFFSET = 0x2454 | ||||||
|     DVD_MAGIC = b"\xC2\x33\x9F\x3D" |     DVD_MAGIC = b"\xC2\x33\x9F\x3D" | ||||||
|  |     __bootbin = None # Disk header | ||||||
|  |     __bi2bin = None  # Disk header Information | ||||||
|  |     __apploaderimg = None | ||||||
|  |     __hex_pattern = re.compile("^0x[0-9a-fA-F]+$") | ||||||
|  |     def __save_conf(self, sys_path:Path): | ||||||
|  |         "Read boot.bin and bi2.bin and save theirs conf in sys/system.conf." | ||||||
|  |  | ||||||
|  |         config = ConfigParser(allow_no_value=True) # allow_no_value to allow adding comments | ||||||
|  |         config.optionxform = str # makes options case sensitive | ||||||
|  |         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", "boot.bin_section", "disabled") | ||||||
|  |         config.set("Default", "bi2.bin_section", "disabled") | ||||||
|  |         config.set("Default", "apploader.img_section", "disabled") | ||||||
|  |  | ||||||
|  |         config.add_section("boot.bin") | ||||||
|  |         config.set("boot.bin", "GameCode",         self.__bootbin.game_code()) # 4 bytes ASCII | ||||||
|  |         config.set("boot.bin", "MakerCode",        self.__bootbin.maker_code()) # 2 bytes ASCII | ||||||
|  |         config.set("boot.bin", "DiskNumber",       str(self.__bootbin.disk_number())) # 0-98 | ||||||
|  |         config.set("boot.bin", "GameVersion",      str(self.__bootbin.game_version())) # 0-99 | ||||||
|  |         config.set("boot.bin", "AudioStreaming",   str(self.__bootbin.audio_streaming())) # 0 or 1 flag | ||||||
|  |         config.set("boot.bin", "StreamBufferSize", str(self.__bootbin.stream_buffer_size())) # 0-15 | ||||||
|  |         config.set("boot.bin", "DVDMagic",         "0x" + self.__bootbin.dvd_magic().hex()) | ||||||
|  |         config.set("boot.bin", "GameName",         self.__bootbin.game_name()) # 64 bytes | ||||||
|  |         config.set("boot.bin", "DolOffset",        f"auto") | ||||||
|  |         config.set("boot.bin", "FstOffset",        f"auto") | ||||||
|  |         config.set("boot.bin", "FstLen",           f"auto") | ||||||
|  |         config.set("boot.bin", "FstMaxLen",        f"auto") | ||||||
|  |          | ||||||
|  |         config.add_section("bi2.bin") | ||||||
|  |         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", "ArgumentOffset",      f"0x{self.__bi2bin.argument_offset():x}") | ||||||
|  |         config.set("bi2.bin", "DebugFlag",           str(self.__bi2bin.debug_flag())) | ||||||
|  |         config.set("bi2.bin", "TrackLocation",       f"0x{self.__bi2bin.track_location():x}") | ||||||
|  |         config.set("bi2.bin", "TrackSize",           f"0x{self.__bi2bin.track_size():x}") | ||||||
|  |         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", "LongFileNameSupport", str(self.__bi2bin.long_file_name_support())) # 0, 1 | ||||||
|  |  | ||||||
|  |         config.add_section("apploader.img") | ||||||
|  |         config.set("apploader.img", "Version",     self.__apploaderimg.version()) | ||||||
|  |         config.set("apploader.img", "EntryPoint",  f"0x{self.__apploaderimg.entry_point():x}") | ||||||
|  |         config.set("apploader.img", "Size",        f"0x{self.__apploaderimg.size():x}") | ||||||
|  |         config.set("apploader.img", "TrailerSize", f"0x{self.__apploaderimg.trailer_size():x}") | ||||||
|  |  | ||||||
|  |         with (sys_path / "system.conf").open("w") as conf_file: | ||||||
|  |             config.write(conf_file) | ||||||
|  |     def __load_conf(self, sys_path:Path): | ||||||
|  |         "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.optionxform = str # makes options case sensitive | ||||||
|  |         config.read(sys_path / "system.conf") | ||||||
|  |  | ||||||
|  |         if config["Default"]["boot.bin_section"].lower() not in ["enabled", "disabled"]: | ||||||
|  |             raise InvalidConfValueError("Error - Invalid [Default][boot.bin_section]: must be enabled or disabled.") | ||||||
|  |         if config["Default"]["bi2.bin_section"].lower() not in ["enabled", "disabled"]: | ||||||
|  |             raise InvalidConfValueError("Error - Invalid [Default][bi2.bin_section]: must be enabled or disabled.") | ||||||
|  |         if config["Default"]["apploader.img_section"].lower() not in ["enabled", "disabled"]: | ||||||
|  |             raise InvalidConfValueError("Error - Invalid [Default][apploader.img_section]: must be enabled or disabled.") | ||||||
|  |  | ||||||
|  |         def check_numeric_format(config:ConfigParser, conf_list:list): | ||||||
|  |             for conf in conf_list: | ||||||
|  |                 if not config[conf[0]][conf[1]].isnumeric(): | ||||||
|  |                     raise InvalidConfValueError(f"Error - Invalid [{conf[0]}][{conf[1]}]: must be numeric - 1234.") | ||||||
|  |         def check_hex_format(config:ConfigParser, conf_list:list): | ||||||
|  |             for conf in conf_list: | ||||||
|  |                 if conf[2] and config[conf[0]][conf[1]] == "auto": | ||||||
|  |                     continue | ||||||
|  |                 if not self.__hex_pattern.fullmatch(config[conf[0]][conf[1]]): | ||||||
|  |                     raise InvalidConfValueError(f"Error - Invalid [{conf[0]}][{conf[1]}]: must be hex - 0xabcdef.") | ||||||
|  |  | ||||||
|  |         check_numeric_format(config, [ | ||||||
|  |             ("boot.bin", "DiskNumber"), | ||||||
|  |             ("boot.bin", "GameVersion"), | ||||||
|  |             ("boot.bin", "StreamBufferSize"), | ||||||
|  |             ("bi2.bin", "DebugFlag"), | ||||||
|  |             ("bi2.bin", "TotalDisk")]) | ||||||
|  |  | ||||||
|  |         check_hex_format(config, [ | ||||||
|  |             ("boot.bin", "DVDMagic",           False), | ||||||
|  |             ("boot.bin", "DolOffset",          True), | ||||||
|  |             ("boot.bin", "FstOffset",          True), | ||||||
|  |             ("boot.bin", "FstLen",             True), | ||||||
|  |             ("boot.bin", "FstMaxLen",          True), | ||||||
|  |             ("bi2.bin", "DebugMonitorSize",    False), | ||||||
|  |             ("bi2.bin", "SimulatedMemorySize", False), | ||||||
|  |             ("bi2.bin", "ArgumentOffset",      False), | ||||||
|  |             ("bi2.bin", "TrackLocation",       False), | ||||||
|  |             ("bi2.bin", "TrackSize",           False), | ||||||
|  |             ("apploader.img", "EntryPoint",    False), | ||||||
|  |             ("apploader.img", "Size",          False), | ||||||
|  |             ("apploader.img", "TrailerSize",   False)]) | ||||||
|  |          | ||||||
|  |         self.__bootbin.make_mut() | ||||||
|  |         self.__bi2bin.make_mut() | ||||||
|  |         self.__apploaderimg.make_mut() | ||||||
|  |  | ||||||
|  |         if config["Default"]["boot.bin_section"].lower() == "enabled": | ||||||
|  |             if len(config["boot.bin"]["GameCode"]) != 4: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][GameCode]: must be str with length = 4.") | ||||||
|  |             self.__bootbin.set_game_code( config["boot.bin"]["GameCode"] ) | ||||||
|  |              | ||||||
|  |             if len(config["boot.bin"]["MakerCode"]) != 2: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][MakerCode]: must be str with length = 2.") | ||||||
|  |             self.__bootbin.set_maker_code( config["boot.bin"]["MakerCode"] ) | ||||||
|  |              | ||||||
|  |             disk_number = int(config["boot.bin"]["DiskNumber"]) | ||||||
|  |             if disk_number > 98: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][DiskNumber]: must be int with value < 99.") | ||||||
|  |             self.__bootbin.set_disk_number( disk_number ) | ||||||
|  |  | ||||||
|  |             game_version = int(config["boot.bin"]["GameVersion"]) | ||||||
|  |             if game_version > 99: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][GameVersion]: must be int with value < 100.") | ||||||
|  |             self.__bootbin.set_game_version( game_version ) | ||||||
|  |  | ||||||
|  |             if config["boot.bin"]["AudioStreaming"] not in ["0", "1"]: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][AudioStreaming]: this flag must be 0 or 1.") | ||||||
|  |             self.__bootbin.set_audio_streaming( int(config["boot.bin"]["AudioStreaming"]) ) | ||||||
|  |  | ||||||
|  |             stream_buffer_size = int(config["boot.bin"]["StreamBufferSize"]) | ||||||
|  |             if stream_buffer_size > 15: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][StreamBufferSize]: must be int with value between 0 and 15.") | ||||||
|  |             self.__bootbin.set_stream_buffer_size( stream_buffer_size ) | ||||||
|  |  | ||||||
|  |             if len(config["boot.bin"]["DVDMagic"]) != 10: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][DVDMagic]: must be 8 hex digits begining with 0x.") | ||||||
|  |             self.__bootbin.set_dvd_magic( int(config["boot.bin"]["DVDMagic"], 16) ) | ||||||
|  |              | ||||||
|  |             if len(config["boot.bin"]["GameName"]) > 64: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [boot.bin][GameName]: must be str with length < 64.") | ||||||
|  |             self.__bootbin.set_game_name( config["boot.bin"]["GameName"] ) | ||||||
|  |  | ||||||
|  |             if config["boot.bin"]["DolOffset"] != "auto": | ||||||
|  |                 dol_offset = int(config["boot.bin"]["DolOffset"], 16) | ||||||
|  |                 if dol_offset > 0xffffffff: | ||||||
|  |                     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 ) | ||||||
|  |  | ||||||
|  |             if config["boot.bin"]["FstOffset"] != "auto": | ||||||
|  |                 fst_offset = int(config["boot.bin"]["FstOffset"], 16) | ||||||
|  |                 if fst_offset > 0xffffffff: | ||||||
|  |                     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 ) | ||||||
|  |  | ||||||
|  |             if config["boot.bin"]["FstLen"] != "auto": | ||||||
|  |                 fst_len = int(config["boot.bin"]["FstLen"], 16) | ||||||
|  |                 if fst_len > 0xffffffff: | ||||||
|  |                     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 ) | ||||||
|  |  | ||||||
|  |             if config["boot.bin"]["FstMaxLen"] != "auto": | ||||||
|  |                 fst_max_len = int(config["boot.bin"]["FstMaxLen"], 16) | ||||||
|  |                 if fst_max_len > 0xffffffff: | ||||||
|  |                     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 ) | ||||||
|  |          | ||||||
|  |         if config["Default"]["bi2.bin_section"].lower() == "enabled": | ||||||
|  |             debug_monitor_size = int(config["bi2.bin"]["DebugMonitorSize"], 16) | ||||||
|  |             if debug_monitor_size > 0xffffffff or debug_monitor_size & 31: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][DebugMonitorSize]: must be hex value with length < 5 bytes and aligned to 32.") | ||||||
|  |             self.__bi2bin.set_debug_monitor_size( debug_monitor_size ) | ||||||
|  |  | ||||||
|  |             simulated_memory_size = int(config["bi2.bin"]["SimulatedMemorySize"], 16) | ||||||
|  |             if simulated_memory_size > 0xffffffff or simulated_memory_size & 31: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][SimulatedMemorySize]: must be hex value with length < 5 bytes and aligned to 32.") | ||||||
|  |             self.__bi2bin.set_simulated_memory_size( simulated_memory_size ) | ||||||
|  |  | ||||||
|  |             argument_offset = int(config["bi2.bin"]["ArgumentOffset"], 16) | ||||||
|  |             if argument_offset > 0xffffffff: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][ArgumentOffset]: must be hex value with length < 5 bytes.") | ||||||
|  |             self.__bi2bin.set_argument_offset( argument_offset ) | ||||||
|  |  | ||||||
|  |             debug_flag = int(config["bi2.bin"]["DebugFlag"]) | ||||||
|  |             if debug_flag > 0xffffffff: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][DebugFlag]: must be hex value with length < 5 bytes.") | ||||||
|  |             self.__bi2bin.set_debug_flag( debug_flag ) | ||||||
|  |  | ||||||
|  |             track_location = int(config["bi2.bin"]["TrackLocation"], 16) | ||||||
|  |             if track_location > 0xffffffff: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][TrackLocation]: must be hex value with length < 5 bytes.") | ||||||
|  |             self.__bi2bin.set_track_location( track_location ) | ||||||
|  |  | ||||||
|  |             track_size = int(config["bi2.bin"]["TrackSize"], 16) | ||||||
|  |             if track_size > 0xffffffff: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][TrackSize]: must be hex value with length < 5 bytes.") | ||||||
|  |             self.__bi2bin.set_track_size( track_size ) | ||||||
|  |  | ||||||
|  |             if config["bi2.bin"]["CountryCode"] not in ["0", "1", "2", "4"]: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][CountryCode]: must have 0, 1, 2 or 4 value.") | ||||||
|  |             self.__bi2bin.set_country_code( int(config["bi2.bin"]["CountryCode"]) ) | ||||||
|  |  | ||||||
|  |             if int(config["bi2.bin"]["TotalDisk"]) > 99: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [bi2.bin][TotalDisk]: must between 1 and 99.") | ||||||
|  |             self.__bi2bin.set_total_disk( int(config["bi2.bin"]["TotalDisk"], 16) ) | ||||||
|  |  | ||||||
|  |             if config["bi2.bin"]["LongFileNameSupport"] not in ["0", "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"]) ) | ||||||
|  |  | ||||||
|  |         if config["Default"]["apploader.img_section"].lower() == "enabled": | ||||||
|  |             version = config["apploader.img"]["Version"] | ||||||
|  |             if len(version) > 10: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [apploader.img][Version]: must be 16 byte ascii string.") | ||||||
|  |             self.__apploaderimg.set_version( version ) | ||||||
|  |  | ||||||
|  |             entry_point = int(config["apploader.img"]["EntryPoint"], 16) | ||||||
|  |             if entry_point > 0xffffffff: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [apploader.img][EntryPoint]: must be hex value with length < 5 bytes.") | ||||||
|  |             self.__apploaderimg.set_entry_point( entry_point ) | ||||||
|  |              | ||||||
|  |             size = int(config["apploader.img"]["Size"], 16) | ||||||
|  |             if size > 0xffffffff: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [apploader.img][Size]: must be hex value with length < 5 bytes.") | ||||||
|  |             self.__apploaderimg.set_size( size ) | ||||||
|  |              | ||||||
|  |             trailer_size = int(config["apploader.img"]["TrailerSize"], 16) | ||||||
|  |             if trailer_size > 0xffffffff: | ||||||
|  |                 raise InvalidConfValueError("Error - Invalid [apploader.img][TrailerSize]: must be hex value with length < 5 bytes.") | ||||||
|  |             self.__apploaderimg.set_trailer_size( trailer_size ) | ||||||
|  |  | ||||||
|  |         (sys_path / "boot.bin").write_bytes(self.__bootbin.data()) | ||||||
|  |         (sys_path / "bi2.bin").write_bytes(self.__bi2bin.data()) | ||||||
|  |         (sys_path / "apploader.img").write_bytes(self.__apploaderimg.data()) | ||||||
|     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 | ||||||
|         for i in range(2, int.from_bytes(fstbin_data[8:12], "big", signed=False)): |         for i in range(2, int.from_bytes(fstbin_data[8:12], "big")): | ||||||
|             if int.from_bytes(fstbin_data[i*12:i*12+1], "big", signed=False) == FstTree.TYPE_FILE: |             if int.from_bytes(fstbin_data[i*12:i*12+1], "big") == FstTree.TYPE_FILE: | ||||||
|                 if min_offset is None: |                 if min_offset is None: | ||||||
|                     min_offset = int.from_bytes(fstbin_data[i*12+4:i*12+8], "big", signed=False) |                     min_offset = int.from_bytes(fstbin_data[i*12+4:i*12+8], "big") | ||||||
|                     continue |                     continue | ||||||
|                 min_offset = min(min_offset, int.from_bytes(fstbin_data[i*12+4:i*12+8], "big", signed=False)) |                 min_offset = min(min_offset, int.from_bytes(fstbin_data[i*12+4:i*12+8], "big")) | ||||||
|         return min_offset |         return min_offset | ||||||
|     def unpack(self, iso_path:Path, folder_path:Path): |     def unpack(self, iso_path:Path, folder_path:Path): | ||||||
|         """ |         """ | ||||||
| @@ -355,27 +663,27 @@ class Gcm: | |||||||
|         input: folder_path = Path |         input: folder_path = Path | ||||||
|         """ |         """ | ||||||
|         with iso_path.open("rb") as iso_file: |         with iso_path.open("rb") as iso_file: | ||||||
|             bootbin = BootBin(iso_file.read(BootBin.LEN)) |             self.__bootbin = BootBin(iso_file.read(BootBin.LEN)) | ||||||
|             if bootbin.dvd_magic() != Gcm.DVD_MAGIC: |             if self.__bootbin.dvd_magic() != Gcm.DVD_MAGIC: | ||||||
|                 raise InvalidDVDMagicError("Error - Invalid DVD format - this tool is for ISO/GCM files") |                 raise InvalidDVDMagicError("Error - Invalid DVD format - this tool is for ISO/GCM files.") | ||||||
|  |  | ||||||
|             bi2bin_data = iso_file.read(Gcm.BI2BIN_LEN) |             self.__bi2bin = Bi2Bin(iso_file.read(Bi2Bin.LEN)) | ||||||
|  |  | ||||||
|             iso_file.seek(Gcm.APPLOADERSIZE_OFFSET) |             iso_file.seek(Gcm.APPLOADERLEN_OFFSET) | ||||||
|             size = int.from_bytes(iso_file.read(4), "big", signed=False) |             size = int.from_bytes(iso_file.read(4), "big") | ||||||
|             trailerSize = int.from_bytes(iso_file.read(4), "big", signed=False) |             trailerSize = int.from_bytes(iso_file.read(4), "big") | ||||||
|              |              | ||||||
|             apploader_size = Gcm.APPLOADER_HEADER_LEN + size + trailerSize |             apploader_size = Gcm.APPLOADER_HEADER_LEN + size + trailerSize | ||||||
|              |              | ||||||
|             iso_file.seek(Gcm.APPLOADER_OFFSET) |             iso_file.seek(Gcm.APPLOADER_OFFSET) | ||||||
|             apploaderimg_data = iso_file.read(apploader_size) |             self.__apploaderimg = ApploaderImg(iso_file.read(apploader_size)) | ||||||
|  |  | ||||||
|             fstbin_offset = bootbin.fstbin_offset() |             fstbin_offset = self.__bootbin.fst_offset() | ||||||
|             fstbin_len = bootbin.fstbin_len() |             fstbin_len = self.__bootbin.fst_len() | ||||||
|             iso_file.seek( fstbin_offset ) |             iso_file.seek( fstbin_offset ) | ||||||
|             fstbin_data = iso_file.read( fstbin_len ) |             fstbin_data = iso_file.read( fstbin_len ) | ||||||
|  |  | ||||||
|             dol_offset = bootbin.dol_offset() |             dol_offset = self.__bootbin.dol_offset() | ||||||
|             iso_file.seek( dol_offset ) |             iso_file.seek( dol_offset ) | ||||||
|             dol = Dol() |             dol = Dol() | ||||||
|             dolheader_data = iso_file.read(Dol.HEADER_LEN) |             dolheader_data = iso_file.read(Dol.HEADER_LEN) | ||||||
| @@ -383,7 +691,7 @@ class Gcm: | |||||||
|             bootdol_data = dolheader_data + iso_file.read( dol_len - Dol.HEADER_LEN ) |             bootdol_data = dolheader_data + iso_file.read( dol_len - Dol.HEADER_LEN ) | ||||||
|  |  | ||||||
|             if folder_path == Path("."): |             if folder_path == Path("."): | ||||||
|                 folder_path = Path(f"{bootbin.game_code()}-{bootbin.disc_number():02}") |                 folder_path = Path(f"{self.__bootbin.game_code()}-{self.__bootbin.disk_number():02}") | ||||||
|             if folder_path.is_dir(): |             if folder_path.is_dir(): | ||||||
|                 raise InvalidUnpackFolderError(f"Error - \"{folder_path}\" already exist. Remove this folder or use another name for the unpack folder.") |                 raise InvalidUnpackFolderError(f"Error - \"{folder_path}\" already exist. Remove this folder or use another name for the unpack folder.") | ||||||
|              |              | ||||||
| @@ -392,16 +700,19 @@ class Gcm: | |||||||
|             sys_path.mkdir(parents=True) |             sys_path.mkdir(parents=True) | ||||||
|  |  | ||||||
|             logging.debug(f"{iso_path}(0x0:0x{BootBin.LEN:x}) -> {sys_path / 'boot.bin'}") |             logging.debug(f"{iso_path}(0x0:0x{BootBin.LEN:x}) -> {sys_path / 'boot.bin'}") | ||||||
|             (sys_path / "boot.bin").write_bytes(bootbin.data()) |             (sys_path / "boot.bin").write_bytes(self.__bootbin.data()) | ||||||
|             logging.debug(f"{iso_path}(0x440:0x{Gcm.APPLOADER_OFFSET:x}) -> {sys_path / 'bi2.bin'}") |             logging.debug(f"{iso_path}(0x440:0x{Gcm.APPLOADER_OFFSET:x}) -> {sys_path / 'bi2.bin'}") | ||||||
|             (sys_path / "bi2.bin" ).write_bytes(bi2bin_data) |             (sys_path / "bi2.bin" ).write_bytes(self.__bi2bin.data()) | ||||||
|             logging.debug(f"{iso_path}(0x{Gcm.APPLOADER_OFFSET:x}:0x{Gcm.APPLOADER_OFFSET + apploader_size:x} -> {sys_path / 'apploader.img'}") |             logging.debug(f"{iso_path}(0x{Gcm.APPLOADER_OFFSET:x}:0x{Gcm.APPLOADER_OFFSET + apploader_size:x} -> {sys_path / 'apploader.img'}") | ||||||
|             (sys_path / "apploader.img").write_bytes(apploaderimg_data) |             (sys_path / "apploader.img").write_bytes(self.__apploaderimg.data()) | ||||||
|             logging.debug(f"{iso_path}(0x{fstbin_offset:x}:0x{fstbin_offset + fstbin_len:x}) -> {sys_path / 'fst.bin'}") |             logging.debug(f"{iso_path}(0x{fstbin_offset:x}:0x{fstbin_offset + fstbin_len:x}) -> {sys_path / 'fst.bin'}") | ||||||
|             (sys_path / "fst.bin").write_bytes(fstbin_data) |             (sys_path / "fst.bin").write_bytes(fstbin_data) | ||||||
|             logging.debug(f"{iso_path}(0x{dol_offset:x}:0x{dol_offset + dol_len:x}) -> {sys_path / 'boot.dol'}") |             logging.debug(f"{iso_path}(0x{dol_offset:x}:0x{dol_offset + dol_len:x}) -> {sys_path / 'boot.dol'}") | ||||||
|             (sys_path / "boot.dol").write_bytes(bootdol_data) |             (sys_path / "boot.dol").write_bytes(bootdol_data) | ||||||
|  |  | ||||||
|  |             # Generate conf from sys files | ||||||
|  |             self.__save_conf(sys_path) | ||||||
|  |  | ||||||
|             root_path = folder_path / "root" |             root_path = folder_path / "root" | ||||||
|             root_path.mkdir() |             root_path.mkdir() | ||||||
|              |              | ||||||
| @@ -410,7 +721,7 @@ class Gcm: | |||||||
|             currentdir_path = root_path |             currentdir_path = root_path | ||||||
|  |  | ||||||
|             # root: id=0 so nextdir is the end |             # root: id=0 so nextdir is the end | ||||||
|             nextdir = int.from_bytes(fstbin_data[8:12], "big", signed=False) |             nextdir = int.from_bytes(fstbin_data[8:12], "big") | ||||||
|             # offset of filenames block |             # offset of filenames block | ||||||
|             base_names = nextdir * 12 |             base_names = nextdir * 12 | ||||||
|             # go to parent when id reach next dir |             # go to parent when id reach next dir | ||||||
| @@ -418,24 +729,24 @@ class Gcm: | |||||||
|  |  | ||||||
|             for id in range(1, base_names // 12): |             for id in range(1, base_names // 12): | ||||||
|                 i = id * 12 |                 i = id * 12 | ||||||
|                 file_type = int.from_bytes(fstbin_data[i:i+1], "big", signed=False) |                 file_type = int.from_bytes(fstbin_data[i:i+1], "big") | ||||||
|                 name = fstbin_data[base_names + int.from_bytes(fstbin_data[i+1:i+4], "big", signed=False):].split(b"\x00")[0].decode("utf-8") |                 name = fstbin_data[base_names + int.from_bytes(fstbin_data[i+1:i+4], "big"):].split(b"\x00")[0].decode("utf-8") | ||||||
|                  |                  | ||||||
|                 while id == nextdir_arr[-1]: |                 while id == nextdir_arr[-1]: | ||||||
|                     currentdir_path = currentdir_path.parent |                     currentdir_path = currentdir_path.parent | ||||||
|                     nextdir_arr.pop() |                     nextdir_arr.pop() | ||||||
|  |  | ||||||
|                 if file_type == FstTree.TYPE_DIR: |                 if file_type == FstTree.TYPE_DIR: | ||||||
|                     nextdir = int.from_bytes(fstbin_data[i+8:i+12], "big", signed=False) |                     nextdir = int.from_bytes(fstbin_data[i+8:i+12], "big") | ||||||
|                     parentdir = int.from_bytes(fstbin_data[i+4:i+8], "big", signed=False) |                     parentdir = int.from_bytes(fstbin_data[i+4:i+8], "big") | ||||||
|  |  | ||||||
|                     nextdir_arr.append( nextdir ) |                     nextdir_arr.append( nextdir ) | ||||||
|                     currentdir_path = dir_id_path[parentdir] / name |                     currentdir_path = dir_id_path[parentdir] / name | ||||||
|                     dir_id_path[id] = currentdir_path |                     dir_id_path[id] = currentdir_path | ||||||
|                     currentdir_path.mkdir(exist_ok=True) |                     currentdir_path.mkdir(exist_ok=True) | ||||||
|                 else: |                 else: | ||||||
|                     fileoffset = int.from_bytes(fstbin_data[i+4:i+8], "big", signed=False) |                     fileoffset = int.from_bytes(fstbin_data[i+4:i+8], "big") | ||||||
|                     filesize   = int.from_bytes(fstbin_data[i+8:i+12], "big", signed=False) |                     filesize   = int.from_bytes(fstbin_data[i+8:i+12], "big") | ||||||
|  |  | ||||||
|                     iso_file.seek(fileoffset) |                     iso_file.seek(fileoffset) | ||||||
|                     (currentdir_path / name).write_bytes( iso_file.read(filesize) ) |                     (currentdir_path / name).write_bytes( iso_file.read(filesize) ) | ||||||
| @@ -456,28 +767,52 @@ class Gcm: | |||||||
|             with iso_path.open("wb") as iso_file: |             with iso_path.open("wb") as iso_file: | ||||||
|                 sys_path = folder_path / "sys" |                 sys_path = folder_path / "sys" | ||||||
|                  |                  | ||||||
|                 logging.debug(f"{sys_path / 'boot.bin'}      -> {iso_path}(0x0:0x{BootBin.LEN:x})") |                 self.__bootbin = BootBin((sys_path / "boot.bin").read_bytes()) | ||||||
|                 bootbin = BootBin((sys_path / "boot.bin").read_bytes()) |                 self.__bi2bin = Bi2Bin((sys_path / "bi2.bin").read_bytes()) | ||||||
|                 iso_file.write(bootbin.data()) |                 self.__apploaderimg = ApploaderImg((sys_path / "apploader.img").read_bytes()) | ||||||
|                 logging.debug(f"{sys_path / 'bi2.bin'}       -> {iso_path}(0x{BootBin.LEN:x}:0x{Gcm.APPLOADER_OFFSET:x})") |  | ||||||
|                 iso_file.write((sys_path / "bi2.bin").read_bytes()) |  | ||||||
|                 logging.debug(f"{sys_path / 'apploader.img'} -> {iso_path}(0x{Gcm.APPLOADER_OFFSET:x}:0x{Gcm.APPLOADER_OFFSET + (sys_path / 'apploader.img').stat().st_size:x}") |  | ||||||
|                 iso_file.write((sys_path / "apploader.img").read_bytes()) |  | ||||||
|  |  | ||||||
|                 fstbin_offset = bootbin.fstbin_offset() |                 # Patch boot.bin and bi2.bin if system.conf is enabled | ||||||
|                 fstbin_len = bootbin.fstbin_len() |                 self.__load_conf(sys_path) | ||||||
|  |  | ||||||
|  |                 logging.debug(f"{sys_path / 'boot.bin'}      -> {iso_path}(0x0:0x{BootBin.LEN:x})") | ||||||
|  |                 iso_file.write(self.__bootbin.data()) | ||||||
|  |                 logging.debug(f"{sys_path / 'bi2.bin'}       -> {iso_path}(0x{BootBin.LEN:x}:0x{Gcm.APPLOADER_OFFSET:x})") | ||||||
|  |                 iso_file.write(self.__bi2bin.data()) | ||||||
|  |  | ||||||
|  |                 apploader_end_offset = Gcm.APPLOADER_OFFSET + self.__apploaderimg.len() | ||||||
|  |                 logging.debug(f"{sys_path / 'apploader.img'} -> {iso_path}(0x{Gcm.APPLOADER_OFFSET:x}:0x{apploader_end_offset:x}") | ||||||
|  |                 iso_file.write(self.__apploaderimg.data()) | ||||||
|  |  | ||||||
|  |                 fstbin_offset = self.__bootbin.fst_offset() | ||||||
|  |                 fstbin_len = self.__bootbin.fst_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 size 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() | ||||||
|                 iso_file.write( fstbin_data ) |                 iso_file.write( fstbin_data ) | ||||||
|                  |                  | ||||||
|                 dol_offset = bootbin.dol_offset() |                 dol_offset = self.__bootbin.dol_offset() | ||||||
|                 dol_end_offset = dol_offset + (sys_path / 'boot.dol').stat().st_size |                 dol_end_offset = dol_offset + (sys_path / 'boot.dol').stat().st_size | ||||||
|  |  | ||||||
|  |                 min_file_offset = self.__get_min_file_offset(fstbin_data) | ||||||
|  |  | ||||||
|                 # FST can be before the dol or after |                 # FST can be before the dol or after | ||||||
|                 if not disable_ignore and (dol_offset < fstbin_offset < dol_end_offset or (fstbin_offset < dol_offset and dol_end_offset > self.__get_min_file_offset(fstbin_data))): |                 # We control values to avoid Overflows | ||||||
|                     raise DolSizeOverflowError("Error - The dol size has been increased and overflow on next file or on FST. To solve this use --rebuild-fst.") |                 if not disable_ignore: | ||||||
|  |                     if not Gcm.APPLOADER_OFFSET < dol_offset < dol_end_offset <= fstbin_offset and not \ | ||||||
|  |                         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.") | ||||||
|  |  | ||||||
|  |                 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: | ||||||
|  |                     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.") | ||||||
|  |  | ||||||
|  |                 if Gcm.APPLOADER_OFFSET < dol_offset < apploader_end_offset or \ | ||||||
|  |                     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.") | ||||||
|  |  | ||||||
|                 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 ) | ||||||
|                 iso_file.write( (sys_path / "boot.dol").read_bytes() ) |                 iso_file.write( (sys_path / "boot.dol").read_bytes() ) | ||||||
| @@ -487,7 +822,7 @@ class Gcm: | |||||||
|                 currentdir_path = folder_path / "root" |                 currentdir_path = folder_path / "root" | ||||||
|  |  | ||||||
|                 # root: id=0 so nextdir is the end |                 # root: id=0 so nextdir is the end | ||||||
|                 nextdir = int.from_bytes(fstbin_data[8:12], "big", signed=False) |                 nextdir = int.from_bytes(fstbin_data[8:12], "big") | ||||||
|                 # offset of filenames block |                 # offset of filenames block | ||||||
|                 base_names = nextdir * 12 |                 base_names = nextdir * 12 | ||||||
|                 # go to parent when id reach next dir |                 # go to parent when id reach next dir | ||||||
| @@ -499,16 +834,16 @@ class Gcm: | |||||||
|  |  | ||||||
|                 for id in range(1, base_names // 12): |                 for id in range(1, base_names // 12): | ||||||
|                     i = id * 12 |                     i = id * 12 | ||||||
|                     file_type = int.from_bytes(fstbin_data[i:i+1], "big", signed=False) |                     file_type = int.from_bytes(fstbin_data[i:i+1], "big") | ||||||
|                     name = fstbin_data[base_names + int.from_bytes(fstbin_data[i+1:i+4], "big", signed=False):].split(b"\x00")[0].decode("utf-8") |                     name = fstbin_data[base_names + int.from_bytes(fstbin_data[i+1:i+4], "big"):].split(b"\x00")[0].decode("utf-8") | ||||||
|                      |                      | ||||||
|                     while id == nextdir_arr[-1]: |                     while id == nextdir_arr[-1]: | ||||||
|                         currentdir_path = currentdir_path.parent |                         currentdir_path = currentdir_path.parent | ||||||
|                         nextdir_arr.pop() |                         nextdir_arr.pop() | ||||||
|  |  | ||||||
|                     if file_type == FstTree.TYPE_DIR: |                     if file_type == FstTree.TYPE_DIR: | ||||||
|                         nextdir = int.from_bytes(fstbin_data[i+8:i+12], "big", signed=False) |                         nextdir = int.from_bytes(fstbin_data[i+8:i+12], "big") | ||||||
|                         parentdir = int.from_bytes(fstbin_data[i+4:i+8], "big", signed=False) |                         parentdir = int.from_bytes(fstbin_data[i+4:i+8], "big") | ||||||
|  |  | ||||||
|                         nextdir_arr.append( nextdir ) |                         nextdir_arr.append( nextdir ) | ||||||
|                         currentdir_path = dir_id_path[parentdir] / name |                         currentdir_path = dir_id_path[parentdir] / name | ||||||
| @@ -523,22 +858,24 @@ class Gcm: | |||||||
|                                 "The file has been removed or renamed. Use --rebuild-fst to update the FST and avoid this error." |                                 "The file has been removed or renamed. Use --rebuild-fst to update the FST and avoid this error." | ||||||
|                                 "Warning: DVD SDK use filenames to load files from the GCM/iso.") |                                 "Warning: DVD SDK use filenames to load files from the GCM/iso.") | ||||||
|                          |                          | ||||||
|                         file_offset = int.from_bytes(fstbin_data[i+4:i+8], "big", signed=False) |                         file_offset = int.from_bytes(fstbin_data[i+4:i+8], "big") | ||||||
|                         file_len   = int.from_bytes(fstbin_data[i+8:i+12], "big", signed=False) |                         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 size: {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() ) | ||||||
|         except (InvalidFSTSizeError, DolSizeOverflowError, InvalidRootFileFolderCountError, InvalidFSTFileSizeError, FSTDirNotFoundError, FSTFileNotFoundError): |         except (InvalidFSTSizeError, DolSizeOverflowError, InvalidRootFileFolderCountError, InvalidFSTFileSizeError, \ | ||||||
|  |             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): | ||||||
|         """ |         """ | ||||||
|         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 also patch boot.bin caracteristics and apploader.img or also file system changes. |         it patch boot.bin caracteristics, apploader.img and also file system changes. | ||||||
|         Game dol use filenames to find files so be carrefull when changing the root filesystem. |         Game dol use FST filenames to find files so be carrefull when changing the  | ||||||
|  |         root filesystem. Align is 0x8000 for APDCM. | ||||||
|         input: folder_path = Path |         input: folder_path = Path | ||||||
|         input: align = int |         input: align = int | ||||||
|         """ |         """ | ||||||
| @@ -547,14 +884,14 @@ class Gcm: | |||||||
|  |  | ||||||
|         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 {Path('sys/boot.bin')} offset 0x{BootBin.DOLOFFSET_OFFSET:x} with new dol offset (0x{dol_offset:x})") | ||||||
|         bootbin = BootBin((sys_path / "boot.bin").read_bytes()) |         self.__bootbin = BootBin(bytearray((sys_path / "boot.bin").read_bytes())) | ||||||
|         bootbin.set_dol_offset(dol_offset) |         self.__bootbin.set_dol_offset(dol_offset) | ||||||
|          |          | ||||||
|         fst_offset = align_top(dol_offset + (sys_path / "boot.dol").stat().st_size, align) |         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{fst_offset:x})") |         logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.FSTOFFSET_OFFSET:x} with new FST offset (0x{fstbin_offset:x})") | ||||||
|         bootbin.set_fst_offset(fst_offset) |         self.__bootbin.set_fst_offset(fstbin_offset) | ||||||
|          |          | ||||||
|         fst_tree = FstTree(root_path, fst_offset, align=align) |         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 |         # Sorting paths approach original fst sort, but in original fst specials chars are after and not before chars | ||||||
|         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())) | ||||||
| @@ -567,46 +904,52 @@ class Gcm: | |||||||
|         logging.info(f"Writing fst in {Path('sys/fst.bin')}") |         logging.info(f"Writing fst in {Path('sys/fst.bin')}") | ||||||
|         fst_path.write_bytes( fst_tree.generate_fst() ) |         fst_path.write_bytes( fst_tree.generate_fst() ) | ||||||
|  |  | ||||||
|         fst_size = fst_path.stat().st_size |         fstbin_len = fst_path.stat().st_size | ||||||
|         logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.FSTLEN_OFFSET:x} with new FST size (0x{fst_size:x})") |         logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.FSTLEN_OFFSET:x} with new FST size (0x{fstbin_len:x})") | ||||||
|         bootbin.set_fst_len(fst_size) |         self.__bootbin.set_fst_len(fstbin_len) | ||||||
|         logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.MAXFSTLEN_OFFSET:x} with new FST max size (0x{fst_size:x})") |         logging.info(f"Patching {Path('sys/boot.bin')} offset 0x{BootBin.FSTMAXLEN_OFFSET:x} with new FST max size (0x{fstbin_len:x})") | ||||||
|         bootbin.set_max_fst_len(fst_size) |         self.__bootbin.set_fst_max_len(fstbin_len) | ||||||
|  |  | ||||||
|         (sys_path / "boot.bin").write_bytes(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): | ||||||
|         """ |         """ | ||||||
|         Load system files from an unpacked GCM/iso folder and returns informations for the stats command. |         Load system files from an unpacked GCM/iso folder and returns informations for the stats command. | ||||||
|         input: folder_path = Path |         input: folder_path = Path | ||||||
|         return (BootBin, apploader_size:int, dol_len:int, fstbin_data:bytes) |         return (dol_len:int, fstbin_data:bytes) | ||||||
|  |         load __bootbin, __bi2bin, __apploaderimg | ||||||
|         """ |         """ | ||||||
|         sys_path = folder_path / "sys" |         sys_path = folder_path / "sys" | ||||||
|         bootbin = BootBin((sys_path / "boot.bin").read_bytes()) |         self.__bootbin = BootBin((sys_path / "boot.bin").read_bytes()) | ||||||
|         apploader_size = (sys_path / "apploader.img").stat().st_size |         self.__bi2bin = Bi2Bin((sys_path / "bi2.bin").read_bytes()) | ||||||
|  |         self.__apploaderimg = ApploaderImg((sys_path / "apploader.img").read_bytes()) | ||||||
|  |  | ||||||
|         dol_len = (sys_path / "boot.dol").stat().st_size |         dol_len = (sys_path / "boot.dol").stat().st_size | ||||||
|         fstbin_data = (sys_path / "fst.bin").read_bytes() |         fstbin_data = (sys_path / "fst.bin").read_bytes() | ||||||
|         return (bootbin, apploader_size, dol_len, fstbin_data) |         return (dol_len, fstbin_data) | ||||||
|     def __get_sys_from_file(self, file_path:Path): |     def __get_sys_from_file(self, file_path:Path): | ||||||
|         """ |         """ | ||||||
|         Load system files from a GCM/iso file and returns informations for the stats command. |         Load system files from a GCM/iso file and returns informations for the stats command. | ||||||
|         input: folder_path = Path |         input: folder_path = Path | ||||||
|         return (BootBin, apploader_size:int, dol_len:int, fstbin_data:bytes) |         return (dol_len:int, fstbin_data:bytes) | ||||||
|  |         load __bootbin, __bi2bin, __apploaderimg | ||||||
|         """ |         """ | ||||||
|         bootbin = None |  | ||||||
|         apploader_size = None |  | ||||||
|         dol_len = None |         dol_len = None | ||||||
|         fstbin_data = None |         fstbin_data = None | ||||||
|         with file_path.open("rb") as iso_file: |         with file_path.open("rb") as iso_file: | ||||||
|             bootbin = BootBin(iso_file.read(BootBin.LEN)) |             self.__bootbin = BootBin(iso_file.read(BootBin.LEN)) | ||||||
|             iso_file.seek(Gcm.APPLOADERSIZE_OFFSET) |             self.__bi2bin = Bi2Bin(iso_file.read(Bi2Bin.LEN)) | ||||||
|             apploader_size = Gcm.APPLOADER_HEADER_LEN + int.from_bytes(iso_file.read(4), "big", signed=False) + int.from_bytes(iso_file.read(4), "big", signed=False) |              | ||||||
|  |             iso_file.seek(Gcm.APPLOADERLEN_OFFSET) | ||||||
|  |             apploader_size = Gcm.APPLOADER_HEADER_LEN + int.from_bytes(iso_file.read(4), "big") + int.from_bytes(iso_file.read(4), "big") | ||||||
|  |             iso_file.seek(Gcm.APPLOADER_OFFSET) | ||||||
|  |             self.__apploaderimg = ApploaderImg(iso_file.read(apploader_size)) | ||||||
|  |  | ||||||
|             dol = Dol() |             dol = Dol() | ||||||
|             iso_file.seek( bootbin.dol_offset() ) |             iso_file.seek( self.__bootbin.dol_offset() ) | ||||||
|             dol_len = dol.get_dol_len( iso_file.read(Dol.HEADER_LEN) ) |             dol_len = dol.get_dol_len( iso_file.read(Dol.HEADER_LEN) ) | ||||||
|             iso_file.seek( bootbin.fstbin_offset() ) |             iso_file.seek( self.__bootbin.fst_offset() ) | ||||||
|             fstbin_data = iso_file.read(bootbin.fstbin_len()) |             fstbin_data = iso_file.read(self.__bootbin.fst_len()) | ||||||
|         return (bootbin, apploader_size, dol_len, fstbin_data) |         return (dol_len, fstbin_data) | ||||||
|     def stats(self, path:Path, align:int = 4): |     def stats(self, path:Path, align:int = 4): | ||||||
|         """ |         """ | ||||||
|         Print SYS files informations, global memory mapping, empty spaces inside the GCM/iso |         Print SYS files informations, global memory mapping, empty spaces inside the GCM/iso | ||||||
| @@ -614,7 +957,39 @@ class Gcm: | |||||||
|         * path = Path (folder or iso/GCM file) |         * path = Path (folder or iso/GCM file) | ||||||
|         * align = int |         * align = int | ||||||
|         """ |         """ | ||||||
|         (bootbin, apploader_size, dol_len, fstbin_data) = self.__get_sys_from_folder(path) if path.is_dir() else self.__get_sys_from_file(path) |         (dol_len, fstbin_data) = self.__get_sys_from_folder(path) if path.is_dir() else self.__get_sys_from_file(path) | ||||||
|  |  | ||||||
|  |         global_stats = f"# Stats for \"{path}\":\n\n" + \ | ||||||
|  |             "[boot.bin]\n" + \ | ||||||
|  |             f"GameCode = {self.__bootbin.game_code()}\n" + \ | ||||||
|  |             f"MakerCode = {self.__bootbin.maker_code()}\n" + \ | ||||||
|  |             f"DiskNumber = {self.__bootbin.disk_number()}\n" + \ | ||||||
|  |             f"GameVersion = {self.__bootbin.game_version()}\n" + \ | ||||||
|  |             f"AudioStreaming = {self.__bootbin.audio_streaming()}\n" + \ | ||||||
|  |             f"StreamBufferSize = {self.__bootbin.stream_buffer_size()}\n" + \ | ||||||
|  |             f"DVDMagic = 0x{self.__bootbin.dvd_magic().hex()}\n" + \ | ||||||
|  |             f"GameName = {self.__bootbin.game_name()}\n" + \ | ||||||
|  |             f"DolOffset = 0x{self.__bootbin.dol_offset():x}\n" + \ | ||||||
|  |             f"FstOffset = 0x{self.__bootbin.fst_offset():x}\n" + \ | ||||||
|  |             f"FstLen = 0x{self.__bootbin.fst_len():x}\n" + \ | ||||||
|  |             f"FstMaxLen = 0x{self.__bootbin.fst_max_len():x}\n\n" + \ | ||||||
|  |             "[bi2.bin]\n" + \ | ||||||
|  |             f"DebugMonitorSize = 0x{self.__bi2bin.debug_monitor_size():x}\n" + \ | ||||||
|  |             f"SimulatedMemorySize = 0x{self.__bi2bin.simulated_memory_size():x}\n" + \ | ||||||
|  |             f"ArgumentOffset = 0x{self.__bi2bin.argument_offset():x}\n" + \ | ||||||
|  |             f"DebugFlag = {self.__bi2bin.debug_flag()}\n" + \ | ||||||
|  |             f"TrackLocation = 0x{self.__bi2bin.track_location():x}\n" + \ | ||||||
|  |             f"TrackSize = 0x{self.__bi2bin.track_size():x}\n" + \ | ||||||
|  |             f"CountryCode = {self.__bi2bin.country_code()}\n" + \ | ||||||
|  |             f"TotalDisk = {self.__bi2bin.total_disk()}\n" + \ | ||||||
|  |             f"LongFileNameSupport = {self.__bi2bin.long_file_name_support()}\n\n" + \ | ||||||
|  |             "[apploader.img]\n" + \ | ||||||
|  |             f"Version = {self.__apploaderimg.version()}\n" + \ | ||||||
|  |             f"EntryPoint = 0x{self.__apploaderimg.entry_point():x}\n" + \ | ||||||
|  |             f"Size = 0x{self.__apploaderimg.size():x}\n" + \ | ||||||
|  |             f"TrailerSize = 0x{self.__apploaderimg.trailer_size():x}\n" | ||||||
|  |  | ||||||
|  |         print(global_stats) | ||||||
|  |  | ||||||
|         class MemoryObject: |         class MemoryObject: | ||||||
|             def __init__(self, name:str, beg_offset:int, length:int): |             def __init__(self, name:str, beg_offset:int, length:int): | ||||||
| @@ -627,16 +1002,16 @@ class Gcm: | |||||||
|  |  | ||||||
|         mem_obj_list = [ |         mem_obj_list = [ | ||||||
|             MemoryObject("boot.bin", 0, BootBin.LEN), |             MemoryObject("boot.bin", 0, BootBin.LEN), | ||||||
|             MemoryObject("bi2.bin", 0x440, Gcm.BI2BIN_LEN), |             MemoryObject("bi2.bin", 0x440, Bi2Bin.LEN), | ||||||
|             MemoryObject("apploader.img", Gcm.APPLOADER_OFFSET, apploader_size), |             MemoryObject("apploader.img", Gcm.APPLOADER_OFFSET, self.__apploaderimg.len()), | ||||||
|             MemoryObject("fst.bin", bootbin.fstbin_offset(), bootbin.fstbin_len()), |             MemoryObject("fst.bin", self.__bootbin.fst_offset(), self.__bootbin.fst_len()), | ||||||
|             MemoryObject("boot.dol", bootbin.dol_offset(), dol_len)] |             MemoryObject("boot.dol", self.__bootbin.dol_offset(), dol_len)] | ||||||
|  |  | ||||||
|         dir_id_path = {0: Path(".")} |         dir_id_path = {0: Path(".")} | ||||||
|         currentdir_path = Path(".") |         currentdir_path = Path(".") | ||||||
|  |  | ||||||
|         # root: id=0 so nextdir is the end |         # root: id=0 so nextdir is the end | ||||||
|         nextdir = int.from_bytes(fstbin_data[8:12], "big", signed=False) |         nextdir = int.from_bytes(fstbin_data[8:12], "big") | ||||||
|         # offset of filenames block |         # offset of filenames block | ||||||
|         base_names = nextdir * 12 |         base_names = nextdir * 12 | ||||||
|         # go to parent when id reach next dir |         # go to parent when id reach next dir | ||||||
| @@ -644,23 +1019,23 @@ class Gcm: | |||||||
|  |  | ||||||
|         for id in range(1, base_names // 12): |         for id in range(1, base_names // 12): | ||||||
|             i = id * 12 |             i = id * 12 | ||||||
|             file_type = int.from_bytes(fstbin_data[i:i+1], "big", signed=False) |             file_type = int.from_bytes(fstbin_data[i:i+1], "big") | ||||||
|             name = fstbin_data[base_names + int.from_bytes(fstbin_data[i+1:i+4], "big", signed=False):].split(b"\x00")[0].decode("utf-8") |             name = fstbin_data[base_names + int.from_bytes(fstbin_data[i+1:i+4], "big"):].split(b"\x00")[0].decode("utf-8") | ||||||
|              |              | ||||||
|             while id == nextdir_arr[-1]: |             while id == nextdir_arr[-1]: | ||||||
|                 currentdir_path = currentdir_path.parent |                 currentdir_path = currentdir_path.parent | ||||||
|                 nextdir_arr.pop() |                 nextdir_arr.pop() | ||||||
|  |  | ||||||
|             if file_type == FstTree.TYPE_DIR: |             if file_type == FstTree.TYPE_DIR: | ||||||
|                 nextdir = int.from_bytes(fstbin_data[i+8:i+12], "big", signed=False) |                 nextdir = int.from_bytes(fstbin_data[i+8:i+12], "big") | ||||||
|                 parentdir = int.from_bytes(fstbin_data[i+4:i+8], "big", signed=False) |                 parentdir = int.from_bytes(fstbin_data[i+4:i+8], "big") | ||||||
|  |  | ||||||
|                 nextdir_arr.append( nextdir ) |                 nextdir_arr.append( nextdir ) | ||||||
|                 currentdir_path = dir_id_path[parentdir] / name |                 currentdir_path = dir_id_path[parentdir] / name | ||||||
|                 dir_id_path[id] = currentdir_path |                 dir_id_path[id] = currentdir_path | ||||||
|             else: |             else: | ||||||
|                 fileoffset = int.from_bytes(fstbin_data[i+4:i+8], "big", signed=False) |                 fileoffset = int.from_bytes(fstbin_data[i+4:i+8], "big") | ||||||
|                 filesize   = int.from_bytes(fstbin_data[i+8:i+12], "big", signed=False) |                 filesize   = int.from_bytes(fstbin_data[i+8:i+12], "big") | ||||||
|                 mem_obj_list.append( MemoryObject(str(currentdir_path / name), fileoffset, filesize) ) |                 mem_obj_list.append( MemoryObject(str(currentdir_path / name), fileoffset, filesize) ) | ||||||
|  |  | ||||||
|         mem_obj_list.sort(key=lambda x: x.beg_offset) |         mem_obj_list.sort(key=lambda x: x.beg_offset) | ||||||
| @@ -675,7 +1050,7 @@ class Gcm: | |||||||
|             elif last_aligned > mem_obj.beg_offset: |             elif last_aligned > mem_obj.beg_offset: | ||||||
|                 collision_list += [last_mem_obj, mem_obj] |                 collision_list += [last_mem_obj, mem_obj] | ||||||
|             last_mem_obj = mem_obj |             last_mem_obj = mem_obj | ||||||
|         print(f"# Stats for \"{path}\":") |          | ||||||
|         self.__print("Global memory mapping:", mem_obj_list) |         self.__print("Global memory mapping:", mem_obj_list) | ||||||
|         if empty_space_list: |         if empty_space_list: | ||||||
|             self.__print(f"Empty spaces (align={align}):", empty_space_list) |             self.__print(f"Empty spaces (align={align}):", empty_space_list) | ||||||
| @@ -725,10 +1100,10 @@ def get_argparser(): | |||||||
|     group = parser.add_mutually_exclusive_group(required=True) |     group = parser.add_mutually_exclusive_group(required=True) | ||||||
|     group.add_argument('-p', '--pack', action='store_true', help="-p source_folder (dest_file.iso): Pack source_folder in new file source_folder.iso or dest_file.iso if specified.") |     group.add_argument('-p', '--pack', action='store_true', help="-p source_folder (dest_file.iso): Pack source_folder in new file source_folder.iso or dest_file.iso if specified.") | ||||||
|     group.add_argument('-u', '--unpack', action='store_true', help="-u source_iso.iso (dest_folder): Unpack the GCM/ISO in new folder source_iso or dest_folder if specified.") |     group.add_argument('-u', '--unpack', action='store_true', help="-u source_iso.iso (dest_folder): Unpack the GCM/ISO in new folder source_iso or dest_folder if specified.") | ||||||
|     group.add_argument('-s', '--stats', action='store_true', help="-s source_iso.iso or source_folder: Get stats about GCM, FST, memory, lengths and offsets.") |     group.add_argument('-s', '--stats', action='store_true', help="-s source_iso.iso or source_folder (-a 4): Get stats about GCM, FST, memory, lengths and offsets.") | ||||||
|     group.add_argument('-r', '--rebuild-fst', action='store_true', help="-r game_folder: Rebuild the game_folder/sys/fst.bin using files in game_folder/root.") |     group.add_argument('-r', '--rebuild-fst', action='store_true', help="-r game_folder (-a 4): Rebuild the game_folder/sys/fst.bin using files in game_folder/root. For ADPCM (...) use 0x8000 align.") | ||||||
|     group.add_argument('-ur', '--unpack-rebuild-fst', action='store_true', help="-ur source_iso.iso (dest_folder): Unpack and rebuild the FST.") |     group.add_argument('-ur', '--unpack-rebuild-fst', action='store_true', help="-ur source_iso.iso (dest_folder) (-a 4): Unpack and rebuild the FST.") | ||||||
|     group.add_argument('-rp', '--rebuild-fst-pack', action='store_true', help="-rp source_folder (dest_file.iso): Rebuild the FST and pack.") |     group.add_argument('-rp', '--rebuild-fst-pack', action='store_true', help="-rp source_folder (dest_file.iso) (-a 4): Rebuild the FST and pack.") | ||||||
|     return parser |     return parser | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user