Toolbox for analyzing and editing pkg application files for psp,ps3, ps4 and ps5, includes the most useful functions you might need.
| 1 | import struct |
| 2 | import sys |
| 3 | import os |
| 4 | import logging |
| 5 | import ctypes |
| 6 | import argparse |
| 7 | import io |
| 8 | import json |
| 9 | from contextlib import redirect_stdout |
| 10 | |
| 11 | # Aggiungi la directory root al path di Python |
| 12 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
| 13 | |
| 14 | # Import dei moduli |
| 15 | from GraphicUserInterface.main_window import MainWindow |
| 16 | from packages import PackagePS4, PackagePS5, PackagePS3 |
| 17 | from Utilities.Trophy import Archiver, TrophyFile, TRPCreator, TRPReader |
| 18 | from file_operations import extract_file, inject_file, modify_file_header |
| 19 | from Utilities import Logger, SettingsManager |
| 20 | from tools.repack import Repack |
| 21 | from tools.PS5_Game_Info import PS5GameInfo |
| 22 | from PyQt5.QtWidgets import QApplication |
| 23 | |
| 24 | # Configure logging |
| 25 | logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') |
| 26 | |
| 27 | def check_settings_file_presence(): |
| 28 | """Check and create necessary directories and settings file""" |
| 29 | temp_directory = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PS4PKGToolTemp") |
| 30 | if not os.path.exists(temp_directory): |
| 31 | os.makedirs(temp_directory) |
| 32 | Logger.log_information("Creating PS4PKGToolTemp directory...") |
| 33 | |
| 34 | settings_file_path = os.path.join(temp_directory, "settings.json") |
| 35 | if not os.path.exists(settings_file_path) or os.path.getsize(settings_file_path) == 0: |
| 36 | create_default_settings(settings_file_path) |
| 37 | |
| 38 | return temp_directory, settings_file_path |
| 39 | |
| 40 | def create_default_settings(settings_file_path): |
| 41 | """Create default settings file""" |
| 42 | default_settings = { |
| 43 | "theme": "Light", |
| 44 | "night_mode": False, |
| 45 | "font": "Arial", |
| 46 | "font_size": 12, |
| 47 | "bg_color": "#ffffff", |
| 48 | "text_color": "#000000", |
| 49 | "accent_color": "#3498db", |
| 50 | "auto_expand": True, |
| 51 | "show_hidden": False, |
| 52 | "confirm_exit": True, |
| 53 | "output_path": "", |
| 54 | "temp_path": "", |
| 55 | "pkg_directories": [], |
| 56 | "scan_recursive": False, |
| 57 | "play_bgm": False, |
| 58 | "show_directory_settings_at_startup": True, |
| 59 | "auto_sort_row": False, |
| 60 | "local_server_ip": "", |
| 61 | "ps4_ip": "", |
| 62 | "nodejs_installed": False, |
| 63 | "http_server_installed": False, |
| 64 | "official_update_download_directory": "", |
| 65 | "pkg_color_label": False, |
| 66 | "game_pkg_forecolor": 0xDDDDDD, |
| 67 | "patch_pkg_forecolor": 0xDDDDDD, |
| 68 | "addon_pkg_forecolor": 0xDDDDDD, |
| 69 | "app_pkg_forecolor": 0xDDDDDD, |
| 70 | "game_pkg_backcolor": 0x333333, |
| 71 | "patch_pkg_backcolor": 0x333333, |
| 72 | "addon_pkg_backcolor": 0x333333, |
| 73 | "app_pkg_backcolor": 0x333333, |
| 74 | "rename_custom_format": "", |
| 75 | "ps5bc_json_download_date": "", |
| 76 | "psvr_neo_ps5bc_check": False, |
| 77 | "pkg_titleId_column": True, |
| 78 | "pkg_contentId_column": True, |
| 79 | "pkg_region_column": True, |
| 80 | "pkg_minimum_firmware_column": True, |
| 81 | "pkg_version_column": True, |
| 82 | "pkg_type_column": True, |
| 83 | "pkg_category_column": True, |
| 84 | "pkg_size_column": True, |
| 85 | "pkg_location_column": True, |
| 86 | "pkg_backport_column": True |
| 87 | } |
| 88 | |
| 89 | with open(settings_file_path, 'w') as f: |
| 90 | json.dump(default_settings, f, indent=4) |
| 91 | Logger.log_information("Default settings created.") |
| 92 | |
| 93 | def execute_command(cmd, pkg, file, out, update_callback_info): |
| 94 | """Execute PKG related commands""" |
| 95 | logging.debug(f"execute_command called with cmd={cmd}, pkg={pkg}, file={file}, out={out}") |
| 96 | if not cmd or not pkg: |
| 97 | raise ValueError("The 'Command' and 'PKG' fields are required.") |
| 98 | |
| 99 | try: |
| 100 | # Determine package type and create appropriate instance |
| 101 | with open(pkg, "rb") as fp: |
| 102 | magic = struct.unpack(">I", fp.read(4))[0] |
| 103 | if magic == PackagePS4.MAGIC_PS4: |
| 104 | target = PackagePS4(pkg) |
| 105 | elif magic == PackagePS5.MAGIC_PS5: |
| 106 | target = PackagePS5(pkg) |
| 107 | elif magic == PackagePS3.MAGIC_PS3: |
| 108 | target = PackagePS3(pkg) |
| 109 | else: |
| 110 | raise ValueError(f"Unknown PKG format: {magic:08X}") |
| 111 | |
| 112 | # Execute requested command |
| 113 | if cmd == "info": |
| 114 | return get_pkg_info(target, update_callback_info) |
| 115 | elif cmd == "extract": |
| 116 | return extract_pkg_file(target, file, out, update_callback_info) |
| 117 | elif cmd == "dump": |
| 118 | return dump_pkg(target, out, update_callback_info) |
| 119 | elif cmd == "inject": |
| 120 | return inject_pkg_file(target, file, out) |
| 121 | elif cmd == "modify": |
| 122 | return modify_pkg_header(target, file, out) |
| 123 | else: |
| 124 | raise ValueError(f"Unknown command: {cmd}") |
| 125 | |
| 126 | except Exception as e: |
| 127 | logging.error(f"Error executing command: {str(e)}") |
| 128 | raise |
| 129 | |
| 130 | def get_pkg_info(package, callback): |
| 131 | """Get PKG information""" |
| 132 | f = io.StringIO() |
| 133 | with redirect_stdout(f): |
| 134 | package.info() |
| 135 | info_output = f.getvalue() |
| 136 | |
| 137 | if not info_output: |
| 138 | raise ValueError("No information found in the PKG file.") |
| 139 | |
| 140 | info_dict = {} |
| 141 | for line in info_output.split('\n'): |
| 142 | if ':' in line: |
| 143 | key, value = line.split(':', 1) |
| 144 | info_dict[key.strip()] = value.strip() |
| 145 | |
| 146 | callback(info_dict) |
| 147 | return info_output |
| 148 | |
| 149 | def extract_pkg_file(package, file_path, output_path, callback): |
| 150 | """Extract file from PKG""" |
| 151 | file_info = package.get_file_info(file_path) |
| 152 | extract_file(package.original_file, file_info, output_path, callback) |
| 153 | return f"File extracted: {file_path}" |
| 154 | |
| 155 | def dump_pkg(package, output_path, callback): |
| 156 | """Dump PKG contents""" |
| 157 | try: |
| 158 | result = package.dump(output_path, callback) |
| 159 | return result |
| 160 | except Exception as e: |
| 161 | raise ValueError(f"Error during dump: {str(e)}") |
| 162 | |
| 163 | def inject_pkg_file(package, file_path, input_path): |
| 164 | """Inject file into PKG""" |
| 165 | file_info = package.get_file_info(file_path) |
| 166 | injected_size = inject_file(package.original_file, file_info, input_path) |
| 167 | return f"Injected {injected_size} bytes" |
| 168 | |
| 169 | def modify_pkg_header(package, offset, new_data): |
| 170 | """Modify PKG header""" |
| 171 | modified_size = modify_file_header(package.original_file, int(offset, 16), new_data.encode()) |
| 172 | return f"Modified {modified_size} bytes" |
| 173 | |
| 174 | def is_admin(): |
| 175 | """Check if running with admin privileges""" |
| 176 | try: |
| 177 | return ctypes.windll.shell32.IsUserAnAdmin() |
| 178 | except: |
| 179 | return False |
| 180 | |
| 181 | def main(): |
| 182 | """Main application entry point""" |
| 183 | # Initialize application |
| 184 | app = QApplication(sys.argv) |
| 185 | app.setStyle('Fusion') |
| 186 | |
| 187 | # Setup directories and settings |
| 188 | temp_directory, settings_file_path = check_settings_file_presence() |
| 189 | |
| 190 | # Create and show main window |
| 191 | window = MainWindow(temp_directory) |
| 192 | window.show() |
| 193 | |
| 194 | # Start application |
| 195 | sys.exit(app.exec_()) |
| 196 | |
| 197 | if __name__ == "__main__": |
| 198 | main() |