psptool 2.8__tar.gz → 3.0__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 (41) hide show
  1. {psptool-2.8 → psptool-3.0}/PKG-INFO +41 -37
  2. {psptool-2.8 → psptool-3.0}/README.md +36 -35
  3. {psptool-2.8 → psptool-3.0}/psptool/__main__.py +61 -60
  4. {psptool-2.8 → psptool-3.0}/psptool/blob.py +60 -66
  5. {psptool-2.8 → psptool-3.0}/psptool/cert_tree.py +51 -49
  6. {psptool-2.8 → psptool-3.0}/psptool/crypto.py +2 -3
  7. psptool-3.0/psptool/directory.py +205 -0
  8. psptool-3.0/psptool/entry.py +96 -0
  9. {psptool-2.8 → psptool-3.0}/psptool/errors.py +1 -2
  10. {psptool-2.8 → psptool-3.0}/psptool/fet.py +13 -43
  11. psptool-3.0/psptool/file.py +295 -0
  12. psptool-3.0/psptool/header_file.py +214 -0
  13. psptool-3.0/psptool/key_store_file.py +253 -0
  14. {psptool-2.8 → psptool-3.0}/psptool/psptool.py +81 -77
  15. psptool-3.0/psptool/pubkey_file.py +178 -0
  16. {psptool-2.8 → psptool-3.0}/psptool/rom.py +22 -21
  17. {psptool-2.8 → psptool-3.0}/psptool.egg-info/PKG-INFO +41 -37
  18. {psptool-2.8 → psptool-3.0}/psptool.egg-info/SOURCES.txt +4 -0
  19. {psptool-2.8 → psptool-3.0}/setup.cfg +1 -1
  20. {psptool-2.8 → psptool-3.0}/tests/integration/test_rom_files.py +4 -4
  21. {psptool-2.8 → psptool-3.0}/tests/metrics.txt +187 -173
  22. psptool-2.8/psptool/directory.py +0 -200
  23. psptool-2.8/psptool/entry.py +0 -1040
  24. {psptool-2.8 → psptool-3.0}/.github/workflows/python-tests.yml +0 -0
  25. {psptool-2.8 → psptool-3.0}/.gitignore +0 -0
  26. {psptool-2.8 → psptool-3.0}/.gitmodules +0 -0
  27. {psptool-2.8 → psptool-3.0}/LICENSE +0 -0
  28. {psptool-2.8 → psptool-3.0}/psptool/__init__.py +0 -0
  29. {psptool-2.8 → psptool-3.0}/psptool/firmware.py +0 -0
  30. {psptool-2.8 → psptool-3.0}/psptool/utils.py +0 -0
  31. {psptool-2.8 → psptool-3.0}/psptool.egg-info/dependency_links.txt +0 -0
  32. {psptool-2.8 → psptool-3.0}/psptool.egg-info/entry_points.txt +0 -0
  33. {psptool-2.8 → psptool-3.0}/psptool.egg-info/requires.txt +0 -0
  34. {psptool-2.8 → psptool-3.0}/psptool.egg-info/top_level.txt +0 -0
  35. {psptool-2.8 → psptool-3.0}/setup.py +0 -0
  36. {psptool-2.8 → psptool-3.0}/tests/__init__.py +0 -0
  37. {psptool-2.8 → psptool-3.0}/tests/create_metrics.sh +0 -0
  38. {psptool-2.8 → psptool-3.0}/tests/integration/__init__.py +0 -0
  39. {psptool-2.8 → psptool-3.0}/tests/integration/test_psptrace.py +0 -0
  40. {psptool-2.8 → psptool-3.0}/tests/unit/__init__.py +0 -0
  41. {psptool-2.8 → psptool-3.0}/tests/unit/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: psptool
3
- Version: 2.8
3
+ Version: 3.0
4
4
  Summary: PSPTool is a tool for dealing with AMD binary blobs
5
5
  Home-page: https://github.com/PSPReverse/PSPTool
6
6
  Author: Christian Werling
@@ -16,6 +16,9 @@ License-File: LICENSE
16
16
  Requires-Dist: setuptools
17
17
  Requires-Dist: cryptography>=38.0.0
18
18
  Requires-Dist: prettytable
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: license-file
19
22
 
20
23
  # PSPTool
21
24
 
@@ -798,40 +801,41 @@ usage: psptool [-V | -E | -X | -R] [file]
798
801
  Display, extract, and manipulate AMD PSP firmware inside BIOS ROMs.
