Toolbox for analyzing and editing pkg application files for psp,ps3, ps4 and ps5, includes the most useful functions you might need.
| 1 | import os |
| 2 | import re |
| 3 | import logging |
| 4 | from Crypto.Cipher import AES |
| 5 | from Crypto.Util.Padding import unpad |
| 6 | import xml.etree.ElementTree as ET |
| 7 | import requests |
| 8 | import time |
| 9 | |
| 10 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
| 11 | logger = logging.getLogger(__name__) |
| 12 | |
| 13 | class ESMFDecrypter: |
| 14 | def __init__(self): |
| 15 | self.trophy_key = bytes([ |
| 16 | 0x21, 0xF4, 0x1A, 0x6B, 0xAD, 0x8A, 0x1D, 0x3E, |
| 17 | 0xCA, 0x7A, 0xD5, 0x86, 0xC1, 0x01, 0xB7, 0xA9 |
| 18 | ]) |
| 19 | self.valid_np_com_ids = [] |
| 20 | |
| 21 | def decrypt_esfm_file(self, file_path, np_com_id, output_folder): |
| 22 | logger.info(f"Starting decryption of file: {file_path}") |
| 23 | |
| 24 | iv = bytes([0] * 16) |
| 25 | cipher = AES.new(self.trophy_key, AES.MODE_CBC, iv) |
| 26 | key = cipher.encrypt(np_com_id.ljust(16, '\0').encode()) |
| 27 | |
| 28 | with open(file_path, 'rb') as file: |
| 29 | encrypted_data = file.read() |
| 30 | |
| 31 | total_size = len(encrypted_data) |
| 32 | chunk_size = AES.block_size |
| 33 | |
| 34 | cipher = AES.new(key, AES.MODE_CBC, iv) |
| 35 | decrypted_data = bytearray() |
| 36 | for i in range(0, total_size, chunk_size): |
| 37 | chunk = encrypted_data[i:i + chunk_size] |
| 38 | decrypted_chunk = cipher.decrypt(chunk) |
| 39 | decrypted_data.extend(decrypted_chunk) |
| 40 | logger.debug(f"Decrypted {i + chunk_size} of {total_size} bytes") |
| 41 | |
| 42 | decrypted_data = unpad(decrypted_data, AES.block_size) |
| 43 | decrypted_data = ''.join(chr(byte) for byte in decrypted_data if 32 <= byte <= 126 or byte in (9, 10, 13)) |
| 44 | |
| 45 | try: |
| 46 | decrypted_xml = ET.fromstring(decrypted_data) |
| 47 | logger.info("XML decrypted and parsed successfully") |
| 48 | except ET.ParseError as e: |
| 49 | logger.error(f"Error parsing decrypted XML: {e}") |
| 50 | return None |
| 51 | |
| 52 | output_file_path = os.path.join(output_folder, os.path.basename(file_path)[:-5] + ".xml") |
| 53 | with open(output_file_path, 'w', encoding='utf-8') as output_file: |
| 54 | output_file.write(decrypted_data) |
| 55 | |
| 56 | logger.info(f"Decrypted file saved to: {output_file_path}") |
| 57 | return output_file_path |
| 58 | |
| 59 | @staticmethod |
| 60 | def validate_np_com_id(np_com_id): |
| 61 | return re.match(r'^NPWR\d{5}_\d{2}$', np_com_id) is not None |
| 62 | |
| 63 | def brute_force_np_com_ids(self, start=1, end=25000, delay=1): |
| 64 | base_url = "https://m.np.playstation.com/api/trophy/v1/npCommunicationIds/{}/trophyGroups" |
| 65 | headers = { |
| 66 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3" |
| 67 | } |
| 68 | |
| 69 | for i in range(start, end + 1): |
| 70 | np_com_id = f"NPWR{i:05d}_00" |
| 71 | url = base_url.format(np_com_id) |
| 72 | |
| 73 | try: |
| 74 | response = requests.get(url, headers=headers) |
| 75 | if response.status_code == 200: |
| 76 | data = response.json() |
| 77 | title_name = data.get('trophyTitleName', 'Unknown') |
| 78 | logger.info(f"Valid NP Communication ID found: {np_com_id} - {title_name}") |
| 79 | self.valid_np_com_ids.append((np_com_id, title_name)) |
| 80 | else: |
| 81 | logger.debug(f"Invalid or non-existent NP Communication ID: {np_com_id}") |
| 82 | except Exception as e: |
| 83 | logger.error(f"Error checking NP Communication ID {np_com_id}: {str(e)}") |
| 84 | |
| 85 | time.sleep(delay) |
| 86 | |
| 87 | logger.info(f"Brute force completed. Found {len(self.valid_np_com_ids)} valid NP Communication IDs.") |
| 88 | return self.valid_np_com_ids |
| 89 | |
| 90 | def decrypt_esfm_file(file_path, np_com_id, output_folder): |
| 91 | decrypter = ESMFDecrypter() |
| 92 | if not decrypter.validate_np_com_id(np_com_id): |
| 93 | logger.error("Invalid NP communication ID. Correct format: NPWRYYYYY_ZZ") |
| 94 | return None |
| 95 | return decrypter.decrypt_esfm_file(file_path, np_com_id, output_folder) |
| 96 | |
| 97 | if __name__ == "__main__": |
| 98 | decrypter = ESMFDecrypter() |
| 99 | valid_ids = decrypter.brute_force_np_com_ids(start=1, end=100) |
| 100 | |
| 101 | for np_com_id, title_name in valid_ids: |
| 102 | print(f"NP Communication ID: {np_com_id} - Title: {title_name}") |
| 103 | |
| 104 | |
| 105 | file_path = input("Enter the path of the ESFM file: ") |
| 106 | output_folder = input("Enter the output folder: ") |
| 107 | np_com_id = input("Enter the NP communication ID (format: NPWRYYYYY_ZZ): ") |
| 108 | |
| 109 | decrypted_file_path = decrypt_esfm_file(file_path, np_com_id, output_folder) |
| 110 | if decrypted_file_path: |
| 111 | logger.info(f"Decryption completed. File saved to: {decrypted_file_path}") |
| 112 | else: |
| 113 | logger.error("Error during file decryption.") |