/* ico2png.c - Create icon and cursor files from PNG images
 *
 * minimalistic version extracted from package 'icoutil'
 * 'icoutil' is copyrighted as follows :
 *
 * Copyright (C) 1998-2005 Oskar Liljeblad
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#ifndef _GNU_SOURCE
# define _GNU_SOURCE 1
#endif

#include <stdint.h>		/* POSIX/Gnulib */
#include <stdio.h>		/* C89 */
#include <stdbool.h>		/* POSIX/Gnulib */
#include <stdlib.h>		/* C89 */
#include <stdarg.h>		/* C89 */
#include <stddef.h>
#include <dirent.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <error.h>
#include <errno.h>
#include <getopt.h>

#include <png.h>

/* Version number of program/package */
#define PROGRAM "ico2png"
#define PACKAGE "icoutils"
#define BUGREPORT "oskar@osk.mine.nu"
#define VERSION "0.26.0"

# ifndef __attribute__
#  if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
#   define __attribute__(x)
#  endif
# endif
#define PACKED __attribute__ ((packed))

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

struct MessageHeader {
	struct MessageHeader *old;
	char *message;
};

static char *error_message = NULL;
static struct MessageHeader *message_header = NULL;

#define memclear(m, s)		memset((m), 0, (s))

void
xalloc_die (void)
{
  error (1, 0, "%s", "memory exhausted");

  /* The `noreturn' cannot be given to error, since it may return if
     its first argument is 0.  To help compilers understand the
     xalloc_die does not return, call abort.  Also, the abort is a
     safety feature if exit_failure is 0 (which shouldn't happen).  */
  abort ();
}

#define NO_MEMORY_CHECK
#ifdef NO_MEMORY_CHECK

#define xmalloc malloc
#define xrealloc realloc
#define xcalloc(s) calloc(1, ((s)))
#define xstrdup strdup

#else
#define xalloc_oversized(n, s) \
    ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n))

static inline void *
xnmalloc_inline (size_t n, size_t s)
{
  void *p;
  if (xalloc_oversized (n, s) || (! (p = malloc (n * s)) && n != 0))
    xalloc_die ();
  return p;
}

void *
xmalloc (size_t n)
{
  return xnmalloc_inline (n, 1);
}

static inline void *
xnrealloc_inline (void *p, size_t n, size_t s)
{
  if (xalloc_oversized (n, s) || (! (p = realloc (p, n * s)) && n != 0))
    xalloc_die ();
  return p;
}

void *
xrealloc (void *p, size_t n)
{
  return xnrealloc_inline (p, n, 1);
}

void *
xcalloc (size_t s)
{
  return memset (xmalloc (s), 0, s);
}

/* Clone an object P of size S, with error checking.  There's no need
   for xnmemdup (P, N, S), since xmemdup (P, N * S) works without any
   need for an arithmetic overflow check.  */

/* Clone STRING.  */
char *
xstrdup (char const *string)
{
  size_t s = strlen (string) + 1;
  return  memcpy (xmalloc (s), string, s);
}
#endif

typedef void (*IteratorFunc)(void *);

typedef struct _IteratorClass IteratorClass;
typedef struct _Iterator Iterator;

struct _IteratorClass {
	bool (*has_next)(Iterator *it);
	void *(*next)(Iterator *it);
	void (*remove)(Iterator *it);
	void (*free)(Iterator *it);
	void (*restart)(Iterator *it);
	void *(*previous)(Iterator *it);
	void (*add)(Iterator *it, void *value);
};

struct _Iterator {
	IteratorClass *class;
};

bool
iterator_has_next(Iterator *it)
{
	return it->class->has_next(it);
}

void *
iterator_next(Iterator *it)
{
	return it->class->next(it);
}

void
iterator_free(Iterator *it)
{
	if (it->class->free == NULL)
		free(it);
	else 
		it->class->free(it);
}

static inline const char *
get_message_header(void)
{
	if (message_header != NULL)
		return message_header->message;
	return PROGRAM;
}

static void
v_warn(const char *msg, va_list ap)
{
	fprintf(stderr, "%s: ", get_message_header());
	if (msg != NULL)
		vfprintf(stderr, msg, ap);
	fprintf(stderr, "\n");
}

void
warn(const char *msg, ...)
{
	va_list ap;
	
	va_start(ap, msg);
	v_warn(msg, ap);
	va_end(ap);
}

/**
 * Free all global memory allocated by the error facilities
 * provided here.
 */
void
free_error(void)
{
	struct MessageHeader *hdr;

	for (hdr = message_header; hdr != NULL; hdr = hdr->old) {
		free(hdr->message);
		free(hdr);
	}
	if (error_message != NULL)
		free(error_message);
}

#define errstr strerror(errno)

static void
v_warn_errno(const char *msg, va_list ap)
{
	fprintf(stderr, "%s: ", get_message_header());
	if (msg != NULL) {
		vfprintf(stderr, msg, ap);
		fprintf(stderr, ": ");
	}
	fprintf(stderr, "%s\n", strerror(errno));
}

void
warn_errno(const char *msg, ...)
{
	va_list ap;
	
	va_start(ap, msg);
	v_warn_errno(msg, ap);
	va_end(ap);
}

void
die_errno(const char *msg, ...)
{
	va_list ap;
	
	va_start(ap, msg);
	v_warn_errno(msg, ap);
	va_end(ap);

	free_error();
	exit(1);
}

void
die_memory(void)
{
	errno = ENOMEM;
	die_errno(NULL);
}

void *
check_memory(void *mem)
{
	if (mem == NULL)
		die_memory();
	return mem;
}

typedef struct _Entry Entry;

struct _Entry {
	void *key;
	void *value;
	Entry *next;
};

typedef uint32_t (*HashFunc)(const void *);
typedef int32_t (*CompareFunc)(const void *, const void *);

typedef struct _HMap HMap;

struct _HMap {
	Entry **buckets;
	uint32_t buckets_length;
	uint32_t threshold;
	float load_factor;
	uint32_t size;
	
	HashFunc hash_key;
	CompareFunc compare_keys;
};

uint32_t
string_hash(const char *key)
{
	uint32_t hash = 0;

	for (; *key != '\0'; key++)
		hash = (hash << 5) - hash + *key;

	return hash;
}

HMap *
hmap_new_specific(uint32_t initial_capacity, float load_factor)
{
	HMap *map = xmalloc(sizeof(HMap));

	map->buckets = xmalloc(initial_capacity * sizeof(Entry *));
	memclear(map->buckets, initial_capacity * sizeof(Entry *));

	map->buckets_length = initial_capacity;
	map->threshold = (uint32_t) (initial_capacity * load_factor);
	map->load_factor = load_factor;
	map->size = 0;

	map->hash_key = (HashFunc) string_hash;
	map->compare_keys = (CompareFunc) strcmp;

	return map;
}

#define DEFAULT_CAPACITY 16
#define DEFAULT_LOAD_FACTOR           0.75F