799
802
 
800
803
  positional arguments:
801
- file Binary file to be parsed for PSP firmware
804
+ file Binary file to be parsed for PSP firmware
802
805
 
803
806
  optional arguments:
804
807
  -V, --version
805
- -E, --entries Default: Parse and display PSP firmware entries.
806
- [-n] [-j] [-t]
807
-
808
- -n: list unique entries only ordered by their offset
809
- -j: output in JSON format instead of tables
810
- -t: print tree of all signed entities and their certifying keys
811
-
812
- -X, --extract-entry Extract one or more PSP firmware entries.
813
- [[-r idx] -d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]
814
-
815
- -r idx: specifies rom_index (default: 0)
816
- -d idx: specifies directory_index (default: all directories)
817
- -e idx: specifies entry_index (default: all entries)
818
- -n: skip duplicate entries and extract unique entries only
819
- -u: uncompress compressed entries
820
- -c: try to decrypt entries
821
- -k: convert pubkeys into PEM format
822
- -o file: specifies outfile/outdir (default: stdout/{file}_extracted)
823
-
824
- -R, --replace-entry Copy a new entry (including header and signature) into the
825
- ROM file and update metadata accordingly.
826
- [-r idx] -d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]
827
-
828
- -r idx: specifies rom_index (default: 0)
829
- -d idx: specifies directory_index
830
- -e idx: specifies entry_index
831
- -s file: specifies subfile (i.e. the new entry contents)
832
- -o file: specifies outfile
833
- -p file: specifies file-stub (e.g. 'keys/id') for the re-signing keys
834
- -a pass: specifies password for the re-signing keys
808
+ -E, --entries Default: Parse and display PSP firmware entries.
809
+ [-n] [-j] [-t]
810
+
811
+ -n: list unique entries only ordered by their offset
812
+ -j: output in JSON format instead of tables
813
+ -t: print tree of all signed entities and their certifying keys
814
+ -m: print parsing metrics for testing
815
+
816
+ -X, --extract-file Extract one or more PSP firmware files.
817
+ [-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]
818
+
819
+ -r idx: specifies rom_index (default: 0)
820
+ -d idx: specifies directory_index (default: all directories)
821
+ -e idx: specifies file_index (default: all files)
822
+ -n: skip duplicate files and extract unique files only
823
+ -u: uncompress compressed files
824
+ -c: try to decrypt files
825
+ -k: convert pubkeys into PEM format
826
+ -o file: specifies outfile/outdir (default: stdout/{file}_extracted)
827
+
828
+ -R, --replace-file Copy a new file (including header and signature) into the
829
+ ROM file and update file and other metadata accordingly.
830
+ -d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]
831
+
832
+ -r idx: specifies rom_index (default: 0)
833
+ -d idx: specifies directory_index
834
+ -e idx: specifies file_index
835
+ -s file: specifies subfile (i.e. the new file contents)
836
+ -o file: specifies outfile
837
+ -p file: specifies file-stub (e.g. 'keys/id') for the re-signing keys
838
+ -a pass: specifies password for the re-signing keys
835
839
  ```
836
840
 
837
841
  ## Python Usage
@@ -867,13 +871,13 @@ PSPTool can be **used as a Python module**, e.g. in an interactive IPython sessi
867
871
  | | 14 | 0xeba00 | 0xbd60 | 0x36 | | AR6B | 17.9.18.12 | OEM_PSP_FW_PUBLIC_KEY |
868
872
  | | 15 | 0x149000 | 0x400 | 0x40 | !PL2_SECONDARY_DIRECTORY | | | |
869
873
  +---+-------+----------+---------+------+-----------------------------+-------+------------+-----------------------+
870
- > psp.blob.roms[0].directories[0].entries[0]
871
- PubkeyEntry(type=0x0, address=0x77400, size=0x240, len(references)=1)
872
- > psp.blob.directories[0].entries[0].get_bytes()
874
+ > psp.blob.roms[0].directories[0].files[0]
875
+ PubkeyFile(type=0x0, address=0x77400, size=0x240, len(references)=1)
876
+ > psp.blob.directories[0].files[0].get_bytes()
873
877
  b'\x01\x00\x00\x00\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
874
878
  [...]
875
879
  > my_stuff = [...]
876
- > psp.blob.roms[0].directories[0].entries[1].move_buffer(0x60000, 0x1000)
880
+ > psp.blob.roms[0].directories[0].files[1].move_buffer(0x60000, 0x1000)
877
881
  > psp.blob.roms[0].set_bytes(0x60000, 0x1000, my_stuff)
878
882
  > psp.to_file('my_modified_bios.bin')
879
883
  ```
