#!/usr/bin/perl

# 6809 disassembler
# by Ciaran Anscomb, 2011

# Disclaimer:
# Possibly not fit for any purpose.  You're free to use, copy, modify and
# redistribute so long as I don't get the blame for anything.

my %need_labels = ();
my %labels = ();
my %def_labels = ();
my %forceorg = ();
my %comment_bytes = ();

my $origin = 0xc000;
my $code_end;
my @sections = ();
while (scalar(@ARGV) > 0) {
	my $arg = $ARGV[0];
	if ($arg =~ /^org=(\d+|0x[\da-f]+)$/i) {
		eval "\$origin = $1;";
	} elsif ($arg =~ /^end=(\d+|0x[\da-f]+)$/i) {
		eval "\$code_end = $1;";
	} elsif ($arg =~ /^forceorg=(\d+|0x[\da-f]+)$/i) {
		my $addr;
		eval "\$addr = $1;";
		$forceorg{$addr} = 1;
	} elsif ($arg =~ /^commentbyte=(\d+|0x[\da-f]+)$/i) {
		my $addr;
		eval "\$addr = $1;";
		$comment_bytes{$addr} = 1;
	} elsif ($arg =~ /^label=(\w+),(\d+|0x[\da-f]+)$/i) {
		my ($label, $addr);
		eval "\$label = $1; \$addr = $2;";
		$labels{$addr} = $label;
		$def_labels{$addr} = $label;
		$need_labels{$addr} = 1;
	} elsif ($arg =~ /^fdb=(\d+|0x[\da-f]+),(\d+|0x[\da-f]+)$/i) {
		my ($start, $end);
		eval "\$start = $1; \$end = $2;";
		push @sections, [ $start, $end, 'FDB' ];
	} elsif ($arg =~ /^fcb=(\d+|0x[\da-f]+),(\d+|0x[\da-f]+)$/i) {
		my ($start, $end);
		eval "\$start = $1; \$end = $2;";
		push @sections, [ $start, $end, 'FCB' ];
	} elsif ($arg =~ /^fcc=(\d+|0x[\da-f]+),(\d+|0x[\da-f]+)$/i) {
		my ($start, $end);
		eval "\$start = $1; \$end = $2;";
		push @sections, [ $start, $end, 'FCC' ];
	} else {
		last;
	}
	shift @ARGV;
}

die unless (scalar(@ARGV) > 0);
my $file;
open($file, "<", $ARGV[0]) or die "$ARGV[0]: $!\n";

@sections = sort { $$a[0] <=> $$b[0] } @sections;
my $section_num = 0;
my $section = $sections[$section_num];  # undef is fine here

