/*
 * wig-bisect.c - command line tool for dumping Wherigo cartridge contents
 *
 * Copyright (C) 2010-2011 Tommi Saviranta <wnd@iki.fi>
 *
 * 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 3 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, see <http://www.gnu.org/licenses>.
 *
 * GWC structure info was retrieved from
 * http://ati.land.cz/gps/wherigo/gwc.htm
 *
 * Version history:
 *  14 Nov 2010 - 0.1 - initial version
 *  15 Feb 2011 - 0.2 - fixed header parsing, dump invalid objects too!
 *
 * And yes, I know it's ugly. That's not the point!
 */


#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


int16_t
to_int16(char *buf, int i)
{
	int16_t t;
	memcpy(&t, buf + i, 2);
	return t;
}


uint16_t
to_uint16(char *buf, int i)
{
	uint16_t t;
	memcpy(&t, buf + i, 2);
	return t;
}


double
to_double(char *buf, int i)
{
	double t;
	memcpy(&t, buf + i, 8);
	return t;
}


int32_t
to_int32(char *buf, int i)
{
	int32_t t;
	memcpy(&t, buf + i, 4);
	return t;
}


int
read_header(char *buf, int i)
{
	/* skip header length */
	i += 4;

	{
		double lat = to_double(buf, i    );
		double lon = to_double(buf, i + 8);
		printf("base coordinates: %.6f %.6f\n", lat, lon);
		i += 16;
	}


	{
		int32_t u0 = to_int32(buf, 0);
		int32_t u1 = to_int32(buf, 4);
		int32_t u2 = to_int32(buf, 8);
		int32_t u3 = to_int32(buf, 12);
		printf("unknown integers: %x %x %x %x\n", u0, u1, u2, u3);
		i += 16;
	}

	{
		int splash = to_int16(buf, i);
		int icon = to_int16(buf, i + 2);
		i += 4;
		printf("splash id: %d\n", splash);
		printf("icon id: %d\n", icon);
	}

	{
		printf("type: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("player: %s\n", buf + i);
		i += strlen(buf + i) + 1;
	}

	{
		int32_t u0 = to_int32(buf, 0);
		int32_t u1 = to_int32(buf, 4);
		printf("unknown integers: %x %x\n", u0, u1);
		i += 8;
	}

	{
		printf("name: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("guid: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("desc: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("location: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("version: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("author: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("company: %s\n", buf + i);
		i += strlen(buf + i) + 1;
		printf("device: %s\n", buf + i);
		i += strlen(buf + i) + 1;
	}

	{
		int32_t u0 = to_int32(buf, 0);
		printf("unknown integer: %x\n", u0);
		i += 4;
	}

	{
		printf("completion code: %s\n", buf + i);
		i += strlen(buf + i) + 1;
	}

	return i;
}


void
read_object(char *buf, int i, char *dir)
{
	int idx = i;
	int id;

	i = 9 + i * 6;

	id = to_uint16(buf, i);
	i = to_int32(buf, i + 2);
	printf("\nobject %d: id=%d, pos=%d\n", idx, id, i);

	if (idx != 0) {
		int valid;
		valid = buf[i];
		if (! valid) {
			printf("invalid object, trying to recover it\n");
			i++;
		}
		i++;
	}

	{
		int type;
		int bytes;
		char suffix[8];
		char fn[64];
		FILE *out;

		if (idx != 0) {
			type = to_int32(buf, i);
			bytes = to_int32(buf, i + 4);
			printf("type=%d, bytes=%d\n", type, bytes);
			i += 8;
		} else  {
			type = -1;
			bytes = to_int32(buf, i);
			i += 4;
			printf("lua, bytes=%d\n", bytes);
		}

		switch (type) {
			case -1: strcpy(suffix, "lua");  break;
			case  1: strcpy(suffix, "bmp");  break;
			case  2: strcpy(suffix, "png");  break;
			case  3: strcpy(suffix, "jpeg"); break;
			case  4: strcpy(suffix, "gif");  break;
			case 17: strcpy(suffix, "wav");  break;
			case 19: strcpy(suffix, "fdl");  break;
			default: strcpy(suffix, "raw");  break;
		}

		if (dir) {
			sprintf(fn, "%s/%d.%s", dir, id, suffix);
		} else {
			sprintf(fn, "%d.%s", id, suffix);
		}
		out = fopen(fn, "w");
		fwrite(buf + i, 1, bytes, out);
		fclose(out);
	}
}


int
main(int argc, char **argv)
{
	struct stat st;
	char *buf;
	FILE *in;
	size_t len;
	int i;
	int objects;

	if (argc < 2) {
		printf("Usage: %s INPUT [OUTDIR]\n", argv[0]);
		return EXIT_SUCCESS;
	}

	i = stat(argv[1], &st);
	if (i != 0) {
		fprintf(stderr, "failed to stat %s\n", argv[1]);
		return EXIT_FAILURE;
	}

	in = fopen(argv[1], "r");
	assert(in);
	buf = malloc(st.st_size);
	assert(buf);

	len = fread(buf, 1, st.st_size, in);
	fclose(in);

	printf("%d bytes\n", (int) len);

	if (buf[0] != 0x02 || buf[1] != 0x0a ||
			strncmp(buf + 2, "CART", 4) != 0) {
		printf("not wherigo cartridge\n");
	}

	objects = to_uint16(buf, 7);
	printf("%d objects\n\n", objects);

	i = 9 + objects * 6;
	i = read_header(buf, i);

	i += 4;

	for (i = 0; i < objects; i++) {
		read_object(buf, i, argc == 3 ? argv[2] : NULL);
	}

	free(buf);

	return 0;
}