HMap *
hmap_new(void)
{
	return hmap_new_specific(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}

void
hmap_set_hash_function(HMap *map, HashFunc hash_key)
{
	map->hash_key = hash_key;
}

void
hmap_set_compare_function(HMap *map, CompareFunc compare_keys)
{
	map->compare_keys = compare_keys;
}

void
hmap_iterate_values(HMap *map, IteratorFunc iterator_func)
{
	uint32_t i;

    for (i = 0; i < map->buckets_length; i++) {
		Entry *entry;
		for (entry = map->buckets[i]; entry != NULL; entry = entry->next)
			iterator_func(entry->value);
	}
}

void
hmap_clear(HMap *map)
{
	uint32_t i;

    for (i = 0; i < map->buckets_length; i++) {
		Entry *entry = map->buckets[i];
		while (entry != NULL) {
			Entry *next = entry->next;
			free(entry);
			entry = next;
		}
		map->buckets[i] = NULL;
	}

	map->size = 0;
}

void
hmap_free(HMap *map)
{
	hmap_clear(map);
	free(map->buckets);
	free(map);
}

inline uint32_t
hmap_hash(HMap *map, const void *key)
{
//	printf("hash %s into -> %d pos %d (%d)\n", key, map->hash_key(key), map->hash_key(key) % map->buckets_length, map->buckets_length);
	return (key == NULL ? 0 : (map->hash_key(key) % map->buckets_length));
}

bool
hmap_contains_key(HMap *map, const void *key)
{
	if (key == NULL) {
		Entry *entry = map->buckets[0];
		for (; entry != NULL; entry = entry->next) {
			if (entry->key == NULL)
				return true;
		}
	} else {
		Entry *entry = map->buckets[hmap_hash(map, key)];
		for (; entry != NULL; entry = entry->next) {
//			printf("compare %s vs %s -> %d\n",key,entry->key,map->compare_keys(key, entry->key));
			if (map->compare_keys(key, entry->key) == 0)
				return true;
		}
	}

	return false;
}

inline void
hmap_rehash(HMap *map)
{	
	Entry **old_buckets = map->buckets;
	uint32_t old_capacity = map->buckets_length;
	uint32_t i;

	map->buckets_length = (map->buckets_length * 2) + 1;
	map->threshold = (uint32_t) (map->buckets_length * map->load_factor);
	map->buckets = xmalloc(map->buckets_length * sizeof(Entry *));
	memclear(map->buckets, map->buckets_length * sizeof(Entry *));

	for (i = 0; i < old_capacity; i++) {
		Entry *entry = old_buckets[i];
		while (entry != NULL) {
			uint32_t index = hmap_hash(map, entry->key);
			Entry *dest = map->buckets[index];
			Entry *next;

			if (dest != NULL) {
				while (dest->next != NULL)
					dest = dest->next;
				dest->next = entry;
			} else {
				map->buckets[index] = entry;
			}

			next = entry->next;
			entry->next = NULL;
			entry = next;
		}
	}

	free(old_buckets);
}

uint32_t
hmap_size(HMap *map)
{
	return map->size;
}

void *
hmap_get(HMap *map, const void *key)
{
	if (key == NULL) {
		Entry *entry = map->buckets[0];
		for (; entry != NULL; entry = entry->next) {
			if (entry->key == NULL)
				return entry->value;
		}
	} else {
		Entry *entry = map->buckets[hmap_hash(map, key)];
		for (; entry != NULL; entry = entry->next) {
			if (map->compare_keys(key, entry->key) == 0)
				return entry->value;
		}
	}

	return NULL;
}

void *
hmap_put(HMap *map, void *key, void *value)
{
	Entry *entry;
	uint32_t index;

	if (key == NULL) {
		for (entry = map->buckets[0]; entry != NULL; entry = entry->next) {
			if (entry->key == NULL) {
				void *old_value = entry->value;
				entry->value = value;
				return old_value;
			}
		}
		index = 0;
	} else {
		index = hmap_hash(map, key);
		for (entry = map->buckets[index]; entry != NULL; entry = entry->next) {
			if (map->compare_keys(key, entry->key) == 0) {
				void *old_value = entry->value;
				entry->value = value;
				return old_value;
			}
		}
	}

	map->size++;
	if (map->size > map->threshold) {
		hmap_rehash(map);
		index = hmap_hash(map, key);
	}

	entry = xmalloc(sizeof(Entry));
	entry->key = key;
	entry->value = value;
	entry->next = map->buckets[index];
	map->buckets[index] = entry;

	return NULL;
}

void *
hmap_remove(HMap *map, const void *key)
{
	Entry *entry;
	Entry *last = NULL;

	if (key == NULL) {
		for (entry = map->buckets[0]; entry != NULL; entry = entry->next) {
			if (entry->key == NULL) {
				void *value = entry->value;
				if (last == NULL)
					map->buckets[0] = entry->next;
				else
					last->next = entry->next;
				map->size--;
				free(entry);
				return value;
			}
			last = entry;
		}
	} else {
		uint32_t index = hmap_hash(map, key);
		for (entry = map->buckets[index]; entry != NULL; entry = entry->next) {
			if (map->compare_keys(key, entry->key) == 0) {
				void *value = entry->value;
				if (last == NULL)
					map->buckets[index] = entry->next;
				else
					last->next = entry->next;
				map->size--;
				free(entry);
				return value;
			}
			last = entry;
		}
	}

	return NULL;
}

typedef struct _HMapIterator HMapIterator;

struct _HMapIterator {
	Iterator iterator;
	HMap *map;
	uint32_t index;
	Entry *entry;
	Entry *previous_entry;
};

static bool hmap_iterator_has_next(Iterator *it);
static void *hmap_iterator_next(Iterator *it);
static void hmap_iterator_remove(Iterator *it);
static void hmap_iterator_restart(Iterator *iterator);

static IteratorClass hmap_iterator_class = {
	hmap_iterator_has_next,
	hmap_iterator_next,
	hmap_iterator_remove,
	NULL,
	hmap_iterator_restart,
	NULL,
	NULL,
};

static bool
hmap_iterator_has_next(Iterator *it)
{
	return ((HMapIterator *) it)->entry != NULL;
}

static void *
hmap_iterator_next(Iterator *iterator)
{
	HMapIterator *it = (HMapIterator *) iterator;
	HMap *map = it->map;
	void *data;

	if (it->entry == NULL)
		return NULL;

	data = it->entry->value;
	it->previous_entry = it->entry;

	it->entry = it->entry->next;
	if (it->entry == NULL) {
		uint32_t i = it->index + 1;
		for (; i < map->buckets_length && map->buckets[i] == NULL; i++);
		it->index = i;
		it->entry = (i < map->buckets_length ? map->buckets[i] : NULL);
	}

	return data;
}

static void
hmap_iterator_remove(Iterator *iterator)
{
	HMapIterator *it = (HMapIterator *) iterator;
	if (it->previous_entry != NULL) {
		hmap_remove(it->map, it->previous_entry->key);
		it->previous_entry = NULL;
	}
}

static void
hmap_iterator_restart(Iterator *iterator)
{
	HMapIterator *it = (HMapIterator *) iterator;
	uint32_t i;

	for (i = 0; i < it->map->buckets_length && it->map->buckets[i] == NULL; i++);
	it->index = i;
	it->entry = (i < it->map->buckets_length ? it->map->buckets[i] : NULL);
	it->previous_entry = NULL;
}

Iterator *
hmap_value_iterator(HMap *map)
{
	HMapIterator *it = xmalloc(sizeof(HMapIterator));
	it->iterator.class = &hmap_iterator_class;
	it->map = map;
	hmap_iterator_restart(&it->iterator);
	return &it->iterator;
}

/* Main Windows BMP data structures */

typedef struct {
    uint8_t width;
    uint8_t height;
    uint8_t color_count;
    uint8_t reserved;
} Win32IconResDir;

typedef struct {
    uint16_t width;
    uint16_t height;
} Win32CursorDir;

typedef struct {
    union {
		Win32IconResDir icon;
		Win32CursorDir cursor;
    } res_info;
    uint16_t plane_count;
    uint16_t bit_count;
    uint32_t bytes_in_res;
    uint16_t res_id;
} Win32CursorIconDirEntry;

typedef struct {
    uint16_t reserved;
    uint16_t type;
    uint16_t count;
    Win32CursorIconDirEntry entries[0] PACKED;
} Win32CursorIconDir;

typedef struct {
    uint8_t width;
    uint8_t height;
    uint8_t color_count;
    uint8_t reserved;
    uint16_t hotspot_x;		/* sometimes planes... */
    uint16_t hotspot_y;		/* sometimes bit_count... */
    uint32_t dib_size;
    uint32_t dib_offset;
} Win32CursorIconFileDirEntry;

typedef struct {
    uint16_t reserved;
    uint16_t type;
    uint16_t count;
    Win32CursorIconFileDirEntry entries[0] PACKED;
} Win32CursorIconFileDir;

typedef struct {
    uint32_t size;
    int32_t width;
    int32_t height;
    uint16_t planes;
    uint16_t bit_count;
    uint32_t compression;
    uint32_t size_image;
    int32_t x_pels_per_meter;
    int32_t y_pels_per_meter;
    uint32_t clr_used;
    uint32_t clr_important;
} Win32BitmapInfoHeader;

typedef struct {
    uint8_t blue;
    uint8_t green;
    uint8_t red;
    uint8_t reserved;
} Win32RGBQuad;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
#define IMAGE_SIZEOF_SHORT_NAME 8

#define	IMAGE_RESOURCE_NAME_IS_STRING		0x80000000
#define	IMAGE_RESOURCE_DATA_IS_DIRECTORY	0x80000000

#define PE_HEADER(module) \
    ((Win32ImageNTHeaders*)((uint8_t *)(module) + \
    	(((DOSImageHeader*)(module))->lfanew)))

#define PE_SECTIONS(module) \
    ((Win32ImageSectionHeader *)((uint8_t *) &PE_HEADER(module)->optional_header + \
                           PE_HEADER(module)->file_header.size_of_optional_header))

#define IMAGE_DOS_SIGNATURE    0x5A4D     /* MZ */
#define IMAGE_OS2_SIGNATURE    0x454E     /* NE */
#define IMAGE_OS2_SIGNATURE_LE 0x454C     /* LE */
#define IMAGE_OS2_SIGNATURE_LX 0x584C     /* LX */
#define IMAGE_VXD_SIGNATURE    0x454C     /* LE */
#define IMAGE_NT_SIGNATURE     0x00004550 /* PE00 */

#define IMAGE_SCN_CNT_CODE			0x00000020
#define IMAGE_SCN_CNT_INITIALIZED_DATA		0x00000040
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA	0x00000080

#define	IMAGE_DIRECTORY_ENTRY_EXPORT		0
#define	IMAGE_DIRECTORY_ENTRY_IMPORT		1
#define	IMAGE_DIRECTORY_ENTRY_RESOURCE		2
#define	IMAGE_DIRECTORY_ENTRY_EXCEPTION		3
#define	IMAGE_DIRECTORY_ENTRY_SECURITY		4
#define	IMAGE_DIRECTORY_ENTRY_BASERELOC		5
#define	IMAGE_DIRECTORY_ENTRY_DEBUG		6
#define	IMAGE_DIRECTORY_ENTRY_COPYRIGHT		7
#define	IMAGE_DIRECTORY_ENTRY_GLOBALPTR		8   /* (MIPS GP) */
#define	IMAGE_DIRECTORY_ENTRY_TLS		9
#define	IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG	10
#define	IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT	11
#define	IMAGE_DIRECTORY_ENTRY_IAT		12  /* Import Address Table */
#define	IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT	13
#define	IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR	14

#define RT_CURSOR        1
#define RT_BITMAP        2
#define RT_ICON          3
#define RT_MENU          4
#define RT_DIALOG        5
#define RT_STRING        6
#define RT_FONTDIR       7
#define RT_FONT          8
#define RT_ACCELERATOR   9
#define RT_RCDATA        10
#define RT_MESSAGELIST   11
#define RT_GROUP_CURSOR  12
#define RT_GROUP_ICON    14

typedef struct {
    union {
    	struct {
    	    #if BITFIELDS_BIGENDIAN
    	    unsigned name_is_string:1;
    	    unsigned name_offset:31;
    	    #else
    	    unsigned name_offset:31;
    	    unsigned name_is_string:1;
    	    #endif
    	} s1;
    	uint32_t name;
    	struct {
    	    #if WORDS_BIGENDIAN
    	    uint16_t __pad;
    	    uint16_t id;
    	    #else
    	    uint16_t id;
    	    uint16_t __pad;
    	    #endif
    	} s2;
    } u1;
    union {
    	uint32_t offset_to_data;
    	struct {
    	    #if BITFIELDS_BIGENDIAN
    	    unsigned data_is_directory:1;
    	    unsigned offset_to_directory:31;
    	    #else
    	    unsigned offset_to_directory:31;
    	    unsigned data_is_directory:1;
    	    #endif
    	} s;
    } u2;
} Win32ImageResourceDirectoryEntry;

typedef struct {
    uint16_t type_id;
    uint16_t count;
    uint32_t reserved;
} Win16NETypeInfo;

typedef struct {
    uint16_t offset;
    uint16_t length;
    uint16_t flags;
    uint16_t id;
    uint16_t handle;
    uint16_t usage;
} Win16NENameInfo;

typedef struct {
    uint16_t magic;
    uint8_t ver;
    uint8_t rev;
    uint16_t enttab;
    uint16_t cbenttab;
    int32_t crc;
    uint16_t flags;
    uint16_t autodata;
    uint16_t heap;
    uint16_t stack;
    uint32_t csip;
    uint32_t sssp;
    uint16_t cseg;
    uint16_t cmod;
    uint16_t cbnrestab;
    uint16_t segtab;
    uint16_t rsrctab;
    uint16_t restab;
    uint16_t modtab;
    uint16_t imptab;
    uint32_t nrestab;
    uint16_t cmovent;
    uint16_t align;
    uint16_t cres;
    uint8_t exetyp;
    uint8_t flagsothers;
    uint16_t fastload_offset;
    uint16_t fastload_length;
    uint16_t swaparea;
    uint16_t expver;
} OS2ImageHeader;

typedef struct {
    uint16_t magic;
    uint16_t cblp;
    uint16_t cp;
    uint16_t crlc;
    uint16_t cparhdr;
    uint16_t minalloc;
    uint16_t maxalloc;
    uint16_t ss;
    uint16_t sp;
    uint16_t csum;
    uint16_t ip;
    uint16_t cs;
    uint16_t lfarlc;
    uint16_t ovno;
    uint16_t res[4];
    uint16_t oemid;
    uint16_t oeminfo;
    uint16_t res2[10];
    uint32_t lfanew;
} DOSImageHeader;

typedef struct {
    uint16_t machine;
    uint16_t number_of_sections;
    uint32_t time_date_stamp;
    uint32_t pointer_to_symbol_table;
    uint32_t number_of_symbols;
    uint16_t size_of_optional_header;
    uint16_t characteristics;
} Win32ImageFileHeader;

typedef struct {
    uint32_t virtual_address;
    uint32_t size;
} Win32ImageDataDirectory;

typedef struct {
    uint16_t magic;
    uint8_t major_linker_version;
    uint8_t minor_linker_version;
    uint32_t size_of_code;
    uint32_t size_of_initialized_data;
    uint32_t size_of_uninitialized_data;
    uint32_t address_of_entry_point;
    uint32_t base_of_code;
    uint32_t base_of_data;
    uint32_t image_base;
    uint32_t section_alignment;
    uint32_t file_alignment;
    uint16_t  major_operating_system_version;
    uint16_t  minor_operating_system_version;
    uint16_t  major_image_version;
    uint16_t  minor_image_version;
    uint16_t  major_subsystem_version;
    uint16_t  minor_subsystem_version;
    uint32_t win32_version_value;
    uint32_t size_of_image;
    uint32_t size_of_headers;
    uint32_t checksum;
    uint16_t subsystem;
    uint16_t dll_characteristics;
    uint32_t size_of_stack_reserve;
    uint32_t size_of_stack_commit;
    uint32_t size_of_heap_reserve;
    uint32_t size_of_heap_commit;
    uint32_t loader_flags;
    uint32_t number_of_rva_and_sizes;
    Win32ImageDataDirectory data_directory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} Win32ImageOptionalHeader;

typedef struct {
    uint32_t signature;
    Win32ImageFileHeader file_header;
    Win32ImageOptionalHeader optional_header;
} Win32ImageNTHeaders;

typedef struct  {
    uint8_t name[IMAGE_SIZEOF_SHORT_NAME];
    union {
	uint32_t physical_address;
	uint32_t virtual_size;
    } misc;
    uint32_t virtual_address;
    uint32_t size_of_raw_data;
    uint32_t pointer_to_raw_data;
    uint32_t pointer_to_relocations;
    uint32_t pointer_to_linenumbers;
    uint16_t number_of_relocations;
    uint16_t number_of_linenumbers;
    uint32_t characteristics;
} Win32ImageSectionHeader;

typedef struct {
    uint32_t offset_to_data;
    uint32_t size;
    uint32_t code_page;
    uint32_t resource_handle;
} Win32ImageResourceDataEntry;

typedef struct {
    uint32_t characteristics;
    uint32_t time_date_stamp;
    uint16_t major_version;
    uint16_t minor_version;
    uint16_t number_of_named_entries;
    uint16_t number_of_id_entries;
} Win32ImageResourceDirectory;

#if WORDS_BIGENDIAN

void fix_win32_cursor_icon_file_dir_endian(Win32CursorIconFileDir *obj);
void fix_win32_bitmap_info_header_endian(Win32BitmapInfoHeader *obj);
void fix_win32_cursor_icon_file_dir_entry_endian(Win32CursorIconFileDirEntry *obj);
void fix_win32_image_data_directory(Win32ImageDataDirectory *obj);
void fix_os2_image_header_endian(OS2ImageHeader *obj);
void fix_win32_image_section_header(Win32ImageSectionHeader *obj);
void fix_win32_image_header_endian(Win32ImageNTHeaders *obj);

#else

#define fix_win32_bitmap_info_header_endian(x)
#define fix_win32_cursor_icon_file_dir_endian(x)
#define fix_win32_cursor_icon_file_dir_entry_endian(x)
#define fix_win32_image_data_directory(x)
#define fix_os2_image_header_endian(x)
#define fix_win32_image_section_header(x)
#define fix_win32_image_header_endian(x)

#endif /* WORDS_BIGENDIAN */

#define ROW_BYTES(bits) ((((bits) + 31) >> 5) << 2)

typedef struct _Palette {
	HMap *map;
	Iterator *it;
} Palette;

typedef struct {
	uint8_t red;
	uint8_t green;
	uint8_t blue;
	uint32_t index;
} PaletteColor;

static uint32_t
color_hash(PaletteColor *color)
{
	return (color->red << 16) | (color->green << 8) | color->blue;
}

static int32_t
color_compare(PaletteColor *c1, PaletteColor *c2)
{
	if (c1->red != c2->red)
		return c1->red - c2->red;
	if (c1->green != c2->green)
		return c1->green - c2->green;
	if (c1->blue != c2->blue)
		return c1->blue - c2->blue;
	return 0;
}

Palette *
palette_new(void)
{
	Palette *palette = xmalloc(sizeof(Palette));
	palette->map = hmap_new();
	palette->it = NULL;
	hmap_set_hash_function(palette->map, (HashFunc) color_hash);
	hmap_set_compare_function(palette->map, (CompareFunc) color_compare);
	return palette;
}

void
palette_free(Palette *palette)
{
	if (palette->it != NULL)
		iterator_free(palette->it);
	hmap_iterate_values(palette->map, free);
	hmap_free(palette->map);
	free(palette);
}

void
palette_add(Palette *palette, uint8_t r, uint8_t g, uint8_t b)
{
	PaletteColor color = { r, g, b, 0 };

	if (!hmap_contains_key(palette->map, &color)) {
		PaletteColor *new_color = xmalloc(sizeof(PaletteColor));
		new_color->red = r;
		new_color->green = g;
		new_color->blue = b;
		new_color->index = 0;
		hmap_put(palette->map, new_color, new_color);
	}
}

bool
palette_next(Palette *palette, uint8_t *r, uint8_t *g, uint8_t *b)
{
	if (palette->it == NULL)
		palette->it = hmap_value_iterator(palette->map);
	if (iterator_has_next(palette->it)) {
		PaletteColor *color = iterator_next(palette->it);
		*r = color->red;
		*g = color->green;
		*b = color->blue;
		return true;
	}
	iterator_free(palette->it);
	palette->it = NULL;
	return false;
}

void
palette_assign_indices(Palette *palette)
{
	Iterator *it = hmap_value_iterator(palette->map);
	uint32_t c;

	for (c = 0; iterator_has_next(it); c++) {
		PaletteColor *color = iterator_next(it);
		color->index = c;
	}
}

uint32_t
palette_lookup(Palette *palette, uint8_t r, uint8_t g, uint8_t b)
{
	PaletteColor color = { r, g, b, 0 };
	PaletteColor *real_color = hmap_get(palette->map, &color);
	return (real_color != NULL ? real_color->index : -1);
}

uint32_t
palette_count(Palette *palette)
{
	return hmap_size(palette->map);
}

static void
simple_setvec(uint8_t *data, uint32_t ofs, uint8_t size, uint32_t value)
{
	switch (size) {
	case 1:
		data[ofs/8] |= (value & 1) << (7 - ofs%8);
		break;
	case 2:
		data[ofs/4] |= (value & 3) << ((3 - ofs%4) << 1);
		break;
	case 4:
		data[ofs/2] |= (value & 15) << ((1 - ofs%2) << 2);
		break;
	case 8:
		data[ofs] = value;
		break;
	case 16:
		data[2*ofs] = value;
		data[2*ofs+1] = (value >> 8);
		break;
	case 24:
		data[3*ofs] = value;
		data[3*ofs+1] = (value >> 8);
		data[3*ofs+2] = (value >> 16);
		break;
	case 32:
		data[4*ofs] = value;
		data[4*ofs+1] = (value >> 8);
		data[4*ofs+2] = (value >> 16);
		data[4*ofs+3] = (value >> 24);
		break;
	}
}

static uint32_t
simple_vec(uint8_t *data, uint32_t ofs, uint8_t size)
{
	switch (size) {
	case 1:
		return (data[ofs/8] >> (7 - ofs%8)) & 1;
	case 2:
		return (data[ofs/4] >> ((3 - ofs%4) << 1)) & 3;
	case 4:
		return (data[ofs/2] >> ((1 - ofs%2) << 2)) & 15;
	case 8:
		return data[ofs];
	case 16:
		return data[2*ofs] | data[2*ofs+1] << 8;
	case 24:
		return data[3*ofs] | data[3*ofs+1] << 8 | data[3*ofs+2] << 16;
	case 32:
		return data[4*ofs] | data[4*ofs+1] << 8 | data[4*ofs+2] << 16 | data[4*ofs+3] << 24;
	}

	return 0;
}

static bool
xfread(void *ptr, size_t size, FILE *stream)
{
	if (fread(ptr, size, 1, stream) < 1) {
		if (ferror(stream))
			warn_errno("cannot read file");
		else
			warn("premature end");
		return false;
	}
	return true;
}

/**
 * Terminate the program with an error message.
 *
 * @param msg
 *   Error message.
 */
void
die(const char *msg, ...)
{
	va_list ap;
	
	va_start(ap, msg);
	v_warn(msg, ap);
	va_end(ap);

	free_error();
	exit(1);
}

/**
 * Set the current message header.
 */
void
set_message_header(const char *msg, ...)
{
	va_list ap;
	struct MessageHeader *hdr;

	hdr = malloc(sizeof(struct MessageHeader));
	check_memory(hdr);
	hdr->old = message_header;
	va_start(ap, msg);
	if (vasprintf(&hdr->message, msg, ap) < 0)
		die_memory();
	message_header = hdr;
	va_end(ap);
}

/**
 * Restore the message header to the default.
 */
void
restore_message_header(void)
{
	if (message_header != NULL) {
		struct MessageHeader *old;
		old = message_header->old;
		free(message_header->message);
		free(message_header);
		message_header = old;
	}
}

/**
 * Set a global error message.
 */
void
set_error(const char *format, ...)
{
	va_list ap;

	if (error_message != NULL)
		free(error_message);

	if (format != NULL) {
		va_start(ap, format);
		if (vasprintf(&error_message, format, ap) < 0)
			die_memory();
		va_end(ap);
	} else {
		error_message = NULL;
	}
}

/**
 * Die printing the current error message.
 */
void
die_error(void)
{
	warn(error_message);
	free_error();
	exit(1);
}

typedef struct {
	uint32_t capacity;
	uint32_t count;
	char value[0];
} StringBuffer;

static inline StringBuffer *
get_string_buffer(char **buf)
{
	return (StringBuffer *) (*buf - sizeof(StringBuffer));
}

static inline char *
new_strbuf(uint32_t capacity, uint32_t count)
{
	StringBuffer *strbuf;
	strbuf = xmalloc(sizeof(StringBuffer) + sizeof(char)*capacity);
	strbuf->capacity = capacity;
	strbuf->count = count;
	return strbuf->value;
}

char *
strbuf_new(void)
{
	char *buf;
	buf = new_strbuf(DEFAULT_CAPACITY, 0);
	buf[0] = '\0';
	return buf;
}

static inline void
ensure_capacity(char **buf, StringBuffer **strbuf, uint32_t min_capacity)
{
	if (min_capacity > (*strbuf)->capacity) {
		uint32_t max = (min_capacity > (*strbuf)->count ? (*strbuf)->count*2+2 : (*strbuf)->count);
		min_capacity = MAX(min_capacity, max);

		*strbuf = xrealloc(*strbuf, sizeof(StringBuffer) + sizeof(char) * min_capacity);
		(*strbuf)->capacity = min_capacity;
		*buf = (*strbuf)->value;
	}
}

void
strbuf_append_char(char **buf, char ch)
{
	StringBuffer *strbuf = get_string_buffer(buf);

	strbuf->count++;
	ensure_capacity(buf, &strbuf, strbuf->count + 1);
	(*buf)[strbuf->count-1] = ch;
	(*buf)[strbuf->count] = '\0';
}

/**
 * Return a newly allocated regular string, while freeing the string
 * buffer.
 */
char *
strbuf_free_to_string(char **buf)
{
	StringBuffer *strbuf = get_string_buffer(buf);
	memmove(strbuf, *buf, strbuf->count + 1);
	return (char *) strbuf;
}

void
strbuf_append_substring(char **buf, const char *string,
		uint32_t substart, uint32_t subend)
{
	StringBuffer *strbuf = get_string_buffer(buf);
	size_t len = subend - substart;

	ensure_capacity(buf, &strbuf, strbuf->count + len + 1);
	memcpy(*buf + strbuf->count, string+substart, len);
	strbuf->count += len;
	(*buf)[strbuf->count] = '\0';
}

void
strbuf_append(char **buf, const char *string)
{
	strbuf_append_substring(buf, string, 0, strlen(string));
}

int
strbuf_appendf(char **buf, const char *format, ...)
{
	va_list ap;
	int rc;
	char *tmp = NULL;

	va_start(ap, format);
	rc = vasprintf(&tmp, format, ap);
	if (rc > 0 && tmp != NULL)
		strbuf_append(buf, tmp);
	if (tmp != NULL)
		free(tmp);
	va_end(ap);

	return rc;
}

void
strbuf_append_substring_to_end(char **buf, const char *string,
		uint32_t substart)
{
	return strbuf_append_substring(buf, string, substart, strlen(string));
}

bool
file_exists(const char *file)
{
    struct stat statbuf;
    return (stat(file, &statbuf) != -1);
}

char *
xvasprintf (const char *format, va_list args)
{
  char *result;

  if (vasprintf (&result, format, args) < 0)
    {
      if (errno == ENOMEM)
	xalloc_die ();
      return NULL;
    }

  return result;
}

char *
xasprintf (const char *format, ...)
{
  va_list args;
  char *result;

  va_start (args, format);
  result = xvasprintf (format, args);
  va_end (args);

  return result;
}

/**
 * Return true if the first string ends with the second.
 *
 * @param str
 *   String to look for end in.
 * @param end
 *   String to look for.
 */
bool
ends_with(const char *str, const char *end)
{
	int c;
	int diff = strlen(str) - strlen(end);

	if (diff < 0)
		return false;

	for (c = 0; end[c] != '\0'; c++) {
		if (end[c] != str[c+diff])
			return false;
	}

	return true;
}

/**
 * Return true if the first string ends with the second,
 * ignoring case.
 *
 * @param str
 *   String to look for end in.
 * @param end
 *   String to look for.
 */
bool
ends_with_nocase(const char *str, const char *end)
{
	int c;
	int diff = strlen(str) - strlen(end);

	if (diff < 0)
		return false;

	for (c = 0; end[c] != '\0'; c++) {
		if (tolower(end[c]) != tolower(str[c+diff]))
			return false;
	}

	return true;
}

/**
 * Return true if the first string starts with the second.
 *
 * @param str
 *   String to look for start in.
 * @param start
 *   String to look for.
 */
bool
starts_with(const char *str, const char *start)
{
	int c;
	for (c = 0; str[c] != '\0'; c++) {
		if (str[c] != start[c])
			break;
	}
	return (start[c] == '\0');
}

/**
 * Return true if the first string starts with the second,
 * ignoring case.
 *
 * @param str
 *   String to look for start in.
 * @param start
 *   String to look for.
 */
bool
starts_with_nocase(const char *str, const char *start)
{
	int c;
	for (c = 0; str[c] != '\0'; c++) {
		if (tolower(str[c]) != tolower(start[c]))
			break;
	}
	return (start[c] == '\0');
}

/**
 * Return the substring of a string, putting it into a newly allocated
 * string.
 *
 * @param start
 *   Index of first character to copy. If this value is
 *   negative, the index is counted from the end of the string.
 *   E.g. -1 is before last non-null character.
 * @param end
 *   Index of first character not to copy. If this value is
 *   negative, the index is counted from the end of the string.
 *   E.g. -1 is before last non-null character.
 */
char *
substring(const char *buf, int start, int end)
{
	int len = strlen(buf);

	if (start < 0)
		start += len;
	if (end < 0)
		end += len;

	return strndup(buf+start, end-start);
}

/* Like dirname, but returns a newly allocated string.
 */
char *
xdirname(const char *path)
{
	char *pos = strrchr(path, '/');

	if (pos == NULL)
		return xstrdup(".");

	if (pos == path)
		return xstrdup("/");

	return substring(path, 0, pos-path);
}

/**
 * Read and discard some number of bytes from a stream.
 */
int
fskip(FILE *file, uint32_t bytes)
{
	for (; bytes > 0; bytes--) {
		if (fgetc(file) == EOF)
			return -1;
	}
	return 0;
}

/**
 * Write a number of bytes of the same value to a stream.
 */
int
fpad(FILE *file, char byte, uint32_t bytes)
{
	for (; bytes > 0; bytes--) {
		if (fwrite(&byte, 1, 1, file) != 1)
			return -1;
	}
	return 0;
}

typedef FILE *(*CreateNameGen)(char **outname);

bool
create_icon(int filec, char **filev, CreateNameGen outfile_gen, bool icon_mode, int32_t hotspot_x, int32_t hotspot_y, int32_t alpha_threshold, int32_t bit_count)
{
	struct {
		FILE *in;
		png_structp png_ptr;
		png_infop info_ptr;
		uint32_t bit_count;
		uint32_t palette_count;
		uint32_t image_size;
		uint32_t mask_size;
		uint32_t width;
		uint32_t height;
		uint8_t *image_data;
		uint8_t **row_datas;
		Palette *palette;
	} *img;

	Win32CursorIconFileDir dir;
	FILE *out;
	char *outname = NULL;
	uint32_t c, d, x;
	uint32_t dib_start;
	png_byte ct;

	img = xcalloc(filec * sizeof(*img));

	for (c = 0; c < filec; c++) {
		char header[8];
		uint32_t row_bytes;
		uint8_t transparency[256];
		uint16_t transparency_count;
		bool need_transparency;

		set_message_header(filev[c]);

		img[c].in = fopen(filev[c], "rb");
    	if (img[c].in == NULL) {
        	warn_errno("cannot open file");
			goto cleanup;
		}
    	if (!xfread(header, 8, img[c].in))
			goto cleanup;
    	if (png_sig_cmp((png_bytep)header, 0, 8)) {
	    
        	warn("not a png file");
			goto cleanup;
		}

		img[c].png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL /*user_error_fn, user_warning_fn*/);
		if (img[c].png_ptr == NULL) {
			warn("cannot initialize PNG library");
			goto cleanup;
		}
		img[c].info_ptr = png_create_info_struct(img[c].png_ptr);
		if (img[c].info_ptr == NULL) {
			warn("cannot create PNG info structure - out of memory");
			goto cleanup;
		}

		png_init_io(img[c].png_ptr, img[c].in);
		png_set_sig_bytes(img[c].png_ptr, 8);
		png_set_strip_16(img[c].png_ptr);
		png_set_expand(img[c].png_ptr);
		png_set_gray_to_rgb(img[c].png_ptr);
		png_set_interlace_handling(img[c].png_ptr);
		png_set_filler(img[c].png_ptr, 0xFF, PNG_FILLER_AFTER);
		png_read_info(img[c].png_ptr, img[c].info_ptr);
		png_read_update_info(img[c].png_ptr, img[c].info_ptr);

		img[c].width = png_get_image_width(img[c].png_ptr, img[c].info_ptr);
		/*if (img[c].width > 255) {
			warn("image too wide (max 255, was %d pixels)", img[c].width);
			goto cleanup;
		}*/
		img[c].height = png_get_image_height(img[c].png_ptr, img[c].info_ptr);
		/*if (img[c].height > 255) {
			warn("image too tall (max 255, was %d pixels)", img[c].height);
			goto cleanup;
		}*/

		row_bytes = png_get_rowbytes(img[c].png_ptr, img[c].info_ptr);
		img[c].row_datas = xmalloc(img[c].height * sizeof(png_bytep *));
		img[c].row_datas[0] = xmalloc(img[c].height * row_bytes);
		for (d = 1; d < img[c].height; d++)
			img[c].row_datas[d] = img[c].row_datas[d-1] + row_bytes;
		png_read_rows(img[c].png_ptr, img[c].row_datas, NULL, img[c].height);

		ct = png_get_color_type(img[c].png_ptr, img[c].info_ptr);
		img[c].palette = palette_new();


		/* Count number of necessary colors in palette and number of transparencies */
		memset(transparency, 0, 256);
		for (d = 0; d < img[c].height; d++) {
			png_bytep row = img[c].row_datas[d];
			for (x = 0; x < img[c].width; x++) {
				if (palette_count(img[c].palette) <= (1 << 8))
				    palette_add(img[c].palette, row[4*x+0], row[4*x+1], row[4*x+2]);
				if (ct & PNG_COLOR_MASK_ALPHA)
				    transparency[row[4*x+3]] = 1;
			}
		}
		transparency_count = 0;
		for (d = 0; d < 256; d++)
		    transparency_count += transparency[d];

		/* If there are more than two steps of transparency, or if the
		 * two steps are NOT either entirely off (0) and entirely on (255),
		 * then we will lose transparency information if bit_count is not 32.
		 */
		need_transparency =
		    transparency_count > 2
		    ||
		    (transparency_count == 2 && (transparency[0] == 0 || transparency[255] == 0));

		/* Can we keep all colors in a palette? */
		if (need_transparency) {
			if (bit_count != -1) {
			    if (bit_count != 32)
				warn("decreasing bit depth will discard variable transparency", transparency_count);
			    /* Why 24 and not bit_count? Otherwise we might decrease below what's possible
			           * due to number of colors in image. The real decrease happens below. */
			    img[c].bit_count = 24;
			} else {
			    img[c].bit_count = 32;
			}
			img[c].palette_count = 0;
		}
		else if (palette_count(img[c].palette) <= 256) {
			for (d = 1; palette_count(img[c].palette) > 1 << d; d <<= 1);
			if (d == 2)	/* four colors (two bits) are not supported */
				d = 4;
			img[c].bit_count = d;
			img[c].palette_count = 1 << d;
		}
		else {
			img[c].bit_count = 24;
			img[c].palette_count = 0;
		}

		/* Does the user want to change number of bits per pixel? */
		if (bit_count != -1) {
			if (img[c].bit_count == bit_count) {
				/* No operation */
			} else if (img[c].bit_count < bit_count) {
				img[c].bit_count = bit_count;
				img[c].palette_count = (bit_count > 16 ? 0 : 1 << bit_count);
			} else {
				warn("cannot decrease bit depth from %d to %d, bit depth not changed", img[c].bit_count, bit_count);
			}
		}
		
		img[c].image_size = img[c].height * ROW_BYTES(img[c].width * img[c].bit_count);
		img[c].mask_size = img[c].height * ROW_BYTES(img[c].width);

		restore_message_header();
	}

	out = outfile_gen(&outname);
	restore_message_header();
	set_message_header(outname);
	if (out == NULL) {
		warn_errno("cannot create file");
		goto cleanup;
	}

	dir.reserved = 0;
	dir.type = (icon_mode ? 1 : 2);
	dir.count = filec;
	fix_win32_cursor_icon_file_dir_endian(&dir);
	if (fwrite(&dir, sizeof(Win32CursorIconFileDir), 1, out) != 1) {
		warn_errno("cannot write to file");
		goto cleanup;
	}

	dib_start = sizeof(Win32CursorIconFileDir) + filec * sizeof(Win32CursorIconFileDirEntry);
	for (c = 0; c < filec; c++) {
		Win32CursorIconFileDirEntry entry;

		entry.width = MIN(255, img[c].width);
		entry.height = MIN(255, img[c].height);
		entry.reserved = 0;
		if (icon_mode) {
			entry.hotspot_x = 0;	 /* some mistake this for planes (XXX) */
			entry.hotspot_y = 0;	 /* some mistake this for bit_count (XXX) */
		} else {
			entry.hotspot_x = hotspot_x;	 /* some mistake this for planes (XXX) */
			entry.hotspot_y = hotspot_y;	 /* some mistake this for bit_count (XXX) */
		}
		entry.dib_offset = dib_start;
		entry.color_count = (img[c].bit_count >= 8 ? 0 : 1 << img[c].bit_count);
		entry.dib_size = img[c].palette_count * sizeof(Win32RGBQuad)
				+ sizeof(Win32BitmapInfoHeader)
				+ img[c].image_size
				+ img[c].mask_size;

		dib_start += entry.dib_size;

		fix_win32_cursor_icon_file_dir_entry_endian(&entry);
		if (fwrite(&entry, sizeof(Win32CursorIconFileDirEntry), 1, out) != 1) {
			warn_errno("cannot write to file");
			goto cleanup;
		}

	}

	for (c = 0; c < filec; c++) {
		Win32BitmapInfoHeader bitmap;

		bitmap.size = sizeof(Win32BitmapInfoHeader);
		bitmap.width = png_get_image_width(img[c].png_ptr, img[c].info_ptr);
		bitmap.height = png_get_image_height(img[c].png_ptr, img[c].info_ptr) * 2;
		bitmap.planes = 1;							// appears to be 1 always (XXX)
		bitmap.bit_count = img[c].bit_count;
		bitmap.compression = 0;
		bitmap.x_pels_per_meter = 0;				// should be 0 always
		bitmap.y_pels_per_meter = 0;				// should be 0 always
		bitmap.clr_important = 0;					// should be 0 always
		bitmap.clr_used = img[c].palette_count;
		bitmap.size_image = img[c].image_size;		// appears to be ok here (may be image_size+mask_size or 0, XXX)

		fix_win32_bitmap_info_header_endian(&bitmap);
		if (fwrite(&bitmap, sizeof(Win32BitmapInfoHeader), 1, out) != 1) {
			warn_errno("cannot write to file");
			goto cleanup;
		}

		if (img[c].bit_count <= 16) {
			Win32RGBQuad color;

			palette_assign_indices(img[c].palette);
			color.reserved = 0;
			while (palette_next(img[c].palette, &color.red, &color.green, &color.blue))
				fwrite(&color, sizeof(Win32RGBQuad), 1, out);

			/* Pad with empty colors. The reason we do this is because we
			 * specify bitmap.clr_used as a base of 2. The latter is probably
			 * not necessary according to the original specs, but many
			 * programs that read icons assume it. Especially gdk-pixbuf.
			 */
		    	memclear(&color, sizeof(Win32RGBQuad));
			for (d = palette_count(img[c].palette); d < 1 << img[c].bit_count; d++)
				fwrite(&color, sizeof(Win32RGBQuad), 1, out);
		}

		img[c].image_data = xcalloc(img[c].image_size);

		for (d = 0; d < img[c].height; d++) {
			png_bytep row = img[c].row_datas[img[c].height - d - 1];
			if (img[c].bit_count < 24) {
				uint32_t imod = d * (img[c].image_size/img[c].height) * 8 / img[c].bit_count;
				for (x = 0; x < img[c].width; x++) {
					uint32_t color;
					color = palette_lookup(img[c].palette, row[4*x+0], row[4*x+1], row[4*x+2]);
					simple_setvec(img[c].image_data, x+imod, img[c].bit_count, color);
				}
			} else if (img[c].bit_count == 24) {
				uint32_t irow = d * (img[c].image_size/img[c].height);
				for (x = 0; x < img[c].width; x++) {
					img[c].image_data[3*x+0 + irow] = row[4*x+2];
					img[c].image_data[3*x+1 + irow] = row[4*x+1];
					img[c].image_data[3*x+2 + irow] = row[4*x+0];
				}
			} else if (img[c].bit_count == 32) {
				uint32_t irow = d * (img[c].image_size/img[c].height);
				for (x = 0; x < img[c].width; x++) {
					img[c].image_data[4*x+0 + irow] = row[4*x+2];
					img[c].image_data[4*x+1 + irow] = row[4*x+1];
					img[c].image_data[4*x+2 + irow] = row[4*x+0];
					img[c].image_data[4*x+3 + irow] = row[4*x+3];
				}
			}
		}

		if (fwrite(img[c].image_data, img[c].image_size, 1, out) != 1) {
			warn_errno("cannot write to file");
			goto cleanup;
		}

		for (d = 0; d < img[c].height; d++) {
			png_bytep row = img[c].row_datas[img[c].height - d - 1];

			for (x = 0; x < img[c].width; x += 8) {
				uint8_t mask = 0;
				mask |= (row[4*(x+0)+3] <= alpha_threshold ? 1 << 7 : 0);
				mask |= (row[4*(x+1)+3] <= alpha_threshold ? 1 << 6 : 0);
				mask |= (row[4*(x+2)+3] <= alpha_threshold ? 1 << 5 : 0);
				mask |= (row[4*(x+3)+3] <= alpha_threshold ? 1 << 4 : 0);
				mask |= (row[4*(x+4)+3] <= alpha_threshold ? 1 << 3 : 0);
				mask |= (row[4*(x+5)+3] <= alpha_threshold ? 1 << 2 : 0);
				mask |= (row[4*(x+6)+3] <= alpha_threshold ? 1 << 1 : 0);
				mask |= (row[4*(x+7)+3] <= alpha_threshold ? 1 << 0 : 0);
				fputc(mask, out);
			}

			fpad(out, 0, img[c].mask_size/img[c].height - x/8);
		}

		free(img[c].image_data);
		palette_free(img[c].palette);
		free(img[c].row_datas[0]);
		free(img[c].row_datas);
		png_read_end(img[c].png_ptr, img[c].info_ptr);
		png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
		fclose(img[c].in);
		memclear(&img[c], sizeof(*img));
	}

	free(outname);
	free(img);
	return true;

cleanup:

	restore_message_header();
	for (c = 0; c < filec; c++) {
		if (img[c].image_data != NULL)
			free(img[c].image_data);
		if (img[c].palette != NULL)
			palette_free(img[c].palette);
		if (img[c].row_datas != NULL && img[c].row_datas[0] != NULL) {
			free(img[c].row_datas[0]);
			free(img[c].row_datas);
		}
		if (img[c].png_ptr != NULL)
			png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
		if (img[c].in != NULL)
			fclose(img[c].in);
	}
	if (outname != NULL)
		free(outname);

	free(img);
	return false;
}

