/* $Id$ */
/* Copyright (c) 2011-2022 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Devel Asm */
/* 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, version 3 of the License.
 *
 * 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/>. */



#include <System.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "Asm.h"

#ifndef PROGNAME_DEASM
# define PROGNAME_DEASM "deasm"
#endif


/* deasm */
/* private */
/* prototypes */
static int _deasm(char const * arch, char const * format, char const * filename,
		int raw);
static int _deasm_buffer(char const * arch, char const * buffer, size_t size);
static int _deasm_string(char const * arch, char const * format,
		char const * string);
static int _deasm_list(void);

static int _usage(void);


/* functions */
/* deasm */
static int _deasm_section(AsmCode * code, AsmSection * section);

static int _deasm(char const * arch, char const * format, char const * filename,
		int raw)
{
	int ret = -1;
	Asm * a;
	AsmCode * code;
	AsmSection * sections;
	size_t sections_cnt;
	size_t i;

	if((a = asm_new(arch, format)) == NULL)
		return -error_print(PROGNAME_DEASM);
	if((code = asm_open_deassemble(a, filename, raw)) == NULL)
		error_print(PROGNAME_DEASM);
	else
	{
		ret = 0;
		printf("%s: %s-%s\n", filename, asm_get_format(a),
				asm_get_arch(a));
		asmcode_get_sections(code, &sections, &sections_cnt);
		for(i = 0; i < sections_cnt; i++)
			if((ret = _deasm_section(code, &sections[i])) != 0)
				break;
		asm_close(a);
	}
	asm_delete(a);
	return ret;
}

static int _deasm_section(AsmCode * code, AsmSection * section)
{
	AsmArchDefinition const * definition;
	size_t size;
	AsmArchInstructionCall * calls = NULL;
	size_t calls_cnt = 0;
	size_t i;

	printf("\nDisassembly of section %s:\n", section->name);
	if(asmcode_decode_section(code, section, &calls, &calls_cnt) != 0)
	{
		error_print(PROGNAME_DEASM);
		return -1;
	}
	definition = asmcode_get_arch_definition(code);
	size = (definition != NULL) ? definition->address_size : 32;
	switch(size)
	{
		case 64:
			printf("\n%016lx:\n", section->base);
			break;
		case 20:
			printf("\n%05lx:\n", section->base);
			break;
		case 16:
			printf("\n%04lx:\n", section->base);
			break;
		case 32:
		default:
			printf("\n%08lx:\n", section->base);
			break;
	}
	for(i = 0; i < calls_cnt; i++)
		asmcode_print(code, &calls[i]);
	free(calls);
	return 0;
}


/* deasm_buffer */
static int _deasm_buffer(char const * arch, char const * buffer, size_t size)
{
	Asm * a;
	AsmCode * code;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if((a = asm_new(arch, NULL)) == NULL)
		return -1;
	if((code = asm_deassemble(a, buffer, size, NULL, NULL)) == NULL)
		error_print(PROGNAME_DEASM);
	else
	{
		/* FIXME implement */
	}
	asm_delete(a);
	return 0;
}


/* deasm_string */
static int _string_hex2bin(int c);
static int _string_ishex(int c);

static int _deasm_string(char const * arch, char const * format,
		char const * string)
{
	int ret;
	unsigned char * str = (unsigned char *)string;
	size_t len = strlen(string);
	char * s;
	size_t i;
	size_t j;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", \"%s\")\n", __func__, arch,
			format, string);
#endif
	if((s = malloc(len + 1)) == NULL)
		return -error_set_print(PROGNAME_DEASM, 1, "%s",
				strerror(errno));
	for(i = 0, j = 0; i < len; i++)
	{
		if(str[i] != '\\')
			s[j++] = str[i];
		else if(str[i + 1] != 'x') /* "\\" */
			s[j++] = str[++i];
		else if(i + 3 < len && _string_ishex(str[i + 2])
				&& _string_ishex(str[i + 3])) /* "\xHH" */
		{
			s[j++] = (_string_hex2bin(str[i + 2]) << 4)
				| _string_hex2bin(str[i + 3]);
			i += 3;
		}
	}
	s[j] = '\0'; /* not really necessary */
	ret = _deasm_buffer(arch, s, j);
	free(s);
	return ret;
}

static int _string_hex2bin(int c)
{
	if(c >= '0' && c <= '9')
		return c - '0';
	if(c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	if(c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	return -1;
}

static int _string_ishex(int c)
{
	return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
		|| (c >= 'A' || c <= 'F');
}


/* deasm_list */
static int _deasm_list(void)
{
	int res = 0;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(asm_plugin_list(APT_ARCH, 1) != 0)
		res = error_print(PROGNAME_DEASM);
	else
		putchar('\n');
	if(asm_plugin_list(APT_FORMAT, 1) != 0)
		res = error_print(PROGNAME_DEASM);
	return (res == 0) ? 0 : 2;
}


/* usage */
static int _usage(void)
{
	fputs("Usage: " PROGNAME_DEASM " [-a arch][-f format][-D] filename\n"
"       " PROGNAME_DEASM " [-a arch] -s string\n"
"       " PROGNAME_DEASM " -l\n"
"  -a	Force the given architecture\n"
"  -f	Force the given file format\n"
"  -D	Disassemble all sections\n"
"  -l	List all the architectures and file formats available\n", stderr);
	return 1;
}


/* public */
/* functions */
/* main */
int main(int argc, char * argv[])
{
	int o;
	char const * arch = NULL;
	char const * format = NULL;
	char const * string = NULL;
	int raw = 0;

	while((o = getopt(argc, argv, "a:f:ls:D")) != -1)
		switch(o)
		{
			case 'a':
				arch = optarg;
				break;
			case 'f':
				format = optarg;
				break;
			case 'l':
				return _deasm_list();
			case 's':
				string = optarg;
				break;
			case 'D':
				raw = 1;
				break;
			default:
				return _usage();
		}
	if(optind == argc && string != NULL)
		return _deasm_string(arch, format, string);
	else if(optind + 1 == argc && string == NULL)
		return (_deasm(arch, format, argv[optind], raw) == 0) ? 0 : 2;
	return _usage();
}
