fnt: Improved error handling, etc.
[deark.git] / modules / palmpdb.c
blob4b55f84a0364e3637c105e85e62c163c536254b0
1 // This file is part of Deark.
2 // Copyright (C) 2017 Jason Summers
3 // See the file COPYING for terms of use.
5 // Palm Database (PDB)
6 // Palm Resource (PRC)
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 {
26 u32 offset;
29 struct rec_list_struct {
30 i64 num_recs;
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;
35 i64 icon_name_count;
38 struct rsrc_type_info_struct {
39 u32 id;
40 u32 flags; // 1=standard Palm resource
41 const char *descr;
42 void* /* rsrc_decoder_fn */ decoder_fn;
45 struct img_gen_info {
46 i64 w, h;
47 i64 bitsperpixel;
48 i64 rowbytes;
49 de_finfo *fi;
50 unsigned int createflags;
53 typedef struct localctx_struct {
54 #define FMT_PDB 1
55 #define FMT_PRC 2
56 int file_fmt;
57 #define SUBFMT_NONE 0
58 #define SUBFMT_PQA 1
59 #define SUBFMT_IMAGEVIEWER 2
60 int file_subfmt;
62 #define TIMESTAMPFMT_UNKNOWN 0
63 #define TIMESTAMPFMT_MACBE 1
64 #define TIMESTAMPFMT_UNIXBE 2
65 #define TIMESTAMPFMT_MACLE 3
66 int timestampfmt;
68 int has_nonzero_ids;
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;
74 i64 appinfo_offs;
75 i64 sortinfo_offs;
76 struct rec_list_struct rec_list;
77 de_ucstring *icon_name;
78 } lctx;
80 static int timestamp_is_plausible(struct de_timestamp *ts)
82 i64 ts_unix;
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
92 return 1;
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];
100 i64 ts_int;
102 de_zeromem(&ts, sizeof(struct de_timestamp));
103 if(returned_ts) {
104 de_zeromem(returned_ts, sizeof(struct de_timestamp));
107 ts_int = de_getu32be(pos);
108 if(ts_int==0) {
109 de_dbg(c, "%s: 0 (not set)", name);
110 goto done;
113 de_dbg(c, "%s: ...", name);
114 de_dbg_indent(c, 1);
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);
145 done:
146 if(returned_ts && d->timestampfmt!=TIMESTAMPFMT_UNKNOWN) {
147 *returned_ts = ts;
151 static void get_db_attr_descr(de_ucstring *s, u32 attribs)
153 size_t i;
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)
179 i64 pos1 = 0;
180 de_ucstring *dname = NULL;
181 de_ucstring *attr_descr = NULL;
182 u32 attribs;
183 u32 version;
184 i64 x;
185 int retval = 0;
187 de_dbg(c, "header at %d", (int)pos1);
188 de_dbg_indent(c, 1);
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;
198 else {
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");
236 else {
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");
244 else {
245 goto done;
248 x = de_getu32be(68);
249 de_dbg(c, "uniqueIDseed: %u", (unsigned int)x);
251 retval = 1;
252 done:
253 de_dbg_indent(c, -1);
254 ucstring_destroy(dname);
255 ucstring_destroy(attr_descr);
256 return retval;
259 static i64 calc_rec_len(deark *c, lctx *d, i64 rec_idx)
261 i64 len;
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);
265 else {
266 len = c->infile->len - (i64)d->rec_list.rec_data[rec_idx].offset;
268 return len;
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)
276 de_finfo *fi = NULL;
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);
282 if(ext_sz) {
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);
289 done:
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)
296 i64 pos = pos1;
297 u8 b1, b2;
298 i64 count;
300 while(pos < pos1+len) {
301 b1 = dbuf_getbyte(inf, pos++);
302 if(b1>128) {
303 count = (i64)b1-127;
304 b2 = dbuf_getbyte(inf, pos++);
305 dbuf_write_run(unc_pixels, b2, count);
307 else {
308 count = (i64)b1+1;
309 dbuf_copy(inf, pos, count, unc_pixels);
310 pos += count;
313 return 1;
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;
320 de_color pal[16];
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);
325 goto done;
327 else if(igi->bitsperpixel==2 || igi->bitsperpixel==4) {
330 else {
331 goto done;
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,
337 pal, img, 0);
338 de_bitmap_write_to_file_finfo(img, igi->fi, igi->createflags);
340 done:
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;
355 if(cmpr_meth==0) {
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);
361 else {
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)
377 u8 imgver;
378 u8 imgtype;
379 unsigned int cmpr_meth;
380 i64 x0, x1;
381 i64 pos = pos1;
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);
390 de_dbg_indent(c, 1);
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);
400 pos += 32;
402 imgver = de_getbyte(pos++);
403 de_dbg(c, "version: 0x%02x", (unsigned int)imgver);
404 cmpr_meth = (unsigned int)(imgver&0x07);
405 de_dbg_indent(c, 1);
406 de_dbg(c, "compression method: %u", cmpr_meth);
407 de_dbg_indent(c, -1);
408 if(imgver>0x01) {
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);
415 de_dbg_indent(c, 1);
416 switch(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);
426 pos += 4;
428 x0 = de_getu32be(pos);
429 de_dbg(c, "note: 0x%08x", (unsigned int)x0);
430 pos += 4;
432 x0 = de_getu16be(pos);
433 pos += 2;
434 x1 = de_getu16be(pos);
435 pos += 2;
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);
440 pos += 4;
442 // TODO: Is the anchor signed or unsigned?
443 x0 = de_getu16be(pos);
444 pos += 2;
445 x1 = de_getu16be(pos);
446 pos += 2;
447 de_dbg(c, "anchor: (%d,%d)", (int)x0, (int)x1);
449 igi->w = de_getu16be(pos);
450 pos += 2;
451 igi->h = de_getu16be(pos);
452 pos += 2;
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);
460 de_dbg_indent(c, 1);
461 do_generate_image(c, d, c->infile, pos, num_raw_image_bytes,
462 cmpr_meth, igi);
463 de_dbg_indent(c, -1);
465 done:
466 de_dbg_indent(c, -1);
467 ucstring_destroy(iname);
468 if(igi) {
469 de_finfo_destroy(c, igi->fi);
470 de_free(c, igi);
474 static void do_imgview_text(deark *c, lctx *d, i64 pos, i64 len)
476 de_ucstring *s = NULL;
478 if(len<1) return;
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".
487 dbuf *outf = NULL;
488 outf = dbuf_create_output_file(c, "comment.txt", NULL, DE_CREATEFLAG_IS_AUX);
489 ucstring_write_as_utf8(c, s, outf, 1);
490 dbuf_close(outf);
493 ucstring_destroy(s);
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)
508 i64 data_offs;
509 u8 attribs;
510 u32 id;
511 i64 data_len;
512 de_ucstring *attr_descr = NULL;
513 char extfull[80];
515 de_dbg(c, "record[%d] at %d", (int)rec_idx, (int)pos1);
516 de_dbg_indent(c, 1);
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;
528 char tmpstr[80];
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";
543 else idname = "?";
545 if(idname)
546 de_snprintf(tmpstr, sizeof(tmpstr), " (%s)", idname);
547 else
548 tmpstr[0] = '\0';
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);
566 return 1;
569 static void do_string_rsrc(deark *c, lctx *d,
570 i64 pos, i64 len,
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);
588 ucstring_destroy(s);
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)
637 size_t i;
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];
644 return NULL;
647 static int do_read_prc_record(deark *c, lctx *d, i64 rec_idx, i64 pos1)
649 u32 id;
650 struct de_fourcc rsrc_type_4cc;
651 i64 data_offs;
652 i64 data_len;
653 int always_extract = 0;
654 de_ucstring *ext_ucstring = NULL;
655 int ext_set = 0;
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);
660 de_dbg_indent(c, 1);
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) {
681 case CODE_Tbmp:
682 ucstring_append_sz(ext_ucstring, ".palm", DE_ENCODING_LATIN1);
683 ext_set = 1;
684 always_extract = 1;
685 break;
686 // TODO: tbmf, taif
687 case CODE_tAIB:
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);
693 ext_set = 1;
694 always_extract = 1;
695 break;
696 case CODE_tAIN:
697 do_string_rsrc(c, d, data_offs, data_len, rti,
698 (d->rec_list.icon_name_count==1)?0x1:0x0);
699 break;
700 case CODE_tAIS:
701 case CODE_tSTR:
702 case CODE_tver:
703 do_string_rsrc(c, d, data_offs, data_len, rti, 0);
704 break;
707 if(!ext_set) {
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);
714 return 1;
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)
721 i64 i;
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];
727 // Put idx at [0]
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)
735 i64 i;
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) {
746 u32 rsrc_type;
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);
756 else {
757 u32 id;
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);
771 return 0;
774 // Record data must not start before the previous record's data.
775 if(i>0) {
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);
779 return 0;
783 return 1;
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)
789 i64 i;
790 i64 x;
791 int retval = 0;
793 de_dbg(c, "%s record list at %d", d->fmt_shortname, (int)pos1);
794 de_dbg_indent(c, 1);
796 // 6-byte header
798 x = de_getu32be(pos1);
799 de_dbg(c, "nextRecordListID: %d", (int)x);
800 if(x!=0) {
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);
807 /////
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++) {
819 i64 n;
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))
823 goto done;
825 else {
826 if(!do_read_pdb_record(c, d, n, pos1+6+d->rec_size*n))
827 goto done;
830 retval = 1;
831 done:
832 de_dbg_indent(c, -1);
833 return retval;
836 static void do_pqa_app_info_block(deark *c, lctx *d, i64 pos1, i64 len)
838 u32 sig;
839 u32 ux;
840 i64 n;
841 de_ucstring *s = NULL;
842 i64 pos = pos1;
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));
859 ucstring_empty(s);
860 pos += 2*n;
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));
866 ucstring_empty(s);
867 pos += 2*n;
869 de_dbg(c, "icon");
870 de_dbg_indent(c, 1);
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);
873 pos += 2*n;
874 de_dbg_indent(c, -1);
876 de_dbg(c, "smIcon");
877 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);
880 pos += 2*n;
881 de_dbg_indent(c, -1);
883 ucstring_destroy(s);
886 static void do_app_info_block(deark *c, lctx *d)
888 i64 len;
890 if(d->appinfo_offs==0) return;
891 de_dbg(c, "app info block at %d", (int)d->appinfo_offs);
893 de_dbg_indent(c, 1);
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;
900 else {
901 len = c->infile->len - d->appinfo_offs;
903 de_dbg(c, "calculated len: %d", (int)len);
905 if(len>0) {
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
908 // format.
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)
921 i64 len;
923 if(d->sortinfo_offs==0) return;
924 de_dbg(c, "sort info block at %d", (int)d->sortinfo_offs);
926 de_dbg_indent(c, 1);
927 if(d->rec_list.num_recs>0) {
928 len = (i64)d->rec_list.rec_data[0].offset - d->sortinfo_offs;
930 else {
931 len = c->infile->len - d->sortinfo_offs;
933 de_dbg(c, "calculated len: %d", (int)len);
935 if(len>0) {
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)
944 if(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);
948 de_free(c, d);
952 static void de_run_pdb_or_prc(deark *c, de_module_params *mparams)
954 const char *s;
955 lctx *d = NULL;
957 d = de_malloc(c, sizeof(lctx));
958 s = de_get_ext_option(c, "palm:timestampfmt");
959 if(s) {
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);
972 done:
973 free_lctx(c, d);
976 static int looks_like_a_4cc_b(const u8 *buf)
978 i64 i;
980 for(i=0; i<4; i++) {
981 if(buf[i]<32 || buf[i]>126) return 0;
983 return 1;
986 static int looks_like_a_4cc_f(dbuf *f, i64 pos)
988 u8 buf[4];
990 dbuf_read(f, buf, pos, 4);
991 return looks_like_a_4cc_b(buf);
994 static int de_identify_pdb_prc(deark *c)
996 u8 has_ext = 0;
997 u8 is_prc;
998 u8 appl_type = 0;
999 u8 id[8];
1000 u8 buf[32];
1001 u32 attribs;
1002 i64 appinfo_offs;
1003 i64 sortinfo_offs;
1004 i64 n;
1005 i64 num_recs;
1006 i64 recdata_offs;
1007 i64 curpos;
1009 static const char *exts[] = {"pdb", "prc", "pqa", "mobi"};
1010 static const char *ids[] = {"vIMGView", "TEXtREAd", "pqa clpr", "BOOKMOBI"};
1011 size_t k;
1013 for(k=0; k<DE_ARRAYCOUNT(exts); k++) {
1014 if(de_input_file_has_ext(c, exts[k])) {
1015 has_ext = 1;
1016 break;
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
1029 de_read(id, 60, 8);
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
1034 if(is_prc) {
1035 if(!de_memcmp(id, (const void*)"appl", 4)) appl_type = 1;
1037 else {
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);
1046 n = 0;
1047 for(k=0; k<32; k++) {
1048 if(buf[k]=='\0') {
1049 n=1;
1050 break;
1052 if(buf[k]<32) return 0;
1054 if(n==0) 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;
1075 if(num_recs>0) {
1076 // Sanity-check the first record.
1077 // TODO? We could check more than one record.
1078 if(is_prc) {
1079 if(!looks_like_a_4cc_f(c->infile, 72+6+0)) return 0;
1081 if(is_prc)
1082 recdata_offs = de_getu32be(72+6+6);
1083 else
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;
1090 if(num_recs>0) {
1091 if(appl_type) return 90;
1092 return 25;
1094 return 9;
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)
1105 mi->id = "palmdb";
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;