blob: d3f03bc18ab660a72f4346dc3a14ca7a9cfc70e3 [file] [log] [blame]
Dave Chapman1aa6cde2008-05-11 18:29:53 +00001/*
Rafaël Carré03f88d52009-10-27 11:19:49 +00002 * Copyright © 2008 Rafaël Carré <rafael.carre@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
17 *
18 */
Dave Chapman1aa6cde2008-05-11 18:29:53 +000019
Rafaël Carré03f88d52009-10-27 11:19:49 +000020#define _ISOC99_SOURCE /* snprintf() */
Dave Chapman1aa6cde2008-05-11 18:29:53 +000021#include <stdio.h>
Dave Chapman1aa6cde2008-05-11 18:29:53 +000022#include <sys/types.h>
23#include <sys/stat.h>
24#include <fcntl.h>
Rafaël Carré03f88d52009-10-27 11:19:49 +000025#include <errno.h>
Dave Chapman1aa6cde2008-05-11 18:29:53 +000026#include <unistd.h>
Rafaël Carré03f88d52009-10-27 11:19:49 +000027#include <stdlib.h>
28#include <inttypes.h>
29#include <string.h>
Dave Chapman1aa6cde2008-05-11 18:29:53 +000030
Rafaël Carré03f88d52009-10-27 11:19:49 +000031#if 1 /* ANSI colors */
Dave Chapman1aa6cde2008-05-11 18:29:53 +000032
Rafaël Carré03f88d52009-10-27 11:19:49 +000033# define color(a) printf("%s",a)
34char OFF[] = { 0x1b, 0x5b, 0x31, 0x3b, '0', '0', 0x6d, '\0' };
35
36char GREY[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '0', 0x6d, '\0' };
37char RED[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '1', 0x6d, '\0' };
38char GREEN[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '2', 0x6d, '\0' };
39char YELLOW[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '3', 0x6d, '\0' };
40char BLUE[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '4', 0x6d, '\0' };
41
42#else
43 /* disable colors */
44# define color(a)
Dave Chapman1aa6cde2008-05-11 18:29:53 +000045#endif
46
Rafaël Carré03f88d52009-10-27 11:19:49 +000047#define LIB_OFFSET 160 /* FIXME (see below) */
48/* The alignement of library blocks (in number of 0x200 bytes blocks)
49 * alignement - md5sum - filename - model
50 * 120 : fc9dd6116001b3e6a150b898f1b091f0 m200p-4.1.08A.bin M200
51 * 128 : 82e3194310d1514e3bbcd06e84c4add3 m200p.bin Fuze
52 * 160 : c12711342169c66e209540cd1f27cd26 m300f.bin CLIP
53 *
54 * Note : the size of library blocks is variable:
55 *
56 * For m200p-4.1.08A.bin it's always 0x1e000 blocks = 240 * 0x200
57 *
58 * For m200p.bin it can be 0x20000 (256*0x200) or 0x40000 (512*0x200)
59 * (for "acp_decoder" and "sd_reload__" blocks)
60 *
61 * For m300f.bin it can be 0x28000 (320*0x200) or 0x14000 (160 * 0x200)
62 *
63 */
Dave Chapman1aa6cde2008-05-11 18:29:53 +000064
Rafaël Carré03f88d52009-10-27 11:19:49 +000065#define bug(...) do { fprintf(stderr,"ERROR: "__VA_ARGS__); exit(1); } while(0)
66#define bugp(a) do { perror("ERROR: "a); exit(1); } while(0)
67
68/* byte swapping */
69#define get32le(a) ((uint32_t) \
70 ( buf[a+3] << 24 | buf[a+2] << 16 | buf[a+1] << 8 | buf[a] ))
71#define get16le(a) ((uint16_t)( buf[a+1] << 8 | buf[a] ))
72
73/* all blocks are sized as a multiple of 0x1ff */
74#define PAD_TO_BOUNDARY(x) (((x) + 0x1ff) & ~0x1ff)
75
76/* If you find a firmware that breaks the known format ^^ */
77#define assert(a) do { if(!(a)) { fprintf(stderr,"Assertion \"%s\" failed in %s() line %d!\n\nPlease send us your firmware!\n",#a,__func__,__LINE__); exit(1); } } while(0)
78
79/* globals */
80
81size_t sz; /* file size */
82uint8_t *buf; /* file content */
Dave Chapman1aa6cde2008-05-11 18:29:53 +000083
84
Rafaël Carré03f88d52009-10-27 11:19:49 +000085/* 1st block description */
86uint32_t idx,checksum,bs_multiplier,firmware_sz;
87uint32_t unknown_4_1; uint8_t unknown_1,id; uint16_t unknown_2;
88uint32_t unknown_4_2,unknown_4_3;
Dave Chapman1aa6cde2008-05-11 18:29:53 +000089
Rafaël Carré03f88d52009-10-27 11:19:49 +000090static void *xmalloc(size_t s) /* malloc helper */
91{
92 void * r = malloc(s);
93 if(!r) bugp("malloc");
94 return r;
Dave Chapman1aa6cde2008-05-11 18:29:53 +000095}
96
Rafaël Carré03f88d52009-10-27 11:19:49 +000097/* known models */
98static const char * model(uint8_t id)
Dave Chapman1aa6cde2008-05-11 18:29:53 +000099{
Rafaël Carré03f88d52009-10-27 11:19:49 +0000100 switch(id)
101 {
102 case 0x1E: return "FUZE"; break;
103 case 0x22: return "CLIP"; break;
104 case 0x23: return "C200"; break;
105 case 0x24: return "E200"; break;
106 case 0x25: return "M200"; break;
107 case 0x27: return "CLV2"; break;
Rafaël Carréc2dae872010-01-13 02:25:58 +0000108 case 0x28: return "CLI+"; break;
Rafaël Carré03f88d52009-10-27 11:19:49 +0000109 case 0x70:
110 case 0x6d: return "FUZ2"; break;
111 default:
112 printf("Unknown ID 0x%x\n", id);
113
Rafaël Carréc2dae872010-01-13 02:25:58 +0000114 assert(id == 0x1E || (id >= 0x22 && id <= 0x28));
Rafaël Carré03f88d52009-10-27 11:19:49 +0000115 return "UNKNOWN!";
116 }
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000117}
118
Rafaël Carré03f88d52009-10-27 11:19:49 +0000119/* checksums the firmware (the firmware header contains the verification) */
120static uint32_t do_checksum(void)
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000121{
Rafaël Carré03f88d52009-10-27 11:19:49 +0000122 uint32_t c = 0;
123
124 size_t i = 0x400/4;
125 while(i<(0x400+firmware_sz)/4)
126 c += ((uint32_t*)buf)[i++];
127
128 return c;
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000129}
130
Rafaël Carré03f88d52009-10-27 11:19:49 +0000131/* verify the firmware header */
132static void check(void)
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000133{
Rafaël Carré03f88d52009-10-27 11:19:49 +0000134 uint32_t checksum2;
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000135
Rafaël Carré03f88d52009-10-27 11:19:49 +0000136 assert(sz >= 0x400 && sz % 0x200 == 0);
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000137
Rafaël Carré03f88d52009-10-27 11:19:49 +0000138 size_t i;
139 checksum2 = 0;
140 for(i=0;i<sz/4-1;i++)
141 checksum2 += ((uint32_t*)buf)[i];
142
143 uint32_t last_word = get32le(sz - 4);
144
145 switch(last_word)
146 {
147 case 0: /* no whole file checksum */
148 break;
149 case 0xefbeadde: /* no whole file checksum */
150 break;
151 default: /* verify whole file checksum */
152 assert(last_word == checksum2);
153 }
154
155 idx = get32le(0);
156 unsigned int shift = (get32le(4) == 0x0000f000) ? 4 : 0;
157 checksum = get32le(4 + shift);
158 bs_multiplier = get32le(8 + shift);
159 firmware_sz = get32le(0xc + shift);
160 assert(bs_multiplier << 9 == PAD_TO_BOUNDARY(firmware_sz)); /* 0x200 * bs_multiplier */
161
162 unknown_4_1 = get32le(0x10 + shift);
163 unknown_1 = buf[0x14 + shift];
164 id = buf[0x15 + shift];
165 unknown_2 = get16le(0x16 + shift);
166 unknown_4_2 = get32le(0x18 + shift);
167 unknown_4_3 = get32le(0x1c + shift);
168
169 color(GREEN);
170 printf("4 Index %d\n",idx);
171 assert(idx == 0);
172 color(GREEN);
173 printf("4 Firmware Checksum %x",checksum);
174 checksum2=do_checksum();
175 color(GREEN);
176 printf(" (%x)\n",checksum2);
177 assert(checksum == checksum2);
178 color(GREEN);
179 printf("4 Block Size Multiplier %x\n",bs_multiplier);
180 color(GREEN);
181 printf("4 Firmware block size %x (%d)\n",firmware_sz,firmware_sz);
182
183 color(GREEN);
184 printf("4 Unknown (should be 3) %x\n",unknown_4_1);
185 assert(unknown_4_1 == 3);
186
187 /* variable */
188 color(GREEN);
189 printf("1 Unknown %x\n",unknown_1);
190
191 color(GREEN);
192 printf("1 Model ID %x (%s)\n",id,model(id));
193
194 color(GREEN);
195 printf("2 Unknown (should be 0) %x\n",unknown_2);
196 assert(unknown_2 == 0);
197
198 color(GREEN);
199 printf("4 Unknown (should be 40) %x\n",unknown_4_2);
200 assert(unknown_4_2 == 0x40 );
201
202 color(GREEN);
203 printf("4 Unknown (should be 1) %x\n",unknown_4_3);
204 assert(unknown_4_3 == 1);
205
206 /* rest of the block is padded with 0xff */
207 for(i=0x20 + shift;i<0x200 - shift;i++)
208 assert(buf[i]==0xff /* normal case */ ||
209 ((id==0x1e||id==0x24) && ( /* Fuze or E200 */
210 (i>=0x3c && i<=0x3f && get32le(0x3c)==0x00005000)
211 )));
212
213 /* the 2nd block is identical, except that the 1st byte has been incremented */
214 assert(buf[0x0]==0&&buf[0x200]==1);
215 assert(!memcmp(&buf[1],&buf[0x201],0x1FF - shift));
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000216}
217
Rafaël Carré03f88d52009-10-27 11:19:49 +0000218typedef enum
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000219{
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000220#if 0
Rafaël Carré03f88d52009-10-27 11:19:49 +0000221 FW_HEADER,
222 FW,
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000223#endif
Rafaël Carré03f88d52009-10-27 11:19:49 +0000224 LIB,
225 PAD,
226 HEADER,
227 UNKNOWN
228} type;
229
230static unsigned int n_libs = 0, n_pads_ff = 0, n_pads_deadbeef = 0, n_unkn = 0, n_headers = 0;
231
232static void show_lib(size_t off)
233{
234 /* first word: char* */
235 uint32_t start = get32le(off+4);
236 uint32_t stop = get32le(off+8);
237
238 uint32_t size = get32le(off+0xc);
239
240#if 0 /* library block hacking */
241 /* assert(stop > start); */
242
243 /* assert(stop - start == size); */
244
245 if(stop - start != size)
246 {
247 color(RED);
248 printf("STOP - START != SIZE || 0x%.8x - 0x%.8x == 0x%.8x != 0x%.8x\n",
249 stop, start, stop - start, size);
250 }
251
252 color(RED);
253 printf("0x%.8x -> 0x%.8x SIZE 0x%.6x\n", start, stop, size);
254
255 uint32_t first = get32le(off+0x10); /* ? */
256 printf("? = 0x%.8x , ",first);
257#endif
258
259 uint32_t funcs = get32le(off+0x14); /* nmbr of functions */
260 color(YELLOW);
261 printf("\t%d funcs",funcs);
262
263 unsigned int i;
264 for(i=0;i<funcs;i++)
265 {
266 uint32_t fptr = get32le(off+0x18+i*4);
267 if(!fptr)
268 {
269 assert(funcs==1); /* if 1 function is exported, it's empty */
270 }
271 else
272 {
273 assert(fptr - start < 0x0000ffff);
274 /* printf("0x%.4x ",fptr); */
275 }
276 }
277
278 color(BLUE);
279 printf("\tBASE 0x%.8x (code + 0x%x) END 0x%.8x : SIZE 0x%.8x\n",start, 0x18 + i*4, stop, stop - start);
280
281 char name[12+1];
282 memcpy(name,&buf[off+get32le(off)],12);
283 name[12] = '\0';
284
285 FILE *out = fopen(name,"w");
286 if(!out)
287 bug("library block");
288
289 if(fwrite(&buf[off],size,1,out)!=1)
290 bug();
291
292 fclose(out);
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000293}
294
Rafaël Carré03f88d52009-10-27 11:19:49 +0000295static int unknown = 0;
296static int padding = 0;
297static void print_block(size_t off, type t)
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000298{
Rafaël Carré03f88d52009-10-27 11:19:49 +0000299 /* reset counters if needed */
300 if(t != UNKNOWN && unknown)
301 { /* print only the number of following blocks */
302 color(GREY);
303 printf("%d unknown blocks (0x%.6x bytes)\n",unknown,unknown*0x200);
304 unknown = 0;
305 }
306 else if(t != PAD && padding)
307 { /* same */
308 color(GREY);
309 printf("%d padding blocks (0x%.6x bytes)\n",padding,padding*0x200);
310 padding = 0;
311 }
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000312
Rafaël Carré03f88d52009-10-27 11:19:49 +0000313 if(t != UNKNOWN && t != PAD) /* for other block types, always print the offset */
314 {
315 color(GREEN);
316 printf("0x%.6x\t", (unsigned int)off);
317 color(OFF);
318 }
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000319
Rafaël Carré03f88d52009-10-27 11:19:49 +0000320 switch(t)
321 {
322 size_t s;
323 FILE *f;
324 char filename[8+4]; /* unknown\0 , 10K max */
325#if 0
326 case FW_HEADER:
327 printf("firmware header 0x%x\n",off);
328 break;
329 case FW:
330 printf("firmware block 0x%x\n",off);
331 break;
332#endif
333 case LIB:
334 s = LIB_OFFSET * 0x200;
335 while(s < get32le(off+12))
336 s <<= 1;
337 color(RED);
338 printf("library block 0x%.6x\t->\t0x%.6x\t\"%s\"\n",
339 (unsigned int)s, (unsigned int)(off+s),
340 &buf[off+get32le(off)]);
341 show_lib(off);
342 n_libs++;
343 break;
344 case PAD:
345 if(buf[off] == 0xff)
346 n_pads_ff++;
347 else
348 n_pads_deadbeef++;
349 padding++;
350 break;
351 case UNKNOWN:
352 unknown++;
353 n_unkn++;
354#if 0 /* do not dump unknown blocks */
355 snprintf(filename, sizeof(filename), "unknown%d", n_unkn);
356 f = fopen(filename, "w");
357 if(f)
358 {
359 if( fwrite(buf+off, 0x200, 1, f) != 1 )
360 bugp("unknown block");
361 fclose(f);
362 }
363 else
364 bugp("unknown block");
365#endif
366 break;
367 case HEADER:
368 color(YELLOW);
369 printf("header block 0x%.6x\t->\t0x%.6x\n",
370 PAD_TO_BOUNDARY(get32le(off)),
371 (unsigned int)PAD_TO_BOUNDARY(off+get32le(off)));
372 snprintf(filename, sizeof(filename), "header%d", n_headers++);
373 f = fopen(filename,"w");
374 if(!f)
375 bug("header");
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000376
Rafaël Carré03f88d52009-10-27 11:19:49 +0000377 if(fwrite(&buf[off],get32le(off),1,f)!=1)
378 bug();
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000379
Rafaël Carré03f88d52009-10-27 11:19:49 +0000380 fclose(f);
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000381
Rafaël Carré03f88d52009-10-27 11:19:49 +0000382 break;
383 default:
384 abort();
385 }
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000386
Rafaël Carré03f88d52009-10-27 11:19:49 +0000387 if(t != PAD && t != UNKNOWN)
388 printf("\n");
389}
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000390
Rafaël Carré03f88d52009-10-27 11:19:49 +0000391static size_t verify_block(size_t off)
392{
393 assert(!(off%0x200));
394 assert(off+0x200 < sz);
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000395
Rafaël Carré03f88d52009-10-27 11:19:49 +0000396 size_t s = 0x200;
397 type t = UNKNOWN;
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000398
Rafaël Carré03f88d52009-10-27 11:19:49 +0000399 size_t offset_str = get32le(off);
400 if(get32le(off) == 0xefbeadde )
401 {
402#if 0 /* some blocks begin with 0xdeadbeef but aren't padded with that value */
403 unsigned int i;
404 for(i=0;i<s;i+=4)
405 assert(get32le(off+i) == 0xefbeadde);
406#endif
407 t = PAD;
408 }
409 else if( *(uint32_t*)(&buf[off]) == 0xffffffff)
410 {
411 unsigned int i;
412 for(i=0;i<s;i++)
413 assert(buf[off+i] == 0xff);
414 t = PAD;
415 }
416 else if(off+offset_str+12<sz) /* XXX: we should check that the address at which
417 * the string is located is included in this
418 * library block's size, but we only know the
419 * block's size after we confirmed that this is
420 * a library block (by looking at the 11 chars
421 * ASCII string). */
422 {
423 short int ok = 1;
424 unsigned int i;
425 for(i=0;i<11;i++)
426 if(buf[off+offset_str+i] >> 7 || !buf[off+offset_str+i])
427 ok = 0;
428 if(buf[off+offset_str+11])
429 ok = 0;
430 if(ok) /* library block */
431 {
432 t = LIB;
433 s = LIB_OFFSET * 0x200;
434 while(s < get32le(off+12)) /* of course the minimum is the size
435 * specified in the block header */
436 s <<= 1;
437 }
438 else
439 t = UNKNOWN;
440 }
441 else
442 t = UNKNOWN;
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000443
Rafaël Carré03f88d52009-10-27 11:19:49 +0000444 if(t==UNKNOWN)
445 {
446 if(!strncmp((char*)buf+off+8,"HEADER",6))
447 {
448 s = PAD_TO_BOUNDARY(get32le(off)); /* first 4 bytes le are the block size */
449 t = HEADER;
450 }
451 }
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000452
Rafaël Carré03f88d52009-10-27 11:19:49 +0000453 print_block(off,t);
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000454
Rafaël Carré03f88d52009-10-27 11:19:49 +0000455 return PAD_TO_BOUNDARY(s);
456}
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000457
Rafaël Carré03f88d52009-10-27 11:19:49 +0000458static void extract(void)
459{
460 FILE *out = fopen("firmware","w");
461 if(!out)
462 bug("firmware");
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000463
Rafaël Carré03f88d52009-10-27 11:19:49 +0000464 if(fwrite(&buf[0x400],firmware_sz,1,out)!=1)
465 bug("firmare writing");
466 fclose(out);
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000467
Rafaël Carré03f88d52009-10-27 11:19:49 +0000468 off_t off = PAD_TO_BOUNDARY(0x400 + firmware_sz);
469 unsigned int n = 0;
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000470
Rafaël Carré03f88d52009-10-27 11:19:49 +0000471 printf("\n");
472 color(RED);
473 printf("Extracting\n\n");
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000474
Rafaël Carré03f88d52009-10-27 11:19:49 +0000475 while((unsigned int)(off+0x200)<sz)
476 {
477 /* look at the next 0x200 bytes if we can recognize a block type */
478 off += verify_block(off); /* then skip its real size */
479 n++; /* and look at the next block ;) */
480 }
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000481
Rafaël Carré03f88d52009-10-27 11:19:49 +0000482 /* statistics */
483 printf("\n");
484 color(RED);
485 printf("TOTAL\t%d\tblocks (%d unknown)\n",n,n_unkn);
486 color(BLUE);
487 printf("\t%d\tlibs\n",n_libs);
488 color(GREY);
489 printf("\t%d\tpads ff\n",n_pads_ff);
490 color(GREY);
491 printf("\t%d\tpads deadbeef\n",n_pads_deadbeef);
492 color(GREEN);
493 printf("\t%d\theaders\n",n_headers);
494}
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000495
Rafaël Carré03f88d52009-10-27 11:19:49 +0000496int main(int argc, const char **argv)
497{
498 int fd;
499 struct stat st;
500 if(argc != 2)
501 bug("Usage: %s <firmware>\n",*argv);
502
503 if( (fd = open(argv[1],O_RDONLY)) == -1 )
504 bugp("opening firmware failed");
505
506 if(fstat(fd,&st) == -1)
507 bugp("firmware stat() failed");
508 sz = st.st_size;
509
510 buf=xmalloc(sz);
511 if(read(fd,buf,sz)!=(ssize_t)sz) /* load the whole file into memory */
512 bugp("reading firmware");
513
514 close(fd);
515
516 check(); /* verify header and checksums */
517 extract(); /* split in blocks */
518
519 free(buf);
520 return 0;
Dave Chapman1aa6cde2008-05-11 18:29:53 +0000521}