my %instructions = (
	0 => {
		0x00 => { text => 'NEG',     type => 'DIRECT' },
		0x01 => { text => 'NEG',     type => 'DIRECT', illegal => 1 },
		0x02 => { text => 'NEG/COM', type => 'DIRECT', illegal => 1 },
		0x03 => { text => 'COM',     type => 'DIRECT' },
		0x04 => { text => 'LSR',     type => 'DIRECT' },
		0x05 => { text => 'LSR',     type => 'DIRECT', illegal => 1 },
		0x06 => { text => 'ROR',     type => 'DIRECT' },
		0x07 => { text => 'ASR',     type => 'DIRECT' },
		0x08 => { text => 'LSL',     type => 'DIRECT' },
		0x09 => { text => 'ROL',     type => 'DIRECT' },
		0x0a => { text => 'DEC',     type => 'DIRECT' },
		0x0b => { text => 'DEC',     type => 'DIRECT', illegal => 1 },
		0x0c => { text => 'INC',     type => 'DIRECT' },
		0x0d => { text => 'TST',     type => 'DIRECT' },
		0x0e => { text => 'JMP',     type => 'DIRECT' },
		0x0f => { text => 'CLR',     type => 'DIRECT' },
		0x10 => { page => 2 },
		0x11 => { page => 3 },
		0x12 => { text => 'NOP',     type => 'INHERENT' },
		0x13 => { text => 'SYNC',    type => 'INHERENT' },
		0x16 => { text => 'LBRA',    type => 'RELATIVE16' },
		0x17 => { text => 'LBSR',    type => 'RELATIVE16' },
		0x19 => { text => 'DAA',     type => 'INHERENT' },
		0x1a => { text => 'ORCC',    type => 'IMMEDIATE' },
		0x1c => { text => 'ANDCC',   type => 'IMMEDIATE' },
		0x1d => { text => 'SEX',     type => 'INHERENT' },
		0x1e => { text => 'EXG',     type => 'REGISTER' },
		0x1f => { text => 'TFR',     type => 'REGISTER' },
		0x20 => { text => 'BRA',     type => 'RELATIVE' },
		0x21 => { text => 'BRN',     type => 'RELATIVE' },
		0x22 => { text => 'BHI',     type => 'RELATIVE' },
		0x23 => { text => 'BLS',     type => 'RELATIVE' },
		0x24 => { text => 'BCC',     type => 'RELATIVE' },
		0x25 => { text => 'BCS',     type => 'RELATIVE' },
		0x26 => { text => 'BNE',     type => 'RELATIVE' },
		0x27 => { text => 'BEQ',     type => 'RELATIVE' },
		0x28 => { text => 'BVC',     type => 'RELATIVE' },
		0x29 => { text => 'BVS',     type => 'RELATIVE' },
		0x2a => { text => 'BPL',     type => 'RELATIVE' },
		0x2b => { text => 'BMI',     type => 'RELATIVE' },
		0x2c => { text => 'BGE',     type => 'RELATIVE' },
		0x2d => { text => 'BLT',     type => 'RELATIVE' },
		0x2e => { text => 'BGT',     type => 'RELATIVE' },
		0x2f => { text => 'BLE',     type => 'RELATIVE' },
		0x30 => { text => 'LEAX',    type => 'INDEXED' },
		0x31 => { text => 'LEAY',    type => 'INDEXED' },
		0x32 => { text => 'LEAS',    type => 'INDEXED' },
		0x33 => { text => 'LEAU',    type => 'INDEXED' },
		0x34 => { text => 'PSHS',    type => 'STACK', bit6 => 'u' },
		0x35 => { text => 'PULS',    type => 'STACK', bit6 => 'u' },
		0x36 => { text => 'PSHU',    type => 'STACK', bit6 => 's' },
		0x37 => { text => 'PULU',    type => 'STACK', bit6 => 's' },
		0x39 => { text => 'RTS',     type => 'INHERENT' },
		0x3a => { text => 'ABX',     type => 'INHERENT' },
		0x3b => { text => 'RTI',     type => 'INHERENT' },
		0x3c => { text => 'CWAI',    type => 'IMMEDIATE' },
		0x3d => { text => 'MUL',     type => 'INHERENT' },
		0x3f => { text => 'SWI',     type => 'INHERENT' },
		0x40 => { text => 'NEGA',    type => 'INHERENT' },
		0x41 => { text => 'NEGA',    type => 'INHERENT', illegal => 1 },
		0x42 => { text => 'COMA',    type => 'INHERENT', illegal => 1 },
		0x43 => { text => 'COMA',    type => 'INHERENT' },
		0x44 => { text => 'LSRA',    type => 'INHERENT' },
		0x45 => { text => 'LSRA',    type => 'INHERENT', illegal => 1 },
		0x46 => { text => 'RORA',    type => 'INHERENT' },
		0x47 => { text => 'ASRA',    type => 'INHERENT' },
		0x48 => { text => 'LSLA',    type => 'INHERENT' },
		0x49 => { text => 'ROLA',    type => 'INHERENT' },
		0x4a => { text => 'DECA',    type => 'INHERENT' },
		0x4b => { text => 'DECA',    type => 'INHERENT', illegal => 1 },
		0x4c => { text => 'INCA',    type => 'INHERENT' },
		0x4d => { text => 'TSTA',    type => 'INHERENT' },
		0x4f => { text => 'CLRA',    type => 'INHERENT' },
		0x50 => { text => 'NEGB',    type => 'INHERENT' },
		0x51 => { text => 'NEGB',    type => 'INHERENT', illegal => 1 },
		0x52 => { text => 'COMB',    type => 'INHERENT', illegal => 1 },
		0x53 => { text => 'COMB',    type => 'INHERENT' },
		0x54 => { text => 'LSRB',    type => 'INHERENT' },
		0x55 => { text => 'LSRB',    type => 'INHERENT', illegal => 1 },
		0x56 => { text => 'RORB',    type => 'INHERENT' },
		0x57 => { text => 'ASRB',    type => 'INHERENT' },
		0x58 => { text => 'LSLB',    type => 'INHERENT' },
		0x59 => { text => 'ROLB',    type => 'INHERENT' },
		0x5a => { text => 'DECB',    type => 'INHERENT' },
		0x5b => { text => 'DECB',    type => 'INHERENT', illegal => 1 },
		0x5c => { text => 'INCB',    type => 'INHERENT' },
		0x5d => { text => 'TSTB',    type => 'INHERENT' },
		0x5f => { text => 'CLRB',    type => 'INHERENT' },
		0x60 => { text => 'NEG',     type => 'INDEXED' },
		0x63 => { text => 'COM',     type => 'INDEXED' },
		0x64 => { text => 'LSR',     type => 'INDEXED' },
		0x66 => { text => 'ROR',     type => 'INDEXED' },
		0x67 => { text => 'ASR',     type => 'INDEXED' },
		0x68 => { text => 'LSL',     type => 'INDEXED' },
		0x69 => { text => 'ROL',     type => 'INDEXED' },
		0x6a => { text => 'DEC',     type => 'INDEXED' },
		0x6c => { text => 'INC',     type => 'INDEXED' },
		0x6d => { text => 'TST',     type => 'INDEXED' },
		0x6e => { text => 'JMP',     type => 'INDEXED' },
		0x6f => { text => 'CLR',     type => 'INDEXED' },
		0x70 => { text => 'NEG',     type => 'EXTENDED' },
		0x73 => { text => 'COM',     type => 'EXTENDED' },
		0x74 => { text => 'LSR',     type => 'EXTENDED' },
		0x76 => { text => 'ROR',     type => 'EXTENDED' },
		0x77 => { text => 'ASR',     type => 'EXTENDED' },
		0x78 => { text => 'LSL',     type => 'EXTENDED' },
		0x79 => { text => 'ROL',     type => 'EXTENDED' },
		0x7a => { text => 'DEC',     type => 'EXTENDED' },
		0x7c => { text => 'INC',     type => 'EXTENDED' },
		0x7d => { text => 'TST',     type => 'EXTENDED' },
		0x7e => { text => 'JMP',     type => 'EXTENDED' },
		0x7f => { text => 'CLR',     type => 'EXTENDED' },
		0x80 => { text => 'SUBA',    type => 'IMMEDIATE' },
		0x81 => { text => 'CMPA',    type => 'IMMEDIATE' },
		0x82 => { text => 'SBCA',    type => 'IMMEDIATE' },
		0x83 => { text => 'SUBD',    type => 'IMMEDIATE16' },
		0x84 => { text => 'ANDA',    type => 'IMMEDIATE' },
		0x85 => { text => 'BITA',    type => 'IMMEDIATE' },
		0x86 => { text => 'LDA',     type => 'IMMEDIATE' },
		0x88 => { text => 'EORA',    type => 'IMMEDIATE' },
		0x89 => { text => 'ADCA',    type => 'IMMEDIATE' },
		0x8a => { text => 'ORA',     type => 'IMMEDIATE' },
		0x8b => { text => 'ADDA',    type => 'IMMEDIATE' },
		0x8c => { text => 'CMPX',    type => 'IMMEDIATE16' },
		0x8d => { text => 'BSR',     type => 'RELATIVE' },
		0x8e => { text => 'LDX',     type => 'IMMEDIATE16' },
		0x90 => { text => 'SUBA',    type => 'DIRECT' },
		0x91 => { text => 'CMPA',    type => 'DIRECT' },
		0x92 => { text => 'SBCA',    type => 'DIRECT' },
		0x93 => { text => 'SUBD',    type => 'DIRECT' },
		0x94 => { text => 'ANDA',    type => 'DIRECT' },
		0x95 => { text => 'BITA',    type => 'DIRECT' },
		0x96 => { text => 'LDA',     type => 'DIRECT' },
		0x97 => { text => 'STA',     type => 'DIRECT' },
		0x98 => { text => 'EORA',    type => 'DIRECT' },
		0x99 => { text => 'ADCA',    type => 'DIRECT' },
		0x9a => { text => 'ORA',     type => 'DIRECT' },
		0x9b => { text => 'ADDA',    type => 'DIRECT' },
		0x9c => { text => 'CMPX',    type => 'DIRECT' },
		0x9d => { text => 'JSR',     type => 'DIRECT' },
		0x9e => { text => 'LDX',     type => 'DIRECT' },
		0x9f => { text => 'STX',     type => 'DIRECT' },
		0xa0 => { text => 'SUBA',    type => 'INDEXED' },
		0xa1 => { text => 'CMPA',    type => 'INDEXED' },
		0xa2 => { text => 'SBCA',    type => 'INDEXED' },
		0xa3 => { text => 'SUBD',    type => 'INDEXED' },
		0xa4 => { text => 'ANDA',    type => 'INDEXED' },
		0xa5 => { text => 'BITA',    type => 'INDEXED' },
		0xa6 => { text => 'LDA',     type => 'INDEXED' },
		0xa7 => { text => 'STA',     type => 'INDEXED' },
		0xa8 => { text => 'EORA',    type => 'INDEXED' },
		0xa9 => { text => 'ADCA',    type => 'INDEXED' },
		0xaa => { text => 'ORA',     type => 'INDEXED' },
		0xab => { text => 'ADDA',    type => 'INDEXED' },
		0xac => { text => 'CMPX',    type => 'INDEXED' },
		0xad => { text => 'JSR',     type => 'INDEXED' },
		0xae => { text => 'LDX',     type => 'INDEXED' },
		0xaf => { text => 'STX',     type => 'INDEXED' },
		0xb0 => { text => 'SUBA',    type => 'EXTENDED' },
		0xb1 => { text => 'CMPA',    type => 'EXTENDED' },
		0xb2 => { text => 'SBCA',    type => 'EXTENDED' },
		0xb3 => { text => 'SUBD',    type => 'EXTENDED' },
		0xb4 => { text => 'ANDA',    type => 'EXTENDED' },
		0xb5 => { text => 'BITA',    type => 'EXTENDED' },
		0xb6 => { text => 'LDA',     type => 'EXTENDED' },
		0xb7 => { text => 'STA',     type => 'EXTENDED' },
		0xb8 => { text => 'EORA',    type => 'EXTENDED' },
		0xb9 => { text => 'ADCA',    type => 'EXTENDED' },
		0xba => { text => 'ORA',     type => 'EXTENDED' },
		0xbb => { text => 'ADDA',    type => 'EXTENDED' },
		0xbc => { text => 'CMPX',    type => 'EXTENDED' },
		0xbd => { text => 'JSR',     type => 'EXTENDED' },
		0xbe => { text => 'LDX',     type => 'EXTENDED' },
		0xbf => { text => 'STX',     type => 'EXTENDED' },
		0xc0 => { text => 'SUBB',    type => 'IMMEDIATE' },
		0xc1 => { text => 'CMPB',    type => 'IMMEDIATE' },
		0xc2 => { text => 'SBCB',    type => 'IMMEDIATE' },
		0xc3 => { text => 'ADDD',    type => 'IMMEDIATE16' },
		0xc4 => { text => 'ANDB',    type => 'IMMEDIATE' },
		0xc5 => { text => 'BITB',    type => 'IMMEDIATE' },
		0xc6 => { text => 'LDB',     type => 'IMMEDIATE' },
		0xc8 => { text => 'EORB',    type => 'IMMEDIATE' },
		0xc9 => { text => 'ADCB',    type => 'IMMEDIATE' },
		0xca => { text => 'ORB',     type => 'IMMEDIATE' },
		0xcb => { text => 'ADDB',    type => 'IMMEDIATE' },
		0xcc => { text => 'LDD',     type => 'IMMEDIATE16' },
		0xce => { text => 'LDU',     type => 'IMMEDIATE16' },
		0xd0 => { text => 'SUBB',    type => 'DIRECT' },
		0xd1 => { text => 'CMPB',    type => 'DIRECT' },
		0xd2 => { text => 'SBCB',    type => 'DIRECT' },
		0xd3 => { text => 'ADDD',    type => 'DIRECT' },
		0xd4 => { text => 'ANDB',    type => 'DIRECT' },
		0xd5 => { text => 'BITB',    type => 'DIRECT' },
		0xd6 => { text => 'LDB',     type => 'DIRECT' },
		0xd7 => { text => 'STB',     type => 'DIRECT' },
		0xd8 => { text => 'EORB',    type => 'DIRECT' },
		0xd9 => { text => 'ADCB',    type => 'DIRECT' },
		0xda => { text => 'ORB',     type => 'DIRECT' },
		0xdb => { text => 'ADDB',    type => 'DIRECT' },
		0xdc => { text => 'LDD',     type => 'DIRECT' },
		0xdd => { text => 'STD',     type => 'DIRECT' },
		0xde => { text => 'LDU',     type => 'DIRECT' },
		0xdf => { text => 'STU',     type => 'DIRECT' },
		0xe0 => { text => 'SUBB',    type => 'INDEXED' },
		0xe1 => { text => 'CMPB',    type => 'INDEXED' },
		0xe2 => { text => 'SBCB',    type => 'INDEXED' },
		0xe3 => { text => 'ADDD',    type => 'INDEXED' },
		0xe4 => { text => 'ANDB',    type => 'INDEXED' },
		0xe5 => { text => 'BITB',    type => 'INDEXED' },
		0xe6 => { text => 'LDB',     type => 'INDEXED' },
		0xe7 => { text => 'STB',     type => 'INDEXED' },
		0xe8 => { text => 'EORB',    type => 'INDEXED' },
		0xe9 => { text => 'ADCB',    type => 'INDEXED' },
		0xea => { text => 'ORB',     type => 'INDEXED' },
		0xeb => { text => 'ADDB',    type => 'INDEXED' },
		0xec => { text => 'LDD',     type => 'INDEXED' },
		0xed => { text => 'STD',     type => 'INDEXED' },
		0xee => { text => 'LDU',     type => 'INDEXED' },
		0xef => { text => 'STU',     type => 'INDEXED' },
		0xf0 => { text => 'SUBB',    type => 'EXTENDED' },
		0xf1 => { text => 'CMPB',    type => 'EXTENDED' },
		0xf2 => { text => 'SBCB',    type => 'EXTENDED' },
		0xf3 => { text => 'ADDD',    type => 'EXTENDED' },
		0xf4 => { text => 'ANDB',    type => 'EXTENDED' },
		0xf5 => { text => 'BITB',    type => 'EXTENDED' },
		0xf6 => { text => 'LDB',     type => 'EXTENDED' },
		0xf7 => { text => 'STB',     type => 'EXTENDED' },
		0xf8 => { text => 'EORB',    type => 'EXTENDED' },
		0xf9 => { text => 'ADCB',    type => 'EXTENDED' },
		0xfa => { text => 'ORB',     type => 'EXTENDED' },
		0xfb => { text => 'ADDB',    type => 'EXTENDED' },
		0xfc => { text => 'LDD',     type => 'EXTENDED' },
		0xfd => { text => 'STD',     type => 'EXTENDED' },
		0xfe => { text => 'LDU',     type => 'EXTENDED' },
		0xff => { text => 'STU',     type => 'EXTENDED' },
	},
	2 => {
		0x21 => { text => 'LBRN',    type => 'RELATIVE16' },
		0x22 => { text => 'LBHI',    type => 'RELATIVE16' },
		0x23 => { text => 'LBLS',    type => 'RELATIVE16' },
		0x24 => { text => 'LBCC',    type => 'RELATIVE16' },
		0x25 => { text => 'LBCS',    type => 'RELATIVE16' },
		0x26 => { text => 'LBNE',    type => 'RELATIVE16' },
		0x27 => { text => 'LBEQ',    type => 'RELATIVE16' },
		0x28 => { text => 'LBVC',    type => 'RELATIVE16' },
		0x29 => { text => 'LBVS',    type => 'RELATIVE16' },
		0x2a => { text => 'LBPL',    type => 'RELATIVE16' },
		0x2b => { text => 'LBMI',    type => 'RELATIVE16' },
		0x2c => { text => 'LBGE',    type => 'RELATIVE16' },
		0x2d => { text => 'LBLT',    type => 'RELATIVE16' },
		0x2e => { text => 'LBGT',    type => 'RELATIVE16' },
		0x2f => { text => 'LBLE',    type => 'RELATIVE16' },
		0x3f => { text => 'SWI2',    type => 'INHERENT' },
		0x83 => { text => 'CMPD',    type => 'IMMEDIATE16' },
		0x8c => { text => 'CMPY',    type => 'IMMEDIATE16' },
		0x8e => { text => 'LDY',     type => 'IMMEDIATE16' },
		0x93 => { text => 'CMPD',    type => 'DIRECT' },
		0x9c => { text => 'CMPY',    type => 'DIRECT' },
		0x9e => { text => 'LDY',     type => 'DIRECT' },
		0x9f => { text => 'STY',     type => 'DIRECT' },
		0xa3 => { text => 'CMPD',    type => 'INDEXED' },
		0xac => { text => 'CMPY',    type => 'INDEXED' },
		0xae => { text => 'LDY',     type => 'INDEXED' },
		0xaf => { text => 'STY',     type => 'INDEXED' },
		0xb3 => { text => 'CMPD',    type => 'EXTENDED' },
		0xbc => { text => 'CMPY',    type => 'EXTENDED' },
		0xbe => { text => 'LDY',     type => 'EXTENDED' },
		0xbf => { text => 'STY',     type => 'EXTENDED' },
		0xce => { text => 'LDS',     type => 'IMMEDIATE16' },
		0xde => { text => 'LDS',     type => 'DIRECT' },
		0xdf => { text => 'STS',     type => 'DIRECT' },
		0xee => { text => 'LDS',     type => 'INDEXED' },
		0xef => { text => 'STS',     type => 'INDEXED' },
		0xfe => { text => 'LDS',     type => 'EXTENDED' },
		0xff => { text => 'STS',     type => 'EXTENDED' },
	},
	3 => {
		0x3f => { text => 'SWI3',    type => 'INHERENT' },
		0x83 => { text => 'CMPU',    type => 'IMMEDIATE16' },
		0x8c => { text => 'CMPS',    type => 'IMMEDIATE16' },
		0x93 => { text => 'CMPU',    type => 'DIRECT' },
		0x9c => { text => 'CMPS',    type => 'DIRECT' },
		0xa3 => { text => 'CMPU',    type => 'INDEXED' },
		0xac => { text => 'CMPS',    type => 'INDEXED' },
		0xb3 => { text => 'CMPU',    type => 'EXTENDED' },
		0xbc => { text => 'CMPS',    type => 'EXTENDED' },
	},
);

