/* actions-firmware-extract - extract firmwares like the one for Acer K330 * * Copyright (C) 2014 Antonio Ospite * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include /* * Based on cdt_parse.c from OpenEZX: * http://cgit.openezx.org/moto-boot-usb/tree/src/cdt_parse.c */ #define ACTIONS_HEADER_DISK_SIZE 64 struct actions_firmware_header { uint8_t magic_string[16]; uint8_t version_string[16]; uint8_t checksum_string[16]; uint32_t base_address; uint32_t length; uint32_t unknown0; uint32_t unknown1; }; #define ACTIONS_SECTION_DISK_SIZE 32 struct actions_firmware_section { uint8_t name[16]; uint32_t start_address; uint32_t length; uint32_t unknown0; uint32_t unknown1; }; #define FIRM_TABLE_OFFSET 0x200 #define FIRM_DATA_OFFSET 0x2000 static void get_string(uint8_t **srcp, uint8_t *dest, unsigned int len) { memcpy(dest, *srcp, len); dest[len - 1] = '\0'; *srcp += len; } static inline uint16_t get_le16(uint8_t **bufferp) { uint16_t tmp; memcpy(&tmp, *bufferp, sizeof (tmp)); *bufferp += sizeof (tmp); return le16toh(tmp); } static inline uint32_t get_le32(uint8_t **bufferp) { uint32_t tmp; memcpy(&tmp, *bufferp, sizeof (tmp)); *bufferp += sizeof (tmp); return le32toh(tmp); } static inline uint64_t get_le64(uint8_t **bufferp) { uint64_t tmp; memcpy(&tmp, *bufferp, sizeof (tmp)); *bufferp += sizeof (tmp); return le64toh(tmp); } static int parse_header(uint8_t **bufferp, struct actions_firmware_header *header) { get_string(bufferp, header->magic_string, 16); if (strncmp((char *)header->magic_string, "ActionsFirmware", 16) != 0) { fprintf(stderr, "Not an Actions firmware.\n"); return -EINVAL; } get_string(bufferp, header->version_string, 16); get_string(bufferp, header->checksum_string, 16); header->base_address = get_le32(bufferp); header->length = get_le32(bufferp); header->unknown0 = get_le32(bufferp); header->unknown1 = get_le32(bufferp); return 0; } static int parse_section(uint8_t **bufferp, struct actions_firmware_section *section) { get_string(bufferp, section->name, 16); if (section->name[0] == '\0') return -EINVAL; section->start_address = get_le32(bufferp); section->length = get_le32(bufferp); section->unknown0 = get_le32(bufferp); section->unknown1 = get_le32(bufferp); return 0; } static void print_header(struct actions_firmware_header *header) { printf("Magic: %s\n" "Version: %s\n" "Checksum: %s\n" "Base address: %d\n" "Content length: %d\n" "unknown0: 0x%08x (%u)\n" "unknown1: 0x%08x (%u)\n" "Complete file size: %d\n", header->magic_string, header->version_string, header->checksum_string, header->base_address, header->length, header->unknown0, header->unknown0, header->unknown1, header->unknown1, header->base_address + header->length); } static void print_section(struct actions_firmware_section *section) { printf("| %-16s | 0x%08x | 0x%08x | 0x%08x | 0x%08x |\n", section->name, section->start_address, section->length, section->unknown0, section->unknown1); } static int dump_section_data(const char *path_prefix, const char *name_prefix, uint8_t *buffer, struct actions_firmware_section *section) { int fd; int ret; char path[PATH_MAX] = { 0 }; ssize_t written; snprintf(path, PATH_MAX, "%s/%s%s", path_prefix, name_prefix ? name_prefix : "", (char *)section->name); fd = creat(path, 0644); if (fd < 0) { perror("creat"); ret = -errno; goto out; } written = write(fd, buffer + section->start_address, section->length); if (written < 0) { perror("write"); ret = -errno; goto out_close_fd; } ret = 0; out_close_fd: close(fd); out: return ret; } static void usage(const char *name) { printf("usage: %s [OPTIONS] \n", name); printf("OPTIONS:\n"); printf("\t-d \t\tdump the firmware sections into the directory\n"); printf("\t-h \t\t\tthis help message\n"); printf("\nEXAMPLE OF USE:\n"); printf("\t%s -d PPX2230 ppx2230_eu_fus_aen.bin\n", name); } int main(int argc, char *argv[]) { int ret; int opt; int fd; struct stat st; size_t size; uint8_t *buffer; uint8_t *buffer_iterator; struct actions_firmware_header header; struct actions_firmware_section section; unsigned int i; const char *path_prefix = NULL; while ((opt = getopt(argc, argv, "d:h")) != -1) { switch (opt) { case 'd': path_prefix = optarg; ret = mkdir(path_prefix, 0755); if (ret < 0) { fprintf(stderr, "Cannot create \"%s\", if it exists remove it first.\n", path_prefix); goto out; } break; case 'h': usage(argv[0]); ret = 0; goto out; default: /* '?' */ usage(argv[0]); ret = -EINVAL; goto out; } } if (argv[optind] == NULL) { usage(argv[0]); ret = -EINVAL; goto out; } fd = open(argv[optind], O_RDONLY); if (fd < 0) { perror("open"); ret = -EINVAL; goto out; } ret = fstat(fd, &st); if (ret < 0) { perror("fstat"); goto out_close_fd; } size = (size_t)st.st_size; buffer = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (buffer == NULL) { perror("mmap"); ret = -errno; goto out_close_fd; } if (size < ACTIONS_HEADER_DISK_SIZE) { fprintf(stderr, "File cannot contain an Actions firmware header.\n"); ret = -EINVAL; goto out_munmap; } /* iterate over a copy of the pointer */ buffer_iterator = buffer; ret = parse_header(&buffer_iterator, &header); if (ret < 0) { fprintf(stderr, "Cannot parse the header.\n"); goto out_munmap; } print_header(&header); i = 0; while (parse_section(&buffer_iterator, §ion) == 0) { print_section(§ion); i++; if (strncmp((char *)section.name, "FIRM", 16) == 0) { uint8_t *firm_buffer_iterator = buffer + section.start_address + FIRM_TABLE_OFFSET; struct actions_firmware_section firm_section; struct actions_firmware_section prev_section = { .start_address = section.start_address + FIRM_DATA_OFFSET, .length = 0, }; while (parse_section(&firm_buffer_iterator, &firm_section) == 0) { /* * unknown1 seems to be some form of checksum for * firm sections, if a sections have the same * checksum of the previous one they are not * duplicated but refer to the same memory * region, so do not increase the start * address. */ if (firm_section.unknown1 == prev_section.unknown1) { firm_section.start_address = prev_section.start_address; } else { firm_section.start_address = prev_section.start_address + prev_section.length; } printf("\t"); print_section(&firm_section); if (path_prefix) dump_section_data(path_prefix, "FIRM_", buffer, &firm_section); prev_section = firm_section; } } else if (strncmp((char *)section.name, "LINUX", 16) == 0) { continue; } if (path_prefix) dump_section_data(path_prefix, NULL, buffer, §ion); } printf("Found %d sections.\n", i); ret = 0; out_munmap: ret = munmap(buffer, size); if (ret < 0) perror("munmap"); out_close_fd: ret = close(fd); if (ret < 0) perror("close"); out: return ret; }