lzhuf: Refactored to avoid direct array access
[deark.git] / modules / palmpdb.c
blob486614e7329f6f59852e28caeb36085d5af42316
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);
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 {
27 u32 offset;
30 struct rec_list_struct {
31 i64 num_recs;
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;
36 i64 icon_name_count;
39 struct rsrc_type_info_struct {
40 u32 id;
41 u32 flags; // 1=standard Palm resource
42 const char *descr;
43 void* /* rsrc_decoder_fn */ decoder_fn;
46 struct img_gen_info {
47 i64 w, h;
48 i64 bitsperpixel;
49 i64 rowbytes;
50 de_finfo *fi;
51 unsigned int createflags;
54 typedef struct localctx_struct {
55 #define FMT_PDB 1
56 #define FMT_PRC 2
57 int file_fmt;
58 #define SUBFMT_NONE 0
59 #define SUBFMT_PQA 1
60 #define SUBFMT_IMAGEVIEWER 2
61 int file_subfmt;
63 #define TIMESTAMPFMT_UNKNOWN 0
64 #define TIMESTAMPFMT_MACBE 1
65 #define TIMESTAMPFMT_UNIXBE 2
66 #define TIMESTAMPFMT_MACLE 3
67 int timestampfmt;
69 int has_nonzero_ids;
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;
75 i64 appinfo_offs;
76 i64 sortinfo_offs;
77 struct rec_list_struct rec_list;
78 de_ucstring *icon_name;
79 } lctx;
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];
86 i64 ts_int;
88 de_zeromem(&ts, sizeof(struct de_timestamp));
89 if(returned_ts) {
90 de_zeromem(returned_ts, sizeof(struct de_timestamp));
93 ts_int = de_getu32be(pos);
94 if(ts_int==0) {
95 de_dbg(c, "%s: 0 (not set)", name);
96 goto done;
99 de_dbg(c, "%s: ...", name);
100 de_dbg_indent(c, 1);
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);
131 done:
132 if(returned_ts && d->timestampfmt!=TIMESTAMPFMT_UNKNOWN) {
133 *returned_ts = ts;
137 static void get_db_attr_descr(de_ucstring *s, u32 attribs)
139 size_t i;
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)
165 i64 pos1 = 0;
166 de_ucstring *dname = NULL;
167 de_ucstring *attr_descr = NULL;
168 u32 attribs;
169 u32 version;
170 i64 x;
171 int retval = 0;
173 de_dbg(c, "header at %d", (int)pos1);
174 de_dbg_indent(c, 1);
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");
216 else {
217 de_declare_fmt(c, "Palm PDB");
220 else if(d->file_fmt==FMT_PRC) {
221 d->fmt_shortname = "PRC";
223 else {
224 goto done;
227 x = de_getu32be(68);
228 de_dbg(c, "uniqueIDseed: %u", (unsigned int)x);
230 retval = 1;
231 done:
232 de_dbg_indent(c, -1);
233 ucstring_destroy(dname);
234 ucstring_destroy(attr_descr);
235 return retval;
238 static i64 calc_rec_len(deark *c, lctx *d, i64 rec_idx)
240 i64 len;
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);
244 else {
245 len = c->infile->len - (i64)d->rec_list.rec_data[rec_idx].offset;
247 return len;
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)
255 de_finfo *fi = NULL;
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);
261 if(ext_sz) {
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);
268 done:
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)
275 i64 pos = pos1;
276 u8 b1, b2;
277 i64 count;
279 while(pos < pos1+len) {
280 b1 = dbuf_getbyte(inf, pos++);
281 if(b1>128) {
282 count = (i64)b1-127;
283 b2 = dbuf_getbyte(inf, pos++);
284 dbuf_write_run(unc_pixels, b2, count);
286 else {
287 count = (i64)b1+1;
288 dbuf_copy(inf, pos, count, unc_pixels);
289 pos += count;
292 return 1;
295 static void do_generate_unc_image(deark *c, lctx *d, dbuf *unc_pixels,
296 struct img_gen_info *igi)
298 i64 i, j;
299 u8 b;
300 u8 b_adj;
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);
306 goto done;
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);
321 done:
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;
335 if(cmpr_meth==0) {
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);
341 else {
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)
357 u8 imgver;
358 u8 imgtype;
359 unsigned int cmpr_meth;
360 i64 x0, x1;
361 i64 pos = pos1;
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);
370 de_dbg_indent(c, 1);
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);
380 pos += 32;
382 imgver = de_getbyte(pos++);
383 de_dbg(c, "version: 0x%02x", (unsigned int)imgver);
384 cmpr_meth = (unsigned int)(imgver&0x07);
385 de_dbg_indent(c, 1);
386 de_dbg(c, "compression method: %u", cmpr_meth);
387 de_dbg_indent(c, -1);
388 if(imgver>0x01) {
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);
395 de_dbg_indent(c, 1);
396 switch(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);
406 pos += 4;
408 x0 = de_getu32be(pos);
409 de_dbg(c, "note: 0x%08x", (unsigned int)x0);
410 pos += 4;
412 x0 = de_getu16be(pos);
413 pos += 2;
414 x1 = de_getu16be(pos);
415 pos += 2;
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);
420 pos += 4;
422 // TODO: Is the anchor signed or unsigned?
423 x0 = de_getu16be(pos);
424 pos += 2;
425 x1 = de_getu16be(pos);
426 pos += 2;
427 de_dbg(c, "anchor: (%d,%d)", (int)x0, (int)x1);
429 igi->w = de_getu16be(pos);
430 pos += 2;
431 igi->h = de_getu16be(pos);
432 pos += 2;
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);
440 de_dbg_indent(c, 1);
441 do_generate_image(c, d, c->infile, pos, num_raw_image_bytes,
442 cmpr_meth, igi);
443 de_dbg_indent(c, -1);
445 done:
446 de_dbg_indent(c, -1);
447 ucstring_destroy(iname);
448 if(igi) {
449 de_finfo_destroy(c, igi->fi);
450 de_free(c, igi);
454 static void do_imgview_text(deark *c, lctx *d, i64 pos, i64 len)
456 de_ucstring *s = NULL;
458 if(len<1) return;
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".
467 dbuf *outf = NULL;
468 outf = dbuf_create_output_file(c, "comment.txt", NULL, DE_CREATEFLAG_IS_AUX);
469 ucstring_write_as_utf8(c, s, outf, 1);
470 dbuf_close(outf);
473 ucstring_destroy(s);
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)
488 i64 data_offs;
489 u8 attribs;
490 u32 id;
491 i64 data_len;
492 de_ucstring *attr_descr = NULL;
493 char extfull[80];
495 de_dbg(c, "record[%d] at %d", (int)rec_idx, (int)pos1);
496 de_dbg_indent(c, 1);
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;
508 char tmpstr[80];
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";
523 else idname = "?";
525 if(idname)
526 de_snprintf(tmpstr, sizeof(tmpstr), " (%s)", idname);
527 else
528 tmpstr[0] = '\0';
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);
546 return 1;
549 static void do_string_rsrc(deark *c, lctx *d,
550 i64 pos, i64 len,
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);
568 ucstring_destroy(s);
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)
617 size_t i;
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];
624 return NULL;
627 static int do_read_prc_record(deark *c, lctx *d, i64 rec_idx, i64 pos1)
629 u32 id;
630 struct de_fourcc rsrc_type_4cc;
631 i64 data_offs;
632 i64 data_len;
633 int always_extract = 0;
634 de_ucstring *ext_ucstring = NULL;
635 int ext_set = 0;
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);
640 de_dbg_indent(c, 1);
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) {
661 case CODE_Tbmp:
662 ucstring_append_sz(ext_ucstring, ".palm", DE_ENCODING_LATIN1);
663 ext_set = 1;
664 always_extract = 1;
665 break;
666 // TODO: tbmf, taif
667 case CODE_tAIB:
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);
673 ext_set = 1;
674 always_extract = 1;
675 break;
676 case CODE_tAIN:
677 do_string_rsrc(c, d, data_offs, data_len, rti,
678 (d->rec_list.icon_name_count==1)?0x1:0x0);
679 break;
680 case CODE_tAIS:
681 case CODE_tSTR:
682 case CODE_tver:
683 do_string_rsrc(c, d, data_offs, data_len, rti, 0);
684 break;
687 if(!ext_set) {
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);
694 return 1;
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)
701 i64 i;
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];
707 // Put idx at [0]
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)
715 i64 i;
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) {
726 u32 rsrc_type;
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);
736 else {
737 u32 id;
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);
751 return 0;
754 // Record data must not start before the previous record's data.
755 if(i>0) {
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);
759 return 0;
763 return 1;
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)
769 i64 i;
770 i64 x;
771 int retval = 0;
773 de_dbg(c, "%s record list at %d", d->fmt_shortname, (int)pos1);
774 de_dbg_indent(c, 1);
776 // 6-byte header
778 x = de_getu32be(pos1);
779 de_dbg(c, "nextRecordListID: %d", (int)x);
780 if(x!=0) {
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);
787 /////
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++) {
799 i64 n;
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))
803 goto done;
805 else {
806 if(!do_read_pdb_record(c, d, n, pos1+6+d->rec_size*n))
807 goto done;
810 retval = 1;
811 done:
812 de_dbg_indent(c, -1);
813 return retval;
816 static void do_pqa_app_info_block(deark *c, lctx *d, i64 pos1, i64 len)
818 u32 sig;
819 u32 ux;
820 i64 n;
821 de_ucstring *s = NULL;
822 i64 pos = pos1;
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));
839 ucstring_empty(s);
840 pos += 2*n;
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));
846 ucstring_empty(s);
847 pos += 2*n;
849 de_dbg(c, "icon");
850 de_dbg_indent(c, 1);
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);
853 pos += 2*n;
854 de_dbg_indent(c, -1);
856 de_dbg(c, "smIcon");
857 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);
860 pos += 2*n;
861 de_dbg_indent(c, -1);
863 ucstring_destroy(s);
866 static void do_app_info_block(deark *c, lctx *d)
868 i64 len;
870 if(d->appinfo_offs==0) return;
871 de_dbg(c, "app info block at %d", (int)d->appinfo_offs);
873 de_dbg_indent(c, 1);
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;
880 else {
881 len = c->infile->len - d->appinfo_offs;
883 de_dbg(c, "calculated len: %d", (int)len);
885 if(len>0) {
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
888 // format.
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)
901 i64 len;
903 if(d->sortinfo_offs==0) return;
904 de_dbg(c, "sort info block at %d", (int)d->sortinfo_offs);
906 de_dbg_indent(c, 1);
907 if(d->rec_list.num_recs>0) {
908 len = (i64)d->rec_list.rec_data[0].offset - d->sortinfo_offs;
910 else {
911 len = c->infile->len - d->sortinfo_offs;
913 de_dbg(c, "calculated len: %d", (int)len);
915 if(len>0) {
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)
924 if(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);
928 de_free(c, d);
932 static void de_run_pdb_or_prc(deark *c, lctx *d, de_module_params *mparams)
934 const char *s;
936 s = de_get_ext_option(c, "palm:timestampfmt");
937 if(s) {
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);
950 done:
954 static void de_run_palmdb(deark *c, de_module_params *mparams)
956 lctx *d = NULL;
957 d = de_malloc(c, sizeof(lctx));
958 d->file_fmt = FMT_PDB;
959 de_run_pdb_or_prc(c, d, mparams);
960 free_lctx(c, d);
963 static void de_run_palmrc(deark *c, de_module_params *mparams)
965 lctx *d = NULL;
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);
970 free_lctx(c, d);
973 static int de_identify_palmdb(deark *c)
975 int has_ext = 0;
976 u8 id[8];
977 u8 buf[32];
978 u32 attribs;
979 i64 appinfo_offs;
980 i64 sortinfo_offs;
981 i64 n;
982 i64 num_recs;
983 i64 recdata_offs;
984 i64 curpos;
986 static const char *exts[] = {"pdb", "prc", "pqa", "mobi"};
987 static const char *ids[] = {"vIMGView", "TEXtREAd", "pqa clpr", "BOOKMOBI"};
988 size_t k;
990 for(k=0; k<DE_ARRAYCOUNT(exts); k++) {
991 if(de_input_file_has_ext(c, exts[k])) {
992 has_ext = 1;
993 break;
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
1006 de_read(id, 60, 8);
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);
1019 n = 0;
1020 for(k=0; k<32; k++) {
1021 if(buf[k]=='\0') {
1022 n=1;
1023 break;
1025 if(buf[k]<32) return 0;
1027 if(n==0) 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;
1048 if(num_recs>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;
1057 return 25;
1060 static int looks_like_a_4cc(dbuf *f, i64 pos)
1062 i64 i;
1063 u8 buf[4];
1064 dbuf_read(f, buf, pos, 4);
1065 for(i=0; i<4; i++) {
1066 if(buf[i]<32 || buf[i]>126) return 0;
1068 return 1;
1071 // returns 1 if it might be a pdb, 2 if it might be a prc
1072 // TODO: pdb
1073 // TODO: Improve this ID algorithm
1074 static int identify_pdb_prc_internal(deark *c, dbuf *f)
1076 i64 nrecs;
1077 u32 attribs;
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;
1085 return 2;
1088 static int de_identify_palmrc(deark *c)
1090 int prc_ext = 0;
1091 int pdb_ext = 0;
1092 int x;
1093 u8 id[8];
1095 if(de_input_file_has_ext(c, "prc"))
1096 prc_ext = 1;
1097 else if(de_input_file_has_ext(c, "pdb"))
1098 pdb_ext = 1;
1099 if(!prc_ext && !pdb_ext) return 0;
1101 de_read(id, 60, 8);
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;
1107 return 0;
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)
1118 mi->id = "palmdb";
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)
1127 mi->id = "palmrc";
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;