Toolbox for analyzing and editing pkg application files for psp,ps3, ps4 and ps5, includes the most useful functions you might need.
| 1 | #Module created by sinajet, implemented by SeregonWar in PkgToolBox. |
| 2 | import json |
| 3 | import os |
| 4 | from pathlib import Path |
| 5 | import io |
| 6 | |
| 7 | class PS5GameInfo: |
| 8 | def __init__(self): |
| 9 | self.gPath = '' |
| 10 | self.gVer = '' |
| 11 | self.region = '' |
| 12 | self.gname = '' |
| 13 | self.sVer = '' |
| 14 | self.Fcheck = '' |
| 15 | self.Fsize = '' |
| 16 | self.main_dict = {} |
| 17 | |
| 18 | def convert_bytes(self, size): |
| 19 | for x in ['bytes', 'KB', 'MB', 'GB', 'TB']: |
| 20 | if size < 1024.0: |
| 21 | return '%3.1f%s' % (size, x) |
| 22 | size /= 1024.0 |
| 23 | return size |
| 24 | |
| 25 | def folder_size(self, path='.'): |
| 26 | total_size = 0 |
| 27 | path = Path(path) |
| 28 | for item in path.glob('**/*'): |
| 29 | if item.is_file(): |
| 30 | total_size += item.stat().st_size |
| 31 | return self.convert_bytes(total_size) |
| 32 | |
| 33 | def region_convertor(self, R): |
| 34 | if R == "UP": |
| 35 | return "US" |
| 36 | elif R == "EP": |
| 37 | return "EU" |
| 38 | else: |
| 39 | return R |
| 40 | |
| 41 | def version_corrector(self, v): |
| 42 | return v[:2] + "." + v[2:4] + "." + v[4:6] + "." + v[6:8] |
| 43 | |
| 44 | def param_table_inputer(self): |
| 45 | with open(os.path.join(self.gPath, "sce_sys/param.json"), "r") as f: |
| 46 | dict_param = json.load(f) |
| 47 | |
| 48 | if "localizedParameters" in dict_param: |
| 49 | self.gname = dict_param["localizedParameters"]["en-US"]["titleName"].replace("â€", "-") |
| 50 | else: |
| 51 | self.gname = "" |
| 52 | |
| 53 | if "contentVersion" in dict_param: |
| 54 | self.gVer = dict_param["contentVersion"] |
| 55 | dict_param.pop("contentVersion") |
| 56 | else: |
| 57 | self.gVer = "" |
| 58 | |
| 59 | if "titleId" in dict_param: |
| 60 | title_id = dict_param["titleId"] |
| 61 | dict_param.pop("titleId") |
| 62 | else: |
| 63 | title_id = "" |
| 64 | |
| 65 | if "contentId" in dict_param: |
| 66 | content_id = dict_param["contentId"] |
| 67 | self.region = self.region_convertor(content_id[:2]) |
| 68 | dict_param.pop("contentId") |
| 69 | else: |
| 70 | content_id = "" |
| 71 | self.region = "" |
| 72 | |
| 73 | if "requiredSystemSoftwareVersion" in dict_param: |
| 74 | sys_ver = self.version_corrector(dict_param["requiredSystemSoftwareVersion"][2:]) |
| 75 | self.sVer = sys_ver[:5] |
| 76 | dict_param.pop("requiredSystemSoftwareVersion") |
| 77 | else: |
| 78 | sys_ver = "" |
| 79 | self.sVer = "" |
| 80 | |
| 81 | if "sdkVersion" in dict_param: |
| 82 | sdk_ver = self.version_corrector(dict_param["sdkVersion"][2:]) |
| 83 | dict_param.pop("sdkVersion") |
| 84 | else: |
| 85 | sdk_ver = "" |
| 86 | |
| 87 | self.main_dict = { |
| 88 | "Title Name": self.gname, |
| 89 | "Content Version": self.gVer, |
| 90 | "Title ID": title_id, |
| 91 | "Content ID": content_id, |
| 92 | "Required System Software Version": sys_ver, |
| 93 | "SDK Version": sdk_ver, |
| 94 | "Fake Self": "True" if self.Fcheck == '(<span style=" color:#55aa00;">Fake</span>)' else "False" |
| 95 | } |
| 96 | |
| 97 | # Aggiungi altri parametri al main_dict |
| 98 | for key, value in dict_param.items(): |
| 99 | if isinstance(value, dict): |
| 100 | for sub_key, sub_value in value.items(): |
| 101 | self.main_dict[f"{key}_{sub_key}"] = str(sub_value) |
| 102 | else: |
| 103 | self.main_dict[key] = str(value) |
| 104 | |
| 105 | def eboot_fake_checker(self): |
| 106 | with open(os.path.join(self.gPath, "eboot.bin"), "r", errors="ignore") as f: |
| 107 | eboot_txt = f.read() |
| 108 | if "ELF" in eboot_txt[:len("ELF")]: |
| 109 | return '(<span style=" color:#036494;">official</span>)' |
| 110 | else: |
| 111 | return '(<span style=" color:#55aa00;">Fake</span>)' |
| 112 | |
| 113 | def eboot_fake_checker_from_data(self, eboot_data): |
| 114 | eboot_txt = eboot_data[:100].decode('utf-8', errors='ignore') |
| 115 | if "ELF" in eboot_txt[:len("ELF")]: |
| 116 | return '(<span style=" color:#036494;">official</span>)' |
| 117 | else: |
| 118 | return '(<span style=" color:#55aa00;">Fake</span>)' |
| 119 | |
| 120 | def param_table_inputer_from_data(self, param_data): |
| 121 | dict_param = json.loads(param_data.decode('utf-8')) |
| 122 | |
| 123 | if "localizedParameters" in dict_param: |
| 124 | self.gname = dict_param["localizedParameters"]["en-US"]["titleName"].replace("â€", "-") |
| 125 | else: |
| 126 | self.gname = "" |
| 127 | |
| 128 | if "contentVersion" in dict_param: |
| 129 | self.gVer = dict_param["contentVersion"] |
| 130 | dict_param.pop("contentVersion") |
| 131 | else: |
| 132 | self.gVer = "" |
| 133 | |
| 134 | if "titleId" in dict_param: |
| 135 | title_id = dict_param["titleId"] |
| 136 | dict_param.pop("titleId") |
| 137 | else: |
| 138 | title_id = "" |
| 139 | |
| 140 | if "contentId" in dict_param: |
| 141 | content_id = dict_param["contentId"] |
| 142 | self.region = self.region_convertor(content_id[:2]) |
| 143 | dict_param.pop("contentId") |
| 144 | else: |
| 145 | content_id = "" |
| 146 | self.region = "" |
| 147 | |
| 148 | if "requiredSystemSoftwareVersion" in dict_param: |
| 149 | sys_ver = self.version_corrector(dict_param["requiredSystemSoftwareVersion"][2:]) |
| 150 | self.sVer = sys_ver[:5] |
| 151 | dict_param.pop("requiredSystemSoftwareVersion") |
| 152 | else: |
| 153 | sys_ver = "" |
| 154 | self.sVer = "" |
| 155 | |
| 156 | if "sdkVersion" in dict_param: |
| 157 | sdk_ver = self.version_corrector(dict_param["sdkVersion"][2:]) |
| 158 | dict_param.pop("sdkVersion") |
| 159 | else: |
| 160 | sdk_ver = "" |
| 161 | |
| 162 | self.main_dict = { |
| 163 | "Title Name": self.gname, |
| 164 | "Content Version": self.gVer, |
| 165 | "Title ID": title_id, |
| 166 | "Content ID": content_id, |
| 167 | "Required System Software Version": sys_ver, |
| 168 | "SDK Version": sdk_ver, |
| 169 | "Fake Self": "True" if self.Fcheck == '(<span style=" color:#55aa00;">Fake</span>)' else "False" |
| 170 | } |
| 171 | |
| 172 | # Aggiungi altri parametri al main_dict |
| 173 | for key, value in dict_param.items(): |
| 174 | if isinstance(value, dict): |
| 175 | for sub_key, sub_value in value.items(): |
| 176 | self.main_dict[f"{key}_{sub_key}"] = str(sub_value) |
| 177 | else: |
| 178 | self.main_dict[key] = str(value) |
| 179 | |
| 180 | def process(self, path): |
| 181 | self.gPath = path |
| 182 | if os.path.exists(os.path.join(self.gPath, "eboot.bin")): |
| 183 | self.Fcheck = self.eboot_fake_checker() |
| 184 | if os.path.exists(os.path.join(self.gPath, "sce_sys/param.json")): |
| 185 | self.param_table_inputer() |
| 186 | self.Fsize = self.folder_size(Path(self.gPath)) |
| 187 | return self.main_dict |
| 188 | else: |
| 189 | return {"error": "Can't find eboot file. Please select correct path."} |
| 190 | |
| 191 | def load_eboot(self, file_path): |
| 192 | """Load info from eboot.bin file""" |
| 193 | try: |
| 194 | # Leggi il file eboot.bin |
| 195 | with open(file_path, 'rb') as f: |
| 196 | data = f.read() |
| 197 | |
| 198 | # Estrai le informazioni dal file eboot.bin |
| 199 | # Questo è un esempio, dovrai adattarlo in base alla struttura reale del file |
| 200 | info = { |
| 201 | 'Title': self.extract_string(data, 0x100, 64), |
| 202 | 'Version': self.extract_string(data, 0x140, 8), |
| 203 | 'Category': self.extract_string(data, 0x148, 16), |
| 204 | 'Content ID': self.extract_string(data, 0x158, 36), |
| 205 | 'System Version': f"{data[0x160]:d}.{data[0x161]:02d}" |
| 206 | } |
| 207 | |
| 208 | self.main_dict = info |
| 209 | return info |
| 210 | |
| 211 | except Exception as e: |
| 212 | raise Exception(f"Error loading eboot.bin: {str(e)}") |
| 213 | |
| 214 | def load_param_json(self, file_path): |
| 215 | """Load info from param.json file""" |
| 216 | try: |
| 217 | import json |
| 218 | with open(file_path, 'r', encoding='utf-8') as f: |
| 219 | self.main_dict = json.load(f) |
| 220 | return self.main_dict |
| 221 | |
| 222 | except Exception as e: |
| 223 | raise Exception(f"Error loading param.json: {str(e)}") |
| 224 | |
| 225 | def extract_string(self, data, offset, length): |
| 226 | """Extract null-terminated string from binary data""" |
| 227 | try: |
| 228 | string_bytes = data[offset:offset+length] |
| 229 | null_pos = string_bytes.find(b'\x00') |
| 230 | if null_pos != -1: |
| 231 | string_bytes = string_bytes[:null_pos] |
| 232 | return string_bytes.decode('utf-8', errors='ignore').strip() |
| 233 | except Exception: |
| 234 | return "" |
| 235 | |
| 236 | def save_eboot(self, file_path, changes): |
| 237 | """Save changes to eboot.bin file""" |
| 238 | try: |
| 239 | # Leggi il file esistente |
| 240 | with open(file_path, 'rb') as f: |
| 241 | data = bytearray(f.read()) |
| 242 | |
| 243 | # Applica le modifiche |
| 244 | for key, value in changes.items(): |
| 245 | if key == "Title": |
| 246 | self.write_string(data, 0x100, value, 64) |
| 247 | elif key == "Version": |
| 248 | self.write_string(data, 0x140, value, 8) |
| 249 | elif key == "Category": |
| 250 | self.write_string(data, 0x148, value, 16) |
| 251 | elif key == "Content ID": |
| 252 | self.write_string(data, 0x158, value, 36) |
| 253 | elif key == "System Version": |
| 254 | try: |
| 255 | major, minor = map(int, value.split('.')) |
| 256 | data[0x160] = major |
| 257 | data[0x161] = minor |
| 258 | except: |
| 259 | pass |
| 260 | |
| 261 | # Salva il file modificato |
| 262 | with open(file_path, 'wb') as f: |
| 263 | f.write(data) |
| 264 | |
| 265 | except Exception as e: |
| 266 | raise Exception(f"Error saving eboot.bin: {str(e)}") |
| 267 | |
| 268 | def save_param_json(self, file_path, changes): |
| 269 | """Save changes to param.json file""" |
| 270 | try: |
| 271 | # Leggi il file esistente |
| 272 | with open(file_path, 'r', encoding='utf-8') as f: |
| 273 | param_data = json.load(f) |
| 274 | |
| 275 | # Applica le modifiche |
| 276 | for key, value in changes.items(): |
| 277 | if key == "Title Name" and "localizedParameters" in param_data: |
| 278 | param_data["localizedParameters"]["en-US"]["titleName"] = value |
| 279 | elif key == "Content Version": |
| 280 | param_data["contentVersion"] = value |
| 281 | elif key == "Title ID": |
| 282 | param_data["titleId"] = value |
| 283 | elif key == "Content ID": |
| 284 | param_data["contentId"] = value |
| 285 | elif key == "Required System Software Version": |
| 286 | param_data["requiredSystemSoftwareVersion"] = f"0{value.replace('.', '')}" |
| 287 | elif key == "SDK Version": |
| 288 | param_data["sdkVersion"] = f"0{value.replace('.', '')}" |
| 289 | else: |
| 290 | # Gestisci altri parametri |
| 291 | if "_" in key: |
| 292 | # Gestisci parametri nidificati |
| 293 | main_key, sub_key = key.split("_", 1) |
| 294 | if main_key in param_data: |
| 295 | if isinstance(param_data[main_key], dict): |
| 296 | param_data[main_key][sub_key] = value |
| 297 | else: |
| 298 | # Parametri diretti |
| 299 | param_data[key] = value |
| 300 | |
| 301 | # Salva il file modificato |
| 302 | with open(file_path, 'w', encoding='utf-8') as f: |
| 303 | json.dump(param_data, f, indent=4, ensure_ascii=False) |
| 304 | |
| 305 | except Exception as e: |
| 306 | raise Exception(f"Error saving param.json: {str(e)}") |
| 307 | |
| 308 | def write_string(self, data, offset, string, max_length): |
| 309 | """Write string to binary data with null termination""" |
| 310 | try: |
| 311 | # Converti la stringa in bytes |
| 312 | string_bytes = string.encode('utf-8') |
| 313 | # Tronca se necessario |
| 314 | if len(string_bytes) > max_length - 1: |
| 315 | string_bytes = string_bytes[:max_length-1] |
| 316 | # Aggiungi terminatore null |
| 317 | string_bytes += b'\x00' * (max_length - len(string_bytes)) |
| 318 | # Scrivi nel buffer |
| 319 | data[offset:offset+max_length] = string_bytes |
| 320 | except Exception as e: |
| 321 | raise Exception(f"Error writing string: {str(e)}") |
| 322 | |
| 323 | def get_ps5_game_info(path): |
| 324 | ps5_info = PS5GameInfo() |
| 325 | return ps5_info.process(path) |