2 * Copyright (C) 2017 Niklas Rosenstein
3 * Copyright (C) 2014 PHPdev32
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
32 #define ZBSZ 1024 * XBSZ
33 #define VERSION "1.0.2"
35 /* Structure to hold the command-line options. */
37 bool stdin
; /* True if data should be read from stdin. */
38 bool noxar
; /* The input data is not a XAR archive but the pbzx Payload. */
39 bool help
; /* Print usage with details and exit. */
40 bool version
; /* Print version and exit. */
43 /* Prints usage information and exit. Optionally, displays an error message and
44 * exits with an error code. */
45 static void usage(char const* error
) {
46 fprintf(stderr
, "usage: pbzx [-v] [-h] [-n] [-] [filename]\n");
48 fprintf(stderr
, "error: %s\n", error
);
53 "pbzx v" VERSION
" stream parser\n"
54 "https://github.com/NiklasRosenstein/pbzx\n"
56 "Licensed under GNU GPL v3.\n"
57 "Copyright (C) 2017 NiklasRosenstein\n"
58 "Copyright (C) 2015 PHPdev32\n"
64 /* Prints the version and exits. */
65 static void version() {
66 printf("pbzx v" VERSION
"\n");
70 /* Parses command-line flags into the #options structure and adjusts the
71 * argument count and values on the fly to remain only positional arguments. */
72 static void parse_args(int* argc
, char const** argv
, struct options
* opts
) {
73 for (int i
= 0; i
< *argc
; ++i
) {
74 /* Skip arguments that are not flags. */
75 if (argv
[i
][0] != '-') continue;
76 /* Match available arguments. */
77 if (strcmp(argv
[i
], "-") == 0) opts
->stdin
= true;
78 else if (strcmp(argv
[i
], "-n") == 0) opts
->noxar
= true;
79 else if (strcmp(argv
[i
], "-h") == 0) opts
->help
= true;
80 else if (strcmp(argv
[i
], "-v") == 0) opts
->version
= true;
81 else usage("unrecognized flag");
82 /* Move all remaining arguments to the front. */
83 for (int j
= 0; j
< (*argc
-1); ++j
) {
90 static inline uint32_t min(uint32_t a
, uint32_t b
) {
91 return (a
< b
? a
: b
);
94 /* Possible types for the #stream structure. */
100 /* Generic datastructure that can represent a streamed file in a XAR archive
101 * or a C FILE pointer. The stream is initialized respectively depending on
102 * the command-line flags. */
104 int type
; /* One of #STREAM_XAR and #STREAM_FP. */
105 xar_t xar
; /* Only valid if #type == #STREAM_XAR. */
106 xar_stream xs
; /* Only valid if #type == #STREAM_XAR. */
107 FILE* fp
; /* Only valid if #type == #STREAM_FP. */
110 /* Initialize an empty stream. */
111 static void stream_init(struct stream
* s
) {
114 memset(&s
->xs
, 0, sizeof(s
->xs
));
118 /* Open a stream of the specified type and filename. */
119 static bool stream_open(struct stream
* s
, int type
, const char* filename
) {
124 s
->xar
= xar_open(filename
, READ
);
125 if (!s
->xar
) return false;
126 xar_iter_t i
= xar_iter_new();
127 xar_file_t f
= xar_file_first(s
->xar
, i
);
129 /* Find the Payload file in the archive. */
130 while (strncmp((path
= xar_get_path(f
)), "Payload", 7) &&
131 (f
= xar_file_next(i
))) {
136 if (!f
) return false; /* No Payload. */
137 if (xar_verify(s
->xar
, f
) != XAR_STREAM_OK
) return false; /* File verification failed. */
138 if (xar_extract_tostream_init(s
->xar
, f
, &s
->xs
) != XAR_STREAM_OK
) return false; /* XAR Stream init failed. */
142 s
->fp
= fopen(filename
, "rb");
143 if (!s
->fp
) return false; /* File can not be opened. */
146 default: return false;
150 /* Close an opened stream. After this function, the stream is initialized
151 * to an empty stream object. */
152 static void stream_close(struct stream
* s
) {
155 xar_extract_tostream_end(&s
->xs
);
165 /* Read bytes from the stream into a buffer. Returns the number of bytes
166 * that have been put into the buffer. */
167 static uint32_t stream_read(char* buf
, uint32_t size
, struct stream
* s
) {
172 s
->xs
.next_out
= buf
;
173 s
->xs
.avail_out
= size
;
174 while (s
->xs
.avail_out
) {
175 if (xar_extract_tostream(&s
->xs
) != XAR_STREAM_OK
) {
176 return size
- s
->xs
.avail_out
;
181 return fread(buf
, size
, 1, s
->fp
);
186 /* Reads a #uint64_t from the stream. */
187 static inline uint64_t stream_read_64(struct stream
* stream
) {
189 stream_read(buf
, 8, stream
);
190 return __builtin_bswap64(*(uint64_t*) buf
);
193 static inline size_t cpio_out(char *buffer
, size_t size
) {
196 c
+= fwrite(buffer
+ c
, 1, size
- c
, stdout
);
201 int main(int argc
, const char** argv
) {
202 /* Parse and validate command-line flags and arguments. */
203 struct options opts
= {0};
204 parse_args(&argc
, argv
, &opts
);
205 if (opts
.version
) version();
206 if (opts
.help
) usage(NULL
);
207 if (!opts
.stdin
&& argc
< 2)
208 usage("missing filename argument");
209 else if ((!opts
.stdin
&& argc
> 2) || (opts
.stdin
&& argc
> 1))
210 usage("unhandled positional argument(s)");
212 char const* filename
= NULL
;
213 if (argc
>= 2) filename
= argv
[1];
215 /* Open a stream to the payload. */
216 struct stream stream
;
217 stream_init(&stream
);
218 bool success
= false;
220 stream
.type
= STREAM_FP
;
224 else if (opts
.noxar
) {
225 success
= stream_open(&stream
, STREAM_FP
, filename
);
228 success
= stream_open(&stream
, STREAM_XAR
, filename
);
231 fprintf(stderr
, "failed to open: %s\n", filename
);
235 /* Start extracting the payload data. */
237 char* zbuf
= malloc(ZBSZ
);
239 /* Make sure we have a pbxz stream. */
240 stream_read(xbuf
, 4, &stream
);
241 if (strncmp(xbuf
, "pbzx", 4) != 0) {
242 fprintf(stderr
, "not a pbzx stream\n");
246 /* Initialize LZMA. */
248 uint64_t flags
= stream_read_64(&stream
);
250 lzma_stream zs
= LZMA_STREAM_INIT
;
251 if (lzma_stream_decoder(&zs
, UINT64_MAX
, LZMA_CONCATENATED
) != LZMA_OK
) {
252 fprintf(stderr
, "LZMA init failed\n");
256 /* Read LZMA chunks. */
257 while (flags
& 1 << 24) {
258 flags
= stream_read_64(&stream
);
259 length
= stream_read_64(&stream
);
260 char plain
= (length
== 0x1000000);
261 stream_read(xbuf
, min(XBSZ
, (uint32_t) length
), &stream
);
262 /* Validate the header. */
263 if (!plain
&& strncmp(xbuf
, "\xfd""7zXZ\0", 6) != 0) {
264 fprintf(stderr
, "Header is not <FD>7zXZ<00>\n");
269 cpio_out(xbuf
, min(XBSZ
, length
));
272 zs
.next_in
= (typeof(zs
.next_in
)) xbuf
;
273 zs
.avail_in
= min(XBSZ
, length
);
274 while (zs
.avail_in
) {
275 zs
.next_out
= (typeof(zs
.next_out
)) zbuf
;
277 if (lzma_code(&zs
, LZMA_RUN
) != LZMA_OK
) {
278 fprintf(stderr
, "LZMA failure");
281 cpio_out(zbuf
, ZBSZ
- zs
.avail_out
);
284 length
-= last
= min(XBSZ
, length
);
285 stream_read(xbuf
, min(XBSZ
, (uint32_t)length
), &stream
);
287 if (!plain
&& strncmp(xbuf
+ last
-2, "YZ", 2) != 0) {
288 fprintf(stderr
, "Footer is not YZ");
294 if (!opts
.stdin
) stream_close(&stream
);