// This file is part of Deark.
// Copyright (C) 2018 Jason Summers
// See the file COPYING for terms of use.

// Mac Resource [Manager] format

#include <deark-config.h>
#include <deark-private.h>
#include <deark-fmtutil.h>
DE_DECLARE_MODULE(de_module_macrsrc);

#define CODE_8BIM 0x3842494dU
#define CODE_ANPA 0x414e5041U
#define CODE_CURS 0x43555253U
#define CODE_MeSa 0x4d655361U
#define CODE_PICT 0x50494354U
#define CODE_SICN 0x5349434eU
#define CODE_cicn 0x6369636eU
#define CODE_crsr 0x63727372U
#define CODE_icns 0x69636e73U
#define CODE_moov 0x6d6f6f76U

struct icns_stream_item {
	int id;
	dbuf *tmpf;
};

typedef struct localctx_struct {
	u8 extract_raw;
	u8 combine_SICN;
	i64 data_offs, map_offs;
	i64 data_size, map_size;

	i64 typeListOffset_abs;
	i64 nameListOffset_abs;
	dbuf *psrc_stream;
	const char *errmsgprefix;

#define MAX_ICNS_STREAMS 64
	size_t num_icns_streams_used;
	struct icns_stream_item icns_strm[MAX_ICNS_STREAMS];
} lctx;

struct rsrctypeinfo {
	struct de_fourcc fcc;
	int is_icns_type;
	int is_psrc_type;
};

struct rsrcinstanceinfo {
	int id;
	UI idx;
	u8 attribs;
	u8 has_name;
	i64 data_offset;
	i64 name_offset;
	i64 name_raw_len;
	de_ucstring *name;
};

#define CODE_ICN_ 0x49434e23U // ICN#
#define CODE_ICON 0x49434f4eU
#define CODE_icl4 0x69636c34U
#define CODE_icl8 0x69636c38U
#define CODE_icm_ 0x69636d23U // icm#
#define CODE_icm4 0x69636d34U
#define CODE_icm8 0x69636d38U
#define CODE_ics_ 0x69637323U // ics#
#define CODE_ics4 0x69637334U
#define CODE_ics8 0x69637338U

// Helper function to set the filename of an finfo.
static void set_resource_filename(deark *c, lctx *d, de_finfo *fi,
	struct rsrctypeinfo *rti, struct rsrcinstanceinfo *rii, const char *token)
{
	de_ucstring *tmpname = NULL;

	tmpname = ucstring_create(c);
	if(c->filenames_from_file && ucstring_isnonempty(rii->name)) {
		ucstring_append_ucstring(tmpname, rii->name);
		ucstring_append_sz(tmpname, ".", DE_ENCODING_LATIN1);
	}

	if(token) {
		ucstring_append_sz(tmpname, token, DE_ENCODING_LATIN1);
	}
	else {
		ucstring_append_sz(tmpname, rti->fcc.id_sanitized_sz, DE_ENCODING_LATIN1);
	}
	de_finfo_set_name_from_ucstring(c, fi, tmpname, 0);
	ucstring_destroy(tmpname);
}

static int is_icns_icon(deark *c, lctx *d, struct rsrctypeinfo *rti)
{
	// TODO: There are many more icns icon types, but it's not clear
	// to me if any others are found in resource forks.
	switch(rti->fcc.id) {
	case CODE_icm_: case CODE_icm4: case CODE_icm8: // 16x12
	case CODE_ics_: case CODE_ics4: case CODE_ics8: // 16x16
	case CODE_ICN_: case CODE_ICON: case CODE_icl4: case CODE_icl8: // 32x32
		return 1;
	}
	return 0;
}

// Some resource files have multiple icons of the same type, but I don't think
// an icns file is supposed to have multiple icons of the same type.
// So, simply writing all icons to the same icns file is not the right thing to
// do.
// What *is* the right thing to do is not clear to me. But separating the
// icons by resource ID (up to some limit) is at least an improvement.
static dbuf *get_icns_stream(deark *c, lctx *d, int id)
{
	size_t entry = 0;
	size_t i;

	for(i=0; i<d->num_icns_streams_used; i++) {
		if(d->icns_strm[i].id == id) {
			entry = i;
			goto found;
		}
	}

	if(d->num_icns_streams_used >= MAX_ICNS_STREAMS) {
		entry = MAX_ICNS_STREAMS - 1; // stream of last resort
	}
	else {
		entry = d->num_icns_streams_used++;
		d->icns_strm[entry].id = id;
	}

found:
	if(!d->icns_strm[entry].tmpf) {
		d->icns_strm[entry].tmpf = dbuf_create_membuf(c, 0, 0);
	}
	return d->icns_strm[entry].tmpf;
}

