psptool 3.0__tar.gz → 3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. psptool-3.2/.github/workflows/gha-metrics.yml +33 -0
  2. psptool-3.2/.github/workflows/publish.yml +51 -0
  3. {psptool-3.0 → psptool-3.2}/PKG-INFO +5 -16
  4. {psptool-3.0 → psptool-3.2}/psptool/__init__.py +2 -2
  5. {psptool-3.0 → psptool-3.2}/psptool/__main__.py +101 -40
  6. {psptool-3.0 → psptool-3.2}/psptool/blob.py +5 -1
  7. {psptool-3.0 → psptool-3.2}/psptool/directory.py +18 -16
  8. psptool-3.2/psptool/entry.py +180 -0
  9. {psptool-3.0 → psptool-3.2}/psptool/fet.py +16 -3
  10. {psptool-3.0 → psptool-3.2}/psptool/file.py +162 -42
  11. psptool-3.2/psptool/microcode_file.py +60 -0
  12. {psptool-3.0 → psptool-3.2}/psptool/psptool.py +30 -3
  13. psptool-3.2/pyproject.toml +21 -0
  14. psptool-3.2/tests/gha_metrics.py +80 -0
  15. {psptool-3.0 → psptool-3.2}/tests/metrics.txt +70 -70
  16. psptool-3.0/psptool/entry.py +0 -96
  17. psptool-3.0/psptool.egg-info/PKG-INFO +0 -883
  18. psptool-3.0/psptool.egg-info/SOURCES.txt +0 -38
  19. psptool-3.0/psptool.egg-info/dependency_links.txt +0 -1
  20. psptool-3.0/psptool.egg-info/entry_points.txt +0 -2
  21. psptool-3.0/psptool.egg-info/requires.txt +0 -3
  22. psptool-3.0/psptool.egg-info/top_level.txt +0 -1
  23. psptool-3.0/setup.cfg +0 -27
  24. psptool-3.0/setup.py +0 -13
  25. {psptool-3.0 → psptool-3.2}/.github/workflows/python-tests.yml +0 -0
  26. {psptool-3.0 → psptool-3.2}/.gitignore +0 -0
  27. {psptool-3.0 → psptool-3.2}/.gitmodules +0 -0
  28. {psptool-3.0 → psptool-3.2}/LICENSE +0 -0
  29. {psptool-3.0 → psptool-3.2}/README.md +0 -0
  30. {psptool-3.0 → psptool-3.2}/psptool/cert_tree.py +0 -0
  31. {psptool-3.0 → psptool-3.2}/psptool/crypto.py +0 -0
  32. {psptool-3.0 → psptool-3.2}/psptool/errors.py +0 -0
  33. {psptool-3.0 → psptool-3.2}/psptool/firmware.py +0 -0
  34. {psptool-3.0 → psptool-3.2}/psptool/header_file.py +0 -0
  35. {psptool-3.0 → psptool-3.2}/psptool/key_store_file.py +0 -0
  36. {psptool-3.0 → psptool-3.2}/psptool/pubkey_file.py +0 -0
  37. {psptool-3.0 → psptool-3.2}/psptool/rom.py +0 -0
  38. {psptool-3.0 → psptool-3.2}/psptool/utils.py +0 -0
  39. {psptool-3.0 → psptool-3.2}/tests/__init__.py +0 -0
  40. {psptool-3.0 → psptool-3.2}/tests/create_metrics.sh +0 -0
  41. {psptool-3.0 → psptool-3.2}/tests/integration/__init__.py +0 -0
  42. {psptool-3.0 → psptool-3.2}/tests/integration/test_psptrace.py +0 -0
  43. {psptool-3.0 → psptool-3.2}/tests/integration/test_rom_files.py +0 -0
  44. {psptool-3.0 → psptool-3.2}/tests/unit/__init__.py +0 -0
  45. {psptool-3.0 → psptool-3.2}/tests/unit/test_cli.py +0 -0
