<?php

// Copyright (C) 2007 Cygnus <cygnus2 at post.cz>
//
// 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

define ("CTRY","\033[1;34;40m -\033[0m ");
define ("COK","\033[1;32;40m *\033[0m ");
define ("CWARN","\033[1;33;40m ?\033[0m ");
define ("CFAIL","\033[1;31;40m !\033[0m ");
define ("CWHITE","\033[1;37;40m");
define ("CDEF","\033[0m");

// configuration
define ('HEX_PREFIX', '0');		// 0x # $
define ('HEX_SUFFIX', 'h');		// $ h

define ('BIN_PREFIX', '');		// %
define ('BIN_SUFFIX', 'b');		// b

$cfg_infile		= './infile.bin';
$cfg_outfile		= './outfile.za';

$cfg_first_address	= 0;		// first address in binary file
$cfg_from_address	= 36644;	// convert from address
$cfg_length		= 64;		// 0 = no limit (input file)
$cfg_output_format	= 'b';		// a = ascii, b = binary, h = hex, d = dec
$cfg_bytes_per_line	= 1;		// bytes per line in db

// (1 = enable, 0 = disable)
$cfg_hex_labels		= 0;		// generate labels with hexadecimal address on every line
$cfg_dec_labels		= 0;		// generate labels with decadic address on every line
$cfg_hex_caps		= 0;		// big letters in hexadecimal numbers
$cfg_dec_comments	= 1;		// decadic addresses in comments
$cfg_hex_comments	= 1;		// hexadecimal addresses in comments
$cfg_double_tabs	= 1;		// 2x tab on every start of line
$cfg_db			= 1;		// generate db for Z80 assembler AS
$cfg_ram_limit		= 1;		// if enabled, last address will 65535

// --------------------------------------------------------------------------------------

function to_hex ($byte)
{
	global $cfg_hex_caps;

	if ($cfg_hex_caps)
		$hextab = array ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
	else
		$hextab = array ('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');

	$b = floor($byte / 16);
	$a = $byte - ($b * 16);

	return ($hextab[$b].$hextab[$a]);

}

function to_hex_16b ($word)
{
	$h = floor ($word / 256);
	$l = $word - 256 * $h;

	return (to_hex($h).to_hex($l));
}

function to_binary ($byte)
{
	$bin = decbin($byte);
	return (substr("00000000",0,8 - strlen($bin)).$bin);
}

function to_ascii ($byte)
{
	if ($byte < 32) $a = 32;
	if ($byte == 127) $a = 'c';
	if ($byte >= 32 && $byte < 127) $a = $byte;
	if ($byte > 127) $a = '.';

	return ($a);
}

// --------------------------------------------------------------------------------------
function line_out ($ln, $out)
{
	global $cfg_dec_comments;
	global $cfg_hex_comments;
	global $cfg_output_format;

	if ($cfg_dec_comments || $cfg_hex_comments)
	{
		if ($cfg_output_format == 'a')
			$ln['string'] .= '"';

		$ln['string'] .= "\t\t; ";

		if ($cfg_dec_comments)
		{
			$ln['string'] .= '('.$ln['address'].')';
			if ($cfg_hex_comments)
				$ln['string'] .= ', ';
		}

		if ($cfg_hex_comments)
			$ln['string'] .= '('.to_hex_16b($ln['address']).')';
	}

	$ln['string'] .= "\n";

	fwrite ($out, $ln['string']);
}