@@ -779,40 +779,41 @@ usage: psptool [-V | -E | -X | -R] [file]
779
779
  Display, extract, and manipulate AMD PSP firmware inside BIOS ROMs.
780
780
 
781
781
  positional arguments:
782
- file Binary file to be parsed for PSP firmware
782
+ file Binary file to be parsed for PSP firmware
783
783
 
784
784
  optional arguments:
785
785
  -V, --version
786
- -E, --entries Default: Parse and display PSP firmware entries.
787
- [-n] [-j] [-t]
788
-
789
- -n: list unique entries only ordered by their offset
790
- -j: output in JSON format instead of tables
791
- -t: print tree of all signed entities and their certifying keys
792
-
793
- -X, --extract-entry Extract one or more PSP firmware entries.
794
- [[-r idx] -d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]
795
-
796
- -r idx: specifies rom_index (default: 0)
797
- -d idx: specifies directory_index (default: all directories)
798
- -e idx: specifies entry_index (default: all entries)
799
- -n: skip duplicate entries and extract unique entries only
800
- -u: uncompress compressed entries
801
- -c: try to decrypt entries
802
- -k: convert pubkeys into PEM format
803
- -o file: specifies outfile/outdir (default: stdout/{file}_extracted)
804
-
805
- -R, --replace-entry Copy a new entry (including header and signature) into the
806
- ROM file and update metadata accordingly.
807
- [-r idx] -d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]
808
-
809
- -r idx: specifies rom_index (default: 0)
810
- -d idx: specifies directory_index
811
- -e idx: specifies entry_index
812
- -s file: specifies subfile (i.e. the new entry contents)
813
- -o file: specifies outfile
814
- -p file: specifies file-stub (e.g. 'keys/id') for the re-signing keys
815
- -a pass: specifies password for the re-signing keys
786
+ -E, --entries Default: Parse and display PSP firmware entries.
787
+ [-n] [-j] [-t]
788
+
789
+ -n: list unique entries only ordered by their offset
790
+ -j: output in JSON format instead of tables
791
+ -t: print tree of all signed entities and their certifying keys
792
+ -m: print parsing metrics for testing
793
+
794
+ -X, --extract-file Extract one or more PSP firmware files.
795
+ [-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]
796
+
797
+ -r idx: specifies rom_index (default: 0)
798
+ -d idx: specifies directory_index (default: all directories)
799
+ -e idx: specifies file_index (default: all files)
800
+ -n: skip duplicate files and extract unique files only
801
+ -u: uncompress compressed files
802
+ -c: try to decrypt files
803
+ -k: convert pubkeys into PEM format
804
+ -o file: specifies outfile/outdir (default: stdout/{file}_extracted)
805
+
806
+ -R, --replace-file Copy a new file (including header and signature) into the
807
+ ROM file and update file and other metadata accordingly.
808
+ -d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]
809
+
810
+ -r idx: specifies rom_index (default: 0)
811
+ -d idx: specifies directory_index
812
+ -e idx: specifies file_index
813
+ -s file: specifies subfile (i.e. the new file contents)
814
+ -o file: specifies outfile
815
+ -p file: specifies file-stub (e.g. 'keys/id') for the re-signing keys
816
+ -a pass: specifies password for the re-signing keys
816
817
  ```
817
818
 
818
819
  ## Python Usage
@@ -848,13 +849,13 @@ PSPTool can be **used as a Python module**, e.g. in an interactive IPython sessi
848
849
  | | 14 | 0xeba00 | 0xbd60 | 0x36 | | AR6B | 17.9.18.12 | OEM_PSP_FW_PUBLIC_KEY |
849
850
  | | 15 | 0x149000 | 0x400 | 0x40 | !PL2_SECONDARY_DIRECTORY | | | |
850
851
  +---+-------+----------+---------+------+-----------------------------+-------+------------+-----------------------+
851
- > psp.blob.roms[0].directories[0].entries[0]
852
- PubkeyEntry(type=0x0, address=0x77400, size=0x240, len(references)=1)
853
- > psp.blob.directories[0].entries[0].get_bytes()
852
+ > psp.blob.roms[0].directories[0].files[0]
853
+ PubkeyFile(type=0x0, address=0x77400, size=0x240, len(references)=1)
854
+ > psp.blob.directories[0].files[0].get_bytes()
854
855
  b'\x01\x00\x00\x00\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
855
856
  [...]
856
857
  > my_stuff = [...]
857
- > psp.blob.roms[0].directories[0].entries[1].move_buffer(0x60000, 0x1000)
858
+ > psp.blob.roms[0].directories[0].files[1].move_buffer(0x60000, 0x1000)
858
859
  > psp.blob.roms[0].set_bytes(0x60000, 0x1000, my_stuff)
859
860
  > psp.to_file('my_modified_bios.bin')
860
861
  ```
