sceutils 0.1.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 (36) hide show
  1. sceutils-0.1.0/LICENSE +21 -0
  2. sceutils-0.1.0/PKG-INFO +106 -0
  3. sceutils-0.1.0/README.md +96 -0
  4. sceutils-0.1.0/pyproject.toml +24 -0
  5. sceutils-0.1.0/setup.cfg +4 -0
  6. sceutils-0.1.0/src/sceutils/__init__.py +57 -0
  7. sceutils-0.1.0/src/sceutils/__main__.py +57 -0
  8. sceutils-0.1.0/src/sceutils/axfs.py +308 -0
  9. sceutils-0.1.0/src/sceutils/decrypt_cpupdate.py +136 -0
  10. sceutils-0.1.0/src/sceutils/emmc_extract.py +231 -0
  11. sceutils-0.1.0/src/sceutils/extract_idstorage.py +27 -0
  12. sceutils-0.1.0/src/sceutils/find_imported_by.py +35 -0
  13. sceutils-0.1.0/src/sceutils/keys.py +57 -0
  14. sceutils-0.1.0/src/sceutils/keys_external.py +333 -0
  15. sceutils-0.1.0/src/sceutils/keys_internal.py +121 -0
  16. sceutils-0.1.0/src/sceutils/keys_proto.py +137 -0
  17. sceutils-0.1.0/src/sceutils/module_info.py +138 -0
  18. sceutils-0.1.0/src/sceutils/pup_fiction.py +379 -0
  19. sceutils-0.1.0/src/sceutils/pup_info.py +100 -0
  20. sceutils-0.1.0/src/sceutils/scedecrypt.py +52 -0
  21. sceutils-0.1.0/src/sceutils/scetypes.py +692 -0
  22. sceutils-0.1.0/src/sceutils/sceutils.py +94 -0
  23. sceutils-0.1.0/src/sceutils/self2elf.py +242 -0
  24. sceutils-0.1.0/src/sceutils/selfinfo.py +249 -0
  25. sceutils-0.1.0/src/sceutils/ud0_extract_pup.py +42 -0
  26. sceutils-0.1.0/src/sceutils/unpack_bootimage_new.py +19 -0
  27. sceutils-0.1.0/src/sceutils/util.py +72 -0
  28. sceutils-0.1.0/src/sceutils.egg-info/PKG-INFO +106 -0
  29. sceutils-0.1.0/src/sceutils.egg-info/SOURCES.txt +34 -0
  30. sceutils-0.1.0/src/sceutils.egg-info/dependency_links.txt +1 -0
  31. sceutils-0.1.0/src/sceutils.egg-info/entry_points.txt +3 -0
  32. sceutils-0.1.0/src/sceutils.egg-info/requires.txt +1 -0
  33. sceutils-0.1.0/src/sceutils.egg-info/top_level.txt +2 -0
  34. sceutils-0.1.0/src/unarzl/__init__.py +25 -0
  35. sceutils-0.1.0/src/unarzl/__main__.py +29 -0
  36. sceutils-0.1.0/src/unarzl/arzl.py +497 -0