typedef FILE *(*ExtractNameGen)(char **outname, int width, int height, int bitcount, int index);
typedef bool (*ExtractFilter)(int index, int width, int height, int bitdepth, int palettesize, bool icon, int hotspot_x, int hotspot_y);

int
extract_icons(FILE *in, char *inname, bool listmode, ExtractNameGen outfile_gen, ExtractFilter filter)
{
	Win32CursorIconFileDir dir;
	Win32CursorIconFileDirEntry *entries = NULL;
	uint32_t offset;
	uint32_t c, d;
	int completed;
	int matched = 0;

	set_message_header(inname);

	if (!xfread(&dir, sizeof(Win32CursorIconFileDir), in))
		goto cleanup;
	fix_win32_cursor_icon_file_dir_endian(&dir);

	if (dir.reserved != 0) {
		warn("not an icon or cursor file (reserved non-zero)");
		goto cleanup;
	}
	if (dir.type != 1 && dir.type != 2) {
		warn("not an icon or cursor file (wrong type)");
		goto cleanup;
	}

	entries = xmalloc(dir.count * sizeof(Win32CursorIconFileDirEntry));
	for (c = 0; c < dir.count; c++) {
		if (!xfread(&entries[c], sizeof(Win32CursorIconFileDirEntry), in))
			goto cleanup;
		fix_win32_cursor_icon_file_dir_entry_endian(&entries[c]);
		if (entries[c].reserved != 0)
			warn("reserved is not zero");
	}
	offset = sizeof(Win32CursorIconFileDir) + dir.count * sizeof(Win32CursorIconFileDirEntry);

	for (completed = 0; completed < dir.count; ) {
		uint32_t min_offset = UINT32_MAX;
		int previous = completed;

		for (c = 0; c < dir.count; c++) {
			if (entries[c].dib_offset == offset) {
				Win32BitmapInfoHeader bitmap;
				Win32RGBQuad *palette = NULL;
				uint32_t palette_count = 0;
				uint32_t image_size, mask_size;
				uint32_t width = 0, height = 0;
				uint8_t *image_data = NULL, *mask_data = NULL;
				png_structp png_ptr = NULL;
				png_infop info_ptr = NULL;
				png_byte *row = NULL;
				char *outname = NULL;
				FILE *out = NULL;

				if (!xfread(&bitmap, sizeof(Win32BitmapInfoHeader), in))
					goto local_cleanup;

				fix_win32_bitmap_info_header_endian(&bitmap);
				if (bitmap.size < sizeof(Win32BitmapInfoHeader)) {
					warn("bitmap header is too short");
					goto local_cleanup;
				}
				if (bitmap.compression != 0) {
				        warn("compressed image found (at index %d)", c);
		                        fseek(in, offset, SEEK_SET);
                                        mask_size = 0;
                                        image_size = bitmap.size_image;
                                        goto direct;
				}
				if (bitmap.x_pels_per_meter != 0)
					warn("x_pels_per_meter field in bitmap should be zero");
				if (bitmap.y_pels_per_meter != 0)
					warn("y_pels_per_meter field in bitmap should be zero");
				if (bitmap.clr_important != 0)
					warn("clr_important field in bitmap should be zero");
				if (bitmap.planes != 1)
					warn("planes field in bitmap should be one");
				if (bitmap.size != sizeof(Win32BitmapInfoHeader)) {
					uint32_t skip = bitmap.size - sizeof(Win32BitmapInfoHeader);
					warn("skipping %d bytes of extended bitmap header", skip);
					fskip(in, skip);
				}
				offset += bitmap.size;

				if (bitmap.clr_used != 0 || bitmap.bit_count < 24) {
					palette_count = (bitmap.clr_used != 0 ? bitmap.clr_used : 1 << bitmap.bit_count);
					palette = xmalloc(sizeof(Win32RGBQuad) * palette_count);
					if (!xfread(palette, sizeof(Win32RGBQuad) * palette_count, in))
						goto local_cleanup;
					offset += sizeof(Win32RGBQuad) * palette_count;
				}

				width = bitmap.width;
				height = abs(bitmap.height)/2;
				
				image_size = height * ROW_BYTES(width * bitmap.bit_count);
				mask_size = height * ROW_BYTES(width);

				if (entries[c].dib_size	!= bitmap.size + image_size + mask_size + palette_count * sizeof(Win32RGBQuad))
					warn("incorrect total size of bitmap (%d specified; %d real)",
					    entries[c].dib_size,
					    bitmap.size + image_size + mask_size + palette_count * sizeof(Win32RGBQuad)
					);

			direct:
				image_data = xmalloc(image_size);
				if (!xfread(image_data, image_size, in))
					goto local_cleanup;

				if (mask_size) {
				    mask_data = xmalloc(mask_size);
				    if (!xfread(mask_data, mask_size, in))
					goto local_cleanup;
				}

				offset += image_size;
				offset += mask_size;
				completed++;

				if (!filter(completed, width, height, bitmap.bit_count, palette_count, dir.type == 1,
						(dir.type == 1 ? 0 : entries[c].hotspot_x),
						(dir.type == 1 ? 0 : entries[c].hotspot_y))) {
					if (bitmap.compression != 0) goto local_cleanup;
					goto done;
				}
				matched++;

				if (!listmode) {
				        if (bitmap.compression) {
                                        /* Just hope it's an otherwise
                                           readable image ... */
					    width = 0;
					    height = 0;
                                            outname = inname;
					    out = outfile_gen(&outname, width, height, bitmap.bit_count, completed);
					    if (out) {
                                                fwrite(image_data, 1, image_size, out);
					    }
					    goto local_cleanup;
					}

					png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL /*user_error_fn, user_warning_fn*/);
					if (!png_ptr) {
						warn("cannot initialize PNG library");
						goto local_cleanup;
					}
					info_ptr = png_create_info_struct(png_ptr);
					if (!info_ptr) {
						warn("cannot create PNG info structure - out of memory");
						goto local_cleanup;
					}

					outname = inname;
					out = outfile_gen(&outname, width, height, bitmap.bit_count, completed);
					restore_message_header();
					set_message_header(outname);

					if (out == NULL) {
						warn_errno("cannot create file");
						goto local_cleanup;
					}
					png_init_io(png_ptr, out);

					restore_message_header();
					set_message_header(inname);

					png_set_IHDR(png_ptr, info_ptr,	width, height, 8,
							PNG_COLOR_TYPE_RGB_ALPHA,
							PNG_INTERLACE_NONE,
							PNG_COMPRESSION_TYPE_DEFAULT,
							PNG_FILTER_TYPE_DEFAULT);
					png_write_info(png_ptr, info_ptr);
				}

				row = xmalloc(width * 4);

				for (d = 0; d < height; d++) {
					uint32_t x;
					uint32_t y = (bitmap.height < 0 ? d : height - d - 1);
					uint32_t imod = y * (image_size / height) * 8 / bitmap.bit_count;
					uint32_t mmod = y * (mask_size / height) * 8;

					for (x = 0; x < width; x++) {
						uint32_t color = simple_vec(image_data, x + imod, bitmap.bit_count);

						if (bitmap.bit_count <= 16) {
							if (color >= palette_count) {
								warn("color out of range in image data");
								goto local_cleanup;
							}
							row[4*x+0] = palette[color].red;
							row[4*x+1] = palette[color].green;
							row[4*x+2] = palette[color].blue;
						} else {
							row[4*x+0] = (color >> 16) & 0xFF;
							row[4*x+1] = (color >>  8) & 0xFF;
							row[4*x+2] = (color >>  0) & 0xFF;
						}
						if (bitmap.bit_count == 32)
						    row[4*x+3] = (color >> 24) & 0xFF;
						else
						    row[4*x+3] = simple_vec(mask_data, x + mmod, 1) ? 0 : 0xFF;
					}

					if (!listmode)
						png_write_row(png_ptr, row);
				}

				if (listmode) {
					printf("--%s --index=%d --width=%d --height=%d --bit-depth=%d --palette-size=%d",
							(dir.type == 1 ? "icon" : "cursor"), completed, width, height,
							bitmap.bit_count, palette_count);
					if (dir.type == 2)
					  printf(" --hotspot-x=%d --hotspot-y=%d", entries[c].hotspot_x, entries[c].hotspot_y);
					printf("\n");
				} else {
					png_write_end(png_ptr, info_ptr);
					png_destroy_write_struct(&png_ptr, &info_ptr);
					/*restore_message_header();*/
				}

			done:

				if (row != NULL)
					free(row);
				if (palette != NULL)
					free(palette);
				if (outname != NULL)
					free(outname);
				if (image_data != NULL) {
					free(image_data);
					free(mask_data);
				}
				if (out != NULL)
					fclose(out);
				continue;

			local_cleanup:

				/*restore_message_header();*/
				if (row != NULL)
					free(row);
				if (palette != NULL)
					free(palette);
				if (image_data != NULL) {
					free(image_data);
					free(mask_data);
				}
				if (out != NULL)
					fclose(out);
				if (png_ptr != NULL)
					png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				if (outname != NULL)
					free(outname);
				goto cleanup;
			} else {
				if (entries[c].dib_offset > offset)
						min_offset = MIN(min_offset, entries[c].dib_offset);
			}
		}

		if (previous == completed) {
			if (min_offset < offset) {
				warn("offset of bitmap header incorrect (too low)");
				goto cleanup;
			}
			warn("skipping %d bytes of garbage at %d", min_offset-offset, offset);
			fskip(in, min_offset - offset);
			offset = min_offset;
		}
	}

	restore_message_header();
	free(entries);
	return matched;