@@ -20,7 +20,8 @@ import pkg_resources
20
20
 
21
21
  from .psptool import PSPTool
22
22
  from .utils import ObligingArgumentParser, PrintHelper
23
- from .entry import PubkeyEntry, HeaderEntry
23
+ from .header_file import HeaderFile
24
+ from .pubkey_file import PubkeyFile
24
25
  from .crypto import PrivateKeyDict
25
26
 
26
27
  from argparse import RawTextHelpFormatter, SUPPRESS
@@ -37,7 +38,7 @@ def main():
37
38
 
38
39
  parser.add_argument('-r', '--rom-index', help=SUPPRESS, type=int, default=0)
39
40
  parser.add_argument('-d', '--directory-index', help=SUPPRESS, type=int)
40
- parser.add_argument('-e', '--entry-index', help=SUPPRESS, type=int)
41
+ parser.add_argument('-e', '--file-index', help=SUPPRESS, type=int)
41
42
  parser.add_argument('-s', '--subfile', help=SUPPRESS)
42
43
  parser.add_argument('-o', '--outfile', help=SUPPRESS)
43
44
  parser.add_argument('-u', '--decompress', help=SUPPRESS, action='store_true')
@@ -61,32 +62,32 @@ def main():
61
62
  '-n: list unique entries only ordered by their offset',
62
63
  '-j: output in JSON format instead of tables',
63
64
  '-t: print tree of all signed entities and their certifying keys',
64
- '-m: print entry parsing metrics for testing',
65
+ '-m: print parsing metrics for testing',
65
66
  '', '']), action='store_true')
66
67
 