// Construct an .icns file from the suitable icon resources found
// in this file.
static void finalize_icns_stream(deark *c, lctx *d, dbuf *tmpf)
{
	dbuf *outf = NULL;

	if(!tmpf) return;
	outf = dbuf_create_output_file(c, "icns", NULL, 0);
	dbuf_writeu32be(outf, CODE_icns);
	dbuf_writeu32be(outf, 8+tmpf->len);
	dbuf_copy(tmpf, 0, tmpf->len, outf);
	dbuf_close(outf);
}

static void finalize_icns_streams(deark *c, lctx *d)
{
	size_t i;

	for(i=0; i<d->num_icns_streams_used; i++) {
		if(d->icns_strm[i].tmpf) {
			finalize_icns_stream(c, d, d->icns_strm[i].tmpf);
			dbuf_close(d->icns_strm[i].tmpf);
			d->icns_strm[i].tmpf = NULL;
		}
	}
}

static void open_psrc_stream(deark *c, lctx *d)
{
	if(d->psrc_stream) return;
	d->psrc_stream = dbuf_create_membuf(c, 0, 0);
}

static void finalize_psrc_stream(deark *c, lctx *d)
{
	if(!d->psrc_stream) return;
	fmtutil_handle_photoshop_rsrc(c, d->psrc_stream, 0, d->psrc_stream->len, 0x1);
	dbuf_close(d->psrc_stream);
	d->psrc_stream = NULL;
}

static void do_psrc_resource(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii, i64 dpos, i64 dlen)
{
	if(!d->psrc_stream) {
		open_psrc_stream(c, d);
	}

	// Convert this exploded format to the normal Photoshop Resources format,
	// which we will eventually write to a file.
	// It would be messy to implement a way to directly use the decoder in the
	// psd module. And even if we did that, the option to do this conversion
	// might still be useful.

	de_dbg(c, "[Photoshop resource]");
	dbuf_write(d->psrc_stream, rti->fcc.bytes, 4);
	dbuf_writei16be(d->psrc_stream, (i64)rii->id);
	if(rii->has_name) {
		dbuf_copy(c->infile, rii->name_offset, rii->name_raw_len, d->psrc_stream);
		if(rii->name_raw_len%2) {
			dbuf_writebyte(d->psrc_stream, 0); // padding byte for name
		}
	}
	else {
		dbuf_write_zeroes(d->psrc_stream, 2);
	}
	dbuf_writeu32be(d->psrc_stream, dlen);
	dbuf_copy(c->infile, dpos, dlen, d->psrc_stream);
	if(dlen%2) {
		dbuf_writebyte(d->psrc_stream, 0); // padding byte for data
	}
}