cleanup:

	restore_message_header();
	free(entries);
	return -1;
}

static int32_t image_index = -1;
static int32_t width = -1;
static int32_t height = -1;
static int32_t bitdepth = -1;
static int32_t palettesize = -1;
static int32_t hotspot_x = 0;
static int32_t hotspot_y = 0;
static bool hotspot_x_set = false;
static bool hotspot_y_set = false;
static int32_t alpha_threshold = 127;
static bool icon_only = false;	
static bool cursor_only = false;
static char *output = NULL;

enum {
    VERSION_OPT	= 1000,
    HELP_OPT,
    ICON_OPT,
    CURSOR_OPT,
};

static char *short_opts = "xlco:i:w:h:p:b:X:Y:t:";
static struct option long_opts[] = {
    { "extract",		no_argument,    	NULL, 'x' },
    { "list",			no_argument,		NULL, 'l' },
    { "create",			no_argument,       	NULL, 'c' },
    { "version",		no_argument, 	    	NULL, VERSION_OPT },
    { "help", 	    	 	no_argument,	    	NULL, HELP_OPT },
    { "output", 		required_argument, 	NULL, 'o' },
    { "index",	    	 	required_argument, 	NULL, 'i' },
    { "width",	    	 	required_argument, 	NULL, 'w' },
    { "height", 		required_argument, 	NULL, 'h' },
    { "palette-size",		required_argument, 	NULL, 'p' },
    { "bit-depth",		required_argument, 	NULL, 'b' },
    { "hotspot-x",       	required_argument, 	NULL, 'X' },
    { "hotspot-y",       	required_argument, 	NULL, 'Y' },
    { "alpha-threshold", 	required_argument, 	NULL, 't' },
    { "icon",       	 	no_argument,       	NULL, ICON_OPT	},
    { "cursor",     	 	no_argument,       	NULL, CURSOR_OPT },
    { 0, 0, 0, 0 }
};

