/*
 *   Copyright (C) 2006-2010  Michael Buesch <m@bues.ch>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License version 2
 *   as published by the Free Software Foundation.
 *
 *   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.
 */

#include "main.h"
#include "list.h"
#include "util.h"
#include "args.h"

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>


struct bin_instruction {
	unsigned int opcode;
	unsigned int operands[3];
};

struct statement {
	enum {
		STMT_INSN,
		STMT_LABEL,
	} type;

	union {
		struct {
			struct bin_instruction *bin;
			const char *name;
			const char *operands[5];

			int labelref_operand;
			unsigned int labeladdr;
			struct statement *labelref;
		} insn;
		struct {
			char *name;
		} label;
	} u;

	struct list_head list;
};

struct disassembler_context {
	/* The architecture of the input file. */
	unsigned int arch;

	struct bin_instruction *code;
	size_t nr_insns;

	struct list_head stmt_list;
};


FILE *infile;
FILE *outfile;
const char *infile_name;
const char *outfile_name;


#define _msg_helper(type, msg, x...)	do {		\
	fprintf(stderr, "Disassembler " type		\
		":\n  " msg "\n" ,##x);			\
					} while (0)

#define dasm_error(msg, x...)	do {		\
	_msg_helper("ERROR", msg ,##x);		\
	exit(1);				\
				} while (0)

