modified: FGI/OYK.pm
[GalaxyCodeBases.git] / tools / pbzx / pbzx.c
blobd01ef358948ddf9c8ad2e22436261884da75dc57
1 /**
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/>.
18 * \file pbzx.c
19 * \created 2014-06-20
22 #include <errno.h>
23 #include <stdint.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <lzma.h>
29 #include <xar/xar.h>
31 #define XBSZ 4 * 1024
32 #define ZBSZ 1024 * XBSZ
33 #define VERSION "1.0.2"
35 /* Structure to hold the command-line options. */
36 struct 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");
47 if (error) {
48 fprintf(stderr, "error: %s\n", error);
49 exit(EINVAL);
51 fprintf(stderr,
52 "\n"
53 "pbzx v" VERSION " stream parser\n"
54 "https://github.com/NiklasRosenstein/pbzx\n"
55 "\n"
56 "Licensed under GNU GPL v3.\n"
57 "Copyright (C) 2017 NiklasRosenstein\n"
58 "Copyright (C) 2015 PHPdev32\n"
59 "\n"
61 exit(0);
64 /* Prints the version and exits. */
65 static void version() {
66 printf("pbzx v" VERSION "\n");
67 exit(0);
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) {
84 argv[j] = argv[j+1];
86 (*argc)--;
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. */
95 enum {
96 STREAM_XAR = 1,
97 STREAM_FP
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. */
103 struct stream {
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) {
112 s->type = 0;
113 s->xar = NULL;
114 memset(&s->xs, 0, sizeof(s->xs));
115 s->fp = NULL;
118 /* Open a stream of the specified type and filename. */
119 static bool stream_open(struct stream* s, int type, const char* filename) {
120 stream_init(s);
121 s->type = type;
122 switch (type) {
123 case STREAM_XAR: {
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);
128 char* path = NULL;
129 /* Find the Payload file in the archive. */
130 while (strncmp((path = xar_get_path(f)), "Payload", 7) &&
131 (f = xar_file_next(i))) {
132 free(path);
134 free(path);
135 xar_iter_free(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. */
139 return true;
141 case STREAM_FP: {
142 s->fp = fopen(filename, "rb");
143 if (!s->fp) return false; /* File can not be opened. */
144 return true;
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) {
153 switch (s->type) {
154 case STREAM_XAR:
155 xar_extract_tostream_end(&s->xs);
156 xar_close(s->xar);
157 break;
158 case STREAM_FP:
159 fclose(s->fp);
160 break;
162 stream_init(s);
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) {
168 if (!s) return 0;
169 switch (s->type) {
170 case STREAM_XAR:
171 default:
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;
179 return size;
180 case STREAM_FP:
181 return fread(buf, size, 1, s->fp);
183 abort();
186 /* Reads a #uint64_t from the stream. */
187 static inline uint64_t stream_read_64(struct stream* stream) {
188 char buf[8];
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) {
194 size_t c = 0;
195 while (c < size) {
196 c+= fwrite(buffer + c, 1, size - c, stdout);
198 return c;
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;
219 if (opts.stdin) {
220 stream.type = STREAM_FP;
221 stream.fp = stdin;
222 success = true;
224 else if (opts.noxar) {
225 success = stream_open(&stream, STREAM_FP, filename);
227 else {
228 success = stream_open(&stream, STREAM_XAR, filename);
230 if (!success) {
231 fprintf(stderr, "failed to open: %s\n", filename);
232 return 1;
235 /* Start extracting the payload data. */
236 char xbuf[XBSZ];
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");
243 return 1;
246 /* Initialize LZMA. */
247 uint64_t length = 0;
248 uint64_t flags = stream_read_64(&stream);
249 uint64_t last = 0;
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");
253 return 1;
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");
265 return 1;
267 while (length) {
268 if (plain) {
269 cpio_out(xbuf, min(XBSZ, length));
271 else {
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;
276 zs.avail_out = ZBSZ;
277 if (lzma_code(&zs, LZMA_RUN) != LZMA_OK) {
278 fprintf(stderr, "LZMA failure");
279 return 1;
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");
289 return 1;
292 free(zbuf);
293 lzma_end(&zs);
294 if (!opts.stdin) stream_close(&stream);
295 return 0;