Toolbox for analyzing and editing pkg application files for psp,ps3, ps4 and ps5, includes the most useful functions you might need.
| 1 | import hashlib |
| 2 | import struct |
| 3 | |
| 4 | class TrophyFile: |
| 5 | def __init__(self, file_path=None): |
| 6 | self.SHA1 = "" |
| 7 | self.Bytes = None |
| 8 | self.Readbytes = False |
| 9 | self.trophyItemList = [] |
| 10 | self._iserror = False |
| 11 | self._error = "" |
| 12 | self._inputfile = "" |
| 13 | self._calculatedsha1 = "" |
| 14 | self.trphy = self.TrophyHeader() |
| 15 | |
| 16 | if file_path: |
| 17 | self.load(file_path) |
| 18 | |
| 19 | class TrophyHeader: |
| 20 | def __init__(self): |
| 21 | self.magic = bytearray(4) |
| 22 | self.version = bytearray(4) |
| 23 | self.file_size = bytearray(8) |
| 24 | self.files_count = bytearray(4) |
| 25 | self.element_size = bytearray(4) |
| 26 | self.dev_flag = bytearray(4) |
| 27 | self.sha1 = bytearray(20) |
| 28 | self.padding = bytearray(36) |
| 29 | |
| 30 | class TrophyItem: |
| 31 | def __init__(self, index, name, offset, size, total_bytes): |
| 32 | self.Index = index |
| 33 | self.Name = name |
| 34 | self.Offset = offset |
| 35 | self.Size = size |
| 36 | self.TotalBytes = total_bytes |
| 37 | |
| 38 | @property |
| 39 | def file_count(self): |
| 40 | return struct.unpack('<I', self.trphy.files_count)[0] |
| 41 | |
| 42 | @property |
| 43 | def version(self): |
| 44 | return struct.unpack('<I', self.trphy.version)[0] |
| 45 | |
| 46 | def load_header(self, fs): |
| 47 | hdr = self.TrophyHeader() |
| 48 | hdr.magic = fs.read(4) |
| 49 | hdr.version = fs.read(4) |
| 50 | hdr.file_size = fs.read(8) |
| 51 | hdr.files_count = fs.read(4) |
| 52 | hdr.element_size = fs.read(4) |
| 53 | hdr.dev_flag = fs.read(4) |
| 54 | version = struct.unpack('<I', hdr.version)[0] |
| 55 | if 1 <= version <= 3: |
| 56 | if version == 1: |
| 57 | hdr.padding = fs.read(36) |
| 58 | elif version == 2: |
| 59 | hdr.sha1 = fs.read(20) |
| 60 | hdr.padding = fs.read(16) |
| 61 | elif version == 3: |
| 62 | hdr.sha1 = fs.read(20) |
| 63 | hdr.padding = fs.read(48) |
| 64 | return hdr |
| 65 | |
| 66 | def read_content(self, fs): |
| 67 | for i in range(self.file_count): |
| 68 | array = fs.read(36) |
| 69 | array2 = fs.read(4) |
| 70 | array3 = fs.read(8) |
| 71 | array4 = fs.read(4) |
| 72 | fs.seek(12, 1) |
| 73 | name = array.decode('utf-8').replace('\0', '') |
| 74 | offset = int.from_bytes(array2, 'little') |
| 75 | size = int.from_bytes(array3, 'little') |
| 76 | if self.Readbytes: |
| 77 | with memoryview(self.Bytes) as mv: |
| 78 | total_bytes = mv[offset:offset + size].tobytes() |
| 79 | self.trophyItemList.append(self.TrophyItem(i, name, offset, size, total_bytes)) |
| 80 | else: |
| 81 | self.trophyItemList.append(self.TrophyItem(i, name, offset, size, None)) |
| 82 | |
| 83 | def calculate_sha1_hash(self): |
| 84 | if self.version > 1: |
| 85 | sha1 = hashlib.sha1() |
| 86 | with memoryview(self.Bytes) as mv: |
| 87 | sha1.update(mv[:28]) |
| 88 | sha1.update(b'\x00' * 20) |
| 89 | sha1.update(mv[48:]) |
| 90 | return sha1.hexdigest().upper() |
| 91 | return None |
| 92 | |
| 93 | def load(self, file_path): |
| 94 | self.SHA1 = "" |
| 95 | self.trophyItemList = [] |
| 96 | with open(file_path, 'rb') as file_stream: |
| 97 | self.Bytes = file_stream.read() |
| 98 | file_stream.seek(0) |
| 99 | self.trphy = self.load_header(file_stream) |
| 100 | if self.trphy.magic != b'\xdc\xa2\x4d\x00': |
| 101 | raise Exception("This file is not supported!") |
| 102 | self.read_content(file_stream) |
| 103 | if self.version > 1: |
| 104 | self.SHA1 = self.calculate_sha1_hash() |
| 105 | |
| 106 | def extract_file_to_memory(self, filename): |
| 107 | for item in self.trophyItemList: |
| 108 | if item.Name == filename: |
| 109 | with memoryview(self.Bytes) as mv: |
| 110 | return mv[item.Offset:item.Offset + item.Size].tobytes() |
| 111 | return None |
| 112 |