67
- action.add_argument('-X', '--extract-entry', help='\n'.join([
68
- 'Extract one or more PSP firmware entries.',
68
+ action.add_argument('-X', '--extract-file', help='\n'.join([
69
+ 'Extract one or more PSP firmware files.',
69
70
  '[-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]',
70
71
  '',
71
72
  '-r idx: specifies rom_index (default: 0)',
72
73
  '-d idx: specifies directory_index (default: all directories)',
73
- '-e idx: specifies entry_index (default: all entries)',
74
- '-n: skip duplicate entries and extract unique entries only',
75
- '-u: uncompress compressed entries',
76
- '-c: try to decrypt entries',
74
+ '-e idx: specifies file_index (default: all files)',
75
+ '-n: skip duplicate files and extract unique files only',
76
+ '-u: uncompress compressed files',
77
+ '-c: try to decrypt files',
77
78
  '-k: convert pubkeys into PEM format',
78
79
  '-o file: specifies outfile/outdir (default: stdout/{file}_extracted)',
79
80
  '', '']), action='store_true')
80
81
 
81
- action.add_argument('-R', '--replace-entry', help='\n'.join([
82
- 'Copy a new entry (including header and signature) into the',
83
- 'ROM file and update metadata accordingly.',
82
+ action.add_argument('-R', '--replace-file', help='\n'.join([
83
+ 'Copy a new file (including header and signature) into the',
84
+ 'ROM file and update file and other metadata accordingly.',
84
85
  '-d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]',
85
86
  '',
86
87
  '-r idx: specifies rom_index (default: 0)',
87
88
  '-d idx: specifies directory_index',
88
- '-e idx: specifies entry_index',
89
- '-s file: specifies subfile (i.e. the new entry contents)',
89
+ '-e idx: specifies file_index',
90
+ '-s file: specifies subfile (i.e. the new file contents)',
90
91
  '-o file: specifies outfile',
91
92
  '-p file: specifies file-stub (e.g. \'keys/id\') for the re-signing keys',
92
93
  '-a pass: specifies password for the re-signing keys'
@@ -105,25 +106,25 @@ def main():
105
106
  psp = PSPTool.from_file(args.file, verbose=args.verbose)
106
107
  output = None
107
108
 
108
- if args.extract_entry:
109
- if args.directory_index is not None and args.entry_index is not None:
110
- entry = psp.blob.roms[args.rom_index].directories[args.directory_index].entries[args.entry_index]
109
+ if args.extract_file:
110
+ if args.directory_index is not None and args.file_index is not None:
111
+ file = psp.blob.roms[args.rom_index].directories[args.directory_index].files[args.file_index]
111
112
 
112
113
  if args.decompress:
113
- if not entry.compressed:
114
- ph.print_error_and_exit(f'Entry is not compressed {entry.get_readable_type()}')
115
- output = entry.get_signed_bytes()
114
+ if not file.compressed:
115
+ ph.print_error_and_exit(f'File is not compressed {file.get_readable_type()}')
116
+ output = file.get_signed_bytes()
116
117
  elif args.decrypt:
117
- if not entry.encrypted:
118
- ph.print_error_and_exit(f'Entry is not encrypted {entry.get_readable_type()}')
119
- output = entry.to_decrypted_entry_bytes()
118
+ if not file.encrypted:
119
+ ph.print_error_and_exit(f'File is not encrypted {file.get_readable_type()}')
120
+ output = file.to_decrypted_file_bytes()
120
121
  elif args.pem_key:
121
- output = entry.get_pem_encoded()
122
+ output = file.get_pem_encoded()
122
123
  else:
123
- output = entry.get_bytes()
124
+ output = file.get_bytes()
124
125
 
125
126
  else:
126
- if args.entry_index is None: # if neither directory_index nor entry_index are specified
127
+ if args.file_index is None: # if neither directory_index nor file_index are specified
127
128
  if args.directory_index is not None:
128
129
  directories = [psp.blob.roms[args.rom_index].directories[args.directory_index]]
129
130
  else:
@@ -132,40 +133,40 @@ def main():
132
133
  if args.no_duplicates is False:
133
134
  outdir = args.outfile or f'./{psp.filename}_extracted'
134
135
  for dir_index, directory in enumerate(directories):
135
- for entry_index, entry in enumerate(directory.entries):
136
- if args.decompress and type(entry) is HeaderEntry:
137
- out_bytes = entry.get_signed_bytes()
138
- elif args.decrypt and type(entry) is HeaderEntry:
139
- out_bytes = entry.get_decrypted()
140
- elif args.pem_key and type(entry) is PubkeyEntry:
141
- out_bytes = entry.get_pem_encoded()
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()
142
143
  else:
143
- out_bytes = entry.get_bytes()
144
+ out_bytes = file.get_bytes()
144
145
 
145
- outpath = outdir + '/d%.2d_e%.2d_%s' % (dir_index, entry_index, entry.get_readable_type())
146
- if type(entry) is HeaderEntry:
147
- outpath += f'_{entry.get_readable_version()}'
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()}'
148
149
 
149
150
  os.makedirs(os.path.dirname(outpath), exist_ok=True)
150
151
  with open(outpath, 'wb') as f:
151
152
  f.write(out_bytes)
152
- ph.print_info(f"Extracted all entries to {outdir}")
153
+ ph.print_info(f"Extracted all files to {outdir}")
153
154
  else: # no_duplicates is True
154
- for entry in psp.blob.roms[args.rom_index].unique_entries:
155
- if args.decompress and type(entry) is HeaderEntry:
156
- out_bytes = entry.get_signed_bytes()
157
- elif args.decrypt and type(entry) is HeaderEntry:
158
- out_bytes = entry.get_decrypted()
159
- elif args.pem_key and type(entry) is PubkeyEntry:
160
- out_bytes = entry.get_pem_encoded()
155
+ for file in psp.blob.roms[args.rom_index].unique_files:
156
+ if args.decompress and type(file) is HeaderFile:
157
+ out_bytes = file.get_signed_bytes()
158
+ elif args.decrypt and type(file) is HeaderFile:
159
+ out_bytes = file.get_decrypted()
160
+ elif args.pem_key and type(file) is PubkeyFile:
161
+ out_bytes = file.get_pem_encoded()
161
162
  else:
162
- out_bytes = entry.get_bytes()
163
+ out_bytes = file.get_bytes()
163
164
 
164
165
  outdir = args.outfile or f'./{psp.filename}_unique_extracted'
165
- outpath = outdir + '/%s' % (entry.get_readable_type())
166
+ outpath = outdir + '/%s' % (file.get_readable_type())
166
167
 
167
- if type(entry) is HeaderEntry:
168
- outpath += f'_{entry.get_readable_version()}'
168
+ if issubclass(type(file), HeaderFile):
169
+ outpath += f'_{file.get_readable_version()}'
169
170
 
170
171
  os.makedirs(os.path.dirname(outpath), exist_ok=True)
171
172
  with open(outpath, 'wb') as f:
@@ -173,26 +174,26 @@ def main():
173
174
  else:
174
175
  parser.print_help(sys.stderr)
175
176
 
176
- elif args.replace_entry:
177
- if args.directory_index is not None and args.entry_index is not None and args.outfile is not None:
178
- entry = psp.blob.roms[args.rom_index].directories[args.directory_index].entries[args.entry_index]
177
+ elif args.replace_file:
178
+ if args.directory_index is not None and args.file_index is not None and args.outfile is not None:
179
+ file = psp.blob.roms[args.rom_index].directories[args.directory_index].files[args.file_index]
179
180
 
180
- # Substituting an entry is actually optional to allow plain re-signs
181
+ # Substituting an file is actually optional to allow plain re-signs
181
182
  if args.subfile is not None:
182
183
  with open(args.subfile, 'rb') as f:
183
184
  sub_binary = f.read()
184
- # Keep the existing entry's address, but adapt its size
185
- entry.move_buffer(entry.get_address(), len(sub_binary))
186
- entry.set_bytes(0, len(sub_binary), sub_binary)
185
+ # Keep the existing file's address, but adapt its size
186
+ file.move_buffer(file.get_address(), len(sub_binary))
187
+ file.set_bytes(0, len(sub_binary), sub_binary)
187
188
 
188
189
  privkeys = None
189
190
  if args.privkeystub:
190
191
  privkeys = PrivateKeyDict.read_from_files(args.privkeystub, args.privkeypass)
191
192
 
192
- if hasattr(entry, 'signed_entity') and entry.signed_entity:
193
- entry.signed_entity.resign_and_replace(privkeys=privkeys, recursive=True)
193
+ if hasattr(file, 'signed_entity') and file.signed_entity:
194
+ file.signed_entity.resign_and_replace(privkeys=privkeys, recursive=True)
194
195
  else:
195
- ph.print_warning("Did not resign anything since target entry is not signed")
196
+ ph.print_warning("Did not resign anything since target file is not signed")
196
197
 
197
198
  psp.to_file(args.outfile)
198
199
 
@@ -208,7 +209,7 @@ def main():
208
209
  elif args.metrics:
209
210
  psp.print_metrics()
210
211
  elif args.no_duplicates:
211
- psp.ls_entries(verbose=args.verbose)
212
+ psp.ls_files(verbose=args.verbose)
212
213
  else:
213
214
  psp.ls(verbose=args.verbose)
214
215
 
@@ -22,13 +22,14 @@ from typing import List
22
22
  from .fet import EmptyFet
23
23
  from .rom import Rom
24
24
  from .utils import NestedBuffer, RangeDict
25
- from .entry import Entry, PubkeyEntry
25
+ from .file import File
26
+ from .pubkey_file import PubkeyFile, InlinePubkeyFile
26
27
 
27
28
 
28
29
  class Blob(NestedBuffer):
29
30
  _FIRMWARE_ENTRY_MAGIC = b'\xAA\x55\xAA\x55'
30
- _FIRMWARE_ENTRY_TABLE_BASE_ADDRESS = 0x20000
31
-
31
+ # All structures per Rom must be in 16MB windows
32
+ _MAX_PAGE_SIZE = 16 * 1024 * 1024
32
33
  class NoFirmwareEntryTableError(Exception):
33
34
  pass
34
35
 
@@ -38,7 +39,7 @@ class Blob(NestedBuffer):
38
39
  self.psptool = psptool
39
40
  self.roms: List[Rom] = []
40
41
 
41
- potential_fet_offsets = [
42
+ possible_fet_offsets = [
42
43
  # as seen by a PSPTrace Zen 1 boot
43
44
  0x020000,
44
45
  0xfa0000,
@@ -48,24 +49,25 @@ class Blob(NestedBuffer):
48
49
  0x820000,
49
50
  ]
50
51
 
51
- rom_size = 0x1000000
52
- if self.buffer_size < rom_size:
53
- self.psptool.ph.print_warning("Input file < 16M, will assume 8M ROM ...")
54
- rom_size = 0x800000
52
+ possible_rom_sizes = [32, 16, 8]
53
+ _rom_size = max(value for value in possible_rom_sizes if value * 1024 * 1024 <= self.buffer_size)
54
+ rom_size = _rom_size * 1024 * 1024
55
+ self.psptool.ph.print_warning(f"Input file is {self.buffer_size:#x}, will assume ROM size of {_rom_size}M")
55
56
 
56
57
  # For each FET, we try to create a 16MB ROM starting at `FET - offset`
57
58
  for fet_location in self._find_fets():
58
59
  fet_parsed = False
59
- for fet_offset in potential_fet_offsets:
60
+ for fet_offset in possible_fet_offsets:
60
61
  if fet_location < fet_offset:
61
62
  # would lead to Blob underflow
62
63
  continue
63
- if fet_location - fet_offset + rom_size > self.buffer_size:
64
+ rom_page = int(fet_location / self._MAX_PAGE_SIZE)
65
+ if fet_location - fet_offset + rom_size - rom_page * self._MAX_PAGE_SIZE > self.buffer_size:
64
66
  # would lead to Blob overflow
65
67
  continue
66
68
  try:
67
69
  rom_offset = fet_location - fet_offset # e.g. 0x20800 - 0x20000 = 0x0800
68
- potential_rom = Rom(self, rom_size, rom_offset, fet_offset, psptool)
70
+ potential_rom = Rom(self, min(rom_size, self._MAX_PAGE_SIZE), rom_offset, fet_offset, psptool)
69
71
  self.roms.append(potential_rom)
70
72
  fet_parsed = True
71
73
  break # found correct fet_offset!
@@ -84,16 +86,16 @@ class Blob(NestedBuffer):
84
86
  return f'Blob({self.roms=})'
85
87
 
86
88
  def _construct_range_dict(self):
87
- all_entries = self.unique_entries()
89
+ all_files = self.unique_files()
88
90
 
89
91
  # create RangeDict in order to find entries, directories and fets for a given address
90
92
  directories = [directory for rom in self.roms for directory in rom.directories]
91
93
  self.range_dict = RangeDict({
92
94
  **{
93
- range(entry.get_address(),
94
- entry.get_address() + entry.buffer_size): # key is start and end address of the entry
95
- entry
96
- for entry in all_entries if entry.buffer_size != 0xffffffff # value is its type
95
+ range(file.get_address(),
96
+ file.get_address() + file.buffer_size): # key is start and end address of the file
97
+ file
98
+ for file in all_files if file.buffer_size != 0xffffffff # value is its type
97
99
  }, **{
98
100
  range(directory.get_address(), directory.get_address() + len(directory)):
99
101
  directory
@@ -105,73 +107,65 @@ class Blob(NestedBuffer):
105
107
  }
106
108
  })
107
109
 
108
- def unique_entries(self) -> set:
110
+ def unique_files(self) -> set:
109
111
  directories = [directory for rom in self.roms for directory in rom.directories]
110
- directory_entries = [directory.entries for directory in directories]
112
+ directory_files = [directory.files for directory in directories]
111
113
  # flatten list of lists
112
- all_entries = [entry for sublist in directory_entries for entry in sublist]
114
+ all_files = [file for sublist in directory_files for file in sublist]
113
115
  # filter duplicates through set
114
- unique_entries = set(all_entries)
115
- return unique_entries
116
+ unique_files = set(all_files)
117
+ return unique_files
116
118
 
117
119
  def _find_fets(self):
118
120
  # AA55AA55 is to unspecific, so we require a word of padding before (to be tested)
119
121
  for m in re.finditer(b'\xff\xff\xff\xff' + self._FIRMWARE_ENTRY_MAGIC, self.get_buffer()):
120
122
  fet_offset = m.start() + 4
121
123
  yield fet_offset
124
+ for m in re.finditer(b'\x00\x00\x00\x00' + self._FIRMWARE_ENTRY_MAGIC, self.get_buffer()):
125
+ fet_offset = m.start() + 4
126
+ yield fet_offset
122
127
 
123
128
  def _find_inline_pubkeys(self, fp):
124
129
 
125
- """ Try to find a pubkey anywhere in the blob.
130
+ """ Try to find a pubkey in any of the found files.
126
131
  The pubkey is identified by its fingerprint. If found, the pubkey is
127
132
  added to the list of pubkeys of the blob """
128
133
  found_pubkeys = []
129
134
 
130
- m = re.finditer(re.escape(binascii.a2b_hex(fp)), self.get_bytes())
131
- for index in m:
132
- start = index.start() - 4
133
- if int.from_bytes(self[start:start + 4], 'little') == 1:
134
- # Maybe a pubkey. Determine its size:
135
- pub_exp_size = int.from_bytes(self[start + 0x38: start + 0x3c],
136
- 'little')
137
- if pub_exp_size == 2048:
138
- size = 0x240
139
- elif pub_exp_size == 4096:
140
- size = 0x440
141
- else:
142
- continue
143
-
144
- key_id = self[start + 0x04: start + 0x14]
145
- cert_id = self[start + 0x14: start + 0x24]
146
-
147
- if key_id != cert_id and cert_id != b'\0' * 0x10:
135
+ for file in self.unique_files():
136
+ if type(file) == PubkeyFile:
137
+ continue # Pubkeys don't have inline keys but will only produce false positives
138
+ m = re.finditer(re.escape(binascii.a2b_hex(fp)), file.get_bytes())
139
+ for index in m:
140
+ start_offset = index.start() - 4
141
+ if int.from_bytes(self[start_offset:start_offset + 4], 'little') in PubkeyFile.KNOWN_VERSIONS:
142
+ # Maybe a pubkey. Determine its size:
143
+ pub_exp_size = int.from_bytes(self[start_offset + 0x38: start_offset + 0x3c],
144
+ 'little')
148
145
  if pub_exp_size == 2048:
149
- size += 0x100
146
+ size = 0x240
147
+ elif pub_exp_size == 4096:
148
+ size = 0x440
150
149
  else:
151
- size += 0x200
152
-
153
- try:
154
- entry = PubkeyEntry(self, self, 0xdead, 0, size, start, self, self.psptool)
155
- # todo: use from_fields factory instead of PubkeyEntry init
156
- # entry = Entry.from_fields(self, self.parent_buffer,
157
- # 0xdead,
158
- # size,
159
- # start,
160
- # self,
161
- # self.psptool)
162
- assert isinstance(entry, PubkeyEntry)
163
- entry.is_inline = True
164
- entry.parent_entry = self.range_dict[entry.get_address()]
165
- if type(entry.parent_entry) == PubkeyEntry:
166
- break
167
- entry.parent_entry.inline_keys.add(entry)
168
- found_pubkeys.append(entry)
169
- except Entry.ParseError as e:
170
- self.psptool.ph.print_warning(f"_find_pubkey: Entry parse error at 0x{start:x}")
171
- self.psptool.ph.print_warning(f'{e}')
172
- except Exception as e:
173
- self.psptool.ph.print_warning(f"_find_pubkey: Error couldn't convert key at: 0x{start:x}")
174
- self.psptool.ph.print_warning(f'{e}')
150
+ continue
151
+
152
+ key_id = self[start_offset + 0x04: start_offset + 0x14]
153
+ cert_id = self[start_offset + 0x14: start_offset + 0x24]
154
+
155
+ if key_id != cert_id and cert_id != b'\0' * 0x10:
156
+ if pub_exp_size == 2048:
157
+ size += 0x100
158
+ else:
159
+ size += 0x200
160
+
161
+ try:
162
+ file = InlinePubkeyFile(file, start_offset, size, self, self.psptool)
163
+ assert isinstance(file, PubkeyFile)
164
+ file.inline_keys.add(file)
165
+ found_pubkeys.append(file)
166
+ except File.ParseError as e:
167
+ self.psptool.ph.print_warning(f"_find_pubkey: File parse error at 0x{start_offset:x}")
168
+ self.psptool.ph.print_warning(f'{e}')
175
169
 
176
170
  return found_pubkeys
177
171
 
@@ -181,7 +175,7 @@ class Blob(NestedBuffer):
181
175
  found_pkes += self._find_inline_pubkeys(key_id)
182
176
  return found_pkes
183
177
 
184
- def get_entries_by_type(self, type_) -> List[Entry]:
178
+ def get_entries_by_type(self, type_) -> List[File]:
185
179
  entries = []
186
180
 
187
181
  for rom in self.roms: