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
);
12 #define CODE_Tbmp 0x54626d70U
13 #define CODE_View 0x56696577U
14 #define CODE_appl 0x6170706cU
15 #define CODE_clpr 0x636c7072U
16 #define CODE_lnch 0x6c6e6368U
17 #define CODE_pqa 0x70716120U
18 #define CODE_tAIB 0x74414942U
19 #define CODE_tAIN 0x7441494eU
20 #define CODE_tAIS 0x74414953U
21 #define CODE_tSTR 0x74535452U
22 #define CODE_tver 0x74766572U
23 #define CODE_vIMG 0x76494d47U
25 struct rec_data_struct
{
29 struct rec_list_struct
{
31 // The rec_data items are in the order they appear in the file
32 struct rec_data_struct
*rec_data
;
33 // A list of all the rec_data indices, in the order we should read them
34 size_t *order_to_read
;
38 struct rsrc_type_info_struct
{
40 u32 flags
; // 1=standard Palm resource
42 void* /* rsrc_decoder_fn */ decoder_fn
;
50 unsigned int createflags
;
53 typedef struct localctx_struct
{
59 #define SUBFMT_IMAGEVIEWER 2
62 #define TIMESTAMPFMT_UNKNOWN 0
63 #define TIMESTAMPFMT_MACBE 1
64 #define TIMESTAMPFMT_UNIXBE 2
65 #define TIMESTAMPFMT_MACLE 3
69 const char *fmt_shortname
;
70 i64 rec_size
; // bytes per record
71 struct de_fourcc dtype4cc
;
72 struct de_fourcc creator4cc
;
73 struct de_timestamp mod_time
;
76 struct rec_list_struct rec_list
;
77 de_ucstring
*icon_name
;
80 static int timestamp_is_plausible(struct de_timestamp
*ts
)
84 if(!ts
->is_valid
) return 0;
85 ts_unix
= de_timestamp_to_unix_time(ts
);
86 // I assume PDB/PRC format wasn't in use until 1996 or a little before, when
87 // Palm OS was released.
88 // But it's not necessarily safe to assume all timestamps older than that
89 // are wrong -- an older file could have been converted to this format.
90 // For now at least, I'll arbitrarily draw the line at 1985.
91 if(ts_unix
< 473385600) return 0; // = 01 Jan 1985
95 static void handle_palm_timestamp(deark
*c
, lctx
*d
, i64 pos
, const char *name
,
96 struct de_timestamp
*returned_ts
)
98 struct de_timestamp ts
;
99 char timestamp_buf
[64];
102 de_zeromem(&ts
, sizeof(struct de_timestamp
));
104 de_zeromem(returned_ts
, sizeof(struct de_timestamp
));
107 ts_int
= de_getu32be(pos
);
109 de_dbg(c
, "%s: 0 (not set)", name
);
113 de_dbg(c
, "%s: ...", name
);
116 // I've seen three different ways to interpret this 32-bit timestamp, and
117 // I don't know how to guess the correct one.
119 if(d
->timestampfmt
==TIMESTAMPFMT_MACBE
|| d
->timestampfmt
==TIMESTAMPFMT_UNKNOWN
) {
120 de_mac_time_to_timestamp(ts_int
, &ts
);
121 de_timestamp_to_string(&ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
122 de_dbg(c
, "... if Mac-BE: %"I64_FMT
" (%s)", ts_int
, timestamp_buf
);
125 if(d
->timestampfmt
==TIMESTAMPFMT_UNIXBE
|| d
->timestampfmt
==TIMESTAMPFMT_UNKNOWN
) {
126 ts_int
= de_geti32be(pos
);
127 de_unix_time_to_timestamp(ts_int
, &ts
, 0x1);
128 if(d
->timestampfmt
==TIMESTAMPFMT_UNIXBE
|| timestamp_is_plausible(&ts
)) {
129 de_timestamp_to_string(&ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
130 de_dbg(c
, "... if Unix-BE: %"I64_FMT
" (%s)", ts_int
, timestamp_buf
);
134 if(d
->timestampfmt
==TIMESTAMPFMT_MACLE
|| d
->timestampfmt
==TIMESTAMPFMT_UNKNOWN
) {
135 ts_int
= de_getu32le(pos
);
136 de_mac_time_to_timestamp(ts_int
, &ts
);
137 if(d
->timestampfmt
==TIMESTAMPFMT_MACLE
|| timestamp_is_plausible(&ts
)) {
138 de_timestamp_to_string(&ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
139 de_dbg(c
, "... if Mac-LE: %"I64_FMT
" (%s)", ts_int
, timestamp_buf
);
143 de_dbg_indent(c
, -1);
146 if(returned_ts
&& d
->timestampfmt
!=TIMESTAMPFMT_UNKNOWN
) {
151 static void get_db_attr_descr(de_ucstring
*s
, u32 attribs
)
154 struct { u32 a
; const char *n
; } flags_arr
[] = {
155 {0x0001, "dmHdrAttrResDB"},
156 {0x0002, "dmHdrAttrReadOnly"},
157 {0x0004, "dmHdrAttrAppInfoDirty"},
158 {0x0008, "dmHdrAttrBackup"},
159 {0x0010, "dmHdrAttrOKToInstallNewer"},
160 {0x0020, "dmHdrAttrResetAfterInstall"},
161 {0x0040, "dmHdrAttrCopyPrevention"},
162 {0x0080, "dmHdrAttrStream"},
163 {0x0100, "dmHdrAttrHidden"},
164 {0x0200, "dmHdrAttrLaunchableData"},
165 {0x0400, "dmHdrAttrRecyclable"},
166 {0x0800, "dmHdrAttrBundle"},
167 {0x8000, "dmHdrAttrOpen"}
169 for(i
=0; i
<DE_ARRAYCOUNT(flags_arr
); i
++) {
170 if(attribs
& flags_arr
[i
].a
)
171 ucstring_append_flags_item(s
, flags_arr
[i
].n
);
174 if(attribs
==0) ucstring_append_flags_item(s
, "none");
177 static int do_read_pdb_prc_header(deark
*c
, lctx
*d
)
180 de_ucstring
*dname
= NULL
;
181 de_ucstring
*attr_descr
= NULL
;
187 de_dbg(c
, "header at %d", (int)pos1
);
190 dname
= ucstring_create(c
);
191 dbuf_read_to_ucstring(c
->infile
, pos1
, 32, dname
, DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
192 de_dbg(c
, "name: \"%s\"", ucstring_getpsz(dname
));
194 attribs
= (u32
)de_getu16be(pos1
+32);
195 if(attribs
& 0x0001) {
196 d
->file_fmt
= FMT_PRC
;
199 d
->file_fmt
= FMT_PDB
;
201 attr_descr
= ucstring_create(c
);
202 get_db_attr_descr(attr_descr
, attribs
);
203 de_dbg(c
, "attributes: 0x%04x (%s)", (unsigned int)attribs
,
204 ucstring_getpsz(attr_descr
));
206 version
= (u32
)de_getu16be(pos1
+34);
207 de_dbg(c
, "version: 0x%04x", (unsigned int)version
);
209 handle_palm_timestamp(c
, d
, pos1
+36, "create date", NULL
);
210 handle_palm_timestamp(c
, d
, pos1
+40, "mod date", &d
->mod_time
);
211 handle_palm_timestamp(c
, d
, pos1
+44, "backup date", NULL
);
213 x
= de_getu32be(pos1
+48);
214 de_dbg(c
, "mod number: %d", (int)x
);
215 d
->appinfo_offs
= de_getu32be(pos1
+52);
216 de_dbg(c
, "app info pos: %d", (int)d
->appinfo_offs
);
217 d
->sortinfo_offs
= de_getu32be(pos1
+56);
218 de_dbg(c
, "sort info pos: %d", (int)d
->sortinfo_offs
);
220 dbuf_read_fourcc(c
->infile
, pos1
+60, &d
->dtype4cc
, 4, 0x0);
221 de_dbg(c
, "type: \"%s\"", d
->dtype4cc
.id_dbgstr
);
223 dbuf_read_fourcc(c
->infile
, pos1
+64, &d
->creator4cc
, 4, 0x0);
224 de_dbg(c
, "creator: \"%s\"", d
->creator4cc
.id_dbgstr
);
226 if(d
->file_fmt
==FMT_PDB
) {
227 d
->fmt_shortname
= "PDB";
228 if(d
->dtype4cc
.id
==CODE_pqa
&& d
->creator4cc
.id
==CODE_clpr
) {
229 d
->file_subfmt
= SUBFMT_PQA
;
230 de_declare_fmt(c
, "Palm PQA");
232 else if(d
->dtype4cc
.id
==CODE_vIMG
&& d
->creator4cc
.id
==CODE_View
) {
233 d
->file_subfmt
= SUBFMT_IMAGEVIEWER
;
234 de_declare_fmt(c
, "Palm Database ImageViewer");
237 de_declare_fmt(c
, "Palm PDB");
240 else if(d
->file_fmt
==FMT_PRC
) {
241 d
->fmt_shortname
= "PRC";
242 de_declare_fmt(c
, "Palm PRC");
249 de_dbg(c
, "uniqueIDseed: %u", (unsigned int)x
);
253 de_dbg_indent(c
, -1);
254 ucstring_destroy(dname
);
255 ucstring_destroy(attr_descr
);
259 static i64
calc_rec_len(deark
*c
, lctx
*d
, i64 rec_idx
)
262 if(rec_idx
+1 < d
->rec_list
.num_recs
) {
263 len
= (i64
)(d
->rec_list
.rec_data
[rec_idx
+1].offset
- d
->rec_list
.rec_data
[rec_idx
].offset
);
266 len
= c
->infile
->len
- (i64
)d
->rec_list
.rec_data
[rec_idx
].offset
;
271 // ext_ucstring will be used if ext_sz is NULL
272 static void extract_item(deark
*c
, lctx
*d
, i64 data_offs
, i64 data_len
,
273 const char *ext_sz
, de_ucstring
*ext_ucstring
,
274 unsigned int createflags
, int always_extract
)
278 if(c
->extract_level
<2 && !always_extract
) goto done
;
279 if(data_offs
<0 || data_len
<0) goto done
;
280 if(data_offs
+data_len
> c
->infile
->len
) goto done
;
281 fi
= de_finfo_create(c
);
283 de_finfo_set_name_from_sz(c
, fi
, ext_sz
, 0, DE_ENCODING_ASCII
);
285 else if(ext_ucstring
) {
286 de_finfo_set_name_from_ucstring(c
, fi
, ext_ucstring
, 0);
288 dbuf_create_file_from_slice(c
->infile
, data_offs
, data_len
, NULL
, fi
, createflags
);
290 de_finfo_destroy(c
, fi
);
293 static int do_decompress_imgview_image(deark
*c
, lctx
*d
, dbuf
*inf
,
294 i64 pos1
, i64 len
, dbuf
*unc_pixels
)
300 while(pos
< pos1
+len
) {
301 b1
= dbuf_getbyte(inf
, pos
++);
304 b2
= dbuf_getbyte(inf
, pos
++);
305 dbuf_write_run(unc_pixels
, b2
, count
);
309 dbuf_copy(inf
, pos
, count
, unc_pixels
);
316 static void do_generate_img_from_unc_pixels(deark
*c
, lctx
*d
, dbuf
*unc_pixels
,
317 struct img_gen_info
*igi
)
319 de_bitmap
*img
= NULL
;
322 if(igi
->bitsperpixel
==1) {
323 de_convert_and_write_image_bilevel(unc_pixels
, 0, igi
->w
, igi
->h
, igi
->rowbytes
,
324 DE_CVTF_WHITEISZERO
, igi
->fi
, igi
->createflags
);
327 else if(igi
->bitsperpixel
==2 || igi
->bitsperpixel
==4) {
334 img
= de_bitmap_create(c
, igi
->w
, igi
->h
, 1);
335 de_make_grayscale_palette(pal
, 1ULL<<igi
->bitsperpixel
, 0x1);
336 de_convert_image_paletted(unc_pixels
, 0, igi
->bitsperpixel
, igi
->rowbytes
,
338 de_bitmap_write_to_file_finfo(img
, igi
->fi
, igi
->createflags
);
341 de_bitmap_destroy(img
);
344 // A wrapper that decompresses the image if necessary, then calls
345 // do_generate_img_from_unc_pixels().
346 static void do_generate_image(deark
*c
, lctx
*d
,
347 dbuf
*inf
, i64 pos
, i64 len
, unsigned int cmpr_meth
,
348 struct img_gen_info
*igi
)
350 dbuf
*unc_pixels
= NULL
;
351 i64 expected_num_uncmpr_image_bytes
;
353 expected_num_uncmpr_image_bytes
= igi
->rowbytes
*igi
->h
;
356 if(expected_num_uncmpr_image_bytes
> len
) {
357 de_warn(c
, "Not enough data for image");
359 unc_pixels
= dbuf_open_input_subfile(inf
, pos
, len
);
362 unc_pixels
= dbuf_create_membuf(c
, expected_num_uncmpr_image_bytes
, 1);
363 do_decompress_imgview_image(c
, d
, inf
, pos
, len
, unc_pixels
);
365 // TODO: The byte counts in this message are not very accurate.
366 de_dbg(c
, "decompressed %d bytes to %d bytes", (int)len
,
367 (int)unc_pixels
->len
);
370 do_generate_img_from_unc_pixels(c
, d
, unc_pixels
, igi
);
372 dbuf_close(unc_pixels
);
375 static void do_imgview_image(deark
*c
, lctx
*d
, i64 pos1
, i64 len
)
379 unsigned int cmpr_meth
;
382 i64 num_raw_image_bytes
;
383 de_ucstring
*iname
= NULL
;
384 struct img_gen_info
*igi
= NULL
;
386 igi
= de_malloc(c
, sizeof(struct img_gen_info
));
387 igi
->fi
= de_finfo_create(c
);
389 de_dbg(c
, "image record at %d", (int)pos1
);
392 igi
->fi
->internal_mod_time
= d
->mod_time
;
394 iname
= ucstring_create(c
);
395 dbuf_read_to_ucstring(c
->infile
, pos
, 32, iname
, DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
396 de_dbg(c
, "name: \"%s\"", ucstring_getpsz(iname
));
397 if(iname
->len
>0 && c
->filenames_from_file
) {
398 de_finfo_set_name_from_ucstring(c
, igi
->fi
, iname
, 0);
402 imgver
= de_getbyte(pos
++);
403 de_dbg(c
, "version: 0x%02x", (unsigned int)imgver
);
404 cmpr_meth
= (unsigned int)(imgver
&0x07);
406 de_dbg(c
, "compression method: %u", cmpr_meth
);
407 de_dbg_indent(c
, -1);
409 de_warn(c
, "This version of ImageViewer format (0x%02x) might not be supported correctly.",
410 (unsigned int)imgver
);
413 imgtype
= de_getbyte(pos
++);
414 de_dbg(c
, "type: 0x%02x", (unsigned int)imgtype
);
417 case 0: igi
->bitsperpixel
= 2; break;
418 case 2: igi
->bitsperpixel
= 4; break;
419 default: igi
->bitsperpixel
= 1;
421 de_dbg(c
, "bits/pixel: %d", (int)igi
->bitsperpixel
);
422 de_dbg_indent(c
, -1);
424 x0
= de_getu32be(pos
);
425 de_dbg(c
, "reserved1: 0x%08x", (unsigned int)x0
);
428 x0
= de_getu32be(pos
);
429 de_dbg(c
, "note: 0x%08x", (unsigned int)x0
);
432 x0
= de_getu16be(pos
);
434 x1
= de_getu16be(pos
);
436 de_dbg(c
, "last: (%d,%d)", (int)x0
, (int)x1
);
438 x0
= de_getu32be(pos
);
439 de_dbg(c
, "reserved2: 0x%08x", (unsigned int)x0
);
442 // TODO: Is the anchor signed or unsigned?
443 x0
= de_getu16be(pos
);
445 x1
= de_getu16be(pos
);
447 de_dbg(c
, "anchor: (%d,%d)", (int)x0
, (int)x1
);
449 igi
->w
= de_getu16be(pos
);
451 igi
->h
= de_getu16be(pos
);
453 de_dbg_dimensions(c
, igi
->w
, igi
->h
);
454 if(!de_good_image_dimensions(c
, igi
->w
, igi
->h
)) goto done
;
456 igi
->rowbytes
= (igi
->w
*igi
->bitsperpixel
+ 7)/8;
457 num_raw_image_bytes
= pos1
+len
-pos
;
459 de_dbg(c
, "image data at %d", (int)pos
);
461 do_generate_image(c
, d
, c
->infile
, pos
, num_raw_image_bytes
,
463 de_dbg_indent(c
, -1);
466 de_dbg_indent(c
, -1);
467 ucstring_destroy(iname
);
469 de_finfo_destroy(c
, igi
->fi
);
474 static void do_imgview_text(deark
*c
, lctx
*d
, i64 pos
, i64 len
)
476 de_ucstring
*s
= NULL
;
480 // (I'm pretty much just guessing the format of this record.)
481 s
= ucstring_create(c
);
482 dbuf_read_to_ucstring(c
->infile
, pos
, len
, s
, DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
484 // TODO: Decide when to write the text record to a file.
485 // Problem is that we're already using -a to mean "write all raw records to files".
488 outf
= dbuf_create_output_file(c
, "comment.txt", NULL
, DE_CREATEFLAG_IS_AUX
);
489 ucstring_write_as_utf8(c
, s
, outf
, 1);
496 static void get_rec_attr_descr(de_ucstring
*s
, u8 attribs
)
498 if(attribs
&0x10) ucstring_append_flags_item(s
, "mRecAttrSecret");
499 if(attribs
&0x20) ucstring_append_flags_item(s
, "dmRecAttrBusy");
500 if(attribs
&0x40) ucstring_append_flags_item(s
, "dmRecAttrDirty");
501 if(attribs
&0x80) ucstring_append_flags_item(s
, "dmRecAttrDelete");
502 if(attribs
==0) ucstring_append_flags_item(s
, "none");
505 // For PDB or PQA format
506 static int do_read_pdb_record(deark
*c
, lctx
*d
, i64 rec_idx
, i64 pos1
)
512 de_ucstring
*attr_descr
= NULL
;
515 de_dbg(c
, "record[%d] at %d", (int)rec_idx
, (int)pos1
);
518 data_offs
= (int)d
->rec_list
.rec_data
[rec_idx
].offset
;
519 de_dbg(c
, "data pos: %d", (int)data_offs
);
521 data_len
= calc_rec_len(c
, d
, rec_idx
);
522 de_dbg(c
, "calculated len: %d", (int)data_len
);
524 de_snprintf(extfull
, sizeof(extfull
), "rec%d.bin", (int)rec_idx
); // May be overridden
527 const char *idname
= NULL
;
530 attribs
= de_getbyte(pos1
+4);
531 attr_descr
= ucstring_create(c
);
532 get_rec_attr_descr(attr_descr
, attribs
);
533 de_dbg(c
, "attributes: 0x%02x (%s)", (unsigned int)attribs
,
534 ucstring_getpsz(attr_descr
));
536 id
= (de_getbyte(pos1
+5)<<16) |
537 (de_getbyte(pos1
+6)<<8) |
538 (de_getbyte(pos1
+7));
540 if(d
->file_subfmt
==SUBFMT_IMAGEVIEWER
) {
541 if(id
==0x6f8000) idname
= "image record";
542 else if(id
==0x6f8001) idname
= "text record";
546 de_snprintf(tmpstr
, sizeof(tmpstr
), " (%s)", idname
);
550 de_dbg(c
, "id: %u (0x%06x)%s", (unsigned int)id
, (unsigned int)id
, tmpstr
);
552 if(d
->has_nonzero_ids
) {
553 de_snprintf(extfull
, sizeof(extfull
), "%06x.bin", (unsigned int)id
);
556 if(d
->file_subfmt
==SUBFMT_IMAGEVIEWER
) {
557 if(id
==0x6f8000) do_imgview_image(c
, d
, data_offs
, data_len
);
558 else if(id
==0x6f8001) do_imgview_text(c
, d
, data_offs
, data_len
);
562 extract_item(c
, d
, data_offs
, data_len
, extfull
, NULL
, 0, 0);
564 de_dbg_indent(c
, -1);
565 ucstring_destroy(attr_descr
);
569 static void do_string_rsrc(deark
*c
, lctx
*d
,
571 const struct rsrc_type_info_struct
*rti
, unsigned int flags
)
573 de_ucstring
*s
= NULL
;
575 if(!rti
|| !rti
->descr
) return;
576 s
= ucstring_create(c
);
577 dbuf_read_to_ucstring_n(c
->infile
, pos
, len
, DE_DBG_MAX_STRLEN
, s
,
578 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
579 de_dbg(c
, "%s: \"%s\"", rti
->descr
, ucstring_getpsz(s
));
581 if((flags
&0x1) & !d
->icon_name
) {
582 // Also save the string to d->icon_name, to be used later
583 d
->icon_name
= ucstring_create(c
);
584 dbuf_read_to_ucstring_n(c
->infile
, pos
, len
, 80, d
->icon_name
,
585 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
591 static const struct rsrc_type_info_struct rsrc_type_info_arr
[] = {
592 //{ FONT, 0x1, "custom font", NULL },
593 { 0x4d424152U
/* MBAR */, 0x1, "menu bar", NULL
},
594 { 0x4d454e55U
/* MENU */, 0x1, "menu", NULL
},
595 //{ TRAP, 0x0, "", NULL },
596 { 0x54616c74U
/* Talt */, 0x1, "alert", NULL
},
597 { CODE_Tbmp
, 0x1, "bitmap image", NULL
},
598 //{ cnty, 0x1, "country-dependent info", NULL },
599 { 0x636f6465U
/* code */, 0x0, "code segment", NULL
},
600 { 0x64617461U
/* data */, 0x0, "data segment", NULL
},
601 //{ libr, 0x0, "", NULL }
602 //{ 0x70726566U /* pref */, 0x0, "", NULL },
603 //{ rloc, 0x0, "", NULL }
604 //{ silk, 0x1, "silk-screened area info", NULL },
605 { CODE_tAIB
, 0x1, "app icon", NULL
},
606 { CODE_tAIN
, 0x1, "app icon name", NULL
},
607 { CODE_tAIS
, 0x1, "app info string", NULL
},
608 { 0x7442544eU
/* tBTN */, 0x1, "command button", NULL
},
609 //{ tCBX, 0x1, "check box", NULL },
610 //{ tFBM, 0x1, "form bitmap", NULL },
611 { 0x74464c44U
/* tFLD */, 0x1, "text field", NULL
},
612 { 0x7446524dU
/* tFRM */, 0x1, "form", NULL
},
613 //{ tGDT, 0x1, "gadget", NULL },
614 //{ tGSI, 0x1, "graffiti shift indicator", NULL },
615 { 0x744c424c /* tLBL */, 0x1, "label", NULL
},
616 //{ tLST, 0x1, "list", NULL },
617 //{ tPBN, 0x1, "push button", NULL },
618 { 0x7450554cU
/* tPUL */, 0x1, "pop-up list", NULL
},
619 { 0x74505554U
/* tPUT */, 0x1, "pop-up trigger", NULL
},
620 //{ tREP, 0x1, "repeating button", NULL },
621 //{ tSCL, 0x1, "scroll bar", NULL },
622 //{ tSLT, 0x1, "selector trigger", NULL },
623 { 0x7453544cU
/* tSTL */, 0x1, "string list", NULL
},
624 { CODE_tSTR
, 0x1, "string", NULL
},
625 { 0x7454424cU
/* tTBL */, 0x1, "table", NULL
},
626 //{ taif, 0x1, "app icon family", NULL },
627 //{ tbmf, 0x1, "bitmap family", NULL },
628 //{ tgbn, 0x1, "graphic button", NULL },
629 //{ tgpb, 0x1, "graphic push button", NULL },
630 //{ tgrb, 0x1, "graphic repeating button", NULL },
631 //{ tint, 0x1, "integer constant", NULL },
632 { CODE_tver
, 0x1, "app version string", NULL
}
635 static const struct rsrc_type_info_struct
*get_rsrc_type_info(u32 id
)
639 for(i
=0; i
<DE_ARRAYCOUNT(rsrc_type_info_arr
); i
++) {
640 if(id
== rsrc_type_info_arr
[i
].id
) {
641 return &rsrc_type_info_arr
[i
];
647 static int do_read_prc_record(deark
*c
, lctx
*d
, i64 rec_idx
, i64 pos1
)
650 struct de_fourcc rsrc_type_4cc
;
653 int always_extract
= 0;
654 de_ucstring
*ext_ucstring
= NULL
;
656 const char *rsrc_type_descr
;
657 const struct rsrc_type_info_struct
*rti
;
659 de_dbg(c
, "record[%d] at %d", (int)rec_idx
, (int)pos1
);
662 dbuf_read_fourcc(c
->infile
, pos1
, &rsrc_type_4cc
, 4, 0x0);
663 rti
= get_rsrc_type_info(rsrc_type_4cc
.id
);
664 if(rti
&& rti
->descr
) rsrc_type_descr
= rti
->descr
;
665 else rsrc_type_descr
= "?";
666 de_dbg(c
, "resource type: '%s' (%s)", rsrc_type_4cc
.id_dbgstr
, rsrc_type_descr
);
668 ext_ucstring
= ucstring_create(c
);
669 // The "filename" always starts with the fourcc.
670 ucstring_append_sz(ext_ucstring
, rsrc_type_4cc
.id_sanitized_sz
, DE_ENCODING_LATIN1
);
672 id
= (u32
)de_getu16be(pos1
+4);
673 de_dbg(c
, "id: %d", (int)id
);
675 data_offs
= (i64
)d
->rec_list
.rec_data
[rec_idx
].offset
;
676 de_dbg(c
, "data pos: %d", (int)data_offs
);
677 data_len
= calc_rec_len(c
, d
, rec_idx
);
678 de_dbg(c
, "calculated len: %d", (int)data_len
);
680 switch(rsrc_type_4cc
.id
) {
682 ucstring_append_sz(ext_ucstring
, ".palm", DE_ENCODING_LATIN1
);
688 if(d
->icon_name
&& c
->filenames_from_file
) {
689 ucstring_append_sz(ext_ucstring
, ".", DE_ENCODING_LATIN1
);
690 ucstring_append_ucstring(ext_ucstring
, d
->icon_name
);
692 ucstring_append_sz(ext_ucstring
, ".palm", DE_ENCODING_LATIN1
);
697 do_string_rsrc(c
, d
, data_offs
, data_len
, rti
,
698 (d
->rec_list
.icon_name_count
==1)?0x1:0x0);
703 do_string_rsrc(c
, d
, data_offs
, data_len
, rti
, 0);
708 ucstring_append_sz(ext_ucstring
, ".bin", DE_ENCODING_LATIN1
);
710 extract_item(c
, d
, data_offs
, data_len
, NULL
, ext_ucstring
, 0, always_extract
);
712 de_dbg_indent(c
, -1);
713 ucstring_destroy(ext_ucstring
);
717 // Put idx at the beginning of the order_to_read array, shifting everything else
718 // over. Assumes items [0] through [idx-1] are valid.
719 static void rec_list_insert_at_start(struct rec_list_struct
*rl
, i64 idx
)
722 // Move [idx-1] to [idx],
723 // [idx-2] to [idx-1], ...
724 for(i
=idx
; i
>0; i
--) {
725 rl
->order_to_read
[i
] = rl
->order_to_read
[i
-1];
728 rl
->order_to_read
[0] = (size_t)idx
;
731 // Allocates and populates the d->rec_data array.
732 // Tests for sanity, and returns 0 if there is a problem.
733 static int do_prescan_records(deark
*c
, lctx
*d
, i64 pos1
)
737 if(d
->rec_list
.num_recs
<1) return 1;
738 // num_recs is untrusted, but it is a 16-bit int that can be at most 65535.
739 d
->rec_list
.rec_data
= de_mallocarray(c
, d
->rec_list
.num_recs
, sizeof(struct rec_data_struct
));
740 d
->rec_list
.order_to_read
= de_mallocarray(c
, d
->rec_list
.num_recs
, sizeof(size_t));
741 for(i
=0; i
<d
->rec_list
.num_recs
; i
++) {
742 // By default, read the records in the order they appear in the file.
743 d
->rec_list
.order_to_read
[i
] = (size_t)i
;
745 if(d
->file_fmt
==FMT_PRC
) {
747 rsrc_type
= (u32
)de_getu32be(pos1
+ d
->rec_size
*i
);
748 if(rsrc_type
==CODE_tAIN
&& d
->rec_list
.icon_name_count
==0) {
749 // "Move" the tAIN record to the beginning, so we will read it
750 // before any tAIB resources.
751 rec_list_insert_at_start(&d
->rec_list
, i
);
752 d
->rec_list
.icon_name_count
++;
754 d
->rec_list
.rec_data
[i
].offset
= (u32
)de_getu32be(pos1
+ d
->rec_size
*i
+ 6);
758 d
->rec_list
.rec_data
[i
].offset
= (u32
)de_getu32be(pos1
+ d
->rec_size
*i
);
759 if(!d
->has_nonzero_ids
) {
760 id
= (de_getbyte(pos1
+d
->rec_size
*i
+5)<<16) |
761 (de_getbyte(pos1
+d
->rec_size
*i
+6)<<8) |
762 (de_getbyte(pos1
+d
->rec_size
*i
+7));
763 if(id
!=0) d
->has_nonzero_ids
= 1;
767 // Record data must not start beyond the end of file.
768 if((i64
)d
->rec_list
.rec_data
[i
].offset
> c
->infile
->len
) {
769 de_err(c
, "Record %d (at %d) starts after end of file (%d)",
770 (int)i
, (int)d
->rec_list
.rec_data
[i
].offset
, (int)c
->infile
->len
);
774 // Record data must not start before the previous record's data.
776 if(d
->rec_list
.rec_data
[i
].offset
< d
->rec_list
.rec_data
[i
-1].offset
) {
777 de_err(c
, "Record %d (at %d) starts before previous record (at %d)",
778 (int)i
, (int)d
->rec_list
.rec_data
[i
].offset
, (int)d
->rec_list
.rec_data
[i
-1].offset
);
786 // Read "Palm Database record list" or PRC records, and the data it refers to
787 static int do_read_pdb_prc_records(deark
*c
, lctx
*d
, i64 pos1
)
793 de_dbg(c
, "%s record list at %d", d
->fmt_shortname
, (int)pos1
);
798 x
= de_getu32be(pos1
);
799 de_dbg(c
, "nextRecordListID: %d", (int)x
);
801 de_warn(c
, "This file contains multiple record lists, which is not supported.");
804 d
->rec_list
.num_recs
= de_getu16be(pos1
+4);
805 de_dbg(c
, "number of records: %d", (int)d
->rec_list
.num_recs
);
809 if(d
->file_fmt
==FMT_PRC
) d
->rec_size
= 10;
810 else d
->rec_size
= 8;
812 de_dbg(c
, "[pre-scanning record list]");
813 if(!do_prescan_records(c
, d
, pos1
+6)) goto done
;
814 de_dbg(c
, "[main pass through record list]");
816 // i is the index in rec_list.order_to_read
817 // n is the index in rec_list.rec_data
818 for(i
=0; i
<d
->rec_list
.num_recs
; i
++) {
820 n
= (i64
)d
->rec_list
.order_to_read
[i
];
821 if(d
->file_fmt
==FMT_PRC
) {
822 if(!do_read_prc_record(c
, d
, n
, pos1
+6+d
->rec_size
*n
))
826 if(!do_read_pdb_record(c
, d
, n
, pos1
+6+d
->rec_size
*n
))
832 de_dbg_indent(c
, -1);
836 static void do_pqa_app_info_block(deark
*c
, lctx
*d
, i64 pos1
, i64 len
)
841 de_ucstring
*s
= NULL
;
844 sig
= (u32
)de_getu32be_p(&pos
);
845 if(sig
!=CODE_lnch
) return; // Apparently not a PQA appinfo block
846 de_dbg(c
, "PQA sig: 0x%08x", (unsigned int)sig
);
848 ux
= (u32
)de_getu16be_p(&pos
);
849 de_dbg(c
, "hdrVersion: 0x%04x", (unsigned int)ux
);
850 ux
= (u32
)de_getu16be_p(&pos
);
851 de_dbg(c
, "encVersion: 0x%04x", (unsigned int)ux
);
853 s
= ucstring_create(c
);
855 n
= de_getu16be_p(&pos
);
856 dbuf_read_to_ucstring_n(c
->infile
, pos
, n
*2, DE_DBG_MAX_STRLEN
, s
,
857 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
858 de_dbg(c
, "verStr: \"%s\"", ucstring_getpsz(s
));
862 n
= de_getu16be_p(&pos
);
863 dbuf_read_to_ucstring_n(c
->infile
, pos
, n
*2, DE_DBG_MAX_STRLEN
, s
,
864 DE_CONVFLAG_STOP_AT_NUL
, DE_ENCODING_PALM
);
865 de_dbg(c
, "pqaTitle: \"%s\"", ucstring_getpsz(s
));
871 n
= de_getu16be_p(&pos
); // iconWords (length prefix)
872 extract_item(c
, d
, pos
, 2*n
, "icon.palm", NULL
, DE_CREATEFLAG_IS_AUX
, 1);
874 de_dbg_indent(c
, -1);
878 n
= de_getu16be_p(&pos
); // smIconWords
879 extract_item(c
, d
, pos
, 2*n
, "smicon.palm", NULL
, DE_CREATEFLAG_IS_AUX
, 1);
881 de_dbg_indent(c
, -1);
886 static void do_app_info_block(deark
*c
, lctx
*d
)
890 if(d
->appinfo_offs
==0) return;
891 de_dbg(c
, "app info block at %d", (int)d
->appinfo_offs
);
894 if(d
->sortinfo_offs
) {
895 len
= d
->sortinfo_offs
- d
->appinfo_offs
;
897 else if(d
->rec_list
.num_recs
>0) {
898 len
= (i64
)d
->rec_list
.rec_data
[0].offset
- d
->appinfo_offs
;
901 len
= c
->infile
->len
- d
->appinfo_offs
;
903 de_dbg(c
, "calculated len: %d", (int)len
);
906 // TODO: In many cases, this can be parsed as a format called "standard
907 // category data". But I don't know how to tell whether it is in that
909 extract_item(c
, d
, d
->appinfo_offs
, len
, "appinfo.bin", NULL
, DE_CREATEFLAG_IS_AUX
, 0);
911 if(d
->file_subfmt
==SUBFMT_PQA
) {
912 do_pqa_app_info_block(c
, d
, d
->appinfo_offs
, len
);
916 de_dbg_indent(c
, -1);
919 static void do_sort_info_block(deark
*c
, lctx
*d
)
923 if(d
->sortinfo_offs
==0) return;
924 de_dbg(c
, "sort info block at %d", (int)d
->sortinfo_offs
);
927 if(d
->rec_list
.num_recs
>0) {
928 len
= (i64
)d
->rec_list
.rec_data
[0].offset
- d
->sortinfo_offs
;
931 len
= c
->infile
->len
- d
->sortinfo_offs
;
933 de_dbg(c
, "calculated len: %d", (int)len
);
936 extract_item(c
, d
, d
->sortinfo_offs
, len
, "sortinfo.bin", NULL
, DE_CREATEFLAG_IS_AUX
, 0);
939 de_dbg_indent(c
, -1);
942 static void free_lctx(deark
*c
, lctx
*d
)
945 de_free(c
, d
->rec_list
.rec_data
);
946 de_free(c
, d
->rec_list
.order_to_read
);
947 ucstring_destroy(d
->icon_name
);
952 static void de_run_pdb_or_prc(deark
*c
, de_module_params
*mparams
)
957 d
= de_malloc(c
, sizeof(lctx
));
958 s
= de_get_ext_option(c
, "palm:timestampfmt");
960 if(!de_strcmp(s
, "macbe"))
961 d
->timestampfmt
= TIMESTAMPFMT_MACBE
;
962 else if(!de_strcmp(s
, "unixbe"))
963 d
->timestampfmt
= TIMESTAMPFMT_UNIXBE
;
964 else if(!de_strcmp(s
, "macle"))
965 d
->timestampfmt
= TIMESTAMPFMT_MACLE
;
968 if(!do_read_pdb_prc_header(c
, d
)) goto done
;
969 if(!do_read_pdb_prc_records(c
, d
, 72)) goto done
;
970 do_app_info_block(c
, d
);
971 do_sort_info_block(c
, d
);
976 static int looks_like_a_4cc_b(const u8
*buf
)
981 if(buf
[i
]<32 || buf
[i
]>126) return 0;
986 static int looks_like_a_4cc_f(dbuf
*f
, i64 pos
)
990 dbuf_read(f
, buf
, pos
, 4);
991 return looks_like_a_4cc_b(buf
);
994 static int de_identify_pdb_prc(deark
*c
)
1009 static const char *exts
[] = {"pdb", "prc", "pqa", "mobi"};
1010 static const char *ids
[] = {"vIMGView", "TEXtREAd", "pqa clpr", "BOOKMOBI"};
1013 for(k
=0; k
<DE_ARRAYCOUNT(exts
); k
++) {
1014 if(de_input_file_has_ext(c
, exts
[k
])) {
1019 if(!has_ext
) return 0;
1021 attribs
= (u32
)de_getu16be(32);
1022 is_prc
= (attribs
& 0x0001)?1:0;
1024 // It is not easy to identify PDB format from its contents.
1025 // But it's good to do what we can, because the .pdb file extension
1026 // is used by several other formats.
1028 // Check for sensible type/creator codes
1030 if(!looks_like_a_4cc_b(id
)) return 0;
1031 if(!looks_like_a_4cc_b(&id
[4])) return 0;
1033 // Check for known file types
1035 if(!de_memcmp(id
, (const void*)"appl", 4)) appl_type
= 1;
1038 for(k
=0; k
<DE_ARRAYCOUNT(ids
); k
++) {
1039 if(!de_memcmp(id
, ids
[k
], 8)) return 100;
1043 // There must be at least one NUL byte in the first 32 bytes,
1044 // and any bytes before the NUL must presumably be printable.
1045 de_read(buf
, 0, 32);
1047 for(k
=0; k
<32; k
++) {
1052 if(buf
[k
]<32) return 0;
1056 appinfo_offs
= de_getu32be(52);
1057 sortinfo_offs
= de_getu32be(56);
1058 num_recs
= de_getu16be(72+4);
1060 curpos
= 72 + 6 + num_recs
*(is_prc
?10:8);
1061 if(curpos
>c
->infile
->len
) return 0;
1063 if(appinfo_offs
!=0) {
1064 if(appinfo_offs
<curpos
) return 0;
1065 curpos
= appinfo_offs
;
1067 if(curpos
>c
->infile
->len
) return 0;
1069 if(sortinfo_offs
!=0) {
1070 if(sortinfo_offs
<curpos
) return 0;
1071 curpos
= sortinfo_offs
;
1073 if(curpos
>c
->infile
->len
) return 0;
1076 // Sanity-check the first record.
1077 // TODO? We could check more than one record.
1079 if(!looks_like_a_4cc_f(c
->infile
, 72+6+0)) return 0;
1082 recdata_offs
= de_getu32be(72+6+6);
1084 recdata_offs
= de_getu32be(72+6+0);
1085 if(recdata_offs
<curpos
) return 0;
1086 curpos
= recdata_offs
;
1087 if(curpos
>c
->infile
->len
) return 0;
1091 if(appl_type
) return 90;
1097 static void de_help_pdb_prc(deark
*c
)
1099 de_msg(c
, "-opt timestampfmt=<macbe|unixbe|macle> : The format of the "
1100 "timestamp fields");
1103 void de_module_palmdb(deark
*c
, struct deark_module_info
*mi
)
1106 mi
->id_alias
[0] = "palmrc";
1107 mi
->desc
= "Palm OS PDB or PRC";
1108 mi
->run_fn
= de_run_pdb_or_prc
;
1109 mi
->identify_fn
= de_identify_pdb_prc
;
1110 mi
->help_fn
= de_help_pdb_prc
;