// Handle 'crsr' and 'CURS' cursors.
// The documentation of 'crsr' in ImagingWithQuickDraw seems to have only
// the vaguest resemblance to reality. The code here is partly based on
// reverse engineering.
static void do_crsr_CURS_resource(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii, i64 pos1, i64 dlen)
{
	struct fmtutil_macbitmap_info *bi = NULL;
	de_bitmap *img_color = NULL;
	de_bitmap *img_bw = NULL;
	de_bitmap *img_mask = NULL;
	de_finfo *fi = NULL;
	i64 pos = pos1;
	i64 pixmap_offs = 0;
	i64 pixdata_offs = 0;
	i64 n;
	i64 colortable_size = 0;
	int is_crsr = (rti->fcc.id==CODE_crsr);

	if(dlen<68) goto done;
	fi = de_finfo_create(c);

	if(is_crsr) {
		// TODO: Do we need special handling for type=0x8000 (b/w cursor)?
		n = de_getu16be_p(&pos);
		de_dbg(c, "cursor type: 0x%04x", (unsigned int)n);
		if(n!=0x8000 && n!=0x8001) {
			de_err(c, "Invalid or unsupported 'crsr' cursor type");;
			goto done;
		}
		pixmap_offs = de_getu32be_p(&pos);
		de_dbg(c, "offset to pixel map: %d", (int)pixmap_offs);
		pixdata_offs = de_getu32be_p(&pos);
		de_dbg(c, "offset to pixel data: %d", (int)pixdata_offs);
		pos += 10; // other fields
	}

	de_dbg(c, "b/w foreground at %"I64_FMT, pos);
	img_bw = de_bitmap_create(c, 16, 16, 2);
	de_convert_image_bilevel(c->infile, pos, 2, img_bw, DE_CVTF_WHITEISZERO);
	pos += 32;

	de_dbg(c, "mask at %"I64_FMT, pos);
	img_mask = de_bitmap_create(c, 16, 16, 1);
	de_convert_image_bilevel(c->infile, pos, 2, img_mask, 0);
	pos += 32;

	// I'm assuming the hotspot is a QuickDraw Point structure.
	fi->hotspot_y = (int)de_geti16be_p(&pos);
	fi->hotspot_x = (int)de_geti16be_p(&pos);
	fi->has_hotspot = 1;
	de_dbg(c, "hotspot: (%d,%d)", fi->hotspot_x, fi->hotspot_y);

	de_bitmap_apply_mask(img_bw, img_mask, 0);
	set_resource_filename(c, d, fi, rti, rii, (is_crsr?"crsr_bw":NULL));
	de_bitmap_write_to_file_finfo(img_bw, fi, 0);
	if(!is_crsr) goto done;

	bi = de_malloc(c, sizeof(struct fmtutil_macbitmap_info));
	if(pixmap_offs >= dlen) goto done;
	pos = pos1+pixmap_offs;
	fmtutil_macbitmap_read_baseaddr(c, c->infile, bi, pos);
	pos += 4;
	fmtutil_macbitmap_read_rowbytes_and_bounds(c, c->infile, bi, pos);
	pos += 10;
	fmtutil_macbitmap_read_pixmap_only_fields(c, c->infile, bi, pos);
	pos += 36;
	if(!de_good_image_dimensions(c, bi->npwidth, bi->height)) goto done;

	if((i64)bi->pmTable != pixdata_offs + bi->rowbytes * bi->height) {
		de_warn(c, "Unexpected color table offset. "
			"Cursor might not be decoded correctly.");
	}
	if(bi->pmTable>0 && bi->pmTable<dlen) {
		pos = pos1 + (i64)bi->pmTable;
	}
	else {
		pos = pos1 + pixdata_offs + bi->rowbytes * bi->height;
	}
	if(!fmtutil_macbitmap_read_colortable(c, c->infile, bi, pos, &colortable_size)) {
		goto done;
	}

	img_color = de_bitmap_create2(c, bi->npwidth, bi->pdwidth, bi->height, 4);

	if(pixdata_offs >= dlen) goto done;
	pos = pos1 + pixdata_offs;
	de_dbg(c, "color pixels at %"I64_FMT, pos);
	de_convert_image_paletted(c->infile, pos, bi->pixelsize, bi->rowbytes,
		bi->pal, img_color, 0);
	de_bitmap_apply_mask(img_color, img_mask, 0);

	set_resource_filename(c, d, fi, rti, rii, NULL);
	if(bi->hdpi>=1.0 && bi->vdpi>=1.0) {
		fi->density.code = DE_DENSITY_DPI;
		fi->density.xdens = bi->hdpi;
		fi->density.ydens = bi->vdpi;
	}
	de_bitmap_write_to_file_finfo(img_color, fi, 0);

done:
	de_free(c, bi);
	de_bitmap_destroy(img_color);
	de_bitmap_destroy(img_bw);
	de_bitmap_destroy(img_mask);
	de_finfo_destroy(c, fi);
}

// Write each small icon in an SICN set to an individual file.
static void do_SICN_resource_1(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii, i64 pos1, i64 numicons)
{
	de_finfo *fi = NULL;
	de_bitmap *img = NULL;
	i64 i;

	fi = de_finfo_create(c);
	set_resource_filename(c, d, fi, rti, rii, NULL);
	img = de_bitmap_create(c, 16, 16, 1);

	for(i=0; i<numicons; i++) {
		de_bitmap_rect(img, 0, 0, img->width, img->height, DE_STOCKCOLOR_BLACK, 0);
		de_convert_image_bilevel(c->infile, pos1+32*i, 2, img, DE_CVTF_WHITEISZERO);
		de_bitmap_write_to_file_finfo(img, fi, DE_CREATEFLAG_IS_BWIMG);
	}

	de_bitmap_destroy(img);
	de_finfo_destroy(c, fi);
}

// Write all icons in an SICN set to a single "gallery" file.
static void do_SICN_resource_gallery(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii, i64 pos1, i64 numicons)
{
#define SICN_PER_ROW 16
	de_finfo *fi = NULL;
	i64 ncols, nrows;
	i64 canvas_w, canvas_h;
	de_bitmap *cnv = NULL;
	i64 i;

	fi = de_finfo_create(c);
	set_resource_filename(c, d, fi, rti, rii, "SICN_gallery");

	ncols = (numicons<SICN_PER_ROW) ? numicons : SICN_PER_ROW;
	nrows = (numicons+SICN_PER_ROW-1) / SICN_PER_ROW;
	canvas_w = 1 + ncols*17;
	canvas_h = 1 + nrows*17;

	cnv = de_bitmap_create(c, canvas_w, canvas_h, 3);
	de_bitmap_rect(cnv, 0, 0, cnv->width, cnv->height, DE_MAKE_RGB(32,32,144), 0);

	for(i=0; i<numicons; i++) {
		de_bitmap *img = NULL;

		img = de_bitmap_create(c, 16, 16, 1);
		de_convert_image_bilevel(c->infile, pos1+32*i, 2, img, DE_CVTF_WHITEISZERO);
		de_bitmap_copy_rect(img, cnv, 0, 0, 16, 16,
			1 + 17*(i%SICN_PER_ROW), 1 + 17*(i/SICN_PER_ROW), 0);
		de_bitmap_destroy(img);
	}

	de_bitmap_write_to_file_finfo(cnv, fi, 0);

	de_bitmap_destroy(cnv);
	de_finfo_destroy(c, fi);
}