#define dasm_int_error(msg, x...) \
	dasm_error("Internal error (bug): " msg ,##x)

#define dasm_warn(msg, x...)	\
	_msg_helper("warning", msg ,##x)

#define asm_info(msg, x...)	\
	_msg_helper("info", msg ,##x)

static const char * gen_raw_code(unsigned int operand)
{
	char *ret;

	ret = xmalloc(32);
	snprintf(ret, 32, "@%X", operand);

	return ret;
}

static const char * disasm_mem_operand(unsigned int operand)
{
	char *ret;

	ret = xmalloc(32);
	snprintf(ret, 32, "[0x%X]", operand);

	return ret;
}

static const char * disasm_indirect_mem_operand(unsigned int operand)
{
	char *ret;
	unsigned int offset, reg;

	switch (cmdargs.arch) {
	case 5:
		offset = (operand & 0x3F);
		reg = ((operand >> 6) & 0x7);
		break;
	case 15:
		offset = (operand & 0x7F);
		reg = ((operand >> 7) & 0x7);
		break;
	default:
		dasm_int_error("disasm_indirect_mem_operand invalid arch");
	}
	ret = xmalloc(32);
	snprintf(ret, 32, "[0x%02X,off%u]", offset, reg);

	return ret;
}

static const char * disasm_imm_operand(unsigned int operand)
{
	char *ret;
	unsigned int signmask;
	unsigned int mask;

	switch (cmdargs.arch) {
	case 5:
		signmask = (1 << 9);
		mask = 0x3FF;
		break;
	case 15:
		signmask = (1 << 10);
		mask = 0x7FF;
		break;
	default:
		dasm_int_error("disasm_imm_operand invalid arch");
	}

	operand &= mask;

	ret = xmalloc(32);
	if (operand & signmask)
		operand = (operand | (~mask & 0xFFFF));
	snprintf(ret, 32, "0x%X", operand);

	return ret;
}

static const char * disasm_spr_operand(unsigned int operand)
{
	char *ret;
	unsigned int mask;

	switch (cmdargs.arch) {
	case 5:
		mask = 0x1FF;
		break;
	case 15:
		mask = 0x7FF;
		break;
	default:
		dasm_int_error("disasm_spr_operand invalid arch");
	}

	ret = xmalloc(32);
	snprintf(ret, 32, "spr%X", (operand & mask));

	return ret;
}

static const char * disasm_gpr_operand(unsigned int operand)
{
	char *ret;
	unsigned int mask;

	switch (cmdargs.arch) {
	case 5:
		mask = 0x3F;
		break;
	case 15:
		mask = 0x7F;
		break;
	default:
		dasm_int_error("disasm_gpr_operand invalid arch");
	}

	ret = xmalloc(32);
	snprintf(ret, 32, "r%u", (operand & mask));

	return ret;
}

static void disasm_raw_operand(struct statement *stmt,
			       int oper_idx,
			       int out_idx)
{
	unsigned int operand = stmt->u.insn.bin->operands[oper_idx];

	stmt->u.insn.operands[out_idx] = gen_raw_code(operand);
}

static void disasm_std_operand(struct statement *stmt,
			       int oper_idx,
			       int out_idx)
{
	unsigned int operand = stmt->u.insn.bin->operands[oper_idx];

	switch (cmdargs.arch) {
	case 5:
		if (!(operand & 0x800)) {
			stmt->u.insn.operands[out_idx] = disasm_mem_operand(operand);
			return;
		} else if ((operand & 0xC00) == 0xC00) { 
			stmt->u.insn.operands[out_idx] = disasm_imm_operand(operand);
			return;
		} else if ((operand & 0xFC0) == 0xBC0) {
			stmt->u.insn.operands[out_idx] = disasm_gpr_operand(operand);
			return;
		} else if ((operand & 0xE00) == 0x800) {
			stmt->u.insn.operands[out_idx] = disasm_spr_operand(operand);
			return;
		} else if ((operand & 0xE00) == 0xA00) {
			stmt->u.insn.operands[out_idx] = disasm_indirect_mem_operand(operand);
			return;
		}
		break;
	case 15:
		if (!(operand & 0x1000)) {
			stmt->u.insn.operands[out_idx] = disasm_mem_operand(operand);
			return;
		} else if ((operand & 0x1800) == 0x1800) { 
			stmt->u.insn.operands[out_idx] = disasm_imm_operand(operand);
			return;
		} else if ((operand & 0x1F80) == 0x1780) {
			stmt->u.insn.operands[out_idx] = disasm_gpr_operand(operand);
			return;
		} else if ((operand & 0x1C00) == 0x1000) {
			stmt->u.insn.operands[out_idx] = disasm_spr_operand(operand);
			return;
		} else if ((operand & 0x1C00) == 0x1400) {
			stmt->u.insn.operands[out_idx] = disasm_indirect_mem_operand(operand);
			return;
		}
		break;
	default:
		dasm_int_error("disasm_std_operand invalid arch");
	}
	/* No luck. Disassemble to raw operand. */
	disasm_raw_operand(stmt, oper_idx, out_idx);
}

static void disasm_opcode_raw(struct disassembler_context *ctx,
			      struct statement *stmt,
			      int raw_operands)
{
	stmt->u.insn.name = gen_raw_code(stmt->u.insn.bin->opcode);
	if (raw_operands) {
		disasm_raw_operand(stmt, 0, 0);
		disasm_raw_operand(stmt, 1, 1);
		disasm_raw_operand(stmt, 2, 2);
	} else {
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
	}
}

static void disasm_constant_opcodes(struct disassembler_context *ctx,
				    struct statement *stmt)
{
	struct bin_instruction *bin = stmt->u.insn.bin;

	switch (bin->opcode) {
	case 0x101:
		stmt->u.insn.name = "mul";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1C0:
		stmt->u.insn.name = "add";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1C2:
		stmt->u.insn.name = "add.";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1C1:
		stmt->u.insn.name = "addc";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1C3:
		stmt->u.insn.name = "addc.";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1D0:
		stmt->u.insn.name = "sub";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1D2:
		stmt->u.insn.name = "sub.";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1D1:
		stmt->u.insn.name = "subc";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1D3:
		stmt->u.insn.name = "subc.";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x130:
		stmt->u.insn.name = "sra";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x160:
		stmt->u.insn.name = "or";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x140:
		stmt->u.insn.name = "and";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x170:
		stmt->u.insn.name = "xor";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x120:
		stmt->u.insn.name = "sr";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x110:
		stmt->u.insn.name = "sl";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1A0:
		stmt->u.insn.name = "rl";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x1B0:
		stmt->u.insn.name = "rr";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x150:
		stmt->u.insn.name = "nand";
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		disasm_std_operand(stmt, 2, 2);
		break;
	case 0x040:
		stmt->u.insn.name = "jand";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x040 | 0x1):
		stmt->u.insn.name = "jnand";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x050:
		stmt->u.insn.name = "js";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x050 | 0x1):
		stmt->u.insn.name = "jns";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x0D0:
		stmt->u.insn.name = "je";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x0D0 | 0x1):
		stmt->u.insn.name = "jne";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x0D2:
		stmt->u.insn.name = "jls";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x0D2 | 0x1):
		stmt->u.insn.name = "jges";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x0D4:
		stmt->u.insn.name = "jgs";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x0D4 | 0x1):
		stmt->u.insn.name = "jles";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x0D6:
		stmt->u.insn.name = "jdn";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x0D6 | 0x1):
		stmt->u.insn.name = "jdpz";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x0D8:
		stmt->u.insn.name = "jdp";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x0D8 | 0x1):
		stmt->u.insn.name = "jdnz";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x0DA:
		stmt->u.insn.name = "jl";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x0DA | 0x1):
		stmt->u.insn.name = "jge";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x0DC:
		stmt->u.insn.name = "jg";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case (0x0DC | 0x1):
		stmt->u.insn.name = "jle";
		stmt->u.insn.labelref_operand = 2;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 1, 1);
		break;
	case 0x002: {
		char *str;

		switch (cmdargs.arch) {
		case 5:
			stmt->u.insn.name = "call";
			stmt->u.insn.labelref_operand = 1;
			stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
			str = xmalloc(32);
			snprintf(str, 32, "lr%u", stmt->u.insn.bin->operands[0]);
			stmt->u.insn.operands[0] = str;
			break;
		case 15:
			//FIXME: This opcode is different on r15. Decode raw for now.
			disasm_opcode_raw(ctx, stmt, 1);
			break;
		}
		break;
	}
	case 0x003: {
		char *str;

		stmt->u.insn.name = "ret";
		str = xmalloc(32);
		snprintf(str, 32, "lr%u", stmt->u.insn.bin->operands[0]);
		stmt->u.insn.operands[0] = str;
		str = xmalloc(32);
		snprintf(str, 32, "lr%u", stmt->u.insn.bin->operands[2]);
		stmt->u.insn.operands[2] = str;
		break;
	}
	case 0x004: {
		if (cmdargs.arch != 15) {
			dasm_error("arch 15 'calls' instruction found in arch %d binary",
				   cmdargs.arch);
		}
		stmt->u.insn.name = "calls";
		stmt->u.insn.labelref_operand = 0;
		stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
		if (stmt->u.insn.bin->operands[0] != 0x1780 ||
		    stmt->u.insn.bin->operands[1] != 0x1780)
			dasm_warn("r15 calls: Invalid first or second argument");
		break;
	}
	case 0x005: {
		if (cmdargs.arch != 15) {
			dasm_error("arch 15 'rets' instruction found in arch %d binary",
				   cmdargs.arch);
		}
		stmt->u.insn.name = "rets";
		if (stmt->u.insn.bin->operands[0] != 0x1780 ||
		    stmt->u.insn.bin->operands[1] != 0x1780 ||
		    stmt->u.insn.bin->operands[2] != 0)
			dasm_warn("r15 rets: Invalid argument(s)");
		break;
	}
	case 0x1E0: {
		unsigned int flags, mask;

		switch (cmdargs.arch) {
		case 5:
			mask = 0x3FF;
			break;
		case 15:
			mask = 0x7FF;
			break;
		default:
			dasm_int_error("TKIP invalid arch");
		}

		flags = stmt->u.insn.bin->operands[1];
		switch (flags & mask) {
		case 0x1:
			stmt->u.insn.name = "tkiph";
			break;
		case (0x1 | 0x2):
			stmt->u.insn.name = "tkiphs";
			break;
		case 0x0:
			stmt->u.insn.name = "tkipl";
			break;
		case (0x0 | 0x2):
			stmt->u.insn.name = "tkipls";
			break;
		default:
			dasm_error("Invalid TKIP flags %X", flags);
		}
		disasm_std_operand(stmt, 0, 0);
		disasm_std_operand(stmt, 2, 2);
		break;
	}
	case 0x001: {
		unsigned int mask;

		stmt->u.insn.name = "nap";
		switch (cmdargs.arch) {
		case 5:
			mask = 0xBC0;
			break;
		case 15:
			mask = 0x1780;
			break;
		default:
			dasm_int_error("NAP invalid arch");
		}
		if (stmt->u.insn.bin->operands[0] != mask) {
			dasm_warn("NAP: invalid first argument 0x%04X\n",
				  stmt->u.insn.bin->operands[0]);
		}
		if (stmt->u.insn.bin->operands[1] != mask) {
			dasm_warn("NAP: invalid second argument 0x%04X\n",
				  stmt->u.insn.bin->operands[1]);
		}
		if (stmt->u.insn.bin->operands[2] != 0) {
			dasm_warn("NAP: invalid third argument 0x%04X\n",
				  stmt->u.insn.bin->operands[2]);
		}
		break;
	}
	case 0x000:
		disasm_opcode_raw(ctx, stmt, 1);
		break;
	default:
		disasm_opcode_raw(ctx, stmt, (cmdargs.unknown_decode == 0));
		break;
	}
}

