Skip to content

Commit 3aa401a

Browse files
authored
Merge pull request #1143 from rxpha3l/geom_uzip
feat(handler): add geom_uzip handler
2 parents ba16867 + 61ff4ab commit 3aa401a

File tree

22 files changed

+281
-0
lines changed

22 files changed

+281
-0
lines changed

overlay.nix

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ final: prev:
2929
];
3030
};
3131

32+
dependencies = (super.dependencies or [ ]) ++ [ final.python3.pkgs.pyzstd ];
33+
3234
# remove this when packaging changes are upstreamed
3335
cargoDeps = final.rustPlatform.importCargoLock {
3436
lockFile = ./Cargo.lock;

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies = [
1919
"pyfatfs>=1.0.5",
2020
"pyperscan>=0.3.0",
2121
"python-magic>=0.4.27",
22+
"pyzstd",
2223
"rarfile>=4.1",
2324
"rich>=13.3.5",
2425
"structlog>=24.1.0",

python/unblob/handlers/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
lzip,
3232
lzma,
3333
lzo,
34+
uzip,
3435
xz,
3536
zlib,
3637
zstd,
@@ -116,6 +117,7 @@
116117
zlib.ZlibHandler,
117118
engenius.EngeniusHandler,
118119
ecc.AutelECCHandler,
120+
uzip.UZIPHandler,
119121
)
120122

121123
BUILTIN_DIR_HANDLERS: DirectoryHandlers = (
+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import lzma
2+
import re
3+
import zlib
4+
from pathlib import Path
5+
from typing import Callable, Optional
6+
7+
import pyzstd
8+
9+
from unblob.file_utils import (
10+
Endian,
11+
FileSystem,
12+
InvalidInputFormat,
13+
StructParser,
14+
iterate_file,
15+
)
16+
from unblob.models import (
17+
Extractor,
18+
ExtractResult,
19+
File,
20+
Regex,
21+
StructHandler,
22+
ValidChunk,
23+
)
24+
25+
# [Ref] https://github.com/freebsd/freebsd-src/tree/master/sys/geom/uzip
26+
C_DEFINITIONS = r"""
27+
typedef struct uzip_header{
28+
char magic[16];
29+
char format[112];
30+
uint32_t block_size;
31+
uint32_t block_count;
32+
uint64_t toc[block_count];
33+
} uzip_header_t;
34+
"""
35+
36+
HEADER_STRUCT = "uzip_header_t"
37+
38+
ZLIB_COMPRESSION = "#!/bin/sh\x0a#V2.0\x20"
39+
LZMA_COMPRESSION = "#!/bin/sh\x0a#L3.0\x0a"
40+
ZSTD_COMPRESSION = "#!/bin/sh\x0a#Z4.0\x20"
41+
42+
43+
class Decompressor:
44+
DECOMPRESSOR: Callable
45+
46+
def __init__(self):
47+
self._decompressor = self.DECOMPRESSOR()
48+
49+
def decompress(self, data: bytes) -> bytes:
50+
return self._decompressor.decompress(data)
51+
52+
def flush(self) -> bytes:
53+
return b""
54+
55+
56+
class LZMADecompressor(Decompressor):
57+
DECOMPRESSOR = lzma.LZMADecompressor
58+
59+
60+
class ZLIBDecompressor(Decompressor):
61+
DECOMPRESSOR = zlib.decompressobj
62+
63+
def flush(self) -> bytes:
64+
return self._decompressor.flush()
65+
66+
67+
class ZSTDDecompressor(Decompressor):
68+
DECOMPRESSOR = pyzstd.EndlessZstdDecompressor
69+
70+
71+
DECOMPRESS_METHOD: dict[bytes, type[Decompressor]] = {
72+
ZLIB_COMPRESSION.encode(): ZLIBDecompressor,
73+
LZMA_COMPRESSION.encode(): LZMADecompressor,
74+
ZSTD_COMPRESSION.encode(): ZSTDDecompressor,
75+
}
76+
77+
78+
class UZIPExtractor(Extractor):
79+
def extract(self, inpath: Path, outdir: Path):
80+
with File.from_path(inpath) as infile:
81+
parser = StructParser(C_DEFINITIONS)
82+
header = parser.parse(HEADER_STRUCT, infile, Endian.BIG)
83+
fs = FileSystem(outdir)
84+
outpath = Path(inpath.stem)
85+
86+
try:
87+
decompressor_cls = DECOMPRESS_METHOD[header.magic]
88+
except LookupError:
89+
raise InvalidInputFormat("unsupported compression format") from None
90+
91+
with fs.open(outpath, "wb+") as outfile:
92+
for current_offset, next_offset in zip(header.toc[:-1], header.toc[1:]):
93+
compressed_len = next_offset - current_offset
94+
if compressed_len == 0:
95+
continue
96+
decompressor = decompressor_cls()
97+
for chunk in iterate_file(infile, current_offset, compressed_len):
98+
outfile.write(decompressor.decompress(chunk))
99+
outfile.write(decompressor.flush())
100+
return ExtractResult(reports=fs.problems)
101+
102+
103+
class UZIPHandler(StructHandler):
104+
NAME = "uzip"
105+
PATTERNS = [
106+
Regex(re.escape(ZLIB_COMPRESSION)),
107+
Regex(re.escape(LZMA_COMPRESSION)),
108+
Regex(re.escape(ZSTD_COMPRESSION)),
109+
]
110+
HEADER_STRUCT = HEADER_STRUCT
111+
C_DEFINITIONS = C_DEFINITIONS
112+
EXTRACTOR = UZIPExtractor()
113+
114+
def is_valid_header(self, header) -> bool:
115+
return (
116+
header.block_count > 0
117+
and header.block_size > 0
118+
and header.block_size % 512 == 0
119+
)
120+
121+
def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
122+
header = self.parse_header(file, Endian.BIG)
123+
124+
if not self.is_valid_header(header):
125+
raise InvalidInputFormat("Invalid uzip header.")
126+
127+
# take the last TOC block offset, end of file is that block offset,
128+
# starting from the start offset
129+
end_offset = start_offset + header.toc[-1]
130+
return ValidChunk(
131+
start_offset=start_offset,
132+
end_offset=end_offset,
133+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:bc53a5de25e6f5326564264fee9e1210067311c237d4d7a8299ebf244652cf05
3+
size 59392
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:c8f164384ebee852bbdc6f5fabcf231fa5fc35d9f236c30e38b9746f871be122
3+
size 59316
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e04449191a0c3eab172e5819c5c1e9c10a9cd2e4ffca2abacf065ac1e3bd1328
3+
size 458752
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:f2c0d5456a983ecd12e314fcfa19879179fc8424343baeb1325457472ae85601
3+
size 76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:1e04c83a5444127b9ec58c4b2d8fef904816cee4609d798589d6e8af6086a322
3+
size 59904
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:cc069dc850a564078858cc13dc743dc5772d1e28edb1ce8a714f5a8749b5d43d
3+
size 60032
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:2ba6c3e09fa4b144f8f9fc29721c71df0bee753507c7071bdb8132409ce182d4
3+
size 59397
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e04449191a0c3eab172e5819c5c1e9c10a9cd2e4ffca2abacf065ac1e3bd1328
3+
size 458752
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e48783451cfffa909b2c92ddb2b4c06b836aaa56f16aaab96349e8e9074d45b8
3+
size 507
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:c33ff1723e6b94ae7e6a0ecad3c8a5fc43ab6f39468170f7467e11a8192f6164
3+
size 64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:7ad2f37110df3519cd58ede90a97a481853f2c9da95db4513d523f94aab9ca8c
3+
size 571
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:2ba6c3e09fa4b144f8f9fc29721c71df0bee753507c7071bdb8132409ce182d4
3+
size 59397
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e04449191a0c3eab172e5819c5c1e9c10a9cd2e4ffca2abacf065ac1e3bd1328
3+
size 458752
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:0f8a24f2de9727324844a152716880a2cb29512d19918ade566811fa3a8ae8d1
3+
size 58368
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:204c31af96edd20c6e074f58663a78106a8671930c76826938dcce4b9553d00e
3+
size 58269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e04449191a0c3eab172e5819c5c1e9c10a9cd2e4ffca2abacf065ac1e3bd1328
3+
size 458752
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:4b298058e1d5fd3f2fa20ead21773912a5dc38da3c0da0bbc7de1adfb6011f1c
3+
size 99

0 commit comments

Comments
 (0)