@@ -0,0 +1,33 @@
1
+ name: Post metrics comment
2
+
3
+ on: [pull_request_target]
4
+
5
+ jobs:
6
+ test:
7
+
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python-version: ["3.9"]
12
+
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ with:
16
+ submodules: recursive
17
+ ssh-key: ${{ secrets.PSPTOOL_FIXTURES_NEW_PRIVATE_KEY }}
18
+ - name: Set up Python ${{ matrix.python-version }}
19
+ uses: actions/setup-python@v3
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - name: Install dependencies
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ pip install .
26
+ - name: Run metrics and post comment
27
+ run: |
28
+ python tests/gha_metrics.py > metrics.md
29
+ gh pr comment $PRNUM --body-file metrics.md
30
+ env:
31
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
+ GH_REPO: ${{ github.repository }}
33
+ PRNUM: ${{ github.event.pull_request.number }}
@@ -0,0 +1,51 @@
1
+ name: Publish Python distribution to PyPI and TestPyPI
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ build:
7
+ name: Build distribution
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ with:
13
+ persist-credentials: false
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.11"
18
+ - name: Install pypa/build
19
+ run: >-
20
+ python3 -m
21
+ pip install
22
+ build
23
+ --user
24
+ - name: Build a binary wheel and a source tarball
25
+ run: python3 -m build
26
+ - name: Store the distribution packages
27
+ uses: actions/upload-artifact@v4
28
+ with:
29
+ name: python-package-distributions
30
+ path: dist/
31
+
32
+ publish-to-pypi:
33
+ name: >-
34
+ Publish Python distribution to PyPI
35
+ if: startsWith(github.ref, 'refs/tags') # only publish on tag push
36
+ needs:
37
+ - build
38
+ runs-on: ubuntu-latest
39
+ environment:
40
+ name: pypi
41
+ url: https://pypi.org/p/psptool/
42
+ permissions:
43
+ id-token: write # important for trusted publishing
44
+ steps:
45
+ - name: Download all the dists
46
+ uses: actions/download-artifact@v4
47
+ with:
48
+ name: python-package-distributions
49
+ path: dist/
50
+ - name: Publish distribution to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,24 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psptool
3
- Version: 3.0
3
+ Version: 3.2
4
4
  Summary: PSPTool is a tool for dealing with AMD binary blobs
5
- Home-page: https://github.com/PSPReverse/PSPTool
6
- Author: Christian Werling
7
- Author-email: cwerling@posteo.de
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Intended Audience :: Science/Research
10
- Classifier: Topic :: Security
11
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
12
- Classifier: Programming Language :: Python :: 3.8
13
- Requires-Python: >=3.8
14
- Description-Content-Type: text/markdown
5
+ Author-email: Christian Werling <cwerling@posteo.de>
15
6
  License-File: LICENSE
16
- Requires-Dist: setuptools
7
+ Requires-Python: >=3.8
17
8
  Requires-Dist: cryptography>=38.0.0
18
9
  Requires-Dist: prettytable
19
- Dynamic: description
20
- Dynamic: description-content-type
21
- Dynamic: license-file
10
+ Description-Content-Type: text/markdown
22
11
 
23
12
  # PSPTool
24
13
 
@@ -880,4 +869,4 @@ b'\x01\x00\x00\x00\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x1b\xb9\x87\xc3
880
869
  > psp.blob.roms[0].directories[0].files[1].move_buffer(0x60000, 0x1000)
881
870
  > psp.blob.roms[0].set_bytes(0x60000, 0x1000, my_stuff)
882
871
  > psp.to_file('my_modified_bios.bin')
883
- ```
872
+ ```
@@ -15,6 +15,6 @@
15
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
 
17
17
  from .psptool import PSPTool
18
+ from importlib.metadata import version
18
19
 
19
- import pkg_resources
20
- __version__ = pkg_resources.get_distribution("psptool").version
20
+ __version__ = version("psptool")
@@ -16,9 +16,10 @@
16
16
 
17
17
  import sys
18
18
  import os
19
- import pkg_resources
19
+ import re
20
20
 
21
21
  from .psptool import PSPTool