// SICN - Small icons - One or more 16x16 images
static void do_SICN_resource(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii, i64 pos1, i64 len)
{
	i64 numicons;

	numicons = len/32;
	de_dbg(c, "num icons: %"I64_FMT, numicons);
	if(numicons>1 && d->combine_SICN) {
		do_SICN_resource_gallery(c, d, rti, rii, pos1, numicons);
	}
	else {
		do_SICN_resource_1(c, d, rti, rii, pos1, numicons);
	}
}

static void do_cicn_resource(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii, i64 dpos, i64 dlen)
{
	struct fmtutil_macbitmap_info *bi_fgcolor = NULL;
	struct fmtutil_macbitmap_info *bi_mask = NULL;
	struct fmtutil_macbitmap_info *bi_bw = NULL;
	de_bitmap *img_fgcolor = NULL;
	de_bitmap *img_mask = NULL;
	de_bitmap *img_bw = NULL;
	de_finfo *fi = NULL;
	i64 fgcolor_bitssize;
	i64 mask_bitssize;
	i64 bw_bitssize = 0;
	int bw_exists = 0;
	i64 colortable_size = 0;
	i64 pos = dpos;
	int needmsg = 1;
	int ok = 0;
	int saved_indent_level;

	de_dbg_indent_save(c, &saved_indent_level);
	bi_fgcolor = de_malloc(c, sizeof(struct fmtutil_macbitmap_info));
	bi_mask = de_malloc(c, sizeof(struct fmtutil_macbitmap_info));
	bi_bw = de_malloc(c, sizeof(struct fmtutil_macbitmap_info));
	fi = de_finfo_create(c);

	de_dbg(c, "[color pixmap header]");
	de_dbg_indent(c, 1);
	fmtutil_macbitmap_read_baseaddr(c, c->infile, bi_fgcolor, pos);
	pos += 4;
	fmtutil_macbitmap_read_rowbytes_and_bounds(c, c->infile, bi_fgcolor, pos);
	if(!bi_fgcolor->pixmap_flag) goto done;
	pos += 10;
	fmtutil_macbitmap_read_pixmap_only_fields(c, c->infile, bi_fgcolor, pos);
	pos += 36;

	// Some images have 0 in one or both of these fields.
	if(bi_fgcolor->cmpcount==0) {
		bi_fgcolor->cmpcount = 1;
	}
	if(bi_fgcolor->cmpsize==0) {
		bi_fgcolor->cmpsize = bi_fgcolor->pixelsize;
	}

	de_dbg_indent(c, -1);

	de_dbg(c, "[mask bitmap header]");
	de_dbg_indent(c, 1);
	fmtutil_macbitmap_read_baseaddr(c, c->infile, bi_mask, pos);
	pos += 4;
	fmtutil_macbitmap_read_rowbytes_and_bounds(c, c->infile, bi_mask, pos);
	pos += 10;
	de_dbg_indent(c, -1);

	de_dbg(c, "[b/w bitmap header]");
	de_dbg_indent(c, 1);
	fmtutil_macbitmap_read_baseaddr(c, c->infile, bi_bw, pos);
	pos += 4;
	fmtutil_macbitmap_read_rowbytes_and_bounds(c, c->infile, bi_bw, pos);
	pos += 10;
	de_dbg_indent(c, -1);

	pos += 4; // "icon data"

	// This is said to be optional, but I don't know how to tell if it exists.
	if(bi_bw->rowbytes && bi_bw->height && bi_bw->npwidth) {
		bw_exists = 1;
	}

	if(!de_good_image_dimensions_noerr(c, bi_fgcolor->npwidth, bi_fgcolor->height)) goto done;
	if(!de_good_image_dimensions_noerr(c, bi_mask->npwidth, bi_mask->height)) goto done;
	if(bw_exists && !de_good_image_dimensions_noerr(c, bi_bw->npwidth, bi_bw->height)) goto done;

	if(bi_fgcolor->pixeltype!=0) goto done;
	// TODO: pixsize=8 w/ cmpsize=4 is possibly legal
	if(bi_fgcolor->pixelsize!=bi_fgcolor->cmpsize) goto done;
	if(bi_fgcolor->cmpcount!=1) goto done;
	if(bi_fgcolor->pixelsize!=1 && bi_fgcolor->pixelsize!=2 &&
		bi_fgcolor->pixelsize!=4 && bi_fgcolor->pixelsize!=8)
	{
		goto done;
	}

	mask_bitssize = bi_mask->rowbytes * bi_mask->height;
	if(bw_exists) bw_bitssize = bi_bw->rowbytes * bi_bw->height;
	fgcolor_bitssize =  bi_fgcolor->rowbytes * bi_fgcolor->height;

	if(pos+mask_bitssize > dpos+dlen) goto done;
	de_dbg(c, "mask bitmap at %"I64_FMT", len=%"I64_FMT, pos, mask_bitssize);
	img_mask = de_bitmap_create2(c, bi_mask->npwidth, bi_mask->pdwidth, bi_mask->height, 1);
	de_convert_image_bilevel(c->infile, pos, bi_mask->rowbytes, img_mask, 0);
	pos += mask_bitssize;

	if(bw_exists) {
		if(pos+bw_bitssize > dpos+dlen) goto done;
		de_dbg(c, "bw bitmap at %"I64_FMT", len=%"I64_FMT, pos, bw_bitssize);
		img_bw = de_bitmap_create2(c, bi_bw->npwidth, bi_bw->pdwidth, bi_bw->height, 2);
		de_convert_image_bilevel(c->infile, pos, bi_bw->rowbytes, img_bw,
			DE_CVTF_WHITEISZERO);
		de_bitmap_apply_mask(img_bw, img_mask, 0);
		set_resource_filename(c, d, fi, rti, rii, "cicn_bw");
		de_bitmap_write_to_file_finfo(img_bw, fi, 0);
		pos += bw_bitssize;
	}
	else {
		de_dbg(c, "[assuming there is no bw bitmap]");
	}

	if(!fmtutil_macbitmap_read_colortable(c, c->infile, bi_fgcolor,
		pos, &colortable_size))
	{
		goto done;
	}
	pos += colortable_size;

	if(pos+fgcolor_bitssize > dpos+dlen) goto done;
	de_dbg(c, "color bitmap at %"I64_FMT", len=%"I64_FMT, pos, fgcolor_bitssize);
	img_fgcolor = de_bitmap_create2(c, bi_fgcolor->npwidth, bi_fgcolor->pdwidth, bi_fgcolor->height, 4);
	de_convert_image_paletted(c->infile, pos, bi_fgcolor->pixelsize, bi_fgcolor->rowbytes,
		bi_fgcolor->pal, img_fgcolor, 0);
	de_bitmap_apply_mask(img_fgcolor, img_mask, 0);
	if(bi_fgcolor->hdpi>=1.0 && bi_fgcolor->vdpi>=1.0) {
		fi->density.code = DE_DENSITY_DPI;
		fi->density.xdens = bi_fgcolor->hdpi;
		fi->density.ydens = bi_fgcolor->vdpi;
	}
	set_resource_filename(c, d, fi, rti, rii, NULL);
	de_bitmap_write_to_file_finfo(img_fgcolor, fi, 0);
	//pos += fgcolor_bitssize;
	ok = 1;

done:
	if(!ok && needmsg) {
		// TODO: There are a small but significant number of 'cicn' resources that
		// appear to use a completely different format than the one I know about.
		// (Or it could be some sort of systematic corruption.)
		de_err(c, "Failed to parse 'cicn' icon resource at %"I64_FMT, dpos);
	}
	de_bitmap_destroy(img_fgcolor);
	de_bitmap_destroy(img_mask);
	de_bitmap_destroy(img_bw);
	de_free(c, bi_fgcolor);
	de_free(c, bi_mask);
	de_free(c, bi_bw);
	de_finfo_destroy(c, fi);
	de_dbg_indent_restore(c, saved_indent_level);
}

