/* katcat -- media extraction tool for Sanyo 6650 (Katana II) * Copyright (C) 2012 Daniel Beer * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Compile with: * * gcc -O1 -Wall -ggdb -o katcat katcat.c -lusb */ #include #include #include #include #include #include /************************************************************************ * Utilities */ static void hexdump(FILE *out, const uint8_t *data, int data_len) { unsigned int addr = 0; int offset = 0; while (offset < data_len) { int i, j; /* Address label */ fprintf(out, " %05x:", offset + addr); /* Hex portion */ for (i = 0; i < 16 && offset + i < data_len; i++) fprintf(out, " %02x", data[offset + i]); for (j = i; j < 16; j++) fprintf(out, " "); /* Printable characters */ fprintf(out, " |"); for (j = 0; j < i; j++) { int c = data[offset + j]; fprintf(out, "%c", (c >= 32 && c <= 126) ? c : '.'); } for (; j < 16; j++) fprintf(out, " "); fprintf(out, "|\n"); offset += i; } } static inline uint16_t r16(const uint8_t *data) { return ((uint16_t)data[0]) | (((uint16_t)data[1]) << 8); } static inline uint32_t r32(const uint8_t *data) { return ((uint32_t)r16(data)) | (((uint32_t)r16(data + 2)) << 16); } static const uint16_t crc_table[256] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; static uint16_t packet_crc(const uint8_t *data, int len) { uint16_t crc = 0xffff; int i; for (i = 0; i < len; i++) crc = (crc >> 8) ^ crc_table[(data[i] ^ crc) & 0xff]; return ~crc; } /************************************************************************ * Katana USB packet IO */ #define KATANA_VENDOR 0x0474 #define KATANA_PRODUCT 0x0745 #define KATANA_INTERFACE 2 #define KATANA_EP_SEND 0x05 #define KATANA_EP_RECV 0x82 #define KATANA_TIMEOUT 200 struct katana { struct usb_dev_handle *handle; int debug; }; static int parse_devspec(const char *spec, int *device_num, int *bus_num) { char *ptr; *bus_num = strtoul(spec, &ptr, 10); if (*ptr != ':') return -1; ptr++; *device_num = strtoul(ptr, &ptr, 10); if (*ptr) return -1; return 0; } static int katana_open(struct katana *k, const char *devspec) { int dev_num = -1; int bus_num = -1; struct usb_device *kdev = NULL; struct usb_bus *bus; if (devspec && parse_devspec(devspec, &dev_num, &bus_num) < 0) { fprintf(stderr, "Invalid device spec: %s\n", devspec); return -1; } for (bus = usb_get_busses(); bus; bus = bus->next) { struct usb_device *dev; if (bus_num >= 0 && atoi(bus->dirname) != bus_num) continue; for (dev = bus->devices; dev; dev = dev->next) { if (dev_num >= 0 && atoi(dev->filename) != dev_num) continue; if (dev->descriptor.idProduct == KATANA_PRODUCT && dev->descriptor.idVendor == KATANA_VENDOR) kdev = dev; } } if (!kdev) { fprintf(stderr, "Unable to find a USB device matching " "%04x:%04x\n", KATANA_VENDOR, KATANA_PRODUCT); return -1; } memset(k, 0, sizeof(*k)); k->handle = usb_open(kdev); if (!k->handle) { fprintf(stderr, "Unable to open device: %s\n", usb_strerror()); return -1; } if (usb_claim_interface(k->handle, KATANA_INTERFACE) < 0) { fprintf(stderr, "Unable to claim interface %d: %s\n", KATANA_INTERFACE, usb_strerror()); usb_close(k->handle); return -1; } return 0; } static void katana_close(struct katana *k) { usb_close(k->handle); } static int push_escape(uint8_t *dst, int max_len, const uint8_t *src, int len) { int blen = 0; int i; for (i = 0; i < len; i++) { const uint8_t b = src[i]; if (blen + 2 >= max_len) return -1; if (b == 0x7e || b == 0x7d) { dst[blen++] = 0x7d; dst[blen++] = b & 0x5f; } else { dst[blen++] = b; } } return blen; } static int katana_send(struct katana *k, const uint8_t *packet, int len) { uint16_t crc = packet_crc(packet, len); uint8_t crc_data[2] = {crc & 0xff, crc >> 8}; uint8_t buf[8192]; int blen; int i; blen = push_escape(buf, sizeof(buf), packet, len); if (blen < 0) goto too_long; i = push_escape(buf + blen, sizeof(buf) - blen, crc_data, 2); if (i < 0) goto too_long; blen += i; if (blen >= sizeof(buf)) goto too_long; buf[blen++] = 0x7e; if (k->debug) { fprintf(stderr, "USB write:\n"); hexdump(stderr, buf, blen); fprintf(stderr, "\n"); } if (usb_bulk_write(k->handle, KATANA_EP_SEND, (char *)buf, blen, KATANA_TIMEOUT) <= 0) { fprintf(stderr, "katana_send: usb_bulk_write: %s\n", usb_strerror()); return -1; } return 0; too_long: fprintf(stderr, "katana_send: packet too long\n"); return -1; } static int katana_recv(struct katana *k, uint8_t *packet, int max_len) { uint16_t crc_calc, crc_recv; int is_escape = 0; int plen = 0; for (;;) { char buf[4096]; int r = usb_bulk_read(k->handle, KATANA_EP_RECV, buf, sizeof(buf), KATANA_TIMEOUT); int i; if (r <= 0) { const char *msg = usb_strerror(); if (r == -ETIMEDOUT) msg = "timeout"; fprintf(stderr, "katana_recv: usb_bulk_read: %s\n", msg); return -1; } if (k->debug) { fprintf(stderr, "USB read:\n"); hexdump(stderr, (const uint8_t *)buf, r); fprintf(stderr, "\n"); } for (i = 0; i < r; i++) { const uint8_t b = buf[i]; if (plen >= max_len) { fprintf(stderr, "katana_recv: maximum packet " "length exceeded.\n"); return -1; } if (is_escape) { packet[plen++] = b | 0x20; is_escape = 0; } else if (b == 0x7d) { is_escape = 1; } else { packet[plen++] = b; } if (b == 0x7e) { if (i + 1 < r) fprintf(stderr, "warning: " "katana_recv: trailing " "garbage\n"); goto end_of_packet; } } } end_of_packet: if (plen < 3) { fprintf(stderr, "katana_recv: short packet\n"); return -1; } crc_calc = packet_crc(packet, plen - 3); crc_recv = r16(packet + plen - 3); if (crc_calc != crc_recv) { fprintf(stderr, "katana_recv: bad checksum\n"); return -1; } return plen - 3; } /************************************************************************ * Higher-level operations */ /* Katana packet types */ #define KATANA_PKT_GET_FWINFO 0x00 #define KATANA_PKT_GET_ESN 0x01 #define KATANA_PKT_GET_ATTRIBUTE 0x26 #define KATANA_PKT_MEDIA 0xfa #define KATANA_ATTRIBUTE_SIZE 129 #define KATANA_ATTR_PHONE_NUM 0xb2 #define KATANA_MEDIA_REQ_SIZE 172 struct katana_fwinfo { char date1[20]; char date2[20]; char fwrev[9]; }; static int katana_get_fwinfo(struct katana *kat, struct katana_fwinfo *fwi) { uint8_t req = KATANA_PKT_GET_FWINFO; uint8_t response[128]; int len; if (katana_send(kat, &req, 1) < 0) return -1; len = katana_recv(kat, response, sizeof(response)); if (len < 0) return -1; memcpy(fwi->date1, response + 1, 19); fwi->date1[19] = 0; memcpy(fwi->date2, response + 20, 19); fwi->date2[19] = 0; memcpy(fwi->fwrev, response + 39, 8); fwi->fwrev[8] = 0; return 0; } static int katana_get_esn(struct katana *kat, uint32_t *esn) { uint8_t request = KATANA_PKT_GET_ESN; uint8_t response[16]; int len; if (katana_send(kat, &request, 1) < 0) return -1; len = katana_recv(kat, response, sizeof(response)); if (len < 0) return -1; if (len != 5) { fprintf(stderr, "katana_get_esn: bad response " "length (%d)\n", len); return -1; } *esn = r32(response + 1); return 0; } static int katana_get_attribute(struct katana *kat, unsigned int attr_id, unsigned int index, uint8_t *out) { uint8_t request[4] = { KATANA_PKT_GET_ATTRIBUTE, attr_id & 0xff, attr_id >> 8, index }; uint8_t response[KATANA_ATTRIBUTE_SIZE + 7]; int len; if (katana_send(kat, request, 4) < 0) goto fail; len = katana_recv(kat, response, sizeof(response)); if (len < 0) goto fail; if (len != KATANA_ATTRIBUTE_SIZE + 4) { fprintf(stderr, "katana_get_attribute: bad response " "length (%d)\n", len); goto fail; } memcpy(out, response + 4, KATANA_ATTRIBUTE_SIZE); out[KATANA_ATTRIBUTE_SIZE - 1] = 0; return 0; fail: fprintf(stderr, "katana_get_attribute: attr_id = 0x%04x\n", attr_id); return -1; } static int katana_media_req(struct katana *kat, unsigned int media_cmd, const uint8_t *request, uint8_t *response) { uint8_t buf[1024]; int len; buf[0] = KATANA_PKT_MEDIA; buf[1] = 0x00; buf[2] = 0x09; buf[3] = media_cmd; buf[4] = 0; buf[5] = 0xff; buf[6] = 0xff; memcpy(buf + 7, request, KATANA_MEDIA_REQ_SIZE + 7); if (katana_send(kat, buf, KATANA_MEDIA_REQ_SIZE + 7) < 0) goto fail; len = katana_recv(kat, buf, sizeof(buf)); if (len != KATANA_MEDIA_REQ_SIZE + 7) { fprintf(stderr, "katana_media: bad response " "length (%d)\n", len); goto fail; } memcpy(response, buf + 7, KATANA_MEDIA_REQ_SIZE); return 0; fail: fprintf(stderr, "katana_media: media_cmd = 0x%02x\n", media_cmd); return -1; } /************************************************************************ * Katana media operations */ #define KATANA_MREQ_CHDIR 0x71 #define KATANA_MREQ_NUM_FILES 0x72 #define KATANA_MREQ_GET_FILENAME 0x73 #define KATANA_MREQ_FRAGMENT 0x74 #define KATANA_DIR_CAMERA 1 #define KATANA_DIR_MEDIA1 2 #define KATANA_DIR_MEDIA2 3 static int katana_media_chdir(struct katana *kat, int dir) { uint8_t blk[KATANA_MEDIA_REQ_SIZE] = {0}; blk[170] = dir; if (katana_media_req(kat, KATANA_MREQ_CHDIR, blk, blk) < 0) { fprintf(stderr, "katana_media_num_files: dir = %d\n", dir); return -1; } return 0; } static int katana_media_num_files(struct katana *kat) { uint8_t blk[KATANA_MEDIA_REQ_SIZE] = {0}; if (katana_media_req(kat, KATANA_MREQ_NUM_FILES, blk, blk) < 0) { fprintf(stderr, "katana_media_num_files: failed\n"); return -1; } return blk[165]; } static int katana_media_get_filename(struct katana *kat, int index, char *buf, int max_len) { uint8_t blk[KATANA_MEDIA_REQ_SIZE] = {0}; int len = 154; blk[161] = index; if (katana_media_req(kat, KATANA_MREQ_GET_FILENAME, blk, blk) < 0) { fprintf(stderr, "katana_media_get_filename: index = %d\n", index); return -1; } if (len + 1 > max_len) len = max_len - 1; memcpy(buf, blk + 1, len); buf[len] = 0; return 0; } typedef void (*katana_fragment_func_t)(void *user_data, unsigned long offset, unsigned int size, const uint8_t *data); static int katana_media_fetch(struct katana *kat, int index, katana_fragment_func_t func, void *user_data) { uint8_t req[KATANA_MEDIA_REQ_SIZE] = {0}; unsigned long offset = 0; req[155] = index; for (;;) { uint8_t response[KATANA_MEDIA_REQ_SIZE]; unsigned int len; if (katana_media_req(kat, KATANA_MREQ_FRAGMENT, req, response) < 0) { fprintf(stderr, "katana_media_fetch: failed to fetch " "slot %d\n", index); return -1; } len = response[151]; func(user_data, offset, len, response + 1); offset += len; if (!response[171]) break; } return 0; } /************************************************************************ * Command-line interface */ #define FLAG_DEBUG 0x01 #define FLAG_HELP 0x02 #define FLAG_VERSION 0x04 struct options; typedef int (*command_func_t)(const struct options *o, struct katana *kat); struct options { int flags; int media_dir; const char *outfile_name; const char *devspec; command_func_t command; int argc; char **argv; }; static int cmd_default(const struct options *o, struct katana *kat) { struct katana_fwinfo fwinfo; uint8_t phone[KATANA_ATTRIBUTE_SIZE]; uint32_t esn; if (katana_get_fwinfo(kat, &fwinfo) < 0) return -1; printf("Firmware date1: %s\n", fwinfo.date1); printf("Firmware date2: %s\n", fwinfo.date1); printf("Firmware rev: %s\n", fwinfo.fwrev); if (katana_get_esn(kat, &esn) < 0) return -1; printf("ESN: %04X\n", esn); if (katana_get_attribute(kat, KATANA_ATTR_PHONE_NUM, 0, phone) < 0) return -1; printf("Phone number: %s\n", phone); return 0; } static int cmd_list(const struct options *o, struct katana *kat) { int ret = 0; int num_files; int i; if (katana_media_chdir(kat, o->media_dir) < 0) return -1; num_files = katana_media_num_files(kat); if (num_files < 0) return -1; printf("%d files in directory %d:\n", num_files, o->media_dir); for (i = 0; i < num_files; i++) { char fname[192]; if (katana_media_get_filename(kat, i, fname, sizeof(fname)) < 0) ret = -1; else printf(" %3d. %s\n", i, fname); } return ret; } struct extract_ctx { FILE *out; int ret; }; static void extract_cb(void *user_data, unsigned long offset, unsigned int size, const uint8_t *data) { struct extract_ctx *ctx = (struct extract_ctx *)user_data; if (fwrite(data, size, 1, ctx->out) != 1) { fprintf(stderr, "Write error at offset %lu: %s\n", offset, strerror(errno)); ctx->ret = -1; } } static int do_extract(struct katana *kat, int slot, const char *outfile_name) { struct extract_ctx ctx; ctx.out = stdout; ctx.ret = 0; if (outfile_name) { ctx.out = fopen(outfile_name, "wb"); if (!ctx.out) { fprintf(stderr, "Failed to open %s: %s\n", outfile_name, strerror(errno)); return -1; } } if (katana_media_fetch(kat, slot, extract_cb, &ctx) < 0) ctx.ret = -1; if (outfile_name && fclose(ctx.out) < 0) { fprintf(stderr, "Error on close: %s: %s\n", outfile_name, strerror(errno)); return -1; } return ctx.ret; } static int cmd_cat(const struct options *o, struct katana *kat) { int slot; int num_files; if (o->argc < 1) { fprintf(stderr, "cat: expected a slot index\n"); return -1; } slot = atoi(o->argv[0]); if (katana_media_chdir(kat, o->media_dir) < 0) return -1; num_files = katana_media_num_files(kat); if (slot < 0 || slot >= num_files) { fprintf(stderr, "cat: slot out of range\n"); return -1; } return do_extract(kat, slot, o->outfile_name); } static int find_file(struct katana *kat, const char *filename) { int num_files = katana_media_num_files(kat); int i; if (num_files < 0) return -1; for (i = 0; i < num_files; i++) { char fname[192]; if (katana_media_get_filename(kat, i, fname, sizeof(fname)) >= 0 && !strcmp(fname, filename)) return i; } return -1; } static int cmd_catfile(const struct options *o, struct katana *kat) { int slot; if (o->argc < 1) { fprintf(stderr, "cat: expected a filename\n"); return -1; } if (katana_media_chdir(kat, o->media_dir) < 0) return -1; slot = find_file(kat, o->argv[0]); if (slot < 0) { fprintf(stderr, "catfile: file not found: %s\n", o->argv[0]); return -1; } return do_extract(kat, slot, o->outfile_name); } static void sanitize(char *fname) { while (*fname) { if (*fname == '/' || *fname == '\\' || *fname < ' ' || *fname >= 127) *fname = '_'; fname++; } } static int cmd_extract_all(const struct options *o, struct katana *kat) { int ret = 0; int num_files; int i; if (katana_media_chdir(kat, o->media_dir) < 0) return -1; num_files = katana_media_num_files(kat); if (num_files < 0) return -1; for (i = 0; i < num_files; i++) { char fname[192]; if (katana_media_get_filename(kat, i, fname, sizeof(fname)) < 0) { ret = -1; } else { sanitize(fname); fprintf(stderr, "%s\n", fname); if (do_extract(kat, i, fname) < 0) ret = -1; } } return ret; } struct command { const char *name; command_func_t func; }; static const struct command command_table[] = { {"list", cmd_list}, {"cat", cmd_cat}, {"catfile", cmd_catfile}, {"extract-all", cmd_extract_all}, {NULL, NULL} }; static command_func_t find_command(const char *name) { int i; for (i = 0; command_table[i].name; i++) if (!strcasecmp(name, command_table[i].name)) return command_table[i].func; return NULL; } static int parse_options(struct options *o, int argc, char **argv) { static const struct option longopts[] = { {"help", 0, 0, 'H'}, {"version", 0, 0, 'V'}, {"debug", 0, 0, 'D'}, {NULL, 0, 0, 0} }; int opt; memset(o, 0, sizeof(*o)); o->media_dir = KATANA_DIR_CAMERA; o->command = cmd_default; while ((opt = getopt_long(argc, argv, "m:o:d:", longopts, NULL)) >= 0) switch (opt) { case 'd': o->devspec = optarg; break; case 'm': o->media_dir = atoi(optarg); break; case 'o': o->outfile_name = optarg; break; case 'H': o->flags |= FLAG_HELP; break; case 'V': o->flags |= FLAG_VERSION; break; case 'D': o->flags |= FLAG_DEBUG; break; case '?': fprintf(stderr, "Try --help for usage information.\n"); return -1; } argc -= optind; argv += optind; if (argc >= 1) { o->command = find_command(argv[0]); if (!o->command) { fprintf(stderr, "Unknown command: %s\n", argv[0]); return -1; } argc--; argv++; } o->argc = argc; o->argv = argv; return 0; } static void version(void) { puts( "katcat -- media extraction tool for Sanyo 6650 (Katana II)\n" "Version 20120628\n" "Copyright (C) 2012 Daniel Beer \n" "\n" "Permission to use, copy, modify, and/or distribute this software for any\n" "purpose with or without fee is hereby granted, provided that the above\n" "copyright notice and this permission notice appear in all copies.\n" "\n" "THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n" "WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n" "MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n" "ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n" "WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n" "ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n" "OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE."); } static void usage(const char *progname) { printf( "Usage: %s [options] [command [args]]\n" "\n" "Options may be any of the following:\n" " -m Specify the media directory number (default 1).\n" " -o Write extracted media to the given file (default\n" " is stdout).\n" " -d Specify a particular USB device.\n" " --help Show this help text and exit.\n" " --version Show version information and exit.\n" " --debug Show hexdumps of USB transfers.\n" "\n" "If no command is specified, basic phone information is displayed.\n" "Available commands are:\n" "\n" " list List files in the media directory.\n" " cat Extract from the given media slot number.\n" " catfile \n" " Extract a media item by filename.\n" " extract-all Extract all files in the media directory, writing\n" " them to files in the current directory. WARNING:\n" " POTENTIALLY DANGEROUS.\n", progname); } int main(int argc, char **argv) { int ret; struct options opt; struct katana kat; if (parse_options(&opt, argc, argv) < 0) return -1; if (opt.flags & FLAG_VERSION) { version(); return 0; } if (opt.flags & FLAG_HELP) { usage(argv[0]); return 0; } usb_init(); usb_find_busses(); usb_find_devices(); if (katana_open(&kat, opt.devspec) < 0) return -1; if (opt.flags & FLAG_DEBUG) kat.debug = 1; ret = opt.command(&opt, &kat); katana_close(&kat); return ret; }