├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── endian.h ├── image.c ├── image.h ├── lzo.c ├── lzo.h └── unimgc.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | /obj 3 | /unimgc 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CPPFLAGS := -D_POSIX_C_SOURCE=200112L -D_FILE_OFFSET_BITS=64 $(CPPFLAGS) 2 | CFLAGS := -std=c99 -O2 -Wall -Wextra -pedantic -Wno-unused-parameter $(CFLAGS) 3 | O = obj/ 4 | 5 | .PHONY: all clean 6 | all: unimgc 7 | 8 | clean: 9 | rm -rf unimgc $(O) 10 | 11 | unimgc: $(O)unimgc.o $(O)image.o $(O)lzo.o 12 | $(CROSS_COMPILE)$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ 13 | 14 | $(O)%.o: %.c | $(O) 15 | $(CROSS_COMPILE)$(CC) $(CPPFLAGS) $(CFLAGS) -c $^ -o $@ 16 | 17 | $(O): 18 | mkdir -p $@ 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unimgc 2 | 3 | `unimgc` is a tool to decompress HDDGuru [HDD Raw Copy Tool](http://www.hddguru.com/software/HDD-Raw-Copy-Tool/)'s proprietary `.imgc` compression format. 4 | 5 | ## Technical summary 6 | 7 | `.imgc` images consist of a header, followed by a list of blocks. Most of the fields in the header are obvious from first inspection, 8 | and include target disk metadata such as the product name, revision and serial number, creator software metadata and finally image metadata. 9 | Of note is that all strings are stored as Pascal-style strings, padded to always take up `0x100` bytes including the length byte. 10 | 11 | Followed by the header is an array of blocks, each with a header describing its type (`char[4]`) and size (`uint32_t`). There are two kind of blocks: 12 | 13 | * `"lol!"`: the block contents are compressed using a [custom LZO algorithm](https://github.com/synopse/mORMot/blob/master/SynLZO.pas); 14 | * `"omg!"`: the block contents is a single 64-bit integer indicating how many zeroes to add to the output; 15 | 16 | The LZO compression algorithm mostly follows the spec as [implemented in the Linux kernel](https://www.infradead.org/~mchehab/kernel_docs/unsorted/lzo.html), 17 | with a few derivations making the bitstreams incompatible: 18 | 19 | * Initial byte encoding affects state differently; 20 | * Instructions `0 <= x <= 15` are only defined if the current state is `0`; 21 | * Instructions `64 <= x <= 127` use a different length addition (`1` versus `3`); 22 | * Instructions `128 <= x <= 255` get dropped and treated as `64 <= x <= 127` instead; 23 | 24 | ## Usage 25 | 26 | ``` 27 | usage: unimgc [-ihvV] [IN] [OUT] 28 | -h show usage 29 | -V show version 30 | -v increase verbosity (level 1: progress, level 2: header dumps) 31 | -i only show image information, don't decompress 32 | IN input file; defaults to standard input 33 | OUT output file; defaults to standard output 34 | ``` 35 | 36 | An example of a common invocation would be `unimgc -v foo.imgc foo.img` to decompress from and to regular files, showing progress while it's working. 37 | 38 | You can also use `unimgc` as part of a pipeline: `curl https://my.host/secret/game.imgc | unimgc -v | gzip -9c > game.img.gz`. 39 | 40 | ## License 41 | 42 | `unimgc` is licensed under the [WTFPL](http://www.wtfpl.net/txt/copying/); see the [LICENSE](LICENSE) file for details. 43 | -------------------------------------------------------------------------------- /endian.h: -------------------------------------------------------------------------------- 1 | #if !defined(UNIMGC_H_ENDIAN) 2 | #define UNIMGC_H_ENDIAN 3 | 4 | #include 5 | 6 | /* portable le*toh() inspired by https://stackoverflow.com/a/2100549 */ 7 | 8 | static inline uint16_t le16toh(uint16_t n) 9 | { 10 | unsigned char *p = (unsigned char *)&n; 11 | return ((uint16_t)p[1] << 8) | (uint16_t)p[0]; 12 | } 13 | 14 | static inline uint32_t le32toh(uint32_t n) 15 | { 16 | unsigned char *p = (unsigned char *)&n; 17 | return ((uint32_t)p[3] << 24) | ((uint32_t)p[2] << 16) 18 | | ((uint32_t)p[1] << 8) | (uint32_t)p[0]; 19 | } 20 | 21 | static inline uint64_t le64toh(uint64_t n) 22 | { 23 | unsigned char *p = (unsigned char *)&n; 24 | return ((uint64_t)p[7] << 56) | ((uint64_t)p[6] << 48) 25 | | ((uint64_t)p[5] << 40) | ((uint64_t)p[4] << 32) 26 | | ((uint64_t)p[3] << 24) | ((uint64_t)p[2] << 16) 27 | | ((uint64_t)p[1] << 8) | (uint64_t)p[0]; 28 | } 29 | 30 | #endif /* !defined(UNIMGC_H_ENDIAN) */ 31 | -------------------------------------------------------------------------------- /image.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "endian.h" 5 | #include "image.h" 6 | #include "lzo.h" 7 | 8 | 9 | /* return a non-owned reference to the C string */ 10 | char *pascal_to_cstr(struct pascal_str *p) 11 | { 12 | return p->data; 13 | } 14 | 15 | /* initialize pascal string */ 16 | int pascal_from_data(struct pascal_str *p, const char *d, size_t len) 17 | { 18 | if (len + 1 > sizeof(p->data)) { 19 | return 0; 20 | } 21 | p->length = len; 22 | memcpy(p->data, d, len); 23 | p->data[len] = 0; 24 | return 1; 25 | } 26 | 27 | /* initialize owned pascal string from C string contents */ 28 | int pascal_from_cstr(struct pascal_str *p, const char *s) 29 | { 30 | return pascal_from_data(p, s, strlen(s)); 31 | } 32 | 33 | 34 | int imgc_parse(const uint8_t *buf, size_t len, struct imgc_header *hdr) 35 | { 36 | if (len < 0x521) 37 | return -1; 38 | memcpy(&hdr->software.name, &buf[0x000], 0x100); 39 | memcpy(&hdr->software.version, &buf[0x100], 0x100); 40 | memcpy(&hdr->volume.model, &buf[0x200], 0x100); 41 | memcpy(&hdr->volume.revision, &buf[0x300], 0x100); 42 | memcpy(&hdr->volume.serial, &buf[0x400], 0x100); 43 | hdr->image.sector_count = le64toh(*(uint64_t *)(buf + 0x500)); 44 | hdr->image.sector_size = le64toh(*(uint64_t *)(buf + 0x508)); 45 | hdr->image.unk1 = le64toh(*(uint64_t *)(buf + 0x510)); 46 | hdr->image.unk2 = le64toh(*(uint64_t *)(buf + 0x518)); 47 | hdr->image.unk3 = buf[0x520]; 48 | return 0; 49 | } 50 | 51 | int imgc_parse_block(const uint8_t *buf, size_t len, struct imgc_block_header *hdr) 52 | { 53 | /* block type: 54 | * "omg!": zero'd block 55 | * "lol!": LZO-decompress following contents 56 | */ 57 | if (len < 8) 58 | return -1; 59 | 60 | if (!strncmp((const char *)buf, "omg!", 4)) 61 | hdr->type = IMGC_BLOCK_ZERO; 62 | else if (!strncmp((const char *)buf, "lol!", 4)) 63 | hdr->type = IMGC_BLOCK_COMPRESSED; 64 | else 65 | return -2; 66 | 67 | hdr->size = le32toh(*(const uint32_t *)(buf + 4)); 68 | return 0; 69 | } 70 | 71 | size_t imgc_decompress_block(const uint8_t *buf, size_t len, uint8_t *out, size_t outlen) 72 | { 73 | if (len < 2) 74 | /* input too small */ 75 | return 0; 76 | 77 | uint32_t size = le16toh(*(const uint16_t *)buf); 78 | buf += 2; 79 | if (size & 0x8000) { 80 | if (len < 4) 81 | /* input too small */ 82 | return 0; 83 | size &= 0x7FFF; 84 | size |= le16toh(*(const uint16_t *)buf) << 15; 85 | buf += 2; 86 | } 87 | 88 | if (!out) 89 | return size; 90 | 91 | if (size > outlen) 92 | /* output too small */ 93 | return 0; 94 | 95 | return lzo_decompress(buf, len - 2 - 2 * (size > 0x7FFF), out, outlen); 96 | } 97 | -------------------------------------------------------------------------------- /image.h: -------------------------------------------------------------------------------- 1 | #if !defined(UNIMGC_H_IMAGE) 2 | #define UNIMGC_H_IMAGE 3 | 4 | #include 5 | 6 | 7 | struct pascal_str { 8 | unsigned char length; 9 | char data[0xFF]; 10 | }; 11 | 12 | 13 | #define IMGC_HEADER_SIZE 0x1000 14 | 15 | struct imgc_header { 16 | /* creator software metadata */ 17 | struct { 18 | /* "HDD Raw Copy Tool" */ 19 | struct pascal_str name; 20 | /* "1.10" */ 21 | struct pascal_str version; 22 | } software; 23 | /* original volume metadata */ 24 | struct { 25 | /* "SMI USB DISK" */ 26 | struct pascal_str model; 27 | /* "0CB0" */ 28 | struct pascal_str revision; 29 | /* "624811604181" */ 30 | struct pascal_str serial; 31 | } volume; 32 | /* image properties */ 33 | struct { 34 | uint64_t sector_count; /* ? */ 35 | uint64_t sector_size; /* ? */ 36 | uint64_t unk1; 37 | uint64_t unk2; 38 | uint8_t unk3; 39 | } image; 40 | }; 41 | 42 | enum imgc_block_type { 43 | IMGC_BLOCK_COMPRESSED = 1, 44 | IMGC_BLOCK_ZERO 45 | }; 46 | 47 | #define IMGC_BLOCK_HEADER_SIZE 8 48 | 49 | struct imgc_block_header { 50 | enum imgc_block_type type; 51 | uint32_t size; 52 | }; 53 | 54 | 55 | char *pascal_to_cstr(struct pascal_str *p); 56 | int pascal_from_cstr(struct pascal_str *p, const char *s); 57 | 58 | int imgc_parse(const uint8_t *buf, size_t len, struct imgc_header *hdr); 59 | int imgc_parse_block(const uint8_t *buf, size_t len, struct imgc_block_header *hdr); 60 | size_t imgc_decompress_block(const uint8_t *buf, size_t len, uint8_t *out, size_t outlen); 61 | 62 | #endif /* !defined(UNIMGC_H_IMAGE) */ 63 | -------------------------------------------------------------------------------- /lzo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "endian.h" 5 | 6 | #define min(a, b) ((a) > (b) ? (b) : (a)) 7 | /* extract n bits starting from position p from value v */ 8 | #define bits(v, p, n) (((v) >> (p)) & ((1 << (n)) - 1)) 9 | /* extract bit from position p from value v */ 10 | #define bit(v, p) bits((v), (p), 1) 11 | 12 | 13 | /* references: 14 | * - https://www.infradead.org/~mchehab/kernel_docs/unsorted/lzo.html 15 | * - https://github.com/synopse/mORMot/blob/master/SynLZO.pas 16 | */ 17 | 18 | static inline unsigned lzo_parse_length(const uint8_t **buf, unsigned bits) 19 | { 20 | unsigned mask = (1 << bits) - 1; 21 | 22 | const uint8_t *p = *buf; 23 | unsigned len = *p++ & mask; 24 | if (!len) { 25 | for (; !*p; p++) 26 | len += 0xFF; 27 | len += *p++ + mask; 28 | } 29 | 30 | *buf = p; 31 | return len; 32 | } 33 | 34 | static inline void lzo_copy(uint8_t **out, const uint8_t **in, size_t n) 35 | { 36 | memcpy(*out, *in, n); 37 | *out += n; 38 | *in += n; 39 | } 40 | 41 | static inline void lzo_copy_distance(uint8_t **out, ptrdiff_t dist, size_t n) 42 | { 43 | /* interestingly, memmove() does *not* work here, at least on macOS */ 44 | uint8_t *p = *out; 45 | const uint8_t *in = p - dist; 46 | while (n--) 47 | *p++ = *in++; 48 | *out = p; 49 | } 50 | 51 | size_t lzo_decompress(const uint8_t *buf, size_t len, uint8_t *out, size_t outlen) 52 | { 53 | const uint8_t *p = buf, *end = buf + len, *oend = out + outlen; 54 | uint8_t *op = out; 55 | uint8_t state = 0; 56 | 57 | while (p < end) { 58 | uint8_t instr = *p; 59 | /* first command is special */ 60 | if (p == buf) { 61 | if (instr > 17) { 62 | lzo_copy(&op, &p, instr - 17); 63 | state = 4; 64 | continue; 65 | } 66 | } 67 | 68 | uint32_t length = 0; 69 | uint16_t follow = 0; 70 | ptrdiff_t distance = 0; 71 | /* general notation for following formats: 72 | * L: length bits 73 | * D: distance bits 74 | * S: state bits 75 | */ 76 | if (instr >= 64) { 77 | /* format: L L L D D D S S; 78 | * follow: D D D D D D D D */ 79 | p++; 80 | length = bits(instr, 5, 3) + 1; 81 | follow = *p++; 82 | distance = (follow << 3) + bits(instr, 2, 3) + 1; 83 | state = bits(instr, 0, 2); 84 | } else if (instr >= 32) { 85 | /* format: 0 0 1 L L L L L; 86 | * follow: D D D D D D D D D D D D D D S S */ 87 | length = lzo_parse_length(&p, 5) + 2; 88 | follow = le16toh(*(const uint16_t *)p); 89 | distance = (follow >> 2) + 1; 90 | state = bits(follow, 0, 2); 91 | p += 2; 92 | } else if (instr >= 16) { 93 | /* format: 0 0 0 1 D L L L; 94 | * follow: D D D D D D D D D D D D D D S S */ 95 | length = lzo_parse_length(&p, 3) + 2; 96 | follow = le16toh(*(const uint16_t *)p); 97 | distance = (bit(instr, 3) << 14) + (follow >> 2) + 0x4000; 98 | state = bits(follow, 0, 2); 99 | p += 2; 100 | } else if (!state) { 101 | /* back up and parse length properly */ 102 | length = lzo_parse_length(&p, 4) + 3; 103 | lzo_copy(&op, &p, length); 104 | state = 4; 105 | continue; 106 | } else { 107 | /* this shouldn't happen */ 108 | return 0; 109 | } 110 | 111 | length = min(length, oend - op); 112 | if (length) 113 | lzo_copy_distance(&op, distance, length); 114 | if (state > 0 && state < 4) 115 | lzo_copy(&op, &p, state); 116 | } 117 | return op - out; 118 | } 119 | -------------------------------------------------------------------------------- /lzo.h: -------------------------------------------------------------------------------- 1 | #if !defined(UNIMGC_H_LZO) 2 | #define UNIMGC_H_LZO 3 | 4 | #include 5 | #include 6 | 7 | size_t lzo_decompress(const uint8_t *buf, size_t len, uint8_t *out, size_t outlen); 8 | 9 | #endif /* !defined(UNIMGC_H_LZO) */ 10 | -------------------------------------------------------------------------------- /unimgc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "endian.h" 11 | #include "image.h" 12 | 13 | #define UNIMGC_VERSION "0.1" 14 | 15 | 16 | enum unimgc_error { 17 | UNIMGC_ERROR_NONE = 0, 18 | UNIMGC_ERROR_IO = 1, 19 | UNIMGC_ERROR_ALLOC = 2, 20 | UNIMGC_ERROR_CORRUPTED_FILE = 3, 21 | UNIMGC_ERROR_CORRUPTED_BLOCK = 4, 22 | UNIMGC_ERROR_CORRUPTED_DATA = 5 23 | }; 24 | 25 | static void fatal(enum unimgc_error code, const char *msg, ...) 26 | { 27 | fprintf(stderr, "unimgc: fatal: "); 28 | va_list ap; 29 | va_start(ap, msg); 30 | vfprintf(stderr, msg, ap); 31 | va_end(ap); 32 | exit(code); 33 | } 34 | 35 | 36 | static double si_ify(uint64_t n, char *u) 37 | { 38 | static const char prefixes[] = {'k', 'M', 'G', 'T', 'P', 'E'}; 39 | for (int i = sizeof(prefixes); i; i--) { 40 | uint64_t div = 1ULL << (10 * i); 41 | if (n > div) { 42 | *u = prefixes[i - 1]; 43 | return n / (float)div; 44 | } 45 | } 46 | *u = ' '; 47 | return (double)n; 48 | } 49 | 50 | static void dump_header(struct imgc_header *hdr) 51 | { 52 | fprintf(stderr, "volume metadata:\n"); 53 | fprintf(stderr, " model: %s\n", pascal_to_cstr(&hdr->volume.model)); 54 | fprintf(stderr, " revision: %s\n", pascal_to_cstr(&hdr->volume.revision)); 55 | fprintf(stderr, " serial number: %s\n", pascal_to_cstr(&hdr->volume.serial)); 56 | fprintf(stderr, "software metadata:\n"); 57 | fprintf(stderr, " name: %s\n", pascal_to_cstr(&hdr->software.name)); 58 | fprintf(stderr, " version: %s\n", pascal_to_cstr(&hdr->software.version)); 59 | 60 | char unit; 61 | double size = si_ify(hdr->image.sector_count * hdr->image.sector_size, &unit); 62 | fprintf(stderr, "image metadata:\n"); 63 | fprintf(stderr, " size: %.02f %ciB (%" PRIu64 " sectors * %" PRIu64 " bytes)\n", 64 | size, unit, hdr->image.sector_count, hdr->image.sector_size); 65 | fprintf(stderr, " unk1: %016" PRIx64 "\n", hdr->image.unk1); 66 | fprintf(stderr, " unk2: %016" PRIx64 "\n", hdr->image.unk2); 67 | fprintf(stderr, " unk3: %02" PRIx8 "\n", hdr->image.unk3); 68 | } 69 | 70 | static void dump_block_header(struct imgc_block_header *bhdr, size_t pos) 71 | { 72 | fprintf(stderr, "block @ 0x%zx\n", pos); 73 | fprintf(stderr, " type: %s\n", bhdr->type == IMGC_BLOCK_COMPRESSED ? "compressed" : "zeroed"); 74 | fprintf(stderr, " size: %u\n", bhdr->size); 75 | } 76 | 77 | 78 | 79 | static struct { 80 | int only_info; 81 | int verbose; 82 | } options; 83 | 84 | static void unimgc_data(struct imgc_header *hdr, FILE *in, FILE *out) 85 | { 86 | uint8_t *bbuf = NULL, *dbuf = NULL; 87 | size_t bsize = 0, dsize = 0; 88 | char zeros[4096] = { 0 }; 89 | 90 | uint64_t total_size = hdr->image.sector_count * hdr->image.sector_size; 91 | for (;;) { 92 | if (options.verbose >= 1) 93 | fprintf(stderr, "\r%04.2f%% (%" PRIu64 " / %" PRIu64 " bytes)...", 94 | (100.0 * ftello(out)) / total_size, ftello(out), total_size); 95 | 96 | /* read block header buf */ 97 | uint8_t bhbuf[IMGC_BLOCK_HEADER_SIZE]; 98 | size_t nread = fread(bhbuf, 1, sizeof(bhbuf), in); 99 | if (nread != sizeof(bhbuf)) { 100 | if (feof(in)) { 101 | if (nread == 0) 102 | break; 103 | else 104 | fatal(UNIMGC_ERROR_CORRUPTED_BLOCK, "truncated input file (block header)\n"); 105 | } else { 106 | fatal(UNIMGC_ERROR_IO, "could not read input file: %s\n", strerror(errno)); 107 | } 108 | } 109 | 110 | /* parse block header */ 111 | struct imgc_block_header bhdr; 112 | if (imgc_parse_block(bhbuf, sizeof(bhbuf), &bhdr) < 0) 113 | fatal(UNIMGC_ERROR_CORRUPTED_BLOCK, "invalid IMGC block header\n"); 114 | if (options.verbose >= 2) 115 | dump_block_header(&bhdr, ftello(in) - IMGC_BLOCK_HEADER_SIZE); 116 | 117 | /* read block data */ 118 | size_t sz = bhdr.size - IMGC_BLOCK_HEADER_SIZE; 119 | if (sz > bsize) { 120 | if (!(bbuf = realloc(bbuf, sz))) 121 | fatal(UNIMGC_ERROR_ALLOC, "could not allocate %zu bytes\n", sz); 122 | bsize = sz; 123 | } 124 | if (fread(bbuf, 1, sz, in) != sz) 125 | fatal(UNIMGC_ERROR_CORRUPTED_DATA, "truncated input file (data)\n"); 126 | 127 | /* process block */ 128 | switch (bhdr.type) { 129 | case IMGC_BLOCK_COMPRESSED: { 130 | /* uncompress pseudo-LZO block */ 131 | size_t dsz = imgc_decompress_block(bbuf, sz, NULL, 0); 132 | if (options.verbose >= 2) 133 | fprintf(stderr, " dec: %zd\n", dsz); 134 | 135 | if (dsz > dsize) { 136 | if (!(dbuf = realloc(dbuf, dsz))) 137 | fatal(UNIMGC_ERROR_ALLOC, "could not allocate %zd bytes\n", dsz); 138 | dsize = dsz; 139 | } 140 | if (!imgc_decompress_block(bbuf, sz, dbuf, dsz)) 141 | fatal(UNIMGC_ERROR_CORRUPTED_DATA, "corrupted IMGC compressed block\n"); 142 | if (fwrite(dbuf, 1, dsz, out) != dsz) 143 | fatal(UNIMGC_ERROR_IO, "could not write %zd bytes to target file: %s\n", dsz, strerror(errno)); 144 | break; 145 | } 146 | case IMGC_BLOCK_ZERO: { 147 | size_t dsz = le64toh(*(uint64_t *)bbuf); 148 | if (options.verbose >= 2) 149 | fprintf(stderr, " zero: %zu\n", dsz); 150 | 151 | while (dsz > sizeof(zeros)) 152 | dsz -= fwrite(zeros, 1, sizeof(zeros), out); 153 | if (fwrite(zeros, 1, dsz, out) != dsz) 154 | fatal(UNIMGC_ERROR_IO, "could not write %zu bytes to target file: %s\n", dsz, strerror(errno)); 155 | break; 156 | } 157 | default: 158 | fatal(UNIMGC_ERROR_CORRUPTED_BLOCK, "unknown IMGC block type: %d\n", bhdr.type); 159 | } 160 | } 161 | 162 | if (options.verbose >= 1) 163 | fputc('\n', stderr); 164 | } 165 | 166 | static void unimgc_header(struct imgc_header *hdr, FILE *in) 167 | { 168 | uint8_t hbuf[IMGC_HEADER_SIZE]; 169 | fread(hbuf, 1, sizeof(hbuf), in); 170 | 171 | if (imgc_parse(hbuf, sizeof(hbuf), hdr) < 0) 172 | fatal(UNIMGC_ERROR_CORRUPTED_FILE, "invalid IMGC header\n"); 173 | 174 | if (options.only_info || options.verbose >= 1) 175 | dump_header(hdr); 176 | } 177 | 178 | 179 | static FILE *open_or(const char *name, const char *mode, FILE *alt) 180 | { 181 | if (!name || !*name || !strcmp(name, "-")) 182 | return alt; 183 | return fopen(name, mode); 184 | } 185 | 186 | static void usage(const char *prog) 187 | { 188 | printf("usage: %s [-ihvV] [IN] [OUT]\n", prog); 189 | puts(" -h \t show usage"); 190 | puts(" -V \t show version"); 191 | puts(" -v \t increase verbosity (level 1: progress, level 2: header dumps)"); 192 | puts(" -i \t only show image information, don't decompress"); 193 | puts(" IN \t input file; defaults to standard input"); 194 | puts(" OUT \t output file; defaults to standard output"); 195 | } 196 | 197 | int main(int argc, char **argv) 198 | { 199 | int opt; 200 | 201 | while ((opt = getopt(argc, argv, "ihvV")) != -1) { 202 | switch(opt) { 203 | case 'i': 204 | options.only_info = 1; 205 | break; 206 | case 'v': 207 | options.verbose++; 208 | break; 209 | case 'V': 210 | printf("unimgc v%s\n", UNIMGC_VERSION); 211 | puts("copyright (c) 2019 shiz; released under the WTFPL"); 212 | exit(0); 213 | break; 214 | case 'h': 215 | usage(argv[0]); 216 | exit(0); 217 | break; 218 | default: 219 | usage(argv[0]); 220 | exit(255); 221 | break; 222 | } 223 | } 224 | argc -= optind; 225 | 226 | /* open files */ 227 | FILE *in = open_or(argc > 0 ? argv[optind] : NULL, "rb", stdin); 228 | if (!in) 229 | fatal(UNIMGC_ERROR_IO, "could not open input %s: %s\n", argv[optind], strerror(errno)); 230 | FILE *out = open_or(argc > 1 ? argv[optind + 1] : NULL, "wb", stdout); 231 | if (!out) 232 | fatal(UNIMGC_ERROR_IO, "could not open output %s: %s\n", argv[optind + 1], strerror(errno)); 233 | 234 | /* do the boogie */ 235 | struct imgc_header hdr; 236 | unimgc_header(&hdr, in); 237 | if (!options.only_info) 238 | unimgc_data(&hdr, in, out); 239 | } 240 | --------------------------------------------------------------------------------