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.
- {psptool-2.8 → psptool-3.1}/PKG-INFO +37 -39
- {psptool-2.8 → psptool-3.1}/README.md +36 -35
- {psptool-2.8 → psptool-3.1}/psptool/__init__.py +2 -2
- psptool-3.1/psptool/__main__.py +282 -0
- {psptool-2.8 → psptool-3.1}/psptool/blob.py +60 -66
- {psptool-2.8 → psptool-3.1}/psptool/cert_tree.py +51 -49
- {psptool-2.8 → psptool-3.1}/psptool/crypto.py +2 -3
- psptool-3.1/psptool/directory.py +205 -0
- psptool-3.1/psptool/entry.py +96 -0
- {psptool-2.8 → psptool-3.1}/psptool/errors.py +1 -2
- {psptool-2.8 → psptool-3.1}/psptool/fet.py +13 -43
- psptool-3.1/psptool/file.py +295 -0
- psptool-3.1/psptool/header_file.py +214 -0
- psptool-3.1/psptool/key_store_file.py +253 -0
- {psptool-2.8 → psptool-3.1}/psptool/psptool.py +81 -77
- psptool-3.1/psptool/pubkey_file.py +178 -0
- {psptool-2.8 → psptool-3.1}/psptool/rom.py +22 -21
- {psptool-2.8 → psptool-3.1}/psptool.egg-info/PKG-INFO +37 -39
- {psptool-2.8 → psptool-3.1}/psptool.egg-info/SOURCES.txt +4 -0
- {psptool-2.8 → psptool-3.1}/psptool.egg-info/requires.txt +0 -1
- {psptool-2.8 → psptool-3.1}/setup.cfg +2 -3
- {psptool-2.8 → psptool-3.1}/tests/integration/test_rom_files.py +4 -4
- {psptool-2.8 → psptool-3.1}/tests/metrics.txt +187 -173
- psptool-2.8/psptool/__main__.py +0 -225
- psptool-2.8/psptool/directory.py +0 -200
- psptool-2.8/psptool/entry.py +0 -1040
- {psptool-2.8 → psptool-3.1}/.github/workflows/python-tests.yml +0 -0
- {psptool-2.8 → psptool-3.1}/.gitignore +0 -0
- {psptool-2.8 → psptool-3.1}/.gitmodules +0 -0
- {psptool-2.8 → psptool-3.1}/LICENSE +0 -0
- {psptool-2.8 → psptool-3.1}/psptool/firmware.py +0 -0
- {psptool-2.8 → psptool-3.1}/psptool/utils.py +0 -0
- {psptool-2.8 → psptool-3.1}/psptool.egg-info/dependency_links.txt +0 -0
- {psptool-2.8 → psptool-3.1}/psptool.egg-info/entry_points.txt +0 -0
- {psptool-2.8 → psptool-3.1}/psptool.egg-info/top_level.txt +0 -0
- {psptool-2.8 → psptool-3.1}/setup.py +0 -0
- {psptool-2.8 → psptool-3.1}/tests/__init__.py +0 -0
- {psptool-2.8 → psptool-3.1}/tests/create_metrics.sh +0 -0
- {psptool-2.8 → psptool-3.1}/tests/integration/__init__.py +0 -0
- {psptool-2.8 → psptool-3.1}/tests/integration/test_psptrace.py +0 -0
- {psptool-2.8 → psptool-3.1}/tests/unit/__init__.py +0 -0
- {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:
|
|
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
|
|
798
|
+
file Binary file to be parsed for PSP firmware
|
|
802
799
|
|
|
803
800
|
optional arguments:
|
|
804
801
|
-V, --version
|
|
805
|
-
-E, --entries
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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].
|
|
871
|
-
|
|
872
|
-
> psp.blob.directories[0].
|
|
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].
|
|
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
|
|
782
|
+
file Binary file to be parsed for PSP firmware
|
|
783
783
|
|
|
784
784
|
optional arguments:
|
|
785
785
|
-V, --version
|
|
786
|
-
-E, --entries
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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].
|
|
852
|
-
|
|
853
|
-
> psp.blob.directories[0].
|
|
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].
|
|
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
|
-
|
|
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()
|