my %tfr_regs = (
	0x00 => "d",
	0x01 => "x",
	0x02 => "y",
	0x03 => "u",
	0x04 => "s",
	0x05 => "pc",
	0x08 => "a",
	0x09 => "b",
	0x0a => "cc",
	0x0b => "dp",
);

my %index_regs = (
	0 => "x",
	1 => "y",
	2 => "u",
	3 => "s",
);

my %illegal_indexed = (
	0x07 => 1, 0x0a => 1, 0x0e => 1, 0x0f => 1,
	0x10 => 1, 0x12 => 1, 0x17 => 1, 0x1a => 1, 0x1e => 1,
);

my $offset = $origin;
my @bytes;

my %decoders = (
	'FCB' => \&decode_fcb,
	'FDB' => \&decode_fdb,
	'FCC' => \&decode_fcc,
	'IMMEDIATE' => \&decode_immediate,
	'IMMEDIATE16' => \&decode_immediate16,
	'EXTENDED' => \&decode_extended,
	'DIRECT' => \&decode_direct,
	'STACK' => \&decode_stack,
	'REGISTER' => \&decode_register,
	'INDEXED' => \&decode_indexed,
	'RELATIVE' => \&decode_relative,
	'RELATIVE16' => \&decode_relative16,
);

my %lines = ();

my $page = 0;
my $pc = $offset;
my $file_rewind = undef;
my $cbyte;
my $skipped_comment = undef;
printf "\torg\t\$\%04x\n", $pc;
for (;;) {
	if ($page == 0) {
		@bytes = ();
		$pc = $offset;
	}
	my $area = 'CODE';
	while (defined $section && $pc > $$section[1]) {
		$section_num++;
		$section = $sections[$section_num];
	}
	if (defined $section && $pc >= $$section[0]) {
		$area = $$section[2];
	}
	my $c = getbyte();
	last if (!defined $c);
	my $ins;
	if ($area eq 'FCB') {
		$ins = { text => 'FCB', type => 'FCB' };
		my $i = 1;
		while ($i < 16 && $offset <= $$section[1]) {
			$c = getbyte();
			last if (!defined $c);
			$i++;
		}
	} elsif ($area eq 'FDB') {
		$ins = { text => 'FDB', type => 'FDB' };
		$c = getbyte();
		last if (!defined $c);
		my $i = 1;
		while ($i < 16 && $offset <= $$section[1]) {
			$c = getbyte();
			last if (!defined $c);
			$c = getbyte();
			last if (!defined $c);
			$i++;
		}
	} elsif ($area eq 'FCC') {
		$ins = { text => 'FCC', type => 'FCC' };
		my $i = 1;
		while ($i < 32 && $offset <= $$section[1]) {
			$c = getbyte();
			last if (!defined $c);
			last if ($c == 0 || $c == 0x0d);
			$i++;
		}
	} else {
		$ins = $instructions{$page}{$c} || { text => 'FCB', type => 'FCB' };
	}

	if (exists $comment_bytes{$pc}) {
		$file_rewind = tell $file;
		$cbyte = $c;
	}

	if (exists $$ins{page}) {
		$page = $$ins{page};
		next;
	}
	my $text = lc $$ins{text};
	my $data = "";
	my $type = $$ins{type};
	my $illegal = $$ins{illegal};
	if (exists $decoders{$type}) {
		$data = $decoders{$type}($ins, \$illegal);
	}
	$illegal = 1 if (!defined $data);
	if ($illegal) {
		my $fcb = join(",", map { sprintf("\$\%02x", $_) } @bytes);
		$lines{$pc} = sprintf "fcb\t\%-16s; \%s \%s", $fcb, $text, $data;
	} else {
		$lines{$pc} = sprintf "\%s\t\%s", $text, $data;
	}

	if (defined $skipped_comment) {
		$lines{$pc} .= "\t; usually skipped by comment byte";
		undef $skipped_comment;
	}

	if (defined $file_rewind) {
		$lines{$pc} = sprintf("fcb\t\$\%02x\t; ", $cbyte).$lines{$pc}." - comment byte";
		seek $file, $file_rewind, 0;
		$offset = $pc + 1;
		undef $file_rewind;
		$skipped_comment = 1;
	}

	$page = 0;
}

for my $label (sort { $a <=> $b} keys %need_labels) {
	next if (exists $lines{$label});
	if ($label < $origin || $label >= $offset) {
		$lines{$label} = sprintf "equ\t\$\%04x", $label;
	} else {
		my $l;
		for ($l = $label - 1; $l >= $origin && !exists $lines{$l}; $l--) { };
		$lines{$label} = sprintf "equ\t%s + \%d", label_for($l), ($label - $l);
		$need_labels{$l} = 1;
	}
	$need_labels{$l} = 1;
}

for my $line (sort { $a <=> $b} keys %lines) {
	if (exists $forceorg{$line}) {
		printf "\torg\t\$\%04x\n", $line;
	}
	if (exists $need_labels{$line}) {
		print label_for($line);
	}
	printf "\t\%s\n", $lines{$line};
}

sub label_for {
	my ($addr) = @_;
	if (!exists $labels{$addr}) {
		$labels{$addr} = sprintf("L_\%04X", $addr);
	}
	return $labels{$addr};
}

sub rel_label_for {
	my ($addr) = @_;
	if (exists $labels{$addr}) {
		return $labels{$addr};
	}
	for my $i (1..2) {
		if (exists $def_labels{$addr-$i}) {
			return $def_labels{$addr-$i}." + $i";
		}
		if (exists $def_labels{$addr+$i}) {
			return $def_labels{$addr+$i}." - $i";
		}
	}
	return undef;
}

