Seregon/PkgToolBox

Toolbox for analyzing and editing pkg application files for psp,ps3, ps4 and ps5, includes the most useful functions you might need.

Python/57.3 KB/No license
tools/PS5_Game_Info.py
PkgToolBox / tools / PS5_Game_Info.py
1#Module created by sinajet, implemented by SeregonWar in PkgToolBox.
2import json
3import os
4from pathlib import Path
5import io
6 
7class 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 
323def get_ps5_game_info(path):
324 ps5_info = PS5GameInfo()
325 return ps5_info.process(path)