22
+ from . import __version__
22
23
  from .utils import ObligingArgumentParser, PrintHelper
23
24
  from .header_file import HeaderFile
24
25
  from .pubkey_file import PubkeyFile
@@ -27,6 +28,36 @@ from .crypto import PrivateKeyDict
27
28
  from argparse import RawTextHelpFormatter, SUPPRESS
28
29
 
29
30
 
31
+ def find_files_by_type_regex(psp, pattern, rom_index=0):
32
+ """Find files whose type matches the given regex pattern.
33
+
34
+ Returns a tuple of (matching_files, are_identical) where:
35
+ - matching_files: list of matching files
36
+ - are_identical: boolean indicating if all matching files have identical content
37
+ """
38
+ matching_files = []
39
+ regex = re.compile(pattern, re.IGNORECASE)
40
+
41
+ # Search through all directories in the specified ROM
42
+ for directory in psp.blob.roms[rom_index].directories:
43
+ for file in directory.files:
44
+ if file is not None:
45
+ file_type = file.get_readable_type()
46
+ if regex.search(file_type):
47
+ matching_files.append(file)
48
+
49
+ # Check if all matching files are identical
50
+ are_identical = True
51
+ if len(matching_files) > 1:
52
+ first_bytes = matching_files[0].get_bytes()
53
+ for file in matching_files[1:]:
54
+ if file.get_bytes() != first_bytes:
55
+ are_identical = False
56
+ break
57
+
58
+ return matching_files, are_identical
59
+
60
+
30
61
  def main():
31
62
  # CLI stuff to create a PSPTool object and interact with it