static void disasm_opcodes(struct disassembler_context *ctx)
{
	struct bin_instruction *bin;
	size_t i;
	struct statement *stmt;
	char *str;

	for (i = 0; i < ctx->nr_insns; i++) {
		bin = &(ctx->code[i]);

		stmt = xmalloc(sizeof(struct statement));
		stmt->type = STMT_INSN;
		INIT_LIST_HEAD(&stmt->list);
		stmt->u.insn.bin = bin;
		stmt->u.insn.labelref_operand = -1; /* none */

		switch (bin->opcode & 0xF00) {
		case 0x200:
			stmt->u.insn.name = "srx";

			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x0F0) >> 4);
			stmt->u.insn.operands[0] = str;
			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x00F));
			stmt->u.insn.operands[1] = str;

			disasm_std_operand(stmt, 0, 2);
			disasm_std_operand(stmt, 1, 3);
			disasm_std_operand(stmt, 2, 4);
			break;
		case 0x300:
			stmt->u.insn.name = "orx";

			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x0F0) >> 4);
			stmt->u.insn.operands[0] = str;
			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x00F));
			stmt->u.insn.operands[1] = str;

			disasm_std_operand(stmt, 0, 2);
			disasm_std_operand(stmt, 1, 3);
			disasm_std_operand(stmt, 2, 4);
			break;
		case 0x400:
			stmt->u.insn.name = "jzx";

			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x0F0) >> 4);
			stmt->u.insn.operands[0] = str;
			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x00F));
			stmt->u.insn.operands[1] = str;

			disasm_std_operand(stmt, 0, 2);
			disasm_std_operand(stmt, 1, 3);
			stmt->u.insn.labelref_operand = 4;
			stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
			break;
		case 0x500:
			stmt->u.insn.name = "jnzx";

			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x0F0) >> 4);
			stmt->u.insn.operands[0] = str;
			str = xmalloc(32);
			snprintf(str, 32, "%d", (bin->opcode & 0x00F));
			stmt->u.insn.operands[1] = str;

			disasm_std_operand(stmt, 0, 2);
			disasm_std_operand(stmt, 1, 3);
			stmt->u.insn.labelref_operand = 4;
			stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
			break;
		case 0x600:
			stmt->u.insn.name = "jnext";

			str = xmalloc(32);
			snprintf(str, 32, "0x%02X", (bin->opcode & 0x0FF));
			stmt->u.insn.operands[0] = str;

			/* We don't disassemble the first and second operand, as
			 * that always is a dummy r0 operand.
			 * disasm_std_operand(stmt, 0, 1);
			 * disasm_std_operand(stmt, 1, 2);
			 * stmt->u.insn.labelref_operand = 3;
			 */
			stmt->u.insn.labelref_operand = 1;
			stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
			break;
		case 0x700:
			stmt->u.insn.name = "jext";

			str = xmalloc(32);
			snprintf(str, 32, "0x%02X", (bin->opcode & 0x0FF));
			stmt->u.insn.operands[0] = str;

			/* We don't disassemble the first and second operand, as
			 * that always is a dummy r0 operand.
			 * disasm_std_operand(stmt, 0, 1);
			 * disasm_std_operand(stmt, 1, 2);
			 * stmt->u.insn.labelref_operand = 3;
			 */
			stmt->u.insn.labelref_operand = 1;
			stmt->u.insn.labeladdr = stmt->u.insn.bin->operands[2];
			break;
		default:
			disasm_constant_opcodes(ctx, stmt);
			break;
		}

		list_add_tail(&stmt->list, &ctx->stmt_list);
	}
}