static int looks_like_pict(deark *c, lctx *d, struct rsrcinstanceinfo *rii,
	i64 pos, i64 len)
{
	if(len>=12 && !dbuf_memcmp(c->infile, pos+10, "\x11\x01", 2)) {
		return 1; // PICTv1
	}
	if(len>=16 && !dbuf_memcmp(c->infile, pos+10, "\x00\x11\x02\xff\x0c\x00", 6)) {
		return 1; // PICTv2
	}
	return 0;
}

static void extract_raw_rsrc(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii, i64 dpos, i64 dlen)
{
	de_finfo *fi = NULL;
	de_ucstring *s = NULL;

	fi = de_finfo_create(c);
	s = ucstring_create(c);

	ucstring_append_sz(s, rti->fcc.id_sanitized_sz, DE_ENCODING_LATIN1);
	ucstring_strip_trailing_spaces(s);
	if(rii->attribs&0x01) {
		ucstring_append_sz(s, ".cmpr", DE_ENCODING_LATIN1);
	}
	else {
		ucstring_append_sz(s, ".bin", DE_ENCODING_LATIN1);
	}
	de_finfo_set_name_from_ucstring(c, fi, s, 0);
	dbuf_create_file_from_slice(c->infile, dpos, dlen, NULL, fi, 0x0);

	de_finfo_destroy(c, fi);
	ucstring_destroy(s);
}