32
63
  parser = ObligingArgumentParser(description='Display, extract, and manipulate AMD PSP firmware inside BIOS ROMs.\n',
@@ -50,6 +81,7 @@ def main():
50
81
  parser.add_argument('-m', '--metrics', help=SUPPRESS, action='store_true')
51
82
  parser.add_argument('-p', '--privkeystub', help=SUPPRESS)
52
83
  parser.add_argument('-a', '--privkeypass', help=SUPPRESS)
84
+ parser.add_argument('-T', '--type-regex', help=SUPPRESS)
53
85
 
54
86
  action = parser.add_mutually_exclusive_group(required=False)
55
87
 
@@ -67,11 +99,15 @@ def main():
67
99
 
68
100
  action.add_argument('-X', '--extract-file', help='\n'.join([
69
101
  'Extract one or more PSP firmware files.',
70
- '[-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]',
102
+ '[-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile] [-T regex]',
71
103
  '',
72
- '-r idx: specifies rom_index (default: 0)',
104
+ '-r idx: specifies rom_index (default: ROM 0 only)',
73
105
  '-d idx: specifies directory_index (default: all directories)',
74
106
  '-e idx: specifies file_index (default: all files)',
107
+ '-T regex: extract file(s) whose type matches the regex pattern',
108
+ ' (e.g., "SMUSCS" matches "FW_PSP_SMUSCS~0x5f")',
109
+ ' If multiple identical files match, extracts one with warning',
110
+ ' If multiple different files match, exits with error',
75
111
  '-n: skip duplicate files and extract unique files only',
76
112
  '-u: uncompress compressed files',
77
113
  '-c: try to decrypt files',
@@ -97,7 +133,7 @@ def main():
97
133
  ph = PrintHelper(args.verbose)
98
134
 
99
135
  if args.version:
100
- print(pkg_resources.get_distribution("psptool").version)
136
+ print(__version__)
101
137
  sys.exit(0)
102
138
  elif not args.file:
103
139
  parser.print_help(sys.stderr)
@@ -107,9 +143,29 @@ def main():
107
143
  output = None
108
144
 
109
145
  if args.extract_file:
110
- if args.directory_index is not None and args.file_index is not None:
146
+ file = None
147
+
148
+ # Option 1) A single file should be extracted and we get it from a regex-based type search
149
+ if args.type_regex:
150
+ matching_files, are_identical = find_files_by_type_regex(psp, args.type_regex, args.rom_index)
151
+
152
+ if not matching_files:
153
+ ph.print_error_and_exit(f'No files found matching type regex: {args.type_regex}')
154
+ elif len(matching_files) > 1:
155
+ if are_identical:
156
+ ph.print_warning(f'Found {len(matching_files)} files matching "{args.type_regex}" but they are identical. Extracting one of them.')
157
+ file = matching_files[0]
158
+ else:
159
+ ph.print_error_and_exit(f'Found {len(matching_files)} files matching "{args.type_regex}" but they have different contents. Not extracting any.')
160
+ else:
161
+ file = matching_files[0]
162
+
163
+ elif args.directory_index is not None and args.file_index is not None:
164
+ # Option 2) A single file should be extracted and we get it from (rom_index, directory_index, file_index)
111
165
  file = psp.blob.roms[args.rom_index].directories[args.directory_index].files[args.file_index]
112
166
 
167
+ if file:
168
+ # For Options 1 and 2, do additional extraction magic if requested
113
169
  if args.decompress:
114
170
  if not file.compressed:
115
171
  ph.print_error_and_exit(f'File is not compressed {file.get_readable_type()}')
@@ -123,36 +179,17 @@ def main():
123
179
  else:
124
180
  output = file.get_bytes()
125
181
 
126
- else:
127
- if args.file_index is None: # if neither directory_index nor file_index are specified
128
- if args.directory_index is not None:
129
- directories = [psp.blob.roms[args.rom_index].directories[args.directory_index]]
130
- else:
131
- directories = psp.blob.roms[args.rom_index].directories
132
-
133
- if args.no_duplicates is False:
134
- outdir = args.outfile or f'./{psp.filename}_extracted'
135
- for dir_index, directory in enumerate(directories):
136
- for file_index, file in enumerate(directory.files):
137
- if args.decompress and type(file) is HeaderFile:
138
- out_bytes = file.get_signed_bytes()
139
- elif args.decrypt and type(file) is HeaderFile:
140
- out_bytes = file.get_decrypted()
141
- elif args.pem_key and type(file) is PubkeyFile:
142
- out_bytes = file.get_pem_encoded()
143
- else:
144
- out_bytes = file.get_bytes()
145
-
146
- outpath = outdir + '/d%.2d_e%.2d_%s' % (dir_index, file_index, file.get_readable_type())
147
- if type(file) is HeaderFile:
148
- outpath += f'_{file.get_readable_version()}'
149
-
150
- os.makedirs(os.path.dirname(outpath), exist_ok=True)
151
- with open(outpath, 'wb') as f:
152
- f.write(out_bytes)
153
- ph.print_info(f"Extracted all files to {outdir}")
154
- else: # no_duplicates is True
155
- for file in psp.blob.roms[args.rom_index].unique_files:
182
+ # Option 3) Multiple files should be extracted
183
+ if args.file_index is None:
184
+ if args.directory_index is not None: # Option 3a) All files from a given directory_index
185
+ directories = [psp.blob.roms[args.rom_index].directories[args.directory_index]]
186
+ else: # Option 3b) All files from all directories from the given ROM (default: 0)
187
+ directories = psp.blob.roms[args.rom_index].directories
188
+
189
+ if args.no_duplicates is False:
190
+ outdir = args.outfile or f'./{psp.filename}_extracted'
191
+ for dir_index, directory in enumerate(directories):
192
+ for file_index, file in enumerate(directory.files):
156
193
  if args.decompress and type(file) is HeaderFile:
157
194
  out_bytes = file.get_signed_bytes()
158
195
  elif args.decrypt and type(file) is HeaderFile:
@@ -162,17 +199,41 @@ def main():
162
199
  else:
163
200
  out_bytes = file.get_bytes()
164
201
 
165
- outdir = args.outfile or f'./{psp.filename}_unique_extracted'
166
- outpath = outdir + '/%s' % (file.get_readable_type())
202
+ outpath = outdir + '/d%.2d_e%.2d_%s' % (dir_index, file_index, file.get_readable_type())
167
203
 
168
- if issubclass(type(file), HeaderFile):
204
+ # We may have same file types but different subprograms, instances
205
+ # Account for it in the filenames
206
+ if file.entry.subprogram != 0 or file.entry.instance != 0:
207
+ outpath += f'_SUB_{hex(file.entry.subprogram)}_INS_{hex(file.entry.instance)}'
208
+ if type(file) is HeaderFile:
169
209
  outpath += f'_{file.get_readable_version()}'
170
210
 
171
211
  os.makedirs(os.path.dirname(outpath), exist_ok=True)
172
212
  with open(outpath, 'wb') as f:
173
213
  f.write(out_bytes)
174
- else:
175
- parser.print_help(sys.stderr)
214
+ else: # no_duplicates is True
215
+ for file in psp.blob.unique_files():
216
+ if args.decompress and type(file) is HeaderFile:
217
+ out_bytes = file.get_signed_bytes()
218
+ elif args.decrypt and type(file) is HeaderFile:
219
+ out_bytes = file.get_decrypted()
220
+ elif args.pem_key and type(file) is PubkeyFile:
221
+ out_bytes = file.get_pem_encoded()
222
+ else:
223
+ out_bytes = file.get_bytes()
224
+
225
+ outdir = args.outfile or f'./{psp.filename}_unique_extracted'
226
+ outpath = outdir + '/%s' % (file.get_readable_type())
227
+
228
+ if issubclass(type(file), HeaderFile):
229
+ outpath += f'_{file.get_readable_version()}'
230
+
231
+ os.makedirs(os.path.dirname(outpath), exist_ok=True)
232
+ with open(outpath, 'wb') as f:
233
+ f.write(out_bytes)
234
+ ph.print_info(f"Extracted all files to {outdir}")
235
+ else:
236
+ parser.print_help(sys.stderr)
176
237
 
177
238
  elif args.replace_file:
178
239
  if args.directory_index is not None and args.file_index is not None and args.outfile is not None:
@@ -67,7 +67,11 @@ class Blob(NestedBuffer):
67
67
  continue
68
68
  try:
69
69
  rom_offset = fet_location - fet_offset # e.g. 0x20800 - 0x20000 = 0x0800
70
- potential_rom = Rom(self, min(rom_size, self._MAX_PAGE_SIZE), rom_offset, fet_offset, psptool)
70
+ # W/A for images with single ROMs that have entries crossing 16MB boundary
71
+ if rom_page == 0:
72
+ potential_rom = Rom(self, rom_size, rom_offset, fet_offset, psptool)
73
+ else:
74
+ potential_rom = Rom(self, min(rom_size, self._MAX_PAGE_SIZE), rom_offset, fet_offset, psptool)
71
75
  self.roms.append(potential_rom)
72
76
  fet_parsed = True
73
77
  break # found correct fet_offset!
@@ -31,10 +31,6 @@ class Directory(NestedBuffer):
31
31
  ENTRY_SIZE = DirectoryEntry.ENTRY_SIZE
32
32
  FILE_CLASS = File
33
33
 
34
- # all directories by offset in rom
35
- # todo: what if we have multi-ROM? then two ROMs share this singleton!
36
- directories_by_offset = {}
37
-
38
34
  class ParseError(Exception):
39
35
  pass
40
36
 
@@ -45,14 +41,15 @@ class Directory(NestedBuffer):
45
41
  @classmethod
46
42
  def create_directories_if_not_exist(cls, offset, fet, zen_generation=None) -> List['Directory']:
47
43
  # Recursively return or create and return found directories
48
- if offset in cls.directories_by_offset:
49
- return [cls.directories_by_offset[offset]]
44
+
45
+ if offset in fet.psptool.directories_by_offset:
46
+ return [fet.psptool.directories_by_offset[offset]]
50
47
  else:
51
48
  # 1. Create the immediate directory in front of us
52
49
  created_directories = []
53
50
  try:
54
51
  directory = cls.from_offset(fet, offset, zen_generation)
55
- cls.directories_by_offset[offset] = directory
52
+ fet.psptool.directories_by_offset[offset] = directory
56
53
  created_directories.append(directory)
57
54
  except Directory.ParseError as e:
58
55
  # Handle empty entries gracefully (like master branch)
@@ -70,8 +67,8 @@ class Directory(NestedBuffer):
70
67
 
71
68
  # 3. Recursively add tertiary directories (double references introduced in Zen 4), if applicable
72
69
  for tertiary_directory_offset in directory.tertiary_directory_offsets:
73
- directory_body = fet.rom.get_bytes(tertiary_directory_offset + 16, 8)
74
- actual_tertiary_offset = int.from_bytes(directory_body[:4], 'little')
70
+ directory_body = fet.rom.get_bytes(tertiary_directory_offset, 32)
71
+ actual_tertiary_offset = int.from_bytes(directory_body[16:20], 'little')
75
72
  # Resolve one more indirection
76
73
  tertiary_directories = cls.create_directories_if_not_exist(actual_tertiary_offset, fet, zen_generation)
77
74
  created_directories += tertiary_directories
@@ -83,7 +80,7 @@ class Directory(NestedBuffer):
83
80
  rom_offset &= fet.rom.addr_mask
84
81
  magic = fet.rom.get_bytes(rom_offset, 4)
85
82
 
86
- if magic == b'\xff\xff\xff\xff':
83
+ if magic == b'\xff\xff\xff\xff' or magic == b'\x00\x00\x00\x00':
87
84
  fet.psptool.ph.print_warning(f"Empty FET entry at ROM address 0x{rom_offset:x}")
88
85
  raise Directory.ParseError("Empty entry")
89
86
  if magic in cls.DIRECTORY_MAGICS:
@@ -106,7 +103,7 @@ class Directory(NestedBuffer):
106
103
  self._count = int.from_bytes(self.rom[self.buffer_offset + 8: self.buffer_offset + 12], 'little')
107
104
  self.magic = self.rom.get_bytes(self.buffer_offset, 4)
108
105
  assert self.magic in self.DIRECTORY_MAGICS
109
- self.reserved = self.rom.get_bytes(self.buffer_offset + 12, 4)
106
+ self.additional_info = self.rom.get_bytes(self.buffer_offset + 12, 4)
110
107
  self.header = NestedBuffer(self, self.HEADER_SIZE)
111
108
  self.body = NestedBuffer(self, self.ENTRY_SIZE * self.count, buffer_offset=self.HEADER_SIZE)
112
109
  self.buffer_size = len(self.header) + len(self.body)
@@ -157,13 +154,18 @@ class Directory(NestedBuffer):
157
154
  self.header[8:12] = struct.pack('<I', self.count)
158
155
  self.update_checksum()
159
156
 
157
+ # 00b: x86 Physical address
158
+ # 01b: Offset from start of the BIOS (flash offset)
159
+ # 10b: Offset from start of directory header
160
+ # 11b: Offset from start of partition
160
161
  @property
161
162
  def address_mode(self):
162
- rsvd = struct.unpack('=L', self.reserved)[0]
163
- return (rsvd & 0x60000000) >> 29 if rsvd != 0 else None
164
- # todo: do we need to still consider the address_mode somewhere?
165
- # at the moment we seem to be running fine by checking entry.rsv0 & (1 << 30) to figure out if we need
166
- # directory-relative or absolute addressing
163
+ info = struct.unpack('<L', self.additional_info)[0]
164
+ version = (info >> 31) & 1
165
+ if version == 1:
166
+ return (info >> 24) & 3
167
+ else:
168
+ return (info >> 29) & 3
167
169
 
168
170
  def verify_checksum(self):
169
171
  data = self[0x8:]
@@ -0,0 +1,180 @@
1
+ # PSPTool - Display, extract and manipulate PSP firmware inside UEFI images
2
+ # Copyright (C) 2021 Christian Werling, Robert Buhren, Hans Niklas Jacob
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ import struct
18
+
19
+ from typing import TYPE_CHECKING
20
+ if TYPE_CHECKING:
21
+ from directory import Directory
22
+
23
+ from .utils import NestedBuffer
24
+
25
+
26
+ class DirectoryEntry(NestedBuffer):
27
+ ENTRY_SIZE = 4 * 4
28
+
29
+ def __init__(self, parent_directory: 'Directory', entry_offset):
30
+ super().__init__(parent_directory.body, self.ENTRY_SIZE, entry_offset)
31
+ self.parent_directory = parent_directory
32
+ self.entry_offset = entry_offset
33
+
34
+ def __repr__(self):
35
+ return f'{self.__class__.__name__}({self.type=:#x}, {self.flags=:#x}, {self.size=:#x}, ' \
36
+ f'{self.offset=:#x}, {self.entry_offset=:#x}, {self.rsv0=:#x})'
37
+
38
+ def file_offset(self):
39
+ # Special case soft fuse chain, no offset or size
40
+ if self.type == 0xb:
41
+ dir_start = self.parent_directory.buffer_offset + self.parent_directory.HEADER_SIZE
42
+ return dir_start + self.entry_offset
43
+
44
+ addr_mode = self.parent_directory.address_mode
45
+ # If directory address mode is 2 or 3 (relative to dir or slot), the
46
+ # entry address mode must be taken into account, otherwise ignored.
47
+ if addr_mode == 2 or addr_mode == 3:
48
+ addr_mode = self.address_mode
49
+
50
+ if addr_mode == 0:
51
+ # x86 physical address, should be in range 0xff000000 - 0xffffffff
52
+ # But some images use flash offset in x86 physical address mode.
53
+ # If ROM is bigger than 16MB and the entry address is in the
54
+ # 0xff000000 - 0xffffffff range, we have to override the mask for
55
+ # physical address.
56
+ rom_size = self.parent_directory.rom.addr_mask + 1
57
+ if self.offset > 0xFF000000 and rom_size > 16 * 1024 * 1024:
58
+ return self.offset & 0x00FFFFFF
59
+ else:
60
+ return self.offset & self.parent_directory.rom.addr_mask
61
+ elif addr_mode == 1:
62
+ # Flash offset from start of BIOS, most common on modern systems
63
+ return self.offset
64
+ elif addr_mode == 2:
65
+ # Flash offset from start of directory header
66
+ return self.parent_directory.buffer_offset + self.offset
67
+ elif addr_mode == 3:
68
+ # Flash offset from start of the slot
69
+ # TODO: How to calculate the offset from slot? Is this correct?
70
+ return self.parent_directory.buffer_offset + self.offset
71
+
72
+ @property
73
+ def type(self):
74
+ return struct.unpack('<B', self[0:1])[0]
75
+
76
+ @type.setter
77
+ def type(self, value):
78
+ self.set_bytes(0, 1, struct.pack('<B', value))
79
+
80
+ @property
81
+ def subprogram(self):
82
+ return struct.unpack('<B', self[1:2])[0]
83
+
84
+ @subprogram.setter
85
+ def subprogram(self, value):
86
+ self.set_bytes(1, 1, struct.pack('<B', value))
87
+
88
+ @property
89
+ def flags(self):
90
+ return struct.unpack('<H', self[2:4])[0]
91
+
92
+ @flags.setter
93
+ def flags(self, value):
94
+ self.set_bytes(2, 2, struct.pack('<H', value))
95
+
96
+ @property
97
+ def instance(self):
98
+ return (self.flags >> 3) & 0xF
99
+
100
+ @instance.setter
101
+ def instance(self, value):
102
+ self.flags = self.flags | ((value & 0xF) << 3)
103
+
104
+ @property
105
+ def size(self):
106
+ return struct.unpack('<I', self[4:8])[0]
107
+
108
+ @size.setter
109
+ def size(self, value):
110
+ self.set_bytes(4, 4, struct.pack('<I', value))
111
+
112
+ @property
113
+ def offset(self):
114
+ return struct.unpack('<I', self[8:12])[0]
115
+
116
+ @offset.setter
117
+ def offset(self, value):
118
+ self.set_bytes(8, 4, struct.pack('<I', value))
119
+
120
+ @property
121
+ def rsv0(self):
122
+ return struct.unpack('<I', self[12:16])[0]
123
+
124
+ @rsv0.setter
125
+ def rsv0(self, value):
126
+ self.set_bytes(12, 4, struct.pack('<I', value))
127
+
128
+ # 00b: x86 Physical address
129
+ # 01b: Offset from start of the BIOS (flash offset)
130
+ # 10b: Offset from start of directory header
131
+ # 11b: Offset from start of partition
132
+ @property
133
+ def address_mode(self):
134
+ return (self.rsv0 >> 30) & 3
135
+
136
+ class BiosDirectoryEntry(DirectoryEntry):
137
+ ENTRY_SIZE = 4 * 6
138
+
139
+ def __repr__(self):
140
+ return super().__repr__()[:-1] + f', {self.destination=:#x})'
141
+
142
+ @property
143
+ def destination(self):
144
+ return struct.unpack('<Q', self[16:24])[0]
145
+
146
+ @destination.setter
147
+ def destination(self, value):
148
+ self.set_bytes(16, 8, struct.pack('<Q', value))
149
+
150
+ @property
151
+ def region_type(self):
152
+ return struct.unpack('<B', self[1:2])[0]
153
+
154
+ @region_type.setter
155
+ def region_type(self, value):
156
+ self.set_bytes(1, 1, struct.pack('<B', value))
157
+
158
+ @property
159
+ def flags(self):
160
+ return struct.unpack('<H', self[2:4])[0]
161
+
162
+ @flags.setter
163
+ def flags(self, value):
164
+ self.set_bytes(2, 2, struct.pack('<H', value))
165
+
166
+ @property
167
+ def subprogram(self):
168
+ return (self.flags >> 8) & 0x7
169
+
170
+ @subprogram.setter
171
+ def subprogram(self, value):
172
+ self.flags = self.flags | ((value & 0x7) << 8)
173
+
174
+ @property
175
+ def instance(self):
176
+ return (self.flags >> 4) & 0xF
177
+
178
+ @instance.setter
179
+ def instance(self, value):
180
+ self.flags = self.flags | ((value & 0xF) << 4)
@@ -69,10 +69,16 @@ class Fet(NestedBuffer):
69
69
  # TODO: Why is 0xFFFFFFFe a possible value here?
70
70
  if rom_addr in [0x0, 0xFFFFFFFF, 0xFFFFFFFe]:
71
71
  continue
72
- rom_addr &= self.rom.addr_mask
72
+ # if ROM is bigger than 16MB, we have to override the mask for
73
+ # physical address
74
+ if rom_addr > 0xFF000000 and self.rom.addr_mask + 1 > 16 * 1024 * 1024:
75
+ rom_addr &= 0x00FFFFFF
76
+ else:
77
+ rom_addr &= self.rom.addr_mask
78
+
73
79
  try:
74
80
  dir_magic = self.rom[rom_addr:rom_addr + 4]
75
- except:
81
+ except AssertionError as e:
76
82
  self.psptool.ph.print_warning(f"FET entry 0x{rom_addr:x} not found or invalid, skipping ...")
77
83
  continue
78
84
  if dir_magic == b'2PSP' or dir_magic == b'2BHD':
@@ -105,7 +111,14 @@ class Fet(NestedBuffer):
105
111
  entry_addr = int.from_bytes(entry, 'little')
106
112
  if entry_addr in [0, 0xFFFFFFFF]:
107
113
  continue
108
- entry_addr &= self.rom.addr_mask
114
+ # if ROM is bigger than 16MB, we have to override the mask for
115
+ # physical address
116
+ rom_size = self.rom.addr_mask + 1
117
+ if entry_addr > 0xFF000000 and rom_size > 16 * 1024 * 1024:
118
+ entry_addr &= 0x00FFFFFF
119
+ else:
120
+ entry_addr &= self.rom.addr_mask
121
+
109
122
  # entry_addr += self.blob_offset
110
123
  zen_generation_id = combo_dir[i*16+5:i*16+8]
111
124
  zen_generation = 'unknown'