static struct statement * get_label_at(struct disassembler_context *ctx,
				       unsigned int addr)
{
	unsigned int addrcnt = 0;
	struct statement *stmt, *ret, *prev;

	list_for_each_entry(stmt, &ctx->stmt_list, list) {
		if (stmt->type != STMT_INSN)
			continue;
		if (addrcnt == addr) {
			prev = list_entry(stmt->list.prev, struct statement, list);
			if (prev->type == STMT_LABEL)
				return prev;
			ret = xmalloc(sizeof(struct statement));
			INIT_LIST_HEAD(&ret->list);
			ret->type = STMT_LABEL;
			list_add(&ret->list, &prev->list);

			return ret;
		}
		addrcnt++;
	}

	return NULL;
}

static void resolve_labels(struct disassembler_context *ctx)
{
	struct statement *stmt;
	struct statement *label;
	struct statement *n;
	unsigned int labeladdr;
	unsigned int namecnt = 0;

	/* Resolve label references */
	list_for_each_entry_safe(stmt, n, &ctx->stmt_list, list) {
		if (stmt->type != STMT_INSN)
			continue;
		if (stmt->u.insn.labelref_operand < 0)
			continue; /* Doesn't have label reference operand. */
		labeladdr = stmt->u.insn.labeladdr;
		label = get_label_at(ctx, labeladdr);
		if (!label)
			dasm_error("Labeladdress %X out of bounds", labeladdr);
		stmt->u.insn.labelref = label;
	}

	/* Name the labels */
	list_for_each_entry(stmt, &ctx->stmt_list, list) {
		if (stmt->type != STMT_LABEL)
			continue;
		stmt->u.label.name = xmalloc(32);
		snprintf(stmt->u.label.name, 32, "L%u", namecnt);
		namecnt++;
	}
}