static bool
default_filter(int i, int w, int h, int bd, int ps, bool icon, int hx, int hy)
{
    if (image_index != -1 && i != image_index)
	return false;
    if (width != -1 && w != width)
	return false;
    if (height != -1 && h != height)
	return false;
    if (bitdepth != -1 && bd != bitdepth)
	return false;
    if (palettesize != -1 && ps != palettesize)
	return false;
    if ((icon_only && !icon) || (cursor_only && icon))
	return false;
    if (hotspot_x_set && hx != hotspot_x)
	return false;
    if (hotspot_y_set && hy != hotspot_y)	
	return false;
    return true;
}

static FILE *
create_outfile_gen(char **out)
{
    if (output != NULL) {
	*out = xstrdup(output);
	return fopen(output, "wb");
    }
    if (isatty(STDOUT_FILENO))
	die("refusing to write binary data to terminal");
    *out = xstrdup("(standard out)");
    return stdout;
}

mode_t
stat_mode(const char *file)
{
	struct stat statbuf;
	return (stat(file, &statbuf) == -1 ? (mode_t) 0 : statbuf.st_mode);
}

static FILE *
extract_outfile_gen(char **outname, int w, int h, int bc, int i)
{
    char *inname = *outname;

    if (output == NULL || S_ISDIR(stat_mode(output))) {
	char *inbase;
	*outname = strbuf_new();
	if (output != NULL) {
	    strbuf_append(outname, output);
	    if (!ends_with(output, "/"))
		strbuf_append(outname, "/");
	}
	inbase = strrchr(inname, '/');
	inbase = (inbase == NULL ? inname : inbase+1);
	if (ends_with_nocase(inbase, ".ico") || ends_with_nocase(inbase, ".cur")) {
	    strbuf_append_substring(outname, inbase, 0, strlen(inbase)-4);
	} else {
	    strbuf_append(outname, inbase);
	}
        if (w!=0 && h!=0)
	    strbuf_appendf(outname, "_%d_%dx%dx%d.png", i, w, h, bc);
        else
	    strbuf_appendf(outname, "_%d_embedded.png", i);
	*outname = strbuf_free_to_string(outname);
	return fopen(*outname, "wb");
    }
    else if (strcmp(output, "-") == 0) {
	*outname = xstrdup("(standard out)");
	return stdout;
    }

    *outname = xstrdup(output);
    return fopen(output, "wb");
}