static void do_resource_data(deark *c, lctx *d, struct rsrctypeinfo *rti,
	struct rsrcinstanceinfo *rii)
{
	i64 dpos, dlen;
	const char *ext = "bin";
	int extr_flag = 0;
	int is_pict = 0;
	int handled = 0;

	de_dbg(c, "resource data at %d", (int)rii->data_offset);
	de_dbg_indent(c, 1);
	dlen = de_getu32be(rii->data_offset);
	dpos = rii->data_offset+4;
	de_dbg(c, "dpos: %d, dlen: %d", (int)dpos, (int)dlen);
	if(dpos+dlen > c->infile->len) goto done;

	if(d->extract_raw) {
		extract_raw_rsrc(c, d, rti, rii, dpos, dlen);
		goto done;
	}

	if(rii->attribs&0x01) {
		; // Compressed. Don't know how to handle this.
	}
	else if(rti->fcc.id==CODE_PICT && looks_like_pict(c, d, rii, dpos, dlen)) {
		ext = "pict";
		extr_flag = 1;
		is_pict = 1;
	}
	else if(rti->fcc.id==CODE_icns) {
		ext = "icns";
		extr_flag = 1;
	}
	else if(rti->fcc.id==CODE_moov) {
		ext = "mov";
		extr_flag = 1;
	}
	else if(rti->fcc.id==CODE_ANPA && rii->id==10000) {
		de_dbg(c, "IPTC data at %"I64_FMT, dpos);
		de_dbg_indent(c, 1);
		fmtutil_handle_iptc(c, c->infile, dpos, dlen, 0x0);
		de_dbg_indent(c, -1);
		handled = 1;
	}
	else if(rti->is_icns_type) {
		dbuf *icns_dbuf;

		de_dbg(c, "[icns resource]");
		icns_dbuf = get_icns_stream(c, d, rii->id);
		dbuf_write(icns_dbuf, rti->fcc.bytes, 4);
		dbuf_writeu32be(icns_dbuf, 8+dlen);
		dbuf_copy(c->infile, dpos, dlen, icns_dbuf);
		handled = 1;
	}
	else if(rti->is_psrc_type) {
		do_psrc_resource(c, d, rti, rii, dpos, dlen);
		handled = 1;
	}
	else if(rti->fcc.id==CODE_CURS || rti->fcc.id==CODE_crsr) {
		do_crsr_CURS_resource(c, d, rti, rii, dpos, dlen);
		handled = 1;
	}
	else if(rti->fcc.id==CODE_cicn) {
		do_cicn_resource(c, d, rti, rii, dpos, dlen);
		handled = 1;
	}
	else if(rti->fcc.id==CODE_SICN) {
		do_SICN_resource(c, d, rti, rii, dpos, dlen);
		handled = 1;
	}

	if(extr_flag) {
		dbuf *outf = NULL;
		de_finfo *fi = NULL;

		fi = de_finfo_create(c);
		set_resource_filename(c, d, fi, rti, rii, ext);
		outf = dbuf_create_output_file(c, NULL, fi, 0);
		de_finfo_destroy(c, fi);
		if(is_pict) {
			dbuf_write_zeroes(outf, 512);
		}
		dbuf_copy(c->infile, dpos, dlen, outf);
		dbuf_close(outf);
		handled = 1;
	}

	if(!handled && c->debug_level>=2) {
		de_dbg_hexdump(c, c->infile, dpos, dlen, 256, NULL, 0x1);
	}

done:
	de_dbg_indent(c, -1);
}

// Sets rii->name_raw_len.
// Sets rii->name.
static void do_resource_name(deark *c, lctx *d, struct rsrcinstanceinfo *rii)
{
	i64 nlen;

	nlen = (i64)de_getbyte(rii->name_offset);
	rii->name_raw_len = 1+nlen;
	rii->name = ucstring_create(c);
	dbuf_read_to_ucstring(c->infile, rii->name_offset+1, nlen, rii->name , 0, DE_ENCODING_MACROMAN);
	de_dbg(c, "name: \"%s\"", ucstring_getpsz_d(rii->name ));
}

static const char *get_ownertype_name(UI n)
{
	static const char *names[8] = { "driver/desk accessory",
		"window def", "menu def", "widget def", "printer driver",
		"package", "control panel", "?" };

	return names[n%8];
}