static void emit_asm(struct disassembler_context *ctx)
{
	struct statement *stmt;
	int first;
	int err;
	unsigned int i, addr = 0;

	err = open_output_file();
	if (err)
		exit(1);

	fprintf(outfile, "%%arch %u\n", ctx->arch);
	fprintf(outfile, "%%start entry\n\n");
	fprintf(outfile, "entry:\n");
	list_for_each_entry(stmt, &ctx->stmt_list, list) {
		switch (stmt->type) {
		case STMT_INSN:
			if (cmdargs.print_addresses)
				fprintf(outfile, "/* %04X */", addr);
			fprintf(outfile, "\t%s", stmt->u.insn.name);
			first = 1;
			for (i = 0; i < ARRAY_SIZE(stmt->u.insn.operands); i++) {
				if (!stmt->u.insn.operands[i] &&
				    (stmt->u.insn.labelref_operand < 0 ||
				     (unsigned int)stmt->u.insn.labelref_operand != i))
					continue;
				if (first)
					fprintf(outfile, "\t");
				if (!first)
					fprintf(outfile, ", ");
				first = 0;
				if (stmt->u.insn.labelref_operand >= 0 &&
				    (unsigned int)stmt->u.insn.labelref_operand == i) {
					fprintf(outfile, "%s",
						stmt->u.insn.labelref->u.label.name);
				} else {
					fprintf(outfile, "%s",
						stmt->u.insn.operands[i]);
				}
			}
			fprintf(outfile, "\n");
			addr++;
			break;
		case STMT_LABEL:
			fprintf(outfile, "%s:\n", stmt->u.label.name);
			break;
		}
	}

	close_output_file();
}

