+/* actions-firmware-extract - extract firmwares like the one for Acer K330
+ *
+ * Copyright (C) 2014 Antonio Ospite <ao2@ao2.it>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <endian.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+/*
+ * 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] <fimrware.bin>\n", name);
+ printf("OPTIONS:\n");
+ printf("\t-d <dest_dir>\t\tdump the firmware sections into the <dest_dir> 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;
+}