#include #include #include #include #include #include #include #include #include #define __PACK__ __attribute__ ((packed)) #define SIGNATURE_LENGTH 4 #define WEBP_SIGNATURE "WEBP" #define WEBP_DEFAULT_OFFSET 0x8000 #define MAX_PAGE_NAME 60 #define DEFAULT_PAGE_PATH "./web" #define DEFAULT_TEMPORARY_PATH "./" #define BUFFER_SIZE 0x4000 #define EXIT_EINVAL 1 #define EXIT_IMAGE_FILE 2 #define EXIT_TEMPORARY 3 #define EXIT_CUT_SECTOR 4 #define EXIT_UNPACK 5 #define EXIT_DECOMPRESS 6 #define TEMPORARY_NAME_LENGTH 10 inline unsigned long SWAP_DWORD(unsigned long dw) { return (((dw << 24) & 0xff000000) | ((dw << 8) & 0x00ff0000) | ((dw >> 8) & 0x0000ff00) | ((dw >> 24) & 0x000000ff)); } typedef struct { unsigned char signature[SIGNATURE_LENGTH]; unsigned long start_address; unsigned long flash_address; unsigned long length; } __PACK__ SECTOR; typedef struct { char name[MAX_PAGE_NAME]; unsigned long size; } __PACK__ PAGE; static int generate_temporary_name( char *new_name, int max_length, const char *temporary_path) { static const char *symbols = {"0123456789abcdefghijklmnopqrstuvwxyz"}; static char generated_name[TEMPORARY_NAME_LENGTH + 1]; int ret_code = 0; memset(generated_name, '\0', sizeof(generated_name)); for (unsigned int i = 0; i < TEMPORARY_NAME_LENGTH; i++) generated_name[i] = symbols[rand() % strlen(symbols)]; if (max_length >= strlen(temporary_path) + strlen(generated_name) + 2) { memset(new_name, '\0', max_length); snprintf(new_name, max_length, "%s/%s", temporary_path, generated_name); } else { ret_code = -1; } return ret_code; } static int read_data(FILE *fp, void *data, unsigned int length, long offset) { int ret_code = 0; if (fseek(fp, offset, SEEK_SET) == 0) { if (fread(data, 1, length, fp) != length) { fprintf(stderr, "Can't read at 0x%08X %d bytes.\n", offset, length); } } else { fprintf(stderr, "Can't seek to 0x%08X.\n", offset); ret_code = -1; } return ret_code; } static int cut_and_store( FILE *input_fp, long offset, unsigned int length, const char *output_file) { int ret_code = 0; if (fseek(input_fp, offset, SEEK_SET) == 0) { FILE *output_fp; if ((output_fp = fopen(output_file, "wb")) == NULL) { ret_code = -1; } else { unsigned int copied = 0; char buffer[BUFFER_SIZE]; while ((ret_code == 0) && (copied != length)) { long bytes_to_copy = (length - copied) >= sizeof(buffer) ? sizeof(buffer) : (length - copied); if ((fread(buffer, bytes_to_copy, 1, input_fp) == 1) && (fwrite(buffer, bytes_to_copy, 1, output_fp) == 1)) { copied += bytes_to_copy; } else { ret_code = -1; } } fclose(output_fp); } } return ret_code; } static int create_directory_for(const char *full_file_name) { int ret_code = 0; char name[FILENAME_MAX]; char path[FILENAME_MAX]; char *p = (char *) name; char *start; memset(name, '\0', sizeof(name)); strncpy(name, full_file_name, sizeof(name) - 1); while ((p = strchr(p, '/')) != NULL) { start = p; while (*p == '/') *p++; memmove(start + 1, p, strlen(p) + 1); p = start + 1; } start = p = name; while ((ret_code == 0) && ((p = strchr(p, '/')) != NULL)) { struct stat path_stat; memset(path, '\0', sizeof(path) - 1); strncpy(path, start, p - start); if (stat(path, &path_stat) != 0) { if (mkdir(path, S_IRUSR | S_IWUSR | S_IXUSR) != 0) ret_code = -1; } else { if (!S_ISDIR(path_stat.st_mode)) ret_code = -1; } p++; } return ret_code; } static int decompress( const char *compressed_name, const char *decompressed_name) { char command[FILENAME_MAX]; memset(command, '\0', sizeof(command)); snprintf(command, sizeof(command) - 1, "bzip2 -dqc %s 2>/dev/null > %s", compressed_name, decompressed_name); return system(command); } static int file_size(const char *name, long *size) { FILE *fp = fopen(name, "rb"); int ret_code = -1; if (fp != NULL) { if (fseek(fp, 0, SEEK_END) == 0) { *size = ftell(fp); ret_code = 0; } fclose(fp); } return ret_code; } static int unpack_pages( const char *storage_name, const char *webpage_path, int swap_bytes) { FILE *storage_fp; int ret_code = 0; long storage_size; if (file_size(storage_name, &storage_size) != 0) { fprintf(stderr, "Can't get web page storage file size.\n"); ret_code = -1; } else if ((storage_fp = fopen(storage_name, "rb")) == NULL) { fprintf(stderr, "Can't open web page binary storage.\n"); ret_code = -1; } else { PAGE page; while ((ret_code == 0) && (ftell(storage_fp) != storage_size)) { memset(&page, '\0', sizeof(page)); if (read_data(storage_fp, &page, sizeof(page), ftell(storage_fp)) == 0) { char page_full_name[FILENAME_MAX]; memset(page_full_name, '\0', sizeof(page_full_name)); snprintf(page_full_name, sizeof(page_full_name) - 1, "%s/%s", webpage_path, page.name); page.size = SWAP_DWORD(page.size); fprintf(stdout, "Storing %s... ", page_full_name); if (create_directory_for(page_full_name) != 0) { fprintf(stdout, "error!\n"); fprintf(stderr, "Can't create directory for %s.\n", page_full_name); ret_code = -1; } else if (cut_and_store(storage_fp, ftell(storage_fp), page.size, page_full_name) == 0) { fprintf(stdout, "done.\n"); } else { fprintf(stdout, "error!\n"); fprintf(stderr, "Can't store a page into %s.\n", page_full_name); ret_code = -1; } } else { fprintf(stderr, "Can't read page description.\n"); ret_code = -1; } } } return ret_code; } static int parse_string( int argc, char *argv[], int *arg_index, char **value, const char *full_option_name, const char *option, int *parse_flag) { int ret_code = 0; if (!(*parse_flag)) { if (argc == *arg_index) { fprintf(stderr, "%s required after %s.\n", full_option_name, option); ret_code = 1; } else { *value = argv[*arg_index]; *parse_flag = 1; (*arg_index)++; } } else { fprintf(stderr, "%s redifinition (%s option).\n", full_option_name, option); ret_code = 1; } return ret_code; } static int parse_hex_or_decimal( int argc, char *argv[], int *arg_index, unsigned long *value, const char *full_option_name, const char *option, int *parse_flag) { int ret_code = 0; if (!(*parse_flag)) { if (argc == *arg_index) { fprintf(stderr, "%s required after %s.\n", full_option_name, option); ret_code = 1; } else { char *end_ptr; long int parsed_value; parsed_value = strtol(argv[*arg_index], &end_ptr, 0); if (*end_ptr == '\0') { *parse_flag = 1; *value = (unsigned long) parsed_value; (*arg_index)++; } else { fprintf(stderr, "Invalid offset value: %s.\n", argv[*arg_index]); ret_code = 1; } } } else { fprintf(stderr, "%s redifinition (%s option).\n", full_option_name, option); ret_code = 1; } return ret_code; } int parse_arguments(int argc, char *argv[], char **pages_path, char **temporary_path, char **image_name, unsigned long *webp_offset, int *swap_bytes) { int ret_code = 0; int image_parsed = 0; int pages_path_parsed = 0; int temporary_path_parsed = 0; int webp_offset_parsed = 0; int swap_bytes_parsed = 0; if (argc < 2) { ret_code = -1; } else { int arg_index = 1; while ((ret_code == 0) && (arg_index < argc)) { if (*argv[arg_index] == '-') { char *string_option = argv[arg_index] + 1; if (strlen(string_option) == 1) { char option = tolower(*(argv[arg_index] + 1)); arg_index++; switch (option) { case 'i': { ret_code = parse_string( argc, argv, &arg_index, image_name, "Image file name", "-i", &image_parsed); break; } case 'o': { ret_code = parse_hex_or_decimal( argc, argv, &arg_index, webp_offset, "Web page offset", "-o", &webp_offset_parsed); break; } case 't': { ret_code = parse_string( argc, argv, &arg_index, temporary_path, "Temporary file path", "-t", &temporary_path_parsed); break; } case 'w': { ret_code = parse_string( argc, argv, &arg_index, pages_path, "Web page file path", "-w", &pages_path_parsed); break; } case 's': { if (!swap_bytes_parsed) { *swap_bytes = 1; } else { fprintf(stderr, "Swap byte flag redefined.\n"); ret_code = 1; } break; } default: { fprintf(stderr, "Unknown option '%c'.\n", option); ret_code = 1; break; } } } else { fprintf(stderr, "Unknown option '%s'.\n", string_option); ret_code = 1; } } else { fprintf(stderr, "An option expected instead of '%s'.\n", argv[arg_index]); ret_code = 1; } } } if ((ret_code == 0) && !image_parsed) { fprintf(stderr, "Image file name (-i option) should be specified.\n"); ret_code = 1; } return ret_code; } int main(int argc, char *argv[]) { char *image_name = ""; char *pages_path = DEFAULT_PAGE_PATH; char *temporary_path = DEFAULT_TEMPORARY_PATH; unsigned long webp_offset = WEBP_DEFAULT_OFFSET; int swap_bytes = 0; FILE *image_fp; SECTOR sector; int ret_code = EXIT_SUCCESS; srand(time(NULL)); fprintf(stdout, "decompweb v1.0 unpacks web pages\n" \ "containing into ZyXEL P-330 firmware binary images.\n\n"); ret_code = parse_arguments(argc, argv, &pages_path, &temporary_path, &image_name, &webp_offset, &swap_bytes); if (ret_code > 0) { ret_code = EXIT_EINVAL; } else if (ret_code == -1) { fprintf(stdout, "Use the following options:\n" \ "\t-i \n" \ "\t-o (default 0x8000)\n" \ "\t-t (default ./)\n" \ "\t-w (default ./web)\n" \ "\t-s swap bytes in size header fields\n\n"); ret_code = EXIT_SUCCESS; } else if ((image_fp = fopen(image_name, "rb")) == NULL) { fprintf(stderr, "Can't open image file: %s.\n", image_name); ret_code = EXIT_IMAGE_FILE; } else { SECTOR webp_sector; fprintf(stdout, "Image name : %s\n" \ "Web page offset : 0x%08X\n" \ "Output directory : %s\n" \ "Temporary directory : %s\n" \ "Swap bytes : %s\n\n", image_name, webp_offset, pages_path, temporary_path, swap_bytes ? "yes" : "no"); if ((read_data(image_fp, &webp_sector, sizeof(webp_sector), webp_offset) == 0) && (memcmp(webp_sector.signature, WEBP_SIGNATURE, sizeof(webp_sector.signature)) == 0)) { if (swap_bytes) { webp_sector.start_address = SWAP_DWORD(webp_sector.start_address); webp_sector.flash_address = SWAP_DWORD(webp_sector.flash_address); webp_sector.length = SWAP_DWORD(webp_sector.length); } fprintf(stdout, "Sector: %s\n" \ "Start : 0x%08X\n" \ "Length: 0x%08X\n" \ "Flash : 0x%08X\n\n", WEBP_SIGNATURE, webp_sector.start_address, webp_sector.length, webp_sector.flash_address); char compressed_file[FILENAME_MAX]; long sector_offset = webp_offset + sizeof(webp_sector); memset(compressed_file, '\0', sizeof(compressed_file)); if (generate_temporary_name(compressed_file, sizeof(compressed_file) - 1, temporary_path) != 0) { fprintf(stderr, "Can't generate a temporary file name."); ret_code = EXIT_TEMPORARY; } else if (cut_and_store(image_fp, sector_offset, webp_sector.length, compressed_file) != 0) { fprintf(stderr, "Can't cut %s sector from %s at 0x%08X.\n", WEBP_SIGNATURE, image_name, sector_offset); ret_code = EXIT_CUT_SECTOR; } else { char decompressed_file[FILENAME_MAX]; memset(decompressed_file, '\0', sizeof(decompressed_file)); if (generate_temporary_name(decompressed_file, sizeof(decompressed_file) - 1, temporary_path) != 0) { fprintf(stderr, "Can't generate a temporary file name."); ret_code = EXIT_TEMPORARY; } else if (decompress(compressed_file, decompressed_file) == 0) { if (unpack_pages(decompressed_file, pages_path, swap_bytes) == 0) { fprintf(stdout, "Web pages were unpacked into %s.\n", pages_path); } else { fprintf(stderr, "Can't unpack web pages into %s.\n", pages_path); ret_code = EXIT_UNPACK; } } else { fprintf(stderr, "Can't decompress webpage set. " \ "Possibly %s image part is damaged.\n", WEBP_SIGNATURE); ret_code = EXIT_DECOMPRESS; } remove(decompressed_file); } remove(compressed_file); } else { fprintf(stderr, "Can't read %s sector at 0x%08X.\n", WEBP_SIGNATURE, webp_offset); ret_code = EXIT_CUT_SECTOR; } fclose(image_fp); } return ret_code; }