psptool 2.8__tar.gz → 3.1__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 (42) hide show
  1. {psptool-2.8 → psptool-3.1}/PKG-INFO +37 -39
  2. {psptool-2.8 → psptool-3.1}/README.md +36 -35
  3. {psptool-2.8 → psptool-3.1}/psptool/__init__.py +2 -2
  4. psptool-3.1/psptool/__main__.py +282 -0
  5. {psptool-2.8 → psptool-3.1}/psptool/blob.py +60 -66
  6. {psptool-2.8 → psptool-3.1}/psptool/cert_tree.py +51 -49
  7. {psptool-2.8 → psptool-3.1}/psptool/crypto.py +2 -3
  8. psptool-3.1/psptool/directory.py +205 -0
  9. psptool-3.1/psptool/entry.py +96 -0
  10. {psptool-2.8 → psptool-3.1}/psptool/errors.py +1 -2
  11. {psptool-2.8 → psptool-3.1}/psptool/fet.py +13 -43
  12. psptool-3.1/psptool/file.py +295 -0
  13. psptool-3.1/psptool/header_file.py +214 -0
  14. psptool-3.1/psptool/key_store_file.py +253 -0
  15. {psptool-2.8 → psptool-3.1}/psptool/psptool.py +81 -77
  16. psptool-3.1/psptool/pubkey_file.py +178 -0
  17. {psptool-2.8 → psptool-3.1}/psptool/rom.py +22 -21
  18. {psptool-2.8 → psptool-3.1}/psptool.egg-info/PKG-INFO +37 -39
  19. {psptool-2.8 → psptool-3.1}/psptool.egg-info/SOURCES.txt +4 -0
  20. {psptool-2.8 → psptool-3.1}/psptool.egg-info/requires.txt +0 -1
  21. {psptool-2.8 → psptool-3.1}/setup.cfg +2 -3
  22. {psptool-2.8 → psptool-3.1}/tests/integration/test_rom_files.py +4 -4
  23. {psptool-2.8 → psptool-3.1}/tests/metrics.txt +187 -173
  24. psptool-2.8/psptool/__main__.py +0 -225
  25. psptool-2.8/psptool/directory.py +0 -200
  26. psptool-2.8/psptool/entry.py +0 -1040
  27. {psptool-2.8 → psptool-3.1}/.github/workflows/python-tests.yml +0 -0
  28. {psptool-2.8 → psptool-3.1}/.gitignore +0 -0
  29. {psptool-2.8 → psptool-3.1}/.gitmodules +0 -0
  30. {psptool-2.8 → psptool-3.1}/LICENSE +0 -0
  31. {psptool-2.8 → psptool-3.1}/psptool/firmware.py +0 -0
  32. {psptool-2.8 → psptool-3.1}/psptool/utils.py +0 -0
  33. {psptool-2.8 → psptool-3.1}/psptool.egg-info/dependency_links.txt +0 -0
  34. {psptool-2.8 → psptool-3.1}/psptool.egg-info/entry_points.txt +0 -0
  35. {psptool-2.8 → psptool-3.1}/psptool.egg-info/top_level.txt +0 -0
  36. {psptool-2.8 → psptool-3.1}/setup.py +0 -0
  37. {psptool-2.8 → psptool-3.1}/tests/__init__.py +0 -0
  38. {psptool-2.8 → psptool-3.1}/tests/create_metrics.sh +0 -0
  39. {psptool-2.8 → psptool-3.1}/tests/integration/__init__.py +0 -0
  40. {psptool-2.8 → psptool-3.1}/tests/integration/test_psptrace.py +0 -0
  41. {psptool-2.8 → psptool-3.1}/tests/unit/__init__.py +0 -0
  42. {psptool-2.8 → psptool-3.1}/tests/unit/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: psptool
3
- Version: 2.8
3
+ Version: 3.1
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
@@ -13,9 +13,6 @@ Classifier: Programming Language :: Python :: 3.8
13
13
  Requires-Python: >=3.8
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: setuptools
17
- Requires-Dist: cryptography>=38.0.0
18
- Requires-Dist: prettytable
19
16
 
20
17
  # PSPTool
21
18
 
@@ -798,40 +795,41 @@ usage: psptool [-V | -E | -X | -R] [file]
798
795
  Display, extract, and manipulate AMD PSP firmware inside BIOS ROMs.
