Initial import
[actions-micro-tools.git] / actions-firmware-extract.c
1 /* actions-firmware-extract - extract firmwares like the one for Acer K330
2  *
3  * Copyright (C) 2014  Antonio Ospite <ao2@ao2.it>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <stdio.h>
20 #include <stdint.h>
21 #include <stddef.h>
22 #include <string.h>
23 #include <endian.h>
24 #include <sys/mman.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <limits.h>
30
31 /*
32  * Based on cdt_parse.c from OpenEZX:
33  * http://cgit.openezx.org/moto-boot-usb/tree/src/cdt_parse.c
34  */
35
36 #define ACTIONS_HEADER_DISK_SIZE 64
37 struct actions_firmware_header {
38         uint8_t magic_string[16];
39         uint8_t version_string[16];
40         uint8_t checksum_string[16];
41         uint32_t base_address;
42         uint32_t length;
43         uint32_t unknown0;
44         uint32_t unknown1;
45 };
46
47 #define ACTIONS_SECTION_DISK_SIZE 32
48 struct actions_firmware_section {
49         uint8_t name[16];
50         uint32_t start_address;
51         uint32_t length;
52         uint32_t unknown0;
53         uint32_t unknown1;
54 };
55
56 #define FIRM_TABLE_OFFSET 0x200
57 #define FIRM_DATA_OFFSET 0x2000
58
59 static void get_string(uint8_t **srcp, uint8_t *dest, unsigned int len)
60 {
61         memcpy(dest, *srcp, len);
62         dest[len - 1] = '\0';
63         *srcp += len;
64 }
65
66 static inline uint16_t get_le16(uint8_t **bufferp)
67 {
68         uint16_t tmp;
69
70         memcpy(&tmp, *bufferp, sizeof (tmp));
71         *bufferp += sizeof (tmp);
72
73         return le16toh(tmp);
74 }
75
76 static inline uint32_t get_le32(uint8_t **bufferp)
77 {
78         uint32_t tmp;
79
80         memcpy(&tmp, *bufferp, sizeof (tmp));
81         *bufferp += sizeof (tmp);
82
83         return le32toh(tmp);
84 }
85
86 static inline uint64_t get_le64(uint8_t **bufferp)
87 {
88         uint64_t tmp;
89
90         memcpy(&tmp, *bufferp, sizeof (tmp));
91         *bufferp += sizeof (tmp);
92
93         return le64toh(tmp);
94 }
95
96 static int parse_header(uint8_t **bufferp, struct actions_firmware_header *header)
97 {
98         get_string(bufferp, header->magic_string, 16);
99
100         if (strncmp((char *)header->magic_string, "ActionsFirmware", 16) != 0) {
101                 fprintf(stderr, "Not an Actions firmware.\n");
102                 return -EINVAL;
103         }
104
105         get_string(bufferp, header->version_string, 16);
106         get_string(bufferp, header->checksum_string, 16);
107
108         header->base_address = get_le32(bufferp);
109         header->length = get_le32(bufferp);
110         header->unknown0 = get_le32(bufferp);
111         header->unknown1 = get_le32(bufferp);
112
113         return 0;
114 }
115
116 static int parse_section(uint8_t **bufferp, struct actions_firmware_section *section)
117 {
118         get_string(bufferp, section->name, 16);
119
120         if (section->name[0] == '\0')
121                 return -EINVAL;
122
123         section->start_address = get_le32(bufferp);
124         section->length = get_le32(bufferp);
125         section->unknown0 = get_le32(bufferp);
126         section->unknown1 = get_le32(bufferp);
127
128         return 0;
129 }
130
131 static void print_header(struct actions_firmware_header *header)
132 {
133         printf("Magic: %s\n"
134                "Version: %s\n"
135                "Checksum: %s\n"
136                "Base address: %d\n"
137                "Content length: %d\n"
138                "unknown0: 0x%08x (%u)\n"
139                "unknown1: 0x%08x (%u)\n"
140                "Complete file size: %d\n",
141                header->magic_string,
142                header->version_string,
143                header->checksum_string,
144                header->base_address,
145                header->length,
146                header->unknown0, header->unknown0,
147                header->unknown1, header->unknown1,
148                header->base_address + header->length);
149 }
150
151 static void print_section(struct actions_firmware_section *section)
152 {
153         printf("| %-16s | 0x%08x | 0x%08x | 0x%08x | 0x%08x |\n",
154                section->name,
155                section->start_address,
156                section->length,
157                section->unknown0,
158                section->unknown1);
159 }
160
161 static int dump_section_data(const char *path_prefix,
162                              const char *name_prefix,
163                              uint8_t *buffer,
164                              struct actions_firmware_section *section)
165 {
166         int fd;
167         int ret;
168         char path[PATH_MAX] = { 0 };
169         ssize_t written;
170
171         snprintf(path, PATH_MAX, "%s/%s%s",
172                  path_prefix, name_prefix ? name_prefix : "", (char *)section->name);
173
174         fd = creat(path, 0644);
175         if (fd < 0) {
176                 perror("creat");
177                 ret = -errno;
178                 goto out;
179         }
180
181         written = write(fd, buffer + section->start_address, section->length);
182         if (written < 0) {
183                 perror("write");
184                 ret = -errno;
185                 goto out_close_fd;
186         }
187
188         ret = 0;
189
190 out_close_fd:
191         close(fd);
192 out:
193         return ret;
194 }
195
196 static void usage(const char *name)
197 {
198         printf("usage: %s [OPTIONS] <fimrware.bin>\n", name);
199         printf("OPTIONS:\n");
200         printf("\t-d <dest_dir>\t\tdump the firmware sections into the <dest_dir> directory\n");
201         printf("\t-h \t\t\tthis help message\n");
202         printf("\nEXAMPLE OF USE:\n");
203         printf("\t%s -d PPX2230 ppx2230_eu_fus_aen.bin\n", name);
204 }
205
206 int main(int argc, char *argv[])
207 {
208         int ret;
209         int opt;
210
211         int fd;
212         struct stat st;
213         size_t size;
214         uint8_t *buffer;
215         uint8_t *buffer_iterator;
216         struct actions_firmware_header header;
217         struct actions_firmware_section section;
218         unsigned int i;
219         const char *path_prefix = NULL;
220
221         while ((opt = getopt(argc, argv, "d:h")) != -1) {
222                 switch (opt) {
223                 case 'd':
224                         path_prefix = optarg;
225                         ret = mkdir(path_prefix, 0755);
226                         if (ret < 0) {
227                                 fprintf(stderr, "Cannot create \"%s\", if it exists remove it first.\n", path_prefix);
228                                 goto out;
229                         }
230                         break;
231                 case 'h':
232                         usage(argv[0]);
233                         ret = 0;
234                         goto out;
235                 default: /* '?' */
236                         usage(argv[0]);
237                         ret = -EINVAL;
238                         goto out;
239                 }
240         }
241
242         if (argv[optind] == NULL) {
243                 usage(argv[0]);
244                 ret = -EINVAL;
245                 goto out;
246         }
247
248         fd = open(argv[optind], O_RDONLY);
249         if (fd < 0) {
250                 perror("open");
251                 ret = -EINVAL;
252                 goto out;
253         }
254
255         ret = fstat(fd, &st);
256         if (ret < 0) {
257                 perror("fstat");
258                 goto out_close_fd;
259         }
260         size = (size_t)st.st_size;
261
262         buffer = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
263         if (buffer == NULL) {
264                 perror("mmap");
265                 ret = -errno;
266                 goto out_close_fd;
267         }
268
269         if (size < ACTIONS_HEADER_DISK_SIZE) {
270                 fprintf(stderr, "File cannot contain an Actions firmware header.\n");
271                 ret = -EINVAL;
272                 goto out_munmap;
273         }
274
275         /* iterate over a copy of the pointer */
276         buffer_iterator = buffer;
277
278         ret = parse_header(&buffer_iterator, &header);
279         if (ret < 0) {
280                 fprintf(stderr, "Cannot parse the header.\n");
281                 goto out_munmap;
282         }
283
284         print_header(&header);
285
286         i = 0;
287         while (parse_section(&buffer_iterator, &section) == 0) {
288                 print_section(&section);
289                 i++;
290
291                 if (strncmp((char *)section.name, "FIRM", 16) == 0) {
292                         uint8_t *firm_buffer_iterator = buffer + section.start_address + FIRM_TABLE_OFFSET;
293                         struct actions_firmware_section firm_section;
294                         struct actions_firmware_section prev_section = {
295                                 .start_address = section.start_address + FIRM_DATA_OFFSET,
296                                 .length = 0,
297                         };
298                         while (parse_section(&firm_buffer_iterator, &firm_section) == 0) {
299                                 /*
300                                  * unknown1 seems to be some form of checksum for
301                                  * firm sections, if a sections have the same
302                                  * checksum of the previous one they are not
303                                  * duplicated but refer to the same memory
304                                  * region, so do not increase the start
305                                  * address.
306                                  */
307                                 if (firm_section.unknown1 == prev_section.unknown1) {
308                                         firm_section.start_address = prev_section.start_address;
309                                 } else {
310                                         firm_section.start_address = prev_section.start_address + prev_section.length;
311                                 }
312
313                                 printf("\t");
314                                 print_section(&firm_section);
315
316                                 if (path_prefix)
317                                         dump_section_data(path_prefix,
318                                                           "FIRM_",
319                                                           buffer,
320                                                           &firm_section);
321
322                                 prev_section = firm_section;
323                         }
324                 } else if (strncmp((char *)section.name, "LINUX", 16) == 0) {
325                         continue;
326                 }
327
328                 if (path_prefix)
329                         dump_section_data(path_prefix, NULL, buffer, &section);
330
331         }
332         printf("Found %d sections.\n", i);
333
334         ret = 0;
335
336 out_munmap:
337         ret = munmap(buffer, size);
338         if (ret < 0)
339                 perror("munmap");
340
341 out_close_fd:
342         ret = close(fd);
343         if (ret < 0)
344                 perror("close");
345
346 out:
347         return ret;
348 }