static void
display_help(void)
{
    printf("Usage: %s [OPTION]... [FILE]...\n", PROGRAM);
    printf("Convert and create Win32 icon (.ico) and cursor (.cur) files.\n");
    printf("\nCommands:\n");
    printf("  -x, --extract                extract images from files\n");
    printf("  -l, --list                   print a list of images in files\n");
    printf("  -c, --create                 create an icon file from specified files\n");
    printf("      --help                   display this help and exit\n");
    printf("      --version                output version information and exit\n");
    printf("\nOptions:\n");
    printf("  -i, --index=NUMBER           match index of image (first is 1)\n");
    printf("  -w, --width=PIXELS           match width of image\n");
    printf("  -h, --height=PIXELS          match height of image\n");
    printf("  -p, --palette-size=COUNT     match number of colors in palette (or 0)\n");
    printf("  -b, --bit-depth=COUNT        match number of bits per pixel\n");
    printf("  -X, --hotspot-x=COORD        match or set cursor hotspot x-coordinate\n");
    printf("  -Y, --hotspot-y=COORD        match or set cursor hotspot y-coordinate\n");
    printf("  -t, --alpha-threshold=LEVEL  highest level in alpha channel indicating\n"
	     "                               transparent image portions (default is 127)\n");
    printf("      --icon                   match icons only\n");
    printf("      --cursor                 match cursors only\n");
    printf("  -o, --output=PATH            where to place extracted files\n");
    printf("\n");
    printf("Report bugs to <%s>.\n", BUGREPORT);
}