static void do_resource_record(deark *c, lctx *d, struct rsrctypeinfo *rti,
	i64 pos1, UI idx)
{
	i64 dataOffset_rel;
	i64 nameOffset_rel;
	i64 pos = pos1;
	struct rsrcinstanceinfo rii;

	de_zeromem(&rii, sizeof(struct rsrcinstanceinfo));
	rii.idx = idx;
	rii.id = (int)de_geti16be_p(&pos);
	de_dbg(c, "id: %d", rii.id);
	if(rii.id>=(-16384) && rii.id<=(-1)) {
		UI u;
		UI ownertype, ownerid, subid;

		u = (UI)(rii.id + 65536);
		ownertype = (u & 0xf800) >> 11;
		ownerid = (u & 0x07e0) >> 5;
		subid = (u & 0x001f);
		de_dbg_indent(c, 1);
		de_dbg(c, "owner type: %u (%s)", ownertype, get_ownertype_name(ownertype));
		de_dbg(c, "owner id: %u", ownerid);
		de_dbg(c, "sub id: %u", subid);
		de_dbg_indent(c, -1);
	}

	nameOffset_rel = de_getu16be_p(&pos);
	if(nameOffset_rel!=0xffff) {
		rii.has_name = 1;
		de_dbg(c, "nameOffset: (%d+)%d", (int)d->nameListOffset_abs, (int)nameOffset_rel);
		rii.name_offset = d->nameListOffset_abs+nameOffset_rel;
		do_resource_name(c, d, &rii);
	}
	rii.attribs = de_getbyte_p(&pos);
	if(rii.attribs==0) {
		de_dbg(c, "attributes: 0x%02x", (unsigned int)rii.attribs);
	}
	else {
		de_ucstring *flags_str;
		flags_str = ucstring_create(c);
		if(rii.attribs & 0x40) ucstring_append_flags_item(flags_str, "system heap");
		if(rii.attribs & 0x20) ucstring_append_flags_item(flags_str, "purgeable");
		if(rii.attribs & 0x10) ucstring_append_flags_item(flags_str, "locked");
		if(rii.attribs & 0x08) ucstring_append_flags_item(flags_str, "read-only");
		if(rii.attribs & 0x04) ucstring_append_flags_item(flags_str, "preload");
		if(rii.attribs & 0x01) ucstring_append_flags_item(flags_str, "compressed");
		de_dbg(c, "attributes: 0x%02x (%s)", (unsigned int)rii.attribs, ucstring_getpsz_d(flags_str));
		ucstring_destroy(flags_str);
	}

	dataOffset_rel = dbuf_getint_ext(c->infile, pos, 3, 0, 0);
	rii.data_offset = d->data_offs + dataOffset_rel;
	pos += 3;
	de_dbg(c, "dataOffset: (%d+)%d", (int)d->data_offs, (int)dataOffset_rel);
	do_resource_data(c, d, rti, &rii);

	if(rii.name) ucstring_destroy(rii.name);
}

static void do_resource_list(deark *c, lctx *d, struct rsrctypeinfo *rti,
	i64 rsrc_list_offs, UI count)
{
	UI idx;
	i64 pos = rsrc_list_offs;

	de_dbg(c, "resource list at %d", (int)rsrc_list_offs);
	de_dbg_indent(c, 1);
	for(idx=0; idx<count; idx++) {
		de_dbg(c, "resource record[%u] at %"I64_FMT" (type '%s')", idx, pos,
			rti->fcc.id_dbgstr);
		de_dbg_indent(c, 1);
		do_resource_record(c, d, rti, pos, idx);
		de_dbg_indent(c, -1);
		pos += 12;
	}
	de_dbg_indent(c, -1);
}

static void do_type_item(deark *c, lctx *d, i64 type_list_offs,
	i64 idx, i64 pos1)
{
	i64 pos = pos1;
	UI count;
	i64 list_offs_rel;
	struct rsrctypeinfo rti;

	de_zeromem(&rti, sizeof(struct rsrctypeinfo));
	dbuf_read_fourcc(c->infile, pos, &rti.fcc, 4, 0x0);
	de_dbg(c, "resource type: '%s'", rti.fcc.id_dbgstr);
	pos += 4;
	rti.is_icns_type = is_icns_icon(c, d, &rti);

	// TODO: What other signatures should we include?
	if(rti.fcc.id==CODE_8BIM || rti.fcc.id==CODE_MeSa) {
		rti.is_psrc_type = 1;
	}

	count = (UI)(1+de_getu16be_p(&pos));
	de_dbg(c, "resource count: %u", count);
	list_offs_rel = de_getu16be_p(&pos);
	de_dbg(c, "list offset: (%d+)%d", (int)type_list_offs, (int)list_offs_rel);

	do_resource_list(c, d, &rti, type_list_offs+list_offs_rel, count);
}