799
796
 
800
797
  positional arguments:
801
- file Binary file to be parsed for PSP firmware
798
+ file Binary file to be parsed for PSP firmware
802
799
 
803
800
  optional arguments:
804
801
  -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
802
+ -E, --entries Default: Parse and display PSP firmware entries.
803
+ [-n] [-j] [-t]
804
+
805
+ -n: list unique entries only ordered by their offset
806
+ -j: output in JSON format instead of tables
807
+ -t: print tree of all signed entities and their certifying keys
808
+ -m: print parsing metrics for testing
809
+
810
+ -X, --extract-file Extract one or more PSP firmware files.
811
+ [-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]
812
+
813
+ -r idx: specifies rom_index (default: 0)
814
+ -d idx: specifies directory_index (default: all directories)
815
+ -e idx: specifies file_index (default: all files)
816
+ -n: skip duplicate files and extract unique files only
817
+ -u: uncompress compressed files
818
+ -c: try to decrypt files
819
+ -k: convert pubkeys into PEM format
820
+ -o file: specifies outfile/outdir (default: stdout/{file}_extracted)
821
+
822
+ -R, --replace-file Copy a new file (including header and signature) into the
823
+ ROM file and update file and other metadata accordingly.
824
+ -d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]
825
+
826
+ -r idx: specifies rom_index (default: 0)
827
+ -d idx: specifies directory_index
828
+ -e idx: specifies file_index
829
+ -s file: specifies subfile (i.e. the new file contents)
830
+ -o file: specifies outfile
831
+ -p file: specifies file-stub (e.g. 'keys/id') for the re-signing keys
832
+ -a pass: specifies password for the re-signing keys
835
833
  ```
836
834
 
837
835
  ## Python Usage
@@ -867,13 +865,13 @@ PSPTool can be **used as a Python module**, e.g. in an interactive IPython sessi
867
865
  | | 14 | 0xeba00 | 0xbd60 | 0x36 | | AR6B | 17.9.18.12 | OEM_PSP_FW_PUBLIC_KEY |
868
866
  | | 15 | 0x149000 | 0x400 | 0x40 | !PL2_SECONDARY_DIRECTORY | | | |
869
867
  +---+-------+----------+---------+------+-----------------------------+-------+------------+-----------------------+
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()
868
+ > psp.blob.roms[0].directories[0].files[0]
869
+ PubkeyFile(type=0x0, address=0x77400, size=0x240, len(references)=1)
870
+ > psp.blob.directories[0].files[0].get_bytes()
873
871
  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
872
  [...]
875
873
  > my_stuff = [...]
876
- > psp.blob.roms[0].directories[0].entries[1].move_buffer(0x60000, 0x1000)
874
+ > psp.blob.roms[0].directories[0].files[1].move_buffer(0x60000, 0x1000)
877
875
  > psp.blob.roms[0].set_bytes(0x60000, 0x1000, my_stuff)
878
876
  > psp.to_file('my_modified_bios.bin')
879
877
  ```
