From: Antonio Ospite <ao2@ao2.it>
Date: Sun, 1 Jun 2014 20:13:48 +0000 (+0200)
Subject: Initial import
X-Git-Url: https://git.ao2.it/actions-micro-tools.git/commitdiff_plain/HEAD

Initial import
---

4d979c7828da8d8d8c8760a4b0731c0a599f165f
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d891fd7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+actions-firmware-extract.o
+actions-firmware-extract
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f2e65c8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,54 @@
+CFLAGS ?= -std=c99 -pedantic -Wall -Wextra -O2
+
+CFLAGS += -pedantic-errors
+
+# GCC >= 4.6
+CFLAGS += -Wunused-but-set-variable
+
+CFLAGS += -fno-common \
+  -Wall \
+  -Wextra \
+  -Wformat=2 \
+  -Winit-self \
+  -Winline \
+  -Wpacked \
+  -Wp,-D_FORTIFY_SOURCE=2 \
+  -Wpointer-arith \
+  -Wlarger-than-65500 \
+  -Wmissing-declarations \
+  -Wmissing-format-attribute \
+  -Wmissing-noreturn \
+  -Wmissing-prototypes \
+  -Wnested-externs \
+  -Wold-style-definition \
+  -Wredundant-decls \
+  -Wsign-compare \
+  -Wstrict-aliasing=2 \
+  -Wstrict-prototypes \
+  -Wswitch-enum \
+  -Wundef \
+  -Wunreachable-code \
+  -Wunsafe-loop-optimizations \
+  -Wwrite-strings
+
+# for some functions in endian.h
+CFLAGS += -D_BSD_SOURCE
+
+# for getopt()
+CFLAGS += -D_POSIX_C_SOURCE=2
+
+PREFIX ?= /usr/local
+bindir := $(PREFIX)/sbin
+
+actions-firmware-extract: actions-firmware-extract.o
+
+test: actions-firmware-extract
+	rm -rf PPX2230 && \
+	./actions-firmware-extract -d PPX2230 ppx2230_eu_fus_aen.bin
+
+install: actions-firmware-extract
+	install -d $(DESTDIR)$(bindir)
+	install -m 755 actions-firmware-extract $(DESTDIR)$(bindir)
+
+clean:
+	rm -rf *~ *.o actions-firmware-extract endian endian.h
diff --git a/actions-firmware-extract.c b/actions-firmware-extract.c
new file mode 100644
index 0000000..2deeb8f
--- /dev/null
+++ b/actions-firmware-extract.c
@@ -0,0 +1,348 @@
+/* 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, &section) == 0) {
+		print_section(&section);
+		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, &section);
+
+	}
+	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;
+}