1 // This file is part of Deark.
2 // Copyright (C) 2017 Jason Summers
3 // See the file COPYING for terms of use.
8 #include <deark-config.h>
9 #include <deark-private.h>
10 DE_DECLARE_MODULE(de_module_palmdb
);
11 DE_DECLARE_MODULE(de_module_palmrc
);
13 #define CODE_Tbmp 0x54626d70U
14 #define CODE_View 0x56696577U
15 #define CODE_appl 0x6170706cU
16 #define CODE_clpr 0x636c7072U
17 #define CODE_lnch 0x6c6e6368U
18 #define CODE_pqa 0x70716120U
19 #define CODE_tAIB 0x74414942U
20 #define CODE_tAIN 0x7441494eU
21 #define CODE_tAIS 0x74414953U
22 #define CODE_tSTR 0x74535452U
23 #define CODE_tver 0x74766572U
24 #define CODE_vIMG 0x76494d47U
26 struct rec_data_struct
{
30 struct rec_list_struct
{
32 // The rec_data items are in the order they appear in the file
33 struct rec_data_struct
*rec_data
;
34 // A list of all the rec_data indices, in the order we should read them
35 size_t *order_to_read
;
39 struct rsrc_type_info_struct
{
41 u32 flags
; // 1=standard Palm resource
43 void* /* rsrc_decoder_fn */ decoder_fn
;
51 unsigned int createflags
;
54 typedef struct localctx_struct
{
60 #define SUBFMT_IMAGEVIEWER 2
63 #define TIMESTAMPFMT_UNKNOWN 0
64 #define TIMESTAMPFMT_MACBE 1
65 #define TIMESTAMPFMT_UNIXBE 2
66 #define TIMESTAMPFMT_MACLE 3
70 const char *fmt_shortname
;
71 i64 rec_size
; // bytes per record
72 struct de_fourcc dtype4cc
;
73 struct de_fourcc creator4cc
;
74 struct de_timestamp mod_time
;
77 struct rec_list_struct rec_list
;
78 de_ucstring
*icon_name
;
81 static void handle_palm_timestamp(deark
*c
, lctx
*d
, i64 pos
, const char *name
,
82 struct de_timestamp
*returned_ts
)
84 struct de_timestamp ts
;
85 char timestamp_buf
[64];
88 de_zeromem(&ts
, sizeof(struct de_timestamp
));
90 de_zeromem(returned_ts
, sizeof(struct de_timestamp
));
93 ts_int
= de_getu32be(pos
);
95 de_dbg(c
, "%s: 0 (not set)", name
);
99 de_dbg(c
, "%s: ...", name
);
102 // I've seen three different ways to interpret this 32-bit timestamp, and
103 // I don't know how to guess the correct one.
105 if(d
->timestampfmt
==TIMESTAMPFMT_MACBE
|| d
->timestampfmt
==TIMESTAMPFMT_UNKNOWN
) {
106 de_mac_time_to_timestamp(ts_int
, &ts
);
107 de_timestamp_to_string(&ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
108 de_dbg(c
, "... if Mac-BE: %"I64_FMT
" (%s)", ts_int
, timestamp_buf
);
111 ts_int
= de_geti32be(pos
);
112 if(d
->timestampfmt
==TIMESTAMPFMT_UNIXBE
||
113 (d
->timestampfmt
==TIMESTAMPFMT_UNKNOWN
&& ts_int
>0)) // Assume dates before 1970 are wrong
115 de_unix_time_to_timestamp(ts_int
, &ts
, 0x1);
116 de_timestamp_to_string(&ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
117 de_dbg(c
, "... if Unix-BE: %"I64_FMT
" (%s)", ts_int
, timestamp_buf
);
120 ts_int
= de_getu32le(pos
);
121 if(d
->timestampfmt
==TIMESTAMPFMT_MACLE
||
122 (d
->timestampfmt
==TIMESTAMPFMT_UNKNOWN
&& ts_int
>2082844800))
124 de_mac_time_to_timestamp(ts_int
, &ts
);
125 de_timestamp_to_string(&ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
126 de_dbg(c
, "... if Mac-LE: %"I64_FMT
" (%s)", ts_int
, timestamp_buf
);
129 de_dbg_indent(c
, -1);
132 if(returned_ts
&& d
->timestampfmt
!=TIMESTAMPFMT_UNKNOWN
) {
137 static void get_db_attr_descr(de_ucstring
*s
, u32 attribs
)
140 struct { u32 a
; const char *n
; } flags_arr
[] = {
141 {0x0001, "dmHdrAttrResDB"},
142 {0x0002, "dmHdrAttrReadOnly"},
143 {0x0004, "dmHdrAttrAppInfoDirty"},
144 {0x0008, "dmHdrAttrBackup"},
145 {0x0010, "dmHdrAttrOKToInstallNewer"},
146 {0x0020, "dmHdrAttrResetAfterInstall"},
147 {0x0040, "dmHdrAttrCopyPrevention"},
148 {0x0080, "dmHdrAttrStream"},
149 {0x0100, "dmHdrAttrHidden"},
150 {0x0200, "dmHdrAttrLaunchableData"},
151 {0x0400, "dmHdrAttrRecyclable"},
152 {0x0800, "dmHdrAttrBundle"},
153 {0x8000, "dmHdrAttrOpen"}
155 for(i
=0; i
<DE_ARRAYCOUNT(flags_arr
); i
++) {
156 if(attribs
& flags_arr
[i
].a
)
157 ucstring_append_flags_item(s
, flags_arr
[i
].n
);
160 if(attribs
==0) ucstring_append_flags_item(s
, "none");
163 static int do_read_pdb_prc_header(deark
*c
, lctx
*d
)
166 de_ucstring
*dname
= NULL
;
167 de_ucstring
*attr_descr
= NULL
;
173 de_dbg(c
, "header at %d", (int)pos1
);
176 dname
= ucstring_create(c
);
177 dbuf_read_to_ucstring(c
->infile
, pos1
, 32, dname
, DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
178 de_dbg(c
, "name: \"%s\"", ucstring_getpsz(dname
));
180 attribs
= (u32
)de_getu16be(pos1
+32);
181 attr_descr
= ucstring_create(c
);
182 get_db_attr_descr(attr_descr
, attribs
);
183 de_dbg(c
, "attributes: 0x%04x (%s)", (unsigned int)attribs
,
184 ucstring_getpsz(attr_descr
));
186 version
= (u32
)de_getu16be(pos1
+34);
187 de_dbg(c
, "version: 0x%04x", (unsigned int)version
);
189 handle_palm_timestamp(c
, d
, pos1
+36, "create date", NULL
);
190 handle_palm_timestamp(c
, d
, pos1
+40, "mod date", &d
->mod_time
);
191 handle_palm_timestamp(c
, d
, pos1
+44, "backup date", NULL
);
193 x
= de_getu32be(pos1
+48);
194 de_dbg(c
, "mod number: %d", (int)x
);
195 d
->appinfo_offs
= de_getu32be(pos1
+52);
196 de_dbg(c
, "app info pos: %d", (int)d
->appinfo_offs
);
197 d
->sortinfo_offs
= de_getu32be(pos1
+56);
198 de_dbg(c
, "sort info pos: %d", (int)d
->sortinfo_offs
);
200 dbuf_read_fourcc(c
->infile
, pos1
+60, &d
->dtype4cc
, 4, 0x0);
201 de_dbg(c
, "type: \"%s\"", d
->dtype4cc
.id_dbgstr
);
203 dbuf_read_fourcc(c
->infile
, pos1
+64, &d
->creator4cc
, 4, 0x0);
204 de_dbg(c
, "creator: \"%s\"", d
->creator4cc
.id_dbgstr
);
206 if(d
->file_fmt
==FMT_PDB
) {
207 d
->fmt_shortname
= "PDB";
208 if(d
->dtype4cc
.id
==CODE_pqa
&& d
->creator4cc
.id
==CODE_clpr
) {
209 d
->file_subfmt
= SUBFMT_PQA
;
210 de_declare_fmt(c
, "Palm PQA");
212 else if(d
->dtype4cc
.id
==CODE_vIMG
&& d
->creator4cc
.id
==CODE_View
) {
213 d
->file_subfmt
= SUBFMT_IMAGEVIEWER
;
214 de_declare_fmt(c
, "Palm Database ImageViewer");
217 de_declare_fmt(c
, "Palm PDB");
220 else if(d
->file_fmt
==FMT_PRC
) {
221 d
->fmt_shortname
= "PRC";
228 de_dbg(c
, "uniqueIDseed: %u", (unsigned int)x
);
232 de_dbg_indent(c
, -1);
233 ucstring_destroy(dname
);
234 ucstring_destroy(attr_descr
);
238 static i64
calc_rec_len(deark
*c
, lctx
*d
, i64 rec_idx
)
241 if(rec_idx
+1 < d
->rec_list
.num_recs
) {
242 len
= (i64
)(d
->rec_list
.rec_data
[rec_idx
+1].offset
- d
->rec_list
.rec_data
[rec_idx
].offset
);
245 len
= c
->infile
->len
- (i64
)d
->rec_list
.rec_data
[rec_idx
].offset
;
250 // ext_ucstring will be used if ext_sz is NULL
251 static void extract_item(deark
*c
, lctx
*d
, i64 data_offs
, i64 data_len
,
252 const char *ext_sz
, de_ucstring
*ext_ucstring
,
253 unsigned int createflags
, int always_extract
)
257 if(c
->extract_level
<2 && !always_extract
) goto done
;
258 if(data_offs
<0 || data_len
<0) goto done
;
259 if(data_offs
+data_len
> c
->infile
->len
) goto done
;
260 fi
= de_finfo_create(c
);
262 de_finfo_set_name_from_sz(c
, fi
, ext_sz
, 0, DE_ENCODING_ASCII
);
264 else if(ext_ucstring
) {
265 de_finfo_set_name_from_ucstring(c
, fi
, ext_ucstring
, 0);
267 dbuf_create_file_from_slice(c
->infile
, data_offs
, data_len
, NULL
, fi
, createflags
);
269 de_finfo_destroy(c
, fi
);
272 static int do_decompress_imgview_image(deark
*c
, lctx
*d
, dbuf
*inf
,
273 i64 pos1
, i64 len
, dbuf
*unc_pixels
)
279 while(pos
< pos1
+len
) {
280 b1
= dbuf_getbyte(inf
, pos
++);
283 b2
= dbuf_getbyte(inf
, pos
++);
284 dbuf_write_run(unc_pixels
, b2
, count
);
288 dbuf_copy(inf
, pos
, count
, unc_pixels
);
295 static void do_generate_unc_image(deark
*c
, lctx
*d
, dbuf
*unc_pixels
,
296 struct img_gen_info
*igi
)
301 de_bitmap
*img
= NULL
;
303 if(igi
->bitsperpixel
==1) {
304 de_convert_and_write_image_bilevel(unc_pixels
, 0, igi
->w
, igi
->h
, igi
->rowbytes
,
305 DE_CVTF_WHITEISZERO
, igi
->fi
, igi
->createflags
);
309 img
= de_bitmap_create(c
, igi
->w
, igi
->h
, 1);
311 for(j
=0; j
<igi
->h
; j
++) {
312 for(i
=0; i
<igi
->w
; i
++) {
313 b
= de_get_bits_symbol(unc_pixels
, igi
->bitsperpixel
, igi
->rowbytes
*j
, i
);
314 b_adj
= 255 - de_sample_nbit_to_8bit(igi
->bitsperpixel
, (unsigned int)b
);
315 de_bitmap_setpixel_gray(img
, i
, j
, b_adj
);
319 de_bitmap_write_to_file_finfo(img
, igi
->fi
, igi
->createflags
);
322 de_bitmap_destroy(img
);
325 // A wrapper that decompresses the image if necessary, then calls do_generate_unc_image().
326 static void do_generate_image(deark
*c
, lctx
*d
,
327 dbuf
*inf
, i64 pos
, i64 len
, unsigned int cmpr_meth
,
328 struct img_gen_info
*igi
)
330 dbuf
*unc_pixels
= NULL
;
331 i64 expected_num_uncmpr_image_bytes
;
333 expected_num_uncmpr_image_bytes
= igi
->rowbytes
*igi
->h
;
336 if(expected_num_uncmpr_image_bytes
> len
) {
337 de_warn(c
, "Not enough data for image");
339 unc_pixels
= dbuf_open_input_subfile(inf
, pos
, len
);
342 unc_pixels
= dbuf_create_membuf(c
, expected_num_uncmpr_image_bytes
, 1);
343 do_decompress_imgview_image(c
, d
, inf
, pos
, len
, unc_pixels
);
345 // TODO: The byte counts in this message are not very accurate.
346 de_dbg(c
, "decompressed %d bytes to %d bytes", (int)len
,
347 (int)unc_pixels
->len
);
350 do_generate_unc_image(c
, d
, unc_pixels
, igi
);
352 dbuf_close(unc_pixels
);
355 static void do_imgview_image(deark
*c
, lctx
*d
, i64 pos1
, i64 len
)
359 unsigned int cmpr_meth
;
362 i64 num_raw_image_bytes
;
363 de_ucstring
*iname
= NULL
;
364 struct img_gen_info
*igi
= NULL
;
366 igi
= de_malloc(c
, sizeof(struct img_gen_info
));
367 igi
->fi
= de_finfo_create(c
);
369 de_dbg(c
, "image record at %d", (int)pos1
);
372 igi
->fi
->internal_mod_time
= d
->mod_time
;
374 iname
= ucstring_create(c
);
375 dbuf_read_to_ucstring(c
->infile
, pos
, 32, iname
, DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
376 de_dbg(c
, "name: \"%s\"", ucstring_getpsz(iname
));
377 if(iname
->len
>0 && c
->filenames_from_file
) {
378 de_finfo_set_name_from_ucstring(c
, igi
->fi
, iname
, 0);
382 imgver
= de_getbyte(pos
++);
383 de_dbg(c
, "version: 0x%02x", (unsigned int)imgver
);
384 cmpr_meth
= (unsigned int)(imgver
&0x07);
386 de_dbg(c
, "compression method: %u", cmpr_meth
);
387 de_dbg_indent(c
, -1);
389 de_warn(c
, "This version of ImageViewer format (0x%02x) might not be supported correctly.",
390 (unsigned int)imgver
);
393 imgtype
= de_getbyte(pos
++);
394 de_dbg(c
, "type: 0x%02x", (unsigned int)imgtype
);
397 case 0: igi
->bitsperpixel
= 2; break;
398 case 2: igi
->bitsperpixel
= 4; break;
399 default: igi
->bitsperpixel
= 1;
401 de_dbg(c
, "bits/pixel: %d", (int)igi
->bitsperpixel
);
402 de_dbg_indent(c
, -1);
404 x0
= de_getu32be(pos
);
405 de_dbg(c
, "reserved1: 0x%08x", (unsigned int)x0
);
408 x0
= de_getu32be(pos
);
409 de_dbg(c
, "note: 0x%08x", (unsigned int)x0
);
412 x0
= de_getu16be(pos
);
414 x1
= de_getu16be(pos
);
416 de_dbg(c
, "last: (%d,%d)", (int)x0
, (int)x1
);
418 x0
= de_getu32be(pos
);
419 de_dbg(c
, "reserved2: 0x%08x", (unsigned int)x0
);
422 // TODO: Is the anchor signed or unsigned?
423 x0
= de_getu16be(pos
);
425 x1
= de_getu16be(pos
);
427 de_dbg(c
, "anchor: (%d,%d)", (int)x0
, (int)x1
);
429 igi
->w
= de_getu16be(pos
);
431 igi
->h
= de_getu16be(pos
);
433 de_dbg_dimensions(c
, igi
->w
, igi
->h
);
434 if(!de_good_image_dimensions(c
, igi
->w
, igi
->h
)) goto done
;
436 igi
->rowbytes
= (igi
->w
*igi
->bitsperpixel
+ 7)/8;
437 num_raw_image_bytes
= pos1
+len
-pos
;
439 de_dbg(c
, "image data at %d", (int)pos
);
441 do_generate_image(c
, d
, c
->infile
, pos
, num_raw_image_bytes
,
443 de_dbg_indent(c
, -1);
446 de_dbg_indent(c
, -1);
447 ucstring_destroy(iname
);
449 de_finfo_destroy(c
, igi
->fi
);
454 static void do_imgview_text(deark
*c
, lctx
*d
, i64 pos
, i64 len
)
456 de_ucstring
*s
= NULL
;
460 // (I'm pretty much just guessing the format of this record.)
461 s
= ucstring_create(c
);
462 dbuf_read_to_ucstring(c
->infile
, pos
, len
, s
, DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
464 // TODO: Decide when to write the text record to a file.
465 // Problem is that we're already using -a to mean "write all raw records to files".
468 outf
= dbuf_create_output_file(c
, "comment.txt", NULL
, DE_CREATEFLAG_IS_AUX
);
469 ucstring_write_as_utf8(c
, s
, outf
, 1);
476 static void get_rec_attr_descr(de_ucstring
*s
, u8 attribs
)
478 if(attribs
&0x10) ucstring_append_flags_item(s
, "mRecAttrSecret");
479 if(attribs
&0x20) ucstring_append_flags_item(s
, "dmRecAttrBusy");
480 if(attribs
&0x40) ucstring_append_flags_item(s
, "dmRecAttrDirty");
481 if(attribs
&0x80) ucstring_append_flags_item(s
, "dmRecAttrDelete");
482 if(attribs
==0) ucstring_append_flags_item(s
, "none");
485 // For PDB or PQA format
486 static int do_read_pdb_record(deark
*c
, lctx
*d
, i64 rec_idx
, i64 pos1
)
492 de_ucstring
*attr_descr
= NULL
;
495 de_dbg(c
, "record[%d] at %d", (int)rec_idx
, (int)pos1
);
498 data_offs
= (int)d
->rec_list
.rec_data
[rec_idx
].offset
;
499 de_dbg(c
, "data pos: %d", (int)data_offs
);
501 data_len
= calc_rec_len(c
, d
, rec_idx
);
502 de_dbg(c
, "calculated len: %d", (int)data_len
);
504 de_snprintf(extfull
, sizeof(extfull
), "rec%d.bin", (int)rec_idx
); // May be overridden
507 const char *idname
= NULL
;
510 attribs
= de_getbyte(pos1
+4);
511 attr_descr
= ucstring_create(c
);
512 get_rec_attr_descr(attr_descr
, attribs
);
513 de_dbg(c
, "attributes: 0x%02x (%s)", (unsigned int)attribs
,
514 ucstring_getpsz(attr_descr
));
516 id
= (de_getbyte(pos1
+5)<<16) |
517 (de_getbyte(pos1
+6)<<8) |
518 (de_getbyte(pos1
+7));
520 if(d
->file_subfmt
==SUBFMT_IMAGEVIEWER
) {
521 if(id
==0x6f8000) idname
= "image record";
522 else if(id
==0x6f8001) idname
= "text record";
526 de_snprintf(tmpstr
, sizeof(tmpstr
), " (%s)", idname
);
530 de_dbg(c
, "id: %u (0x%06x)%s", (unsigned int)id
, (unsigned int)id
, tmpstr
);
532 if(d
->has_nonzero_ids
) {
533 de_snprintf(extfull
, sizeof(extfull
), "%06x.bin", (unsigned int)id
);
536 if(d
->file_subfmt
==SUBFMT_IMAGEVIEWER
) {
537 if(id
==0x6f8000) do_imgview_image(c
, d
, data_offs
, data_len
);
538 else if(id
==0x6f8001) do_imgview_text(c
, d
, data_offs
, data_len
);
542 extract_item(c
, d
, data_offs
, data_len
, extfull
, NULL
, 0, 0);
544 de_dbg_indent(c
, -1);
545 ucstring_destroy(attr_descr
);
549 static void do_string_rsrc(deark
*c
, lctx
*d
,
551 const struct rsrc_type_info_struct
*rti
, unsigned int flags
)
553 de_ucstring
*s
= NULL
;
555 if(!rti
|| !rti
->descr
) return;
556 s
= ucstring_create(c
);
557 dbuf_read_to_ucstring_n(c
->infile
, pos
, len
, DE_DBG_MAX_STRLEN
, s
,
558 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
559 de_dbg(c
, "%s: \"%s\"", rti
->descr
, ucstring_getpsz(s
));
561 if((flags
&0x1) & !d
->icon_name
) {
562 // Also save the string to d->icon_name, to be used later
563 d
->icon_name
= ucstring_create(c
);
564 dbuf_read_to_ucstring_n(c
->infile
, pos
, len
, 80, d
->icon_name
,
565 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
571 static const struct rsrc_type_info_struct rsrc_type_info_arr
[] = {
572 //{ FONT, 0x1, "custom font", NULL },
573 { 0x4d424152U
/* MBAR */, 0x1, "menu bar", NULL
},
574 { 0x4d454e55U
/* MENU */, 0x1, "menu", NULL
},
575 //{ TRAP, 0x0, "", NULL },
576 { 0x54616c74U
/* Talt */, 0x1, "alert", NULL
},
577 { CODE_Tbmp
, 0x1, "bitmap image", NULL
},
578 //{ cnty, 0x1, "country-dependent info", NULL },
579 { 0x636f6465U
/* code */, 0x0, "code segment", NULL
},
580 { 0x64617461U
/* data */, 0x0, "data segment", NULL
},
581 //{ libr, 0x0, "", NULL }
582 //{ 0x70726566U /* pref */, 0x0, "", NULL },
583 //{ rloc, 0x0, "", NULL }
584 //{ silk, 0x1, "silk-screened area info", NULL },
585 { CODE_tAIB
, 0x1, "app icon", NULL
},
586 { CODE_tAIN
, 0x1, "app icon name", NULL
},
587 { CODE_tAIS
, 0x1, "app info string", NULL
},
588 { 0x7442544eU
/* tBTN */, 0x1, "command button", NULL
},
589 //{ tCBX, 0x1, "check box", NULL },
590 //{ tFBM, 0x1, "form bitmap", NULL },
591 { 0x74464c44U
/* tFLD */, 0x1, "text field", NULL
},
592 { 0x7446524dU
/* tFRM */, 0x1, "form", NULL
},
593 //{ tGDT, 0x1, "gadget", NULL },
594 //{ tGSI, 0x1, "graffiti shift indicator", NULL },
595 { 0x744c424c /* tLBL */, 0x1, "label", NULL
},
596 //{ tLST, 0x1, "list", NULL },
597 //{ tPBN, 0x1, "push button", NULL },
598 { 0x7450554cU
/* tPUL */, 0x1, "pop-up list", NULL
},
599 { 0x74505554U
/* tPUT */, 0x1, "pop-up trigger", NULL
},
600 //{ tREP, 0x1, "repeating button", NULL },
601 //{ tSCL, 0x1, "scroll bar", NULL },
602 //{ tSLT, 0x1, "selector trigger", NULL },
603 { 0x7453544cU
/* tSTL */, 0x1, "string list", NULL
},
604 { CODE_tSTR
, 0x1, "string", NULL
},
605 { 0x7454424cU
/* tTBL */, 0x1, "table", NULL
},
606 //{ taif, 0x1, "app icon family", NULL },
607 //{ tbmf, 0x1, "bitmap family", NULL },
608 //{ tgbn, 0x1, "graphic button", NULL },
609 //{ tgpb, 0x1, "graphic push button", NULL },
610 //{ tgrb, 0x1, "graphic repeating button", NULL },
611 //{ tint, 0x1, "integer constant", NULL },
612 { CODE_tver
, 0x1, "app version string", NULL
}
615 static const struct rsrc_type_info_struct
*get_rsrc_type_info(u32 id
)
619 for(i
=0; i
<DE_ARRAYCOUNT(rsrc_type_info_arr
); i
++) {
620 if(id
== rsrc_type_info_arr
[i
].id
) {
621 return &rsrc_type_info_arr
[i
];
627 static int do_read_prc_record(deark
*c
, lctx
*d
, i64 rec_idx
, i64 pos1
)
630 struct de_fourcc rsrc_type_4cc
;
633 int always_extract
= 0;
634 de_ucstring
*ext_ucstring
= NULL
;
636 const char *rsrc_type_descr
;
637 const struct rsrc_type_info_struct
*rti
;
639 de_dbg(c
, "record[%d] at %d", (int)rec_idx
, (int)pos1
);
642 dbuf_read_fourcc(c
->infile
, pos1
, &rsrc_type_4cc
, 4, 0x0);
643 rti
= get_rsrc_type_info(rsrc_type_4cc
.id
);
644 if(rti
&& rti
->descr
) rsrc_type_descr
= rti
->descr
;
645 else rsrc_type_descr
= "?";
646 de_dbg(c
, "resource type: '%s' (%s)", rsrc_type_4cc
.id_dbgstr
, rsrc_type_descr
);
648 ext_ucstring
= ucstring_create(c
);
649 // The "filename" always starts with the fourcc.
650 ucstring_append_sz(ext_ucstring
, rsrc_type_4cc
.id_sanitized_sz
, DE_ENCODING_LATIN1
);
652 id
= (u32
)de_getu16be(pos1
+4);
653 de_dbg(c
, "id: %d", (int)id
);
655 data_offs
= (i64
)d
->rec_list
.rec_data
[rec_idx
].offset
;
656 de_dbg(c
, "data pos: %d", (int)data_offs
);
657 data_len
= calc_rec_len(c
, d
, rec_idx
);
658 de_dbg(c
, "calculated len: %d", (int)data_len
);
660 switch(rsrc_type_4cc
.id
) {
662 ucstring_append_sz(ext_ucstring
, ".palm", DE_ENCODING_LATIN1
);
668 if(d
->icon_name
&& c
->filenames_from_file
) {
669 ucstring_append_sz(ext_ucstring
, ".", DE_ENCODING_LATIN1
);
670 ucstring_append_ucstring(ext_ucstring
, d
->icon_name
);
672 ucstring_append_sz(ext_ucstring
, ".palm", DE_ENCODING_LATIN1
);
677 do_string_rsrc(c
, d
, data_offs
, data_len
, rti
,
678 (d
->rec_list
.icon_name_count
==1)?0x1:0x0);
683 do_string_rsrc(c
, d
, data_offs
, data_len
, rti
, 0);
688 ucstring_append_sz(ext_ucstring
, ".bin", DE_ENCODING_LATIN1
);
690 extract_item(c
, d
, data_offs
, data_len
, NULL
, ext_ucstring
, 0, always_extract
);
692 de_dbg_indent(c
, -1);
693 ucstring_destroy(ext_ucstring
);
697 // Put idx at the beginning of the order_to_read array, shifting everything else
698 // over. Assumes items [0] through [idx-1] are valid.
699 static void rec_list_insert_at_start(struct rec_list_struct
*rl
, i64 idx
)
702 // Move [idx-1] to [idx],
703 // [idx-2] to [idx-1], ...
704 for(i
=idx
; i
>0; i
--) {
705 rl
->order_to_read
[i
] = rl
->order_to_read
[i
-1];
708 rl
->order_to_read
[0] = (size_t)idx
;
711 // Allocates and populates the d->rec_data array.
712 // Tests for sanity, and returns 0 if there is a problem.
713 static int do_prescan_records(deark
*c
, lctx
*d
, i64 pos1
)
717 if(d
->rec_list
.num_recs
<1) return 1;
718 // num_recs is untrusted, but it is a 16-bit int that can be at most 65535.
719 d
->rec_list
.rec_data
= de_mallocarray(c
, d
->rec_list
.num_recs
, sizeof(struct rec_data_struct
));
720 d
->rec_list
.order_to_read
= de_mallocarray(c
, d
->rec_list
.num_recs
, sizeof(size_t));
721 for(i
=0; i
<d
->rec_list
.num_recs
; i
++) {
722 // By default, read the records in the order they appear in the file.
723 d
->rec_list
.order_to_read
[i
] = (size_t)i
;
725 if(d
->file_fmt
==FMT_PRC
) {
727 rsrc_type
= (u32
)de_getu32be(pos1
+ d
->rec_size
*i
);
728 if(rsrc_type
==CODE_tAIN
&& d
->rec_list
.icon_name_count
==0) {
729 // "Move" the tAIN record to the beginning, so we will read it
730 // before any tAIB resources.
731 rec_list_insert_at_start(&d
->rec_list
, i
);
732 d
->rec_list
.icon_name_count
++;
734 d
->rec_list
.rec_data
[i
].offset
= (u32
)de_getu32be(pos1
+ d
->rec_size
*i
+ 6);
738 d
->rec_list
.rec_data
[i
].offset
= (u32
)de_getu32be(pos1
+ d
->rec_size
*i
);
739 if(!d
->has_nonzero_ids
) {
740 id
= (de_getbyte(pos1
+d
->rec_size
*i
+5)<<16) |
741 (de_getbyte(pos1
+d
->rec_size
*i
+6)<<8) |
742 (de_getbyte(pos1
+d
->rec_size
*i
+7));
743 if(id
!=0) d
->has_nonzero_ids
= 1;
747 // Record data must not start beyond the end of file.
748 if((i64
)d
->rec_list
.rec_data
[i
].offset
> c
->infile
->len
) {
749 de_err(c
, "Record %d (at %d) starts after end of file (%d)",
750 (int)i
, (int)d
->rec_list
.rec_data
[i
].offset
, (int)c
->infile
->len
);
754 // Record data must not start before the previous record's data.
756 if(d
->rec_list
.rec_data
[i
].offset
< d
->rec_list
.rec_data
[i
-1].offset
) {
757 de_err(c
, "Record %d (at %d) starts before previous record (at %d)",
758 (int)i
, (int)d
->rec_list
.rec_data
[i
].offset
, (int)d
->rec_list
.rec_data
[i
-1].offset
);
766 // Read "Palm Database record list" or PRC records, and the data it refers to
767 static int do_read_pdb_prc_records(deark
*c
, lctx
*d
, i64 pos1
)
773 de_dbg(c
, "%s record list at %d", d
->fmt_shortname
, (int)pos1
);
778 x
= de_getu32be(pos1
);
779 de_dbg(c
, "nextRecordListID: %d", (int)x
);
781 de_warn(c
, "This file contains multiple record lists, which is not supported.");
784 d
->rec_list
.num_recs
= de_getu16be(pos1
+4);
785 de_dbg(c
, "number of records: %d", (int)d
->rec_list
.num_recs
);
789 if(d
->file_fmt
==FMT_PRC
) d
->rec_size
= 10;
790 else d
->rec_size
= 8;
792 de_dbg(c
, "[pre-scanning record list]");
793 if(!do_prescan_records(c
, d
, pos1
+6)) goto done
;
794 de_dbg(c
, "[main pass through record list]");
796 // i is the index in rec_list.order_to_read
797 // n is the index in rec_list.rec_data
798 for(i
=0; i
<d
->rec_list
.num_recs
; i
++) {
800 n
= (i64
)d
->rec_list
.order_to_read
[i
];
801 if(d
->file_fmt
==FMT_PRC
) {
802 if(!do_read_prc_record(c
, d
, n
, pos1
+6+d
->rec_size
*n
))
806 if(!do_read_pdb_record(c
, d
, n
, pos1
+6+d
->rec_size
*n
))
812 de_dbg_indent(c
, -1);
816 static void do_pqa_app_info_block(deark
*c
, lctx
*d
, i64 pos1
, i64 len
)
821 de_ucstring
*s
= NULL
;
824 sig
= (u32
)de_getu32be_p(&pos
);
825 if(sig
!=CODE_lnch
) return; // Apparently not a PQA appinfo block
826 de_dbg(c
, "PQA sig: 0x%08x", (unsigned int)sig
);
828 ux
= (u32
)de_getu16be_p(&pos
);
829 de_dbg(c
, "hdrVersion: 0x%04x", (unsigned int)ux
);
830 ux
= (u32
)de_getu16be_p(&pos
);
831 de_dbg(c
, "encVersion: 0x%04x", (unsigned int)ux
);
833 s
= ucstring_create(c
);
835 n
= de_getu16be_p(&pos
);
836 dbuf_read_to_ucstring_n(c
->infile
, pos
, n
*2, DE_DBG_MAX_STRLEN
, s
,
837 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
838 de_dbg(c
, "verStr: \"%s\"", ucstring_getpsz(s
));
842 n
= de_getu16be_p(&pos
);
843 dbuf_read_to_ucstring_n(c
->infile
, pos
, n
*2, DE_DBG_MAX_STRLEN
, s
,
844 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
845 de_dbg(c
, "pqaTitle: \"%s\"", ucstring_getpsz(s
));
851 n
= de_getu16be_p(&pos
); // iconWords (length prefix)
852 extract_item(c
, d
, pos
, 2*n
, "icon.palm", NULL
, DE_CREATEFLAG_IS_AUX
, 1);
854 de_dbg_indent(c
, -1);
858 n
= de_getu16be_p(&pos
); // smIconWords
859 extract_item(c
, d
, pos
, 2*n
, "smicon.palm", NULL
, DE_CREATEFLAG_IS_AUX
, 1);
861 de_dbg_indent(c
, -1);
866 static void do_app_info_block(deark
*c
, lctx
*d
)
870 if(d
->appinfo_offs
==0) return;
871 de_dbg(c
, "app info block at %d", (int)d
->appinfo_offs
);
874 if(d
->sortinfo_offs
) {
875 len
= d
->sortinfo_offs
- d
->appinfo_offs
;
877 else if(d
->rec_list
.num_recs
>0) {
878 len
= (i64
)d
->rec_list
.rec_data
[0].offset
- d
->appinfo_offs
;
881 len
= c
->infile
->len
- d
->appinfo_offs
;
883 de_dbg(c
, "calculated len: %d", (int)len
);
886 // TODO: In many cases, this can be parsed as a format called "standard
887 // category data". But I don't know how to tell whether it is in that
889 extract_item(c
, d
, d
->appinfo_offs
, len
, "appinfo.bin", NULL
, DE_CREATEFLAG_IS_AUX
, 0);
891 if(d
->file_subfmt
==SUBFMT_PQA
) {
892 do_pqa_app_info_block(c
, d
, d
->appinfo_offs
, len
);
896 de_dbg_indent(c
, -1);
899 static void do_sort_info_block(deark
*c
, lctx
*d
)
903 if(d
->sortinfo_offs
==0) return;
904 de_dbg(c
, "sort info block at %d", (int)d
->sortinfo_offs
);
907 if(d
->rec_list
.num_recs
>0) {
908 len
= (i64
)d
->rec_list
.rec_data
[0].offset
- d
->sortinfo_offs
;
911 len
= c
->infile
->len
- d
->sortinfo_offs
;
913 de_dbg(c
, "calculated len: %d", (int)len
);
916 extract_item(c
, d
, d
->sortinfo_offs
, len
, "sortinfo.bin", NULL
, DE_CREATEFLAG_IS_AUX
, 0);
919 de_dbg_indent(c
, -1);
922 static void free_lctx(deark
*c
, lctx
*d
)
925 de_free(c
, d
->rec_list
.rec_data
);
926 de_free(c
, d
->rec_list
.order_to_read
);
927 ucstring_destroy(d
->icon_name
);
932 static void de_run_pdb_or_prc(deark
*c
, lctx
*d
, de_module_params
*mparams
)
936 s
= de_get_ext_option(c
, "palm:timestampfmt");
938 if(!de_strcmp(s
, "macbe"))
939 d
->timestampfmt
= TIMESTAMPFMT_MACBE
;
940 else if(!de_strcmp(s
, "unixbe"))
941 d
->timestampfmt
= TIMESTAMPFMT_UNIXBE
;
942 else if(!de_strcmp(s
, "macle"))
943 d
->timestampfmt
= TIMESTAMPFMT_MACLE
;
946 if(!do_read_pdb_prc_header(c
, d
)) goto done
;
947 if(!do_read_pdb_prc_records(c
, d
, 72)) goto done
;
948 do_app_info_block(c
, d
);
949 do_sort_info_block(c
, d
);
954 static void de_run_palmdb(deark
*c
, de_module_params
*mparams
)
957 d
= de_malloc(c
, sizeof(lctx
));
958 d
->file_fmt
= FMT_PDB
;
959 de_run_pdb_or_prc(c
, d
, mparams
);
963 static void de_run_palmrc(deark
*c
, de_module_params
*mparams
)
966 d
= de_malloc(c
, sizeof(lctx
));
967 d
->file_fmt
= FMT_PRC
;
968 de_declare_fmt(c
, "Palm PRC");
969 de_run_pdb_or_prc(c
, d
, mparams
);
973 static int de_identify_palmdb(deark
*c
)
986 static const char *exts
[] = {"pdb", "prc", "pqa", "mobi"};
987 static const char *ids
[] = {"vIMGView", "TEXtREAd", "pqa clpr", "BOOKMOBI"};
990 for(k
=0; k
<DE_ARRAYCOUNT(exts
); k
++) {
991 if(de_input_file_has_ext(c
, exts
[k
])) {
996 if(!has_ext
) return 0;
998 attribs
= (u32
)de_getu16be(32);
999 if(attribs
& 0x0001) return 0; // Might be PRC, but is not PDB
1001 // It is not easy to identify PDB format from its contents.
1002 // But it's good to do what we can, because the .pdb file extension
1003 // is used by several other formats.
1005 // The type/creator codes must presumably be printable characters
1007 for(k
=0; k
<8; k
++) {
1008 if(id
[k
]<32) return 0;
1011 // Check for known file types
1012 for(k
=0; k
<DE_ARRAYCOUNT(ids
); k
++) {
1013 if(!de_memcmp(id
, ids
[k
], 8)) return 100;
1016 // There must be at least one NUL byte in the first 32 bytes,
1017 // and any bytes before the NUL must presumably be printable.
1018 de_read(buf
, 0, 32);
1020 for(k
=0; k
<32; k
++) {
1025 if(buf
[k
]<32) return 0;
1029 appinfo_offs
= de_getu32be(52);
1030 sortinfo_offs
= de_getu32be(56);
1031 num_recs
= de_getu16be(72+4);
1033 curpos
= 72 + 6 + num_recs
*8;
1034 if(curpos
>c
->infile
->len
) return 0;
1036 if(appinfo_offs
!=0) {
1037 if(appinfo_offs
<curpos
) return 0;
1038 curpos
= appinfo_offs
;
1040 if(curpos
>c
->infile
->len
) return 0;
1042 if(sortinfo_offs
!=0) {
1043 if(sortinfo_offs
<curpos
) return 0;
1044 curpos
= sortinfo_offs
;
1046 if(curpos
>c
->infile
->len
) return 0;
1049 // Sanity-check the first record.
1050 // TODO? We could check more than one record.
1051 recdata_offs
= de_getu32be(72+6+0);
1052 if(recdata_offs
<curpos
) return 0;
1053 curpos
= recdata_offs
;
1054 if(curpos
>c
->infile
->len
) return 0;
1060 static int looks_like_a_4cc(dbuf
*f
, i64 pos
)
1064 dbuf_read(f
, buf
, pos
, 4);
1065 for(i
=0; i
<4; i
++) {
1066 if(buf
[i
]<32 || buf
[i
]>126) return 0;
1071 // returns 1 if it might be a pdb, 2 if it might be a prc
1073 // TODO: Improve this ID algorithm
1074 static int identify_pdb_prc_internal(deark
*c
, dbuf
*f
)
1078 attribs
= (u32
)dbuf_getu16be(f
, 32);
1079 if(!looks_like_a_4cc(f
, 60)) return 0;
1080 if(!looks_like_a_4cc(f
, 64)) return 0;
1081 nrecs
= dbuf_getu16be(f
, 72+4);
1082 if(nrecs
<1) return 0;
1083 if(!(attribs
&0x0001)) return 0;
1084 if(!looks_like_a_4cc(f
, 72+6+0)) return 0;
1088 static int de_identify_palmrc(deark
*c
)
1095 if(de_input_file_has_ext(c
, "prc"))
1097 else if(de_input_file_has_ext(c
, "pdb"))
1099 if(!prc_ext
&& !pdb_ext
) return 0;
1102 if(!de_memcmp(id
, "appl", 4)) return 100;
1104 x
= identify_pdb_prc_internal(c
, c
->infile
);
1105 if(x
==2 && prc_ext
) return 90;
1106 if(x
==2 && pdb_ext
) return 60;
1110 static void de_help_pdb_prc(deark
*c
)
1112 de_msg(c
, "-opt timestampfmt=<macbe|unixbe|macle> : The format of the "
1113 "timestamp fields");
1116 void de_module_palmdb(deark
*c
, struct deark_module_info
*mi
)
1119 mi
->desc
= "Palm OS PDB";
1120 mi
->run_fn
= de_run_palmdb
;
1121 mi
->identify_fn
= de_identify_palmdb
;
1122 mi
->help_fn
= de_help_pdb_prc
;
1125 void de_module_palmrc(deark
*c
, struct deark_module_info
*mi
)
1128 mi
->desc
= "Palm OS PRC";
1129 mi
->run_fn
= de_run_palmrc
;
1130 mi
->identify_fn
= de_identify_palmrc
;
1131 mi
->help_fn
= de_help_pdb_prc
;