@@ -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
  ```
@@ -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")
@@ -0,0 +1,282 @@
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 sys
18
+ import os
19
+ import re
20
+
21
+ from .psptool import PSPTool
22
+ from . import __version__
23
+ from .utils import ObligingArgumentParser, PrintHelper
24
+ from .header_file import HeaderFile
25
+ from .pubkey_file import PubkeyFile
26
+ from .crypto import PrivateKeyDict
27
+
28
+ from argparse import RawTextHelpFormatter, SUPPRESS
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
+
61
+ def main():
62
+ # CLI stuff to create a PSPTool object and interact with it
63
+ parser = ObligingArgumentParser(description='Display, extract, and manipulate AMD PSP firmware inside BIOS ROMs.\n',
64
+ formatter_class=RawTextHelpFormatter, add_help=False)
65
+
66
+ parser.add_argument('file', help='Binary file to be parsed for PSP firmware', nargs='?')
67
+ parser.add_argument('-h', '--help', action='help', help=SUPPRESS)
68
+ parser.add_argument('-v', '--verbose', help=SUPPRESS, action='store_true')
69
+
70
+ parser.add_argument('-r', '--rom-index', help=SUPPRESS, type=int, default=0)
71
+ parser.add_argument('-d', '--directory-index', help=SUPPRESS, type=int)
72
+ parser.add_argument('-e', '--file-index', help=SUPPRESS, type=int)
73
+ parser.add_argument('-s', '--subfile', help=SUPPRESS)
74
+ parser.add_argument('-o', '--outfile', help=SUPPRESS)
75
+ parser.add_argument('-u', '--decompress', help=SUPPRESS, action='store_true')
76
+ parser.add_argument('-c', '--decrypt', help=SUPPRESS, action='store_true')
77
+ parser.add_argument('-k', '--pem-key', help=SUPPRESS, action='store_true')
78
+ parser.add_argument('-n', '--no-duplicates', help=SUPPRESS, action='store_true')
79
+ parser.add_argument('-j', '--json', help=SUPPRESS, action='store_true')
80
+ parser.add_argument('-t', '--key-tree', help=SUPPRESS, action='store_true')
81
+ parser.add_argument('-m', '--metrics', help=SUPPRESS, action='store_true')
82
+ parser.add_argument('-p', '--privkeystub', help=SUPPRESS)
83
+ parser.add_argument('-a', '--privkeypass', help=SUPPRESS)
84
+ parser.add_argument('-T', '--type-regex', help=SUPPRESS)
85
+
86
+ action = parser.add_mutually_exclusive_group(required=False)
87
+
88
+ action.add_argument('-V', '--version', action='store_true')
89
+
90
+ action.add_argument('-E', '--entries', help='\n'.join([
91
+ 'Default: Parse and display PSP firmware entries.',
92
+ '[-n] [-j] [-t]',
93
+ '',
94
+ '-n: list unique entries only ordered by their offset',
95
+ '-j: output in JSON format instead of tables',
96
+ '-t: print tree of all signed entities and their certifying keys',
97
+ '-m: print parsing metrics for testing',
98
+ '', '']), action='store_true')
99
+
100
+ action.add_argument('-X', '--extract-file', help='\n'.join([
101
+ 'Extract one or more PSP firmware files.',
102
+ '[-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile] [-T regex]',
103
+ '',
104
+ '-r idx: specifies rom_index (default: ROM 0 only)',
105
+ '-d idx: specifies directory_index (default: all directories)',
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',
111
+ '-n: skip duplicate files and extract unique files only',
112
+ '-u: uncompress compressed files',
113
+ '-c: try to decrypt files',
114
+ '-k: convert pubkeys into PEM format',
115
+ '-o file: specifies outfile/outdir (default: stdout/{file}_extracted)',
116
+ '', '']), action='store_true')
117
+
118
+ action.add_argument('-R', '--replace-file', help='\n'.join([
119
+ 'Copy a new file (including header and signature) into the',
120
+ 'ROM file and update file and other metadata accordingly.',
121
+ '-d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]',
122
+ '',
123
+ '-r idx: specifies rom_index (default: 0)',
124
+ '-d idx: specifies directory_index',
125
+ '-e idx: specifies file_index',
126
+ '-s file: specifies subfile (i.e. the new file contents)',
127
+ '-o file: specifies outfile',
128
+ '-p file: specifies file-stub (e.g. \'keys/id\') for the re-signing keys',
129
+ '-a pass: specifies password for the re-signing keys'
130
+ '', '']), action='store_true')
131
+
132
+ args = parser.parse_args()
133
+ ph = PrintHelper(args.verbose)
134
+
135
+ if args.version:
136
+ print(__version__)
137
+ sys.exit(0)
138
+ elif not args.file:
139
+ parser.print_help(sys.stderr)
140
+ sys.exit(0)
141
+
142
+ psp = PSPTool.from_file(args.file, verbose=args.verbose)
143
+ output = None
144
+
145
+ if args.extract_file:
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)
165
+ file = psp.blob.roms[args.rom_index].directories[args.directory_index].files[args.file_index]
166
+
167
+ if file:
168
+ # For Options 1 and 2, do additional extraction magic if requested
169
+ if args.decompress:
170
+ if not file.compressed:
171
+ ph.print_error_and_exit(f'File is not compressed {file.get_readable_type()}')
172
+ output = file.get_signed_bytes()
173
+ elif args.decrypt:
174
+ if not file.encrypted:
175
+ ph.print_error_and_exit(f'File is not encrypted {file.get_readable_type()}')
176
+ output = file.to_decrypted_file_bytes()
177
+ elif args.pem_key:
178
+ output = file.get_pem_encoded()
179
+ else:
180
+ output = file.get_bytes()
181
+
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):
193
+ if args.decompress and type(file) is HeaderFile:
194
+ out_bytes = file.get_signed_bytes()
195
+ elif args.decrypt and type(file) is HeaderFile:
196
+ out_bytes = file.get_decrypted()
197
+ elif args.pem_key and type(file) is PubkeyFile:
198
+ out_bytes = file.get_pem_encoded()
199
+ else:
200
+ out_bytes = file.get_bytes()
201
+
202
+ outpath = outdir + '/d%.2d_e%.2d_%s' % (dir_index, file_index, file.get_readable_type())
203
+ if type(file) is HeaderFile:
204
+ outpath += f'_{file.get_readable_version()}'
205
+
206
+ os.makedirs(os.path.dirname(outpath), exist_ok=True)
207
+ with open(outpath, 'wb') as f:
208
+ f.write(out_bytes)
209
+ else: # no_duplicates is True
210
+ for file in psp.blob.unique_files():
211
+ if args.decompress and type(file) is HeaderFile:
212
+ out_bytes = file.get_signed_bytes()
213
+ elif args.decrypt and type(file) is HeaderFile:
214
+ out_bytes = file.get_decrypted()
215
+ elif args.pem_key and type(file) is PubkeyFile:
216
+ out_bytes = file.get_pem_encoded()
217
+ else:
218
+ out_bytes = file.get_bytes()
219
+
220
+ outdir = args.outfile or f'./{psp.filename}_unique_extracted'
221
+ outpath = outdir + '/%s' % (file.get_readable_type())
222
+
223
+ if issubclass(type(file), HeaderFile):
224
+ outpath += f'_{file.get_readable_version()}'
225
+
226
+ os.makedirs(os.path.dirname(outpath), exist_ok=True)
227
+ with open(outpath, 'wb') as f:
228
+ f.write(out_bytes)
229
+ ph.print_info(f"Extracted all files to {outdir}")
230
+ else:
231
+ parser.print_help(sys.stderr)
232
+
233
+ elif args.replace_file:
234
+ if args.directory_index is not None and args.file_index is not None and args.outfile is not None:
235
+ file = psp.blob.roms[args.rom_index].directories[args.directory_index].files[args.file_index]
236
+
237
+ # Substituting an file is actually optional to allow plain re-signs
238
+ if args.subfile is not None:
239
+ with open(args.subfile, 'rb') as f:
240
+ sub_binary = f.read()
241
+ # Keep the existing file's address, but adapt its size
242
+ file.move_buffer(file.get_address(), len(sub_binary))
243
+ file.set_bytes(0, len(sub_binary), sub_binary)
244
+
245
+ privkeys = None
246
+ if args.privkeystub:
247
+ privkeys = PrivateKeyDict.read_from_files(args.privkeystub, args.privkeypass)
248
+
249
+ if hasattr(file, 'signed_entity') and file.signed_entity:
250
+ file.signed_entity.resign_and_replace(privkeys=privkeys, recursive=True)
251
+ else:
252
+ ph.print_warning("Did not resign anything since target file is not signed")
253
+
254
+ psp.to_file(args.outfile)
255
+
256
+ if privkeys:
257
+ privkeys.save_to_files(args.privkeystub, args.privkeypass)
258
+ else:
259
+ parser.print_help(sys.stderr)
260
+ else:
261
+ if args.json:
262
+ psp.ls_json(verbose=args.verbose)
263
+ elif args.key_tree:
264
+ psp.cert_tree.print_key_tree()
265
+ elif args.metrics:
266
+ psp.print_metrics()
267
+ elif args.no_duplicates:
268
+ psp.ls_files(verbose=args.verbose)
269
+ else:
270
+ psp.ls(verbose=args.verbose)
271
+
272
+ # Output handling (stdout or outfile)
273
+ if output is not None:
274
+ if args.outfile is None:
275
+ sys.stdout.buffer.write(output)
276
+ else:
277
+ with open(args.outfile, 'wb') as f:
278
+ f.write(output)
279
+
280
+
281
+ if __name__ == '__main__':
282
+ main()