sceutils-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 molecule
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: sceutils
3
+ Version: 0.1.0
4
+ Summary: Utilities for PlayStation Vita SCE files
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: pycryptodomex>=3.23.0
9
+ Dynamic: license-file
10
+
11
+ sceutils
12
+ ========
13
+
14
+ Utilities for working with PlayStation Vita SCE / SELF / PUP files.
15
+
16
+ Requirements
17
+ ------------
18
+
19
+ * Python 3.11+
20
+ * pycryptodomex
21
+
22
+ Installation
23
+ ------------
24
+
25
+ ```sh
26
+ pip install .
27
+ ```
28
+
29
+ Usage
30
+ -----
31
+
32
+ ### As a command line tool
33
+
34
+ ```sh
35
+ sceutils --help
36
+ sceutils selfinfo file.self
37
+ sceutils self2elf -i eboot.bin -o eboot.elf -K keys_external.py
38
+ sceutils pup_fiction PSP2UPDATE.pup output keys_external.py
39
+ ```
40
+
41
+ The ARZL helper is also available directly:
42
+
43
+ ```sh
44
+ unarzl kernel_boot_loader.self.seg03 nsbl.bin
45
+ ```
46
+
47
+ ### As a library
48
+
49
+ ```python
50
+ from sceutils import self2elf, read_self_info, scedecrypt, unarzl, keys
51
+
52
+ keys.use_keys("keys_external.py")
53
+
54
+ with open("eboot.bin", "rb") as inf, open("eboot.elf", "wb") as outf:
55
+ self2elf(inf, outf)
56
+
57
+ with open("eboot.bin", "rb") as inf:
58
+ info = read_self_info(inf)
59
+
60
+ with open("kernel_boot_loader.self.seg03", "rb") as f:
61
+ nsbl = unarzl(f.read())
62
+ ```
63
+
64
+ Modules
65
+ -------
66
+
67
+ pup_fiction
68
+ -----------
69
+ ### usage:
70
+ * `sceutils pup_fiction PUP_filename output_folder keys_file`
71
+
72
+ ### examples:
73
+ * `sceutils pup_fiction PSP2UPDATE.pup 368 keys_external.py`
74
+
75
+ ---
76
+
77
+ self2elf
78
+ --------
79
+ ### usage:
80
+ * `sceutils self2elf -i [input self] -o [output elf] -k [RIF file] [-K keys_external.py]`
81
+ * `sceutils self2elf -i [input self] -o [output elf] -z [zRIF]`
82
+
83
+ ### examples:
84
+ * `sceutils self2elf -i eboot.bin -o eboot.elf -z [zRIF here] [-K keys_external.py]`
85
+ * `sceutils self2elf -i eboot.bin -o eboot.elf -k [RIF] [-K keys_external.py]`
86
+
87
+ ---
88
+
89
+ selfinfo
90
+ --------
91
+ ### usage:
92
+ * `sceutils selfinfo [input self] [-K keys_external.py]`
93
+
94
+ Prints the SCE header, SELF header, app info, control info, ELF headers,
95
+ segment metadata and (when possible) module imports/exports.
96
+
97
+ ---
98
+
99
+ axfs
100
+ ----
101
+ ### usage:
102
+ * `sceutils axfs [list|tar] image_name [output_name]`
103
+
104
+ ### examples:
105
+ * `sceutils axfs list fsimage.img`
106
+ * `sceutils axfs tar fsimage.img [fsimage.tar]`
@@ -0,0 +1,96 @@
1
+ sceutils
2
+ ========
3
+
4
+ Utilities for working with PlayStation Vita SCE / SELF / PUP files.
5
+
6
+ Requirements
7
+ ------------
8
+
9
+ * Python 3.11+
10
+ * pycryptodomex
11
+
12
+ Installation
13
+ ------------
14
+
15
+ ```sh
16
+ pip install .
17
+ ```
18
+
19
+ Usage
20
+ -----
21
+
22
+ ### As a command line tool
23
+
24
+ ```sh
25
+ sceutils --help
26
+ sceutils selfinfo file.self
27
+ sceutils self2elf -i eboot.bin -o eboot.elf -K keys_external.py
28
+ sceutils pup_fiction PSP2UPDATE.pup output keys_external.py
29
+ ```
30
+
31
+ The ARZL helper is also available directly:
32
+
33
+ ```sh
34
+ unarzl kernel_boot_loader.self.seg03 nsbl.bin
35
+ ```
36
+
37
+ ### As a library
38
+
39
+ ```python
40
+ from sceutils import self2elf, read_self_info, scedecrypt, unarzl, keys
41
+
42
+ keys.use_keys("keys_external.py")
43
+
44
+ with open("eboot.bin", "rb") as inf, open("eboot.elf", "wb") as outf:
45
+ self2elf(inf, outf)
46
+
47
+ with open("eboot.bin", "rb") as inf:
48
+ info = read_self_info(inf)
49
+
50
+ with open("kernel_boot_loader.self.seg03", "rb") as f:
51
+ nsbl = unarzl(f.read())
52
+ ```
53
+
54
+ Modules
55
+ -------
56
+
57
+ pup_fiction
58
+ -----------
59
+ ### usage:
60
+ * `sceutils pup_fiction PUP_filename output_folder keys_file`
61
+
62
+ ### examples:
63
+ * `sceutils pup_fiction PSP2UPDATE.pup 368 keys_external.py`
64
+
65
+ ---
66
+
67
+ self2elf
68
+ --------
69
+ ### usage:
70
+ * `sceutils self2elf -i [input self] -o [output elf] -k [RIF file] [-K keys_external.py]`
71
+ * `sceutils self2elf -i [input self] -o [output elf] -z [zRIF]`
72
+
73
+ ### examples:
74
+ * `sceutils self2elf -i eboot.bin -o eboot.elf -z [zRIF here] [-K keys_external.py]`
75
+ * `sceutils self2elf -i eboot.bin -o eboot.elf -k [RIF] [-K keys_external.py]`
76
+
77
+ ---
78
+
79
+ selfinfo
80
+ --------
81
+ ### usage:
82
+ * `sceutils selfinfo [input self] [-K keys_external.py]`
83
+
84
+ Prints the SCE header, SELF header, app info, control info, ELF headers,
85
+ segment metadata and (when possible) module imports/exports.
86
+
87
+ ---
88
+
89
+ axfs
90
+ ----
91
+ ### usage:
92
+ * `sceutils axfs [list|tar] image_name [output_name]`
93
+
94
+ ### examples:
95
+ * `sceutils axfs list fsimage.img`
96
+ * `sceutils axfs tar fsimage.img [fsimage.tar]`
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "sceutils"
3
+ version = "0.1.0"
4
+ description = "Utilities for PlayStation Vita SCE files"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "pycryptodomex>=3.23.0",
9
+ ]
10
+
11
+ [build-system]
12
+ requires = ["setuptools", "wheel"]
13
+ build-backend = "setuptools.build_meta"
14
+
15
+ [project.scripts]
16
+ sceutils = "sceutils.__main__:main"
17
+ unarzl = "unarzl.__main__:main"
18
+
19
+ [tool.setuptools]
20
+ package-dir = {"" = "src"}
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
24
+ include = ["sceutils*", "unarzl*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from unarzl import (
4
+ ArlzBufferOverflowError,
5
+ ArlzError,
6
+ ArlzInvalidDataError,
7
+ arzl_arm_filter,
8
+ arzl_decode,
9
+ arzl_decompress,
10
+ unarzl,
11
+ )
12
+
13
+ from . import keys, module_info, scetypes, sceutils
14
+ from .axfs import AXFS
15
+ from .module_info import read_module_info
16
+ from .pup_fiction import (
17
+ extract_bootimage,
18
+ extract_pup,
19
+ pup_decrypt_packages,
20
+ pup_extract_files,
21
+ slb2_decrypt,
22
+ slb2_extract,
23
+ )
24
+ from .scedecrypt import scedecrypt
25
+ from .sceutils import get_key_type, get_segments, zrif_decode
26
+ from .self2elf import self2elf
27
+ from .selfinfo import read_self_info
28
+
29
+ __version__ = "0.1.0"
30
+
31
+ __all__ = [
32
+ "AXFS",
33
+ "ArlzError",
34
+ "ArlzInvalidDataError",
35
+ "ArlzBufferOverflowError",
36
+ "arzl_decode",
37
+ "arzl_decompress",
38
+ "arzl_arm_filter",
39
+ "extract_bootimage",
40
+ "extract_pup",
41
+ "get_key_type",
42
+ "get_segments",
43
+ "keys",
44
+ "module_info",
45
+ "pup_decrypt_packages",
46
+ "pup_extract_files",
47
+ "read_module_info",
48
+ "read_self_info",
49
+ "scetypes",
50
+ "sceutils",
51
+ "scedecrypt",
52
+ "self2elf",
53
+ "slb2_decrypt",
54
+ "slb2_extract",
55
+ "unarzl",
56
+ "zrif_decode",
57
+ ]
@@ -0,0 +1,57 @@
1
+ import importlib
2
+ import sys
3
+
4
+ MODULES = [
5
+ "axfs",
6
+ "emmc_extract",
7
+ "extract_idstorage",
8
+ "find_imported_by",
9
+ "module_info",
10
+ "pup_fiction",
11
+ "pup_info",
12
+ "scedecrypt",
13
+ "self2elf",
14
+ "selfinfo",
15
+ "ud0_extract_pup",
16
+ "unpack_bootimage_new",
17
+ "unarzl",
18
+ ]
19
+
20
+
21
+ def print_modules() -> None:
22
+ print("usage: sceutils <module> [options]")
23
+ print()
24
+ print("modules:")
25
+ for module in MODULES:
26
+ print(f" {module}")
27
+ print()
28
+ print("Use 'sceutils <module> --help' for module specific options.")
29
+
30
+
31
+ def main(argv: list[str] | None = None) -> int:
32
+ argv = list(sys.argv if argv is None else argv)
33
+
34
+ if len(argv) < 2 or argv[1] in ("-h", "--help"):
35
+ print_modules()
36
+ return 0 if len(argv) >= 2 else 1
37
+
38
+ module_name = argv[1]
39
+
40
+ if module_name == "unarzl":
41
+ from unarzl.__main__ import main as unarzl_main
42
+ return unarzl_main(argv[2:])
43
+
44
+ if module_name not in MODULES:
45
+ print(f"Unknown module: {module_name}")
46
+ print()
47
+ print_modules()
48
+ return 1
49
+
50
+ sys.argv = argv[1:]
51
+ module = importlib.import_module(f"sceutils.{module_name}")
52
+ result = module.main(sys.argv)
53
+ return result if isinstance(result, int) else 0
54
+
55
+
56
+ if __name__ == "__main__":
57
+ raise SystemExit(main())
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import os
5
+ import pathlib
6
+ import stat
7
+ import struct
8
+ import tarfile
9
+ import zlib
10
+ import sys
11
+ from dataclasses import dataclass
12
+ from io import BytesIO
13
+ from typing import Literal, Optional, IO
14
+
15
+ PAGE_SHIFT = 12
16
+
17
+
18
+ class ENodeTypes:
19
+ XIP = 0
20
+ Compressed = 1
21
+ Byte_aligned = 2
22
+
23
+
24
+ def MAJOR(dev):
25
+ return dev >> 8
26
+
27
+
28
+ def MINOR(dev):
29
+ return dev & 0xff
30
+
31
+
32
+ @dataclass
33
+ class axfs_super_onmedia:
34
+ magic: bytes # 0x48A0E4CD
35
+ signature: bytes # Advanced XIP FS\x00
36
+ digest: bytes # sha1 digest for checking data integrity (sometimes 0x0)
37
+ cblock_size: int # maximum size of the block being compressed
38
+ files: int # number of inodes/files in fs
39
+ size: int # total image size in bytes
40
+ blocks: bytes # number of nodes in fs
41
+ mmap_size: bytes # size of the memory mapped part of image
42
+ strings: int # offset to strings region descriptor
43
+ xip: int # offset to xip region descriptor
44
+ byte_aligned: int # offset to the byte aligned region desc
45
+ compressed: int # offset to the compressed region desc
46
+ node_type: int # offset to node type region desc
47
+ node_index: int # offset to node index region desc
48
+ cnode_offset: int # offset to cnode offset region desc
49
+ cnode_index: int # offset to cnode index region desc
50
+ banode_offset: int # offset to banode offset region desc
51
+ cblock_offset: int # offset to cblock offset region desc
52
+ inode_file_size: int # offset to inode file size desc
53
+ inode_name_offset: int # offset to inode name region desc
54
+ inode_num_entries: int # offset to inode num_entries region desc
55
+ inode_mode_index: int # offset to inode mode_index region desc
56
+ inode_array_index: int # offset to inode array_index region desc
57
+ modes: int # offset to mode mode region desc
58
+ uids: int # offset to uid index mode region desc
59
+ gids: int # offset to gid index mode region desc
60
+ version_major: int
61
+ version_minor: int
62
+ version_sub: int
63
+ compression_type: int # Identifies type of compression used on FS
64
+ timestamp: int # UNIX time_t of filesystem build time
65
+
66
+
67
+ class axfs_region_desc_onmedia:
68
+ fsoffset: int
69
+ size: int
70
+ compressed_size: int
71
+ max_index: int
72
+ table_byte_depth: int
73
+ incore: int
74
+
75
+
76
+ class axfs_region(axfs_region_desc_onmedia):
77
+ data: bytes
78
+
79
+ def __init__(self, f: IO[bytes]) -> None:
80
+ (
81
+ self.fsoffset, self.size,
82
+ self.compressed_size, self.max_index,
83
+ self.table_byte_depth, self.incore
84
+ ) = struct.unpack(">QQQQBB", f.read(34))
85
+ f.seek(self.fsoffset, os.SEEK_SET)
86
+ self.data = f.read(self.size)
87
+
88
+ def axfs_bytetable_stitch(self, index: int) -> int:
89
+ assert index < self.max_index
90
+ output = 0
91
+ split = self.size // self.table_byte_depth
92
+ for i in range(self.table_byte_depth):
93
+ output += self.data[index + i * split] << (8 * i)
94
+ return output
95
+
96
+
97
+ def offsetBytes(data: bytes, start: int, length: int):
98
+ return data[start:start + length]
99
+
100
+
101
+ def loadRegion(f: IO, offset: int) -> axfs_region:
102
+ f.seek(offset, os.SEEK_SET)
103
+ return axfs_region(f)
104
+
105
+
106
+ class AXFS:
107
+ def __init__(self, f: IO[bytes]):
108
+ self.superblock = axfs_super_onmedia(*struct.unpack('>I16s40sI22Q4BQ', f.read(252)))
109
+ assert self.superblock.signature == b'Advanced XIP FS\x00'
110
+ assert self.superblock.magic == 0x48A0E4CD
111
+ self.xip = loadRegion(f, self.superblock.xip)
112
+ self.strings = loadRegion(f, self.superblock.strings)
113
+ self.compressed = loadRegion(f, self.superblock.compressed)
114
+ self.byte_aligned = loadRegion(f, self.superblock.byte_aligned)
115
+ self.node_type = loadRegion(f, self.superblock.node_type)
116
+ self.node_index = loadRegion(f, self.superblock.node_index)
117
+ self.cnode_offset = loadRegion(f, self.superblock.cnode_offset)
118
+ self.cnode_index = loadRegion(f, self.superblock.cnode_index)
119
+ self.banode_offset = loadRegion(f, self.superblock.banode_offset)
120
+ self.cblock_offset = loadRegion(f, self.superblock.cblock_offset)
121
+ self.inode_file_size = loadRegion(f, self.superblock.inode_file_size)
122
+ self.inode_name_offset = loadRegion(f, self.superblock.inode_name_offset)
123
+ self.inode_num_entries = loadRegion(f, self.superblock.inode_num_entries)
124
+ self.inode_mode_index = loadRegion(f, self.superblock.inode_mode_index)
125
+ self.inode_array_index = loadRegion(f, self.superblock.inode_array_index)
126
+ self.modes = loadRegion(f, self.superblock.modes)
127
+ self.uids = loadRegion(f, self.superblock.uids)
128
+ self.gids = loadRegion(f, self.superblock.gids)
129
+ f.seek(0)
130
+
131
+ def list(self, node_id: int, folder="/"):
132
+ num_files = self.getNumEntries(node_id)
133
+ first = self.getArrayIndex(node_id)
134
+ for i in range(first, first + num_files):
135
+ name = self.getName(i)
136
+ mode = self.getMode(i)
137
+ size = " " * 9
138
+
139
+ if stat.S_ISREG(mode):
140
+ size = str(self.getFileSize(i)).rjust(9)
141
+ if stat.S_ISLNK(mode):
142
+ link_name = self.readFileData(i).decode("utf8")
143
+ name += ' -> ' + link_name
144
+ if stat.S_ISCHR(mode) or stat.S_ISBLK(mode):
145
+ st_rdev = self.getFileSize(i)
146
+ size = f"{str(MAJOR(st_rdev)).rjust(3)} {str(MINOR(st_rdev)).rjust(3)}".rjust(9)
147
+
148
+ print(f"{stat.filemode(mode)} {size} {folder + name}")
149
+
150
+ if stat.S_ISDIR(mode):
151
+ self.list(i, folder + name + "/")
152
+
153
+ def toTar(self, node_id: int, tar: tarfile.TarFile, folder=""):
154
+ num_files = self.getNumEntries(node_id)
155
+ first = self.getArrayIndex(node_id)
156
+ for i in range(first, first + num_files):
157
+ name = self.getName(i)
158
+ mode = self.getMode(i)
159
+
160
+ file = tarfile.TarInfo()
161
+ file.path = folder + name
162
+ file.uid = self.getUid(i)
163
+ file.gid = self.getGid(i)
164
+ file.mode = mode
165
+ file.mtime = self.superblock.timestamp
166
+ data = None
167
+
168
+ if stat.S_ISDIR(mode):
169
+ file.type = tarfile.DIRTYPE
170
+ self.toTar(i, tar, file.path + "/")
171
+
172
+ elif stat.S_ISLNK(mode):
173
+ link_name = self.readFileData(i).decode("utf8")
174
+ file.type = tarfile.SYMTYPE
175
+ file.linkname = link_name
176
+ elif stat.S_ISREG(mode):
177
+ file.type = tarfile.REGTYPE
178
+ file.size = self.getFileSize(i)
179
+ data = self.readFileData(i)
180
+
181
+ elif stat.S_ISCHR(mode):
182
+ file.type = tarfile.CHRTYPE
183
+ elif stat.S_ISBLK(mode):
184
+ file.type = tarfile.BLKTYPE
185
+
186
+ elif stat.S_ISSOCK(mode) or stat.S_ISFIFO(mode):
187
+ pass
188
+ else:
189
+ raise Exception("unkown filetype")
190
+
191
+ if stat.S_ISCHR(mode) or stat.S_ISBLK(mode):
192
+ st_rdev = self.getFileSize(i) # very weird to put that in filesize...
193
+ file.devmajor, file.devminor = MAJOR(st_rdev), MINOR(st_rdev)
194
+
195
+ tar.addfile(file, BytesIO(data))
196
+
197
+ def getName(self, node_id: int) -> int:
198
+ offset = self.inode_name_offset.axfs_bytetable_stitch(node_id)
199
+ ret = self.strings.data[offset:]
200
+ if len(ret):
201
+ ret = ret[:ret.index(b"\00")]
202
+ return ret.decode("utf8")
203
+
204
+ def getFileSize(self, node_id: int) -> int:
205
+ return self.inode_file_size.axfs_bytetable_stitch(node_id)
206
+
207
+ def getMode(self, node_id: int) -> int:
208
+ mode_index = self.inode_mode_index.axfs_bytetable_stitch(node_id)
209
+ return self.modes.axfs_bytetable_stitch(mode_index)
210
+
211
+ def getUid(self, node_id: int) -> int:
212
+ mode_index = self.inode_mode_index.axfs_bytetable_stitch(node_id)
213
+ return self.uids.axfs_bytetable_stitch(mode_index)
214
+
215
+ def getGid(self, node_id: int) -> int:
216
+ mode_index = self.inode_mode_index.axfs_bytetable_stitch(node_id)
217
+ return self.gids.axfs_bytetable_stitch(mode_index)
218
+
219
+ def getNumEntries(self, node_id: int) -> int:
220
+ return self.inode_num_entries.axfs_bytetable_stitch(node_id)
221
+
222
+ def getArrayIndex(self, node_id: int) -> int:
223
+ return self.inode_array_index.axfs_bytetable_stitch(node_id)
224
+
225
+ def getNodeIndex(self, node_id: int) -> int:
226
+ return self.node_index.axfs_bytetable_stitch(node_id)
227
+
228
+ def getNodeType(self, node_id: int) -> int:
229
+ return self.node_type.axfs_bytetable_stitch(node_id)
230
+
231
+ def getByteAlignedOffset(self, node_id: int) -> int:
232
+ return self.banode_offset.axfs_bytetable_stitch(node_id)
233
+
234
+ def readFileData(self, node_id: int) -> bytes:
235
+ file_size = self.getFileSize(node_id)
236
+ array_index = self.getArrayIndex(node_id)
237
+
238
+ length = file_size
239
+ offset = 0
240
+ out = bytearray()
241
+
242
+ while length > 0:
243
+ node_index = self.getNodeIndex(array_index)
244
+ node_type = self.getNodeType(array_index)
245
+
246
+ if node_type == ENodeTypes.Byte_aligned:
247
+ src_offset = self.getByteAlignedOffset(node_index)
248
+ block_size = min(0x1 << PAGE_SHIFT, file_size)
249
+ out[offset:] = offsetBytes(self.byte_aligned.data, src_offset, block_size)
250
+ length -= block_size
251
+ offset += block_size
252
+
253
+ elif node_type == ENodeTypes.XIP:
254
+ out[offset:] = offsetBytes(self.xip.data, node_index << PAGE_SHIFT, 1 << PAGE_SHIFT)
255
+ offset += 1 << PAGE_SHIFT
256
+ length -= 1 << PAGE_SHIFT
257
+
258
+ elif node_type == ENodeTypes.Compressed:
259
+ cnode_offset = self.cnode_offset.axfs_bytetable_stitch(node_index)
260
+ cnode_index = self.cnode_index.axfs_bytetable_stitch(node_index)
261
+ src_offset = self.cblock_offset.axfs_bytetable_stitch(cnode_index)
262
+
263
+ block_length = self.cblock_offset.axfs_bytetable_stitch(cnode_index + 1) - src_offset
264
+ data = zlib.decompress(offsetBytes(self.compressed.data, src_offset, block_length))
265
+ block_length = min(self.superblock.cblock_size - cnode_offset, length)
266
+ out[offset:] = offsetBytes(data, cnode_offset, block_length)
267
+ length -= block_length
268
+ offset += block_length
269
+ else:
270
+ raise Exception("invalid nodeType")
271
+
272
+ array_index += 1
273
+
274
+ return bytes(out)
275
+
276
+
277
+ def main(argv = sys.argv):
278
+ class _args:
279
+ command: Literal["list", "tar"]
280
+ inputfile: pathlib.Path
281
+ output: Optional[pathlib.Path]
282
+
283
+ parser = argparse.ArgumentParser()
284
+ parser.add_argument("command", type=str, choices=["list", "tar"])
285
+ parser.add_argument("inputfile", help="input axfs image", type=pathlib.Path)
286
+ parser.add_argument("output", help="output file name for tar", type=pathlib.Path, required=False) # optional
287
+ args: _args = parser.parse_args(argv[1:])
288
+
289
+ inputfile = args.inputfile.as_posix()
290
+ output = args.output.as_posix() if args.output else None
291
+
292
+ with open(inputfile, "rb") as f:
293
+ fs = AXFS(f)
294
+
295
+ if args.command == "list":
296
+ fs.list(0)
297
+ return
298
+ elif args.command == "tar":
299
+ if not output:
300
+ output = inputfile + ".tar"
301
+ tar = tarfile.open(output, mode="w")
302
+ fs.toTar(0, tar)
303
+ tar.close()
304
+ return
305
+
306
+
307
+ if __name__ == "__main__":
308
+ main()