static int read_input(struct disassembler_context *ctx)
{
	size_t size = 0, pos = 0;
	size_t ret;
	struct bin_instruction *code = NULL;
	unsigned char tmp[sizeof(uint64_t)];
	uint64_t codeword = 0;
	struct fw_header hdr;
	int err;

	err = open_input_file();
	if (err)
		goto error;

	switch (cmdargs.informat) {
	case FMT_RAW_LE32:
	case FMT_RAW_BE32:
		/* Nothing */
		break;
	case FMT_B43:
		ret = fread(&hdr, 1, sizeof(hdr), infile);
		if (ret != sizeof(hdr)) {
			fprintf(stderr, "Corrupt input file (no b43 header found)\n");
			goto err_close;
		}
		if (hdr.type != FW_TYPE_UCODE) {
			fprintf(stderr, "Corrupt input file. Not a b43 microcode image.\n");
			goto err_close;
		}
		if (hdr.ver != FW_HDR_VER) {
			fprintf(stderr, "Invalid input file header version.\n");
			goto err_close;
		}
		break;
	}

	while (1) {
		if (pos >= size) {
			size += 512;
			code = xrealloc(code, size * sizeof(struct bin_instruction));
		}
		ret = fread(tmp, 1, sizeof(uint64_t), infile);
		if (!ret)
			break;
		if (ret != sizeof(uint64_t)) {
			fprintf(stderr, "Corrupt input file (not 8 byte aligned)\n");
			goto err_free_code;
		}

		switch (cmdargs.informat) {
		case FMT_B43:
		case FMT_RAW_BE32:
			codeword = 0;
			codeword |= ((uint64_t)tmp[0]) << 56;
			codeword |= ((uint64_t)tmp[1]) << 48;
			codeword |= ((uint64_t)tmp[2]) << 40;
			codeword |= ((uint64_t)tmp[3]) << 32;
			codeword |= ((uint64_t)tmp[4]) << 24;
			codeword |= ((uint64_t)tmp[5]) << 16;
			codeword |= ((uint64_t)tmp[6]) << 8;
			codeword |= ((uint64_t)tmp[7]);
			codeword = ((codeword & (uint64_t)0xFFFFFFFF00000000ULL) >> 32) |
				   ((codeword & (uint64_t)0x00000000FFFFFFFFULL) << 32);
			break;
		case FMT_RAW_LE32:
			codeword = 0;
			codeword |= ((uint64_t)tmp[7]) << 56;
			codeword |= ((uint64_t)tmp[6]) << 48;
			codeword |= ((uint64_t)tmp[5]) << 40;
			codeword |= ((uint64_t)tmp[4]) << 32;
			codeword |= ((uint64_t)tmp[3]) << 24;
			codeword |= ((uint64_t)tmp[2]) << 16;
			codeword |= ((uint64_t)tmp[1]) << 8;
			codeword |= ((uint64_t)tmp[0]);
			break;
		}

		switch (cmdargs.arch) {
		case 5:
			if (codeword >> 48) {
				fprintf(stderr, "Instruction format error at 0x%X (upper not clear). "
					"Wrong input format or architecture?\n", (unsigned int)pos);
				goto err_free_code;
			}
			code[pos].opcode = (codeword >> 36) & 0xFFF;
			code[pos].operands[2] = codeword & 0xFFF;
			code[pos].operands[1] = (codeword >> 12) & 0xFFF;
			code[pos].operands[0] = (codeword >> 24) & 0xFFF;
			break;
		case 15:
			if (codeword >> 51) {
				fprintf(stderr, "Instruction format error at 0x%X (upper not clear). "
					"Wrong input format or architecture?\n", (unsigned int)pos);
				goto err_free_code;
			}
			code[pos].opcode = (codeword >> 39) & 0xFFF;
			code[pos].operands[2] = codeword & 0x1FFF;
			code[pos].operands[1] = (codeword >> 13) & 0x1FFF;
			code[pos].operands[0] = (codeword >> 26) & 0x1FFF;
			break;
		default:
			fprintf(stderr, "Internal error: read_input unknown arch %u\n",
				cmdargs.arch);
			goto err_free_code;
		}

		pos++;
	}

	ctx->code = code;
	ctx->nr_insns = pos;

	close_input_file();

	return 0;

err_free_code:
	free(code);
err_close:
	close_input_file();
error:
	return -1;
}

static void disassemble(void)
{
	struct disassembler_context ctx;
	int err;

	memset(&ctx, 0, sizeof(ctx));
	INIT_LIST_HEAD(&ctx.stmt_list);
	ctx.arch = cmdargs.arch;

	err = read_input(&ctx);
	if (err)
		exit(1);
	disasm_opcodes(&ctx);
	resolve_labels(&ctx);
	emit_asm(&ctx);
}

int main(int argc, char **argv)
{
	int err, res = 1;

	err = parse_args(argc, argv);
	if (err < 0)
		goto out;
	if (err > 0) {
		res = 0;
		goto out;
	}
	disassemble();
	res = 0;
out:
	/* Lazyman simply leaks all allocated memory. */
	return res;
}
