// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include #include #include #include #include #include #if !defined(__BYTE_ORDER) #error "Unknown byte order" #endif #if __BYTE_ORDER == __BIG_ENDIAN #define cpu_to_be16(x) (x) #define cpu_to_be32(x) (x) #elif __BYTE_ORDER == __LITTLE_ENDIAN #define cpu_to_be16(x) bswap_16(x) #define cpu_to_be32(x) bswap_32(x) #else #error "Unsupported endianness" #endif #define IMAGE_VERSION 1 #define FILE_VERSION 1 #define FILE_DESCRIPTION "OpenWrt" #define FILE_TYPE_MASK 0x1 #define PACKAGE_FLAG 2 #define FILE_TYPE_APPLICATION 0x04000000 #define VERSION_OFFSET_INVALID 0xffffffff #define COMPRESSION_TYPE_NONE 0xffffffff #define COMPRESSION_TYPE_7Z 0x00000002 struct file_header { uint8_t res1[4]; uint32_t header_crc; uint32_t file_type; uint32_t version; uint32_t product_id; uint32_t device_id; uint32_t length_unpadded; uint32_t version_offset; uint16_t year; uint8_t month; uint8_t day; uint8_t res2[1]; uint8_t hour; uint8_t minute; uint8_t second; uint8_t res3[64]; char description[224]; uint32_t length; uint32_t file_crc; uint32_t compression_type; } __attribute__ ((packed)); struct file_desc { uint32_t file_type; uint32_t offset; uint32_t length; uint32_t file_crc; uint32_t version; uint32_t type_mask; } __attribute__ ((packed)); struct image_header { uint32_t version; uint32_t file_count; uint32_t product_id; uint32_t device_id; uint16_t year; uint8_t month; uint8_t day; uint8_t res1[1]; uint8_t hour; uint8_t minute; uint8_t second; uint16_t package_crc; uint16_t package_flag; uint32_t length; struct file_desc files[128]; /* RSA signature, not required */ uint8_t res2[3072]; uint32_t header_crc; } __attribute__ ((packed)); static void *buf; static size_t buflen; static size_t length_unpadded; static size_t length; static uint32_t crc16_xmodem(char *buf, size_t len) { uint32_t poly = 0x1021; uint32_t crc = 0; char b; int i, j; for (i = 0; i < len; i++) { b = buf[i]; crc = crc ^ (b << 8); for (j = 0; j < 8; j++) { crc = crc << 1; if (crc & 0x10000) { crc = (crc ^ poly) & 0xffff; } } } return crc; } static int create_buffer_and_read_file(char *filename) { FILE *f; f = fopen(filename, "r"); if (f == NULL) { fprintf(stderr, "failed to open input file\n"); goto err; } fseek(f, 0L, SEEK_END); length_unpadded = ftell(f); rewind(f); length = length_unpadded; if (length_unpadded % 8 != 0) { length += 8 - length_unpadded % 8; } buflen = sizeof(struct file_header) + sizeof(struct image_header) + length; buf = malloc(buflen); if (!buf) { fprintf(stderr, "failed to allocate buffer\n"); goto err_close; } memset(buf, 0, buflen); if (fread(buf + sizeof(struct file_header) + sizeof(struct image_header), length_unpadded, 1, f) != 1) { fprintf(stderr, "failed to read input file\n"); goto err_free; } fclose(f); return 0; err_free: free(buf); err_close: fclose(f); err: return -1; } static void build_file_header(uint32_t product_id, uint32_t device_id, uint32_t compression_type) { struct file_header *header = buf + sizeof(struct image_header); uint32_t crc; header->file_type = cpu_to_be32(FILE_TYPE_APPLICATION); header->version = cpu_to_be32(FILE_VERSION); header->product_id = cpu_to_be32(product_id); header->device_id = cpu_to_be32(device_id); header->length_unpadded = cpu_to_be32(length_unpadded); header->version_offset = cpu_to_be32(VERSION_OFFSET_INVALID); header->year = cpu_to_be16(1970); header->month = 1; header->day = 1; header->hour = 0; header->minute = 0; header->second = 0; snprintf(header->description, sizeof(header->description), "%s", FILE_DESCRIPTION); header->length = cpu_to_be32(length); crc = crc16_xmodem(buf + sizeof(struct image_header) + sizeof(struct file_header), length); header->file_crc = cpu_to_be32(crc); header->compression_type = cpu_to_be32(compression_type); crc = crc16_xmodem((char *)header + sizeof(header->res1) + sizeof(header->header_crc), sizeof(struct file_header) - sizeof(header->res1) - sizeof(header->header_crc)); header->header_crc = cpu_to_be32(crc); } static void build_image_header(uint32_t product_id, uint32_t device_id) { struct image_header *header = buf; struct file_header *file_header = buf + sizeof(struct image_header); uint32_t crc; header->version = cpu_to_be32(IMAGE_VERSION); header->file_count = cpu_to_be32(1); header->product_id = cpu_to_be32(product_id); header->device_id = cpu_to_be32(device_id); header->year = cpu_to_be16(1970); header->month = 1; header->day = 1; header->hour = 0; header->minute = 0; header->second = 0; crc = crc16_xmodem(buf + sizeof(struct file_header), buflen - sizeof(struct file_header)); header->package_crc = cpu_to_be16(crc); header->package_flag = cpu_to_be16(PACKAGE_FLAG); header->length = cpu_to_be32(buflen - sizeof(struct image_header)); header->files[0].file_type = file_header->file_type; header->files[0].offset = cpu_to_be32(sizeof(struct image_header)); header->files[0].length = cpu_to_be32(sizeof(struct file_header) + length); header->files[0].file_crc = file_header->file_crc; header->files[0].version = file_header->version; header->files[0].type_mask = cpu_to_be32(FILE_TYPE_MASK); crc = crc16_xmodem((char *)header, sizeof(struct image_header) - sizeof(header->header_crc)); header->header_crc = cpu_to_be32(crc); } static int write_output_file(char *filename) { int ret = 0; FILE *f; f = fopen(filename, "w"); if (f == NULL) { fprintf(stderr, "failed to open output file\n"); ret = -1; goto err; } if (fwrite(buf, buflen, 1, f) != 1) { fprintf(stderr, "failed to write output file\n"); ret = -1; } fclose(f); err: return ret; } static void usage(char* argv[]) { printf("Usage: %s [OPTIONS...]\n" "\n" "Options:\n" " -p product id (32-bit unsigned integer)\n" " -d device id (32-bit unsigned integer)\n" " -c compression type of the input file (7z or none)\n" " (in case of 7z only LZMA compression is allowed)\n" " -i input filename\n" " -o output filename\n" , argv[0]); } int main(int argc, char* argv[]) { int ret = EXIT_FAILURE; static uint32_t product_id = 0; static uint32_t device_id = 0; static uint32_t compression_type = COMPRESSION_TYPE_NONE; static char *input_filename = NULL; static char *output_filename = NULL; while ( 1 ) { int c; c = getopt(argc, argv, "p:d:c:i:o:"); if (c == -1) break; switch (c) { case 'p': product_id = strtoul(optarg, NULL, 0); break; case 'd': device_id = strtoul(optarg, NULL, 0); break; case 'c': if (strcmp(optarg, "none") == 0) { compression_type = COMPRESSION_TYPE_NONE; } else if (strcmp(optarg, "7z") == 0) { compression_type = COMPRESSION_TYPE_7Z; } else { usage(argv); return EXIT_FAILURE; } break; case 'i': input_filename = optarg; break; case 'o': output_filename = optarg; break; default: usage(argv); goto err; } } if (!product_id || !device_id || !input_filename || strlen(input_filename) == 0 || !output_filename || strlen(output_filename) == 0) { usage(argv); goto err; } if (create_buffer_and_read_file(input_filename)) { goto err; } build_file_header(product_id, device_id, compression_type); build_image_header(product_id, device_id); if (write_output_file(output_filename)) { goto err_free; } ret = EXIT_SUCCESS; err_free: free(buf); err: return ret; }