sub decode_fcb {
	my ($ins) = @_;
	return join(",", map { sprintf("\$\%02x", $_) } @bytes);
}

sub decode_fdb {
	my ($ins) = @_;
	my @words = ();
	while (@bytes) {
		push @words, ((shift @bytes) << 8) | shift @bytes;
	}
	return join(",", map { rel_label_for($_) || sprintf("\$\%04x", $_) } @words);
}

sub decode_fcc {
	my ($ins) = @_;
	my @strings = ();
	my $snum = 0;
	for my $byte (@bytes) {
		if ($byte >= 32 && $byte < 127 && $byte != 47) {
			$strings[$snum] = "/" if ($strings[$snum] eq "");
			$strings[$snum] .= sprintf("%c", $byte);
		} else {
			if ($strings[$snum] ne "") {
				$strings[$snum] .= "/" if ($strings[$snum] ne "");
				$snum++;
			}
			$strings[$snum] = sprintf("\$\%02x", $byte);
			$snum++;
		}
	}
	$strings[$snum] .= "/" if ($strings[$snum] =~ /^\//);
	return join(",", @strings);
}

sub decode_immediate {
	my ($ins) = @_;
	my $c = getbyte();
	return undef if (!defined $c);
	return sprintf("#\$\%02x", $c);
}

sub decode_immediate16 {
	my ($ins) = @_;
	my $w = getword();
	return undef if (!defined $w);
	my $alabel = rel_label_for($w);
	if ($alabel) {
		return "#$alabel";
	}
	return sprintf("#\$\%04x", $w);
}

sub decode_extended {
	my ($ins) = @_;
	my $w = getword();
	return undef if (!defined $w);
	if (!defined $code_end || $w < $origin || $w > $code_end) {
		return sprintf(">\$\%04x", $w);
	}
	my $label = $w & 0xffff;
	$need_labels{$label} = 1;
	return label_for($label);
}

sub decode_direct {
	my ($ins) = @_;
	my $c = getbyte();
	return undef if (!defined $c);
	return sprintf("<\$\%02x", $c);
}

sub decode_stack {
	my ($ins, $illegal) = @_;
	my $c = getbyte();
	return undef if (!defined $c);
	my @regs = ();
	if ($c & 0x01) { push @regs, "cc"; }
	if ($c & 0x02) { push @regs, "a"; }
	if ($c & 0x04) { push @regs, "b"; }
	if ($c & 0x08) { push @regs, "dp"; }
	if ($c & 0x10) { push @regs, "x"; }
	if ($c & 0x20) { push @regs, "y"; }
	if ($c & 0x40) { push @regs, $$ins{bit6}; }
	if ($c & 0x80) { push @regs, "pc"; }
	if ($c == 0) {
		$$illegal = 1;
	}
	return join(",", @regs);
}

sub decode_register {
	my ($ins, $illegal) = @_;
	my $post = getbyte();
	return undef if (!defined $post);
	my $r1 = ($post & 0xf0) >> 4;
	my $r2 = $post & 0x0f;
	if (($r1 ^ $r2) & 0x08) {
		$$illegal = 1;
	}
	$r1 = $tfr_regs{$r1} || '*';
	$r2 = $tfr_regs{$r2} || '*';
	if ($r1 eq '*' || $r2 eq '*') {
		$$illegal = 1;
	}
	return sprintf("\%s,\%s", $r1, $r2);
}

sub decode_indexed {
	my ($ins, $illegal) = @_;
	my $post = getbyte();
	return undef if (!defined $post);
	my $r = $index_regs{($post>>5)&3};
	if (($post & 0x80) == 0) {
		my $off = sex5($post&0x1f);
		if ($off == 0) {
			# Not really illegal, but a09 will assemble
			# "0,idx" into ",idx"
			$$illegal = 1;
		}
		return sprintf("\%d,\%s", $off, $r);
	}
	$$illegal = $illegal_indexed{$post & 0x1f};
	my $indirect = $post & 0x10;
	my $mode = $post & 0x0f;
	my $text = "";
	if ($mode == 0) {
		$text = sprintf(",\%s+", $r);
	} elsif ($mode == 1) {
		$text = sprintf(",\%s++", $r);
	} elsif ($mode == 2) {
		$text = sprintf(",-\%s", $r);
	} elsif ($mode == 3) {
		$text = sprintf(",--\%s", $r);
	} elsif ($mode == 4) {
		$text = sprintf(",\%s", $r);
	} elsif ($mode == 5) {
		$text = sprintf("b,\%s", $r);
	} elsif ($mode == 6) {
		$text = sprintf("a,\%s", $r);
	} elsif ($mode == 8) {
		my $v = getbyte();
		return undef if (!defined $v);
		$text = sprintf("<\$\%02x,\%s", $v, $r);
	} elsif ($mode == 9) {
		my $v = getword();
		return undef if (!defined $v);
		$text = sprintf(">\$\%04x,\%s", $v, $r);
	} elsif ($mode == 11) {
		$text = sprintf("d,\%s", $r);
	} elsif ($mode == 12) {
		my $o = getbyte();
		return undef if (!defined $o);
		if (($post & 0x60) != 0) {
			# Not illegal, but assemblers will set these bits to 0.
			$$illegal = 1;
		}
		$o = (sex8($o) + $offset) & 0xffff;
		my $absaddr = rel_label_for($o);
		if (defined $absaddr) {
			$text = "<$absaddr,pcr";
		} else {
			$text = sprintf("<\$\%04x,pcr", $o);
		}
	} elsif ($mode == 13) {
		my $o = getword();
		return undef if (!defined $o);
		if (($post & 0x60) != 0) {
			# Not illegal, but assemblers will set these bits to 0.
			$$illegal = 1;
		}
		$o = ($o + $offset) & 0xffff;
		$need_labels{$o} = 1;
		$text = sprintf(">%s,pcr", label_for($o));
		#$text = sprintf(">\$\%04x,pcr", $o);
	} elsif ($mode == 15) {
		my $a = getword();
		return undef if (!defined $a);
		$text = sprintf("\$\%04x", $a);
	}
	if ($indirect) {
		$text = "[$text]";
	}
	return $text;
}

sub decode_relative {
	my ($ins) = @_;
	my $r = getbyte();
	return undef if (!defined $r);
	my $label = ($offset + sex8($r)) & 0xffff;
	$need_labels{$label} = 1;
	return label_for($label);
}

sub decode_relative16 {
	my ($ins) = @_;
	my $r = getword();
	return undef if (!defined $r);
	my $label = ($offset + $r) & 0xffff;
	$need_labels{$label} = 1;
	return label_for($label);
}

sub sex5 {
	my ($v) = @_;
	return int((($v) & 0x0f) - (($v) & 0x10));
}

sub sex8 {
	my ($v) = @_;
	return ($v & 0x80) ? ($v | 0xff00) : $v;
}

sub getbyte {
	my $c = getc($file);
	return undef if (!defined $c);
	$offset++;
	$c = unpack("C", $c);
	push @bytes, $c;
	return $c;
}

sub getword {
	my $h = getbyte();
	return undef if (!defined $h);
	my $l = getbyte();
	return undef if (!defined $l);
	return ($h << 8) | $l;
}