static void
version_etc()
{
    printf("%s (%s) %s\n", PROGRAM, PACKAGE, VERSION);
    printf("Written by Oskar Liljeblad.\n\n");
    printf("Copyright (C) 1998-2005 Oskar Liljeblad\n");
    printf("\
This is free software; see the source for copying conditions.  There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
}

static bool
open_file_or_stdin(char *name, FILE **outfile, char **outname)
{
    if (strcmp(name, "-") == 0) {
        *outfile = stdin;
	*outname = "(standard in)";
    } else {
        *outfile = fopen(name, "rb");
	*outname = name;
	if (*outfile == NULL) {
	    warn("%s: cannot open file", name);
	    return false;
	}
    }
    return true;
}

bool
parse_int32(const char *instr, int32_t *outint)
{
	int32_t value = 0;

	if (*instr == '-') {
		if (instr[1] == '\0')		/* FIXME: this should be done on all!!! */
			return false;
		for (instr++; *instr != '\0'; instr++) {
			int8_t c = *instr - '0';
			if (c < 0 || c > 9)
				return false;
			if (value < INT32_MIN/10L || (value == INT32_MIN/10L && c > -(INT32_MIN%10L)))
				return false;
			value = value*10L - c;
		}
	} else {
		if (*instr == '\0')			/* FIXME: this should be done on all!!! */
			return false;
		for (; *instr != '\0'; instr++) {
			int8_t c = *instr - '0';
			if (c < 0 || c > 9)
				return false;
			if (value > INT32_MAX/10L || (value == INT32_MAX/10L && c > INT32_MAX%10L))
				return false;
			value = value*10L + c;
		}
	}
	*outint = value;

	return true;
}

int
main(int argc, char **argv)
{
    int c;
    bool list_mode = false;
    bool extract_mode = false;
    bool create_mode = false;
    FILE *in;
    char *inname;

    while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
	switch (c) {
	case 'x':
	    extract_mode = true;
	    break;
	case 'l':
	    list_mode = true;
	    break;
	case 'c':
	    create_mode = true;
	    break;
	case VERSION_OPT:
	    version_etc();
	    exit(0);
	case HELP_OPT:
	    display_help();
	    exit(0);
	case 'o':
	    output = optarg;
	    break;
	case 'i':
	    if (!parse_int32(optarg, &image_index))
		die("invalid index value: %s", optarg);
	    break;
	case 'w':
	    if (!parse_int32(optarg, &width))
		die("invalid width value: %s", optarg);
	    break;
	case 'h':
	    if (!parse_int32(optarg, &height))
		die("invalid height value: %s", optarg);
	    break;
	case 'p':
	    if (!parse_int32(optarg, &palettesize))
		die("invalid palette-size value: %s", optarg);
	    break;
	case 'b':
	    if (!parse_int32(optarg, &bitdepth))
		die("invalid bit-depth value: %s", optarg);
	    break;
	case 'X':
	    if (!parse_int32(optarg, &hotspot_x))
		die("invalid hotspot-x value: %s", optarg);
	    hotspot_x_set = true;
	    break;
	case 'Y':
	    if (!parse_int32(optarg, &hotspot_y))
		die("invalid hotspot-y value: %s", optarg);
	    hotspot_y_set = true;
	    break;
	case 't':
	    if (!parse_int32(optarg, &alpha_threshold))
		die("invalid alpha-threshold value: %s", optarg);
	    break;
	case ICON_OPT:
	    icon_only = true;
	    break;
	case CURSOR_OPT:
	    cursor_only = true;
	    break;
	case '?':
	    exit(1);
	}
    }

    if (extract_mode + create_mode + list_mode > 1)
	die("multiple commands specified");
    if (extract_mode + create_mode + list_mode == 0)
	die("missing argument\nTry `%s --help' for more information.", argv[0]);
    if (icon_only && cursor_only)
	die("only one of --icon and --cursor may be specified");

    if (list_mode) {
	if (argc-optind <= 0)
	    die("missing file argument");
	for (c = optind ; c < argc ; c++) {
	    if (open_file_or_stdin(argv[c], &in, &inname)) {
		if (!extract_icons(in, inname, true, NULL, default_filter))
		    exit(1);
		if (in != stdin)
		    fclose(in);
	    }
	}
    }

    if (extract_mode) {
	if (argc-optind <= 0)
	  die("missing arguments");

        for (c = optind ; c < argc ; c++) {
            int matched;

	    if (open_file_or_stdin(argv[c], &in, &inname)) {
	        matched = extract_icons(in, inname, false, extract_outfile_gen, default_filter);
	        if (matched == -1)
	            exit(1);
                if (matched == 0)
                    fprintf(stderr, "%s: no images matched\n", inname);
                if (in != stdin)
                    fclose(in);
            }
        }
    }

    if (create_mode) {
        if (argc-optind <= 0)
	    die("missing arguments");
        if (!create_icon(argc-optind, argv+optind, create_outfile_gen, (icon_only ? true : !cursor_only), hotspot_x, hotspot_y, alpha_threshold, bitdepth))
            exit(1);
    }

    exit(0);
}