function convert ($in, $out)
{
	global $cfg_first_address;	// values
	global $cfg_from_address;
	global $cfg_length;
	global $cfg_output_format;
	global $cfg_bytes_per_line;

	global $cfg_hex_labels;		// (1 = enable, 0 = disable)
	global $cfg_dec_labels;
	global $cfg_dec_comments;
	global $cfg_hex_comments;
	global $cfg_double_tabs;
	global $cfg_db;

	$error = 0;
	$error_msg = '';

	// skip bytes if needed
	if ($cfg_from_address > 0 && $error == 0)
	{
		// skip snapshot header (sna)
		$counter = 0;

		while ($error == 0)
		{
			$byte = fread($in, 1);
			$counter++;

			if ($byte === false)
			{
				$error = 1;
				$error_msg = CFAIL."You trying skip more bytes than are in file.\n".CDEF;
				break;
			}

			if ($counter >= $cfg_from_address)
			{
				echo COK."$counter bytes skipped\n".CDEF;
				break;
			}
		}

	}

	if ($error == 0) echo COK."Converting from address ".($cfg_first_address + $cfg_from_address)."\n".CDEF;

	// convert
	$byte_counter = 0;
	$ln['address'] = $cfg_first_address + $cfg_from_address;
	$ln['bytes'] = 0;
	$ln['counter'] = 0;
	$ln['string'] = '';

	while (1)
	{
		$char = fread($in, 1);

		// end of file?
		if ($char === false || feof($in))
		{
			if ($ln['string'] != '') line_out ($ln, $out);
			echo COK."All OK\n".CDEF;
			break;
		}

		// Z80 address space limit?
		if ($cfg_ram_limit == 1 && ($byte_counter + $cfg_first_address + $cfg_from_address) > 65535)
		{
			if ($ln['string'] != '') line_out ($ln, $out);
			$error = 1;
			echo CFAIL."Wrong address - cannot be bigger than 65535!\n".CDEF;
			break;
		}

		$byte = unpack ('C', $char);	// character must be unpacked to byte = unsigned char

		// length limit?
		if ($error == 0 && $cfg_length > 0 && $byte_counter >= $cfg_length)
		{
			if ($ln['string'] != '') line_out ($ln, $out);
			echo COK."All OK\n".CDEF;
			break;
		}

		// beginning of new line
		if ($error == 0 && $ln['bytes'] == 0)
		{
			if ($cfg_hex_labels)
			{
				$ln['string'] .= ('A_'.to_hex_16b($ln['address']));
			}
			else
			{
				if ($cfg_dec_labels)
					$ln['string'] .= ('A_'.$ln['address']);
			}

			if ($cfg_db)
				$ln['string'] .= "\t\tdb\t";
			else
				if ($cfg_double_tabs)
					$ln['string'] .= "\t\t\t";
				else
				{
					if ($cfg_hex_labels || $cfg_dec_labels)
						$ln['string'] .= "\t";
				}
		}

		// convert byte from file to correct text form (ASL compatible)
		switch ($cfg_output_format)
		{
			// a = ascii, b = binary, h = hex, d = dec
			case 'a':
				if ($ln['bytes'] == 0) $ln['string'] .= "\"";
				$ln['string'] .= to_ascii ($byte[1]);
				$byte_counter++;
				$ln['bytes']++;
				break;
			case 'b':
				if ($ln['bytes'] > 0) $ln['string'] .= ", ";
				$ln['string'] .= BIN_PREFIX.to_binary ($byte[1]).BIN_SUFFIX;
				$byte_counter++;
				$ln['bytes']++;
				break;
			case 'h':
				if ($ln['bytes'] > 0) $ln['string'] .= ", ";
				$ln['string'] .= HEX_PREFIX.to_hex($byte[1]).HEX_SUFFIX;
				$byte_counter++;
				$ln['bytes']++;
				break;
			case 'd':
				if ($ln['bytes'] > 0) $ln['string'] .= ", ";
				$ln['string'] .= $byte[1];
				$byte_counter++;
				$ln['bytes']++;
				break;
		}

		// detect end of line for all cases
		if ($error == 0 && $ln['bytes'] >= $cfg_bytes_per_line)
		{
			line_out ($ln, $out);
			$ln['address'] += $ln['bytes'];
			$ln['bytes'] = 0;
			$ln['counter']++;
			$ln['string'] = '';
		}
	}

	if ($error) echo $error_msg;
}

// --------------------------------------------------------------------------------------

$in = fopen ($cfg_infile, "r");
$out = fopen ($cfg_outfile, "w");

if ($in && $out)
{
	convert ($in, $out);
}
else
{
	if (!$in) echo CFAIL."Warning: opening input file \"$cfg_infile\" failed.\n".CDEF;
	if (!$out) echo CFAIL."Warning: opening output file  \"$cfg_outfile\" failed.\n".CDEF;
}

if ($in) fclose ($in);
if ($out) fclose ($out);

return (0);

?>