static void do_type_list(deark *c, lctx *d)
{
	i64 pos1 = d->typeListOffset_abs;
	i64 pos = pos1;
	i64 type_count_raw;
	i64 type_count;
	i64 k;

	de_dbg(c, "type list at %d", (int)pos1);
	de_dbg_indent(c, 1);
	type_count_raw = de_getu16be_p(&pos);
	type_count = (type_count_raw==0xffff)?0:(type_count_raw+1);
	de_dbg(c, "type count: %d", (int)type_count);

	for(k=0; k<type_count; k++) {
		de_dbg(c, "type record[%d] at %d", (int)k, (int)pos);
		de_dbg_indent(c, 1);
		do_type_item(c, d, pos1, k, pos);
		pos += 8;
		de_dbg_indent(c, -1);
	}
	de_dbg_indent(c, -1);
}

static void do_map(deark *c, lctx *d, i64 map_offs, i64 map_size)
{
	i64 pos = map_offs;
	i64 typeListOffset_rel, nameListOffset_rel;
	i64 n;

	n = de_getu32be(map_offs+4);
	if(n!=map_offs) {
		de_err(c, "%sResource map section not found, expected to be at %"I64_FMT,
			d->errmsgprefix, map_offs);
		return;
	}

	de_dbg(c, "resource map section at %d", (int)map_offs);
	de_dbg_indent(c, 1);

	pos += 16; // copy of header
	pos += 4; // nextResourceMap
	pos += 2; // fileRef
	pos += 2; // attributes

	typeListOffset_rel = de_getu16be_p(&pos);
	de_dbg(c, "type list offset: (%d+)%d", (int)map_offs,
		(int)typeListOffset_rel);
	d->typeListOffset_abs = map_offs + typeListOffset_rel;

	nameListOffset_rel = de_getu16be_p(&pos);
	de_dbg(c, "name list offset: (%d+)%d", (int)map_offs,
		(int)nameListOffset_rel);
	d->nameListOffset_abs = map_offs + nameListOffset_rel;

	if(typeListOffset_rel<28) {
		de_err(c, "%sInvalid typeListOffset", d->errmsgprefix);
		goto done;
	}

	do_type_list(c, d);

done:
	de_dbg_indent(c, -1);
}

static void de_run_macrsrc(deark *c, de_module_params *mparams)
{
	lctx *d = NULL;
	i64 pos;

	d = de_malloc(c, sizeof(lctx));
	if(c->module_disposition==DE_MODDISP_INTERNAL) {
		d->errmsgprefix = "[Resource format] ";
	}
	else {
		d->errmsgprefix = "";
	}

	if(de_get_ext_option(c, "macrsrc:extractraw")) {
		d->extract_raw = 1;
	}
	d->combine_SICN = (u8)de_get_ext_option_bool(c, "macrsrc:combinesicn", 0);

	if(c->infile->len<16) {
		de_err(c, "%sFile too small to be a valid Resource file", d->errmsgprefix);
		goto done;
	}

	pos = 0;
	d->data_offs = de_getu32be_p(&pos);
	d->map_offs = de_getu32be_p(&pos);
	d->data_size = de_getu32be_p(&pos);
	d->map_size = de_getu32be_p(&pos);
	de_dbg(c, "data: pos=%"I64_FMT", len=%"I64_FMT, d->data_offs, d->data_size);
	de_dbg(c, "map: pos=%"I64_FMT", len=%"I64_FMT, d->map_offs, d->map_size);
	do_map(c, d, d->map_offs, d->map_size);

done:
	finalize_icns_streams(c, d);
	finalize_psrc_stream(c, d);
	de_free(c, d);
}

static int de_identify_macrsrc(deark *c)
{
	u8 b[16];
	i64 n[4];
	size_t k;

	if(de_getu32be(0)!=256) return 0;
	de_read(b, 0, 16);
	for(k=0; k<4; k++) {
		n[k] = de_getu32be_direct(&b[4*k]);
	}
	if(n[0]+n[2]>n[1]) return 0; // data can't go past map start
	if(n[3]<30) return 0; // minimum map len
	if(n[1]+n[3]>c->infile->len) return 0; // map can't go past eof
	// map should start with a copy of the header
	if(dbuf_memcmp(c->infile, n[1], (const void*)b, 16)) return 0;
	if(n[1]+n[3]==c->infile->len) return 100;
	return 75;
}

static void de_help_macrsrc(deark *c)
{
	de_msg(c, "-opt macrsrc:extractraw : Extract all resources to files");
	de_msg(c, "-opt macrsrc:combinesicn : Put 'small icon' sets in a single image");
}

void de_module_macrsrc(deark *c, struct deark_module_info *mi)
{
	mi->id = "macrsrc";
	mi->desc = "Macintosh Resource Manager";
	mi->run_fn = de_run_macrsrc;
	mi->identify_fn = de_identify_macrsrc;
	mi->help_fn = de_help_macrsrc;
}
