#    This file is part of Metasm, the Ruby assembly manipulation suite
#    Copyright (C) 2006-2009 Yoann GUILLOT
#
#    Licence is LGPL, see LICENCE in the top-level directory

require 'metasm/cpu/z80/main'

module Metasm

class Z80
	def addop(name, bin, *args)
		o = Opcode.new name, bin
		args.each { |a|
			o.args << a if @fields_mask[a] or @valid_args[a]
			o.props[a] = true if @valid_props[a]
			o.fields[a] = [bin.length-1, @fields_shift[a]] if @fields_mask[a]
			raise "wtf #{a.inspect}" unless @valid_args[a] or @valid_props[a] or @fields_mask[a]
		}
		@opcode_list << o
	end

	def addop_macrocc(name, bin, *args)
		%w[nz z nc c po pe p m].each_with_index { |cc, i|
			dbin = bin.dup
			dbin[0] |= i << 3
			addop name + cc, dbin, *args
		}
	end

	# data from http://www.z80.info/decoding.htm
	def init_z80_common
		@opcode_list = []
		@valid_args.update [:i8, :u8, :i16, :u16, :m16,
			:r_a, :r_af, :r_hl, :r_de, :r_sp, :r_i,
			:m_bc, :m_de, :m_sp, :m_hl, :mf8, :mfc
		].inject({}) { |h, v| h.update v => true }
		@fields_mask.update :rz => 7, :ry => 7, :rp => 3, :rp2 => 3, :iy => 7, :iy8 => 7
		@fields_shift.update :rz => 0, :ry => 3, :rp => 4, :rp2 => 4, :iy => 3, :iy8 => 3

		# some opcodes are in init_z80 when they are not part of the GB ABI
		addop 'nop',  [0b00_000_000]
		addop 'jr',   [0b00_011_000], :setip, :stopexec, :i8
		%w[nz z nc c].each_with_index { |cc, i|
			addop 'jr' + cc, [0b00_100_000 | (i << 3)], :setip, :i8
		}
		addop 'ld',   [0b00_000_001], :rp, :i16
		addop 'add',  [0b00_001_001], :r_hl, :rp

		addop 'ld',   [0b00_000_010], :m_bc, :r_a
		addop 'ld',   [0b00_001_010], :r_a, :m_bc
		addop 'ld',   [0b00_010_010], :m_de, :r_a
		addop 'ld',   [0b00_011_010], :r_a, :m_de

		addop 'inc',  [0b00_000_011], :rp
		addop 'dec',  [0b00_001_011], :rp
		addop 'inc',  [0b00_000_100], :ry
		addop 'dec',  [0b00_000_101], :ry
		addop 'ld',   [0b00_000_110], :ry, :i8

		addop 'rlca', [0b00_000_111]	# rotate
		addop 'rrca', [0b00_001_111]
		addop 'rla',  [0b00_010_111]
		addop 'rra',  [0b00_011_111]

		addop 'daa',  [0b00_100_111]
		addop 'cpl',  [0b00_101_111]
		addop 'scf',  [0b00_110_111]
		addop 'ccf',  [0b00_111_111]

		addop 'halt', [0b01_110_110]	# ld (HL), (HL)
		addop 'ld',   [0b01_000_000], :ry, :rz

		addop 'add',  [0b10_000_000], :r_a, :rz
		addop 'adc',  [0b10_001_000], :r_a, :rz
		addop 'sub',  [0b10_010_000], :r_a, :rz
		addop 'sbc',  [0b10_011_000], :r_a, :rz
		addop 'and',  [0b10_100_000], :r_a, :rz
		addop 'xor',  [0b10_101_000], :r_a, :rz
		addop 'or',   [0b10_110_000], :r_a, :rz
		addop 'cmp',  [0b10_111_000], :r_a, :rz	# alias cp
		addop 'cp',   [0b10_111_000], :r_a, :rz	# compare

		addop_macrocc 'ret', [0b11_000_000], :setip
		addop 'pop',  [0b11_000_001], :rp2
		addop 'ret',  [0b11_001_001], :stopexec, :setip
		addop 'jmp',  [0b11_101_001], :r_hl, :setip, :stopexec	# alias jp
		addop 'jp',   [0b11_101_001], :r_hl, :setip, :stopexec
		addop 'ld',   [0b11_111_001], :r_sp, :r_hl
		addop_macrocc 'j',  [0b11_000_010], :setip, :u16	# alias jp
		addop_macrocc 'jp', [0b11_000_010], :setip, :u16
		addop 'jmp',  [0b11_000_011], :setip, :stopexec, :u16	# alias jp
		addop 'jp',   [0b11_000_011], :setip, :stopexec, :u16

		addop 'di',   [0b11_110_011]				# disable interrupts
		addop 'ei',   [0b11_111_011]
		addop_macrocc 'call', [0b11_000_100], :u16, :setip, :saveip
		addop 'push', [0b11_000_101], :rp2
		addop 'call', [0b11_001_101], :u16, :setip, :saveip, :stopexec

		addop 'add',  [0b11_000_110], :r_a, :i8
		addop 'adc',  [0b11_001_110], :r_a, :i8
		addop 'sub',  [0b11_010_110], :r_a, :i8
		addop 'sbc',  [0b11_011_110], :r_a, :i8
		addop 'and',  [0b11_100_110], :r_a, :i8
		addop 'xor',  [0b11_101_110], :r_a, :i8
		addop 'or',   [0b11_110_110], :r_a, :i8
		addop 'cp',   [0b11_111_110], :r_a, :i8

		addop 'rst',  [0b11_000_111], :iy8		# call off in page 0

		addop 'rlc',  [0xCB, 0b00_000_000], :rz		# rotate
		addop 'rrc',  [0xCB, 0b00_001_000], :rz
		addop 'rl',   [0xCB, 0b00_010_000], :rz
		addop 'rr',   [0xCB, 0b00_011_000], :rz
		addop 'sla',  [0xCB, 0b00_100_000], :rz		# shift
		addop 'sra',  [0xCB, 0b00_101_000], :rz
		addop 'srl',  [0xCB, 0b00_111_000], :rz
		addop 'bit',  [0xCB, 0b01_000_000], :iy, :rz	# bit test
		addop 'res',  [0xCB, 0b10_000_000], :iy, :rz	# bit reset
		addop 'set',  [0xCB, 0b11_000_000], :iy, :rz	# bit set
	end

	# standard z80
	def init_z80
		init_z80_common

		addop 'ex',   [0b00_001_000], :r_af		# XXX really ex AF, AF' ...
		addop 'djnz', [0b00_010_000], :setip, :i8

		addop 'ld',   [0b00_100_010], :m16, :r_hl
		addop 'ld',   [0b00_101_010], :r_hl, :m16
		addop 'ld',   [0b00_110_010], :m16, :r_a
		addop 'ld',   [0b00_111_010], :r_a, :m16

		addop 'exx',  [0b11_011_001]
		addop 'out',  [0b11_010_011], :i8, :r_a
		addop 'in',   [0b11_011_011], :r_a, :i8

		addop 'ex',   [0b11_100_011], :m_sp, :r_hl
		addop 'ex',   [0b11_101_011], :r_de, :r_hl

		addop 'sll',  [0xCB, 0b00_110_000], :rz

		addop 'in',   [0xED, 0b01_110_000], :u16
		addop 'in',   [0xED, 0b01_000_000], :ry, :u16
		addop 'out',  [0xED, 0b01_110_001], :u16
		addop 'out',  [0xED, 0b01_000_001], :u16, :ry
		addop 'sbc',  [0xED, 0b01_000_010], :r_hl, :rp
		addop 'adc',  [0xED, 0b01_001_010], :r_hl, :rp
		addop 'ld',   [0xED, 0b01_000_011], :m16, :rp
		addop 'ld',   [0xED, 0b01_001_011], :rp, :m16
		addop 'neg',  [0xED, 0b01_000_100], :r_a, :iy	# dummy int field
		addop 'retn', [0xED, 0b01_000_101], :stopexec	# dummy int != 1 ? (1 = reti)
		addop 'reti', [0xED, 0b01_001_101], :stopexec, :setip
		addop 'im',   [0xED, 0b01_000_110], :iy
		addop 'ld',   [0xED, 0b01_000_111], :r_i, :r_a
		addop 'ld',   [0xED, 0b01_001_111], :r_r, :r_a
		addop 'ld',   [0xED, 0b01_010_111], :r_a, :r_i
		addop 'ld',   [0xED, 0b01_011_111], :r_a, :r_r
		addop 'rrd',  [0xED, 0b01_100_111]
		addop 'rld',  [0xED, 0b01_101_111]

		addop 'ldi',  [0xED, 0b10_100_000]
		addop 'ldd',  [0xED, 0b10_101_000]
		addop 'ldir', [0xED, 0b10_110_000]
		addop 'lddr', [0xED, 0b10_111_000]
		addop 'cpi',  [0xED, 0b10_100_001]
		addop 'cpd',  [0xED, 0b10_101_001]
		addop 'cpir', [0xED, 0b10_110_001]
		addop 'cpdr', [0xED, 0b10_111_001]
		addop 'ini',  [0xED, 0b10_100_010]
		addop 'ind',  [0xED, 0b10_101_010]
		addop 'inir', [0xED, 0b10_110_010]
		addop 'indr', [0xED, 0b10_111_010]
		addop 'outi', [0xED, 0b10_100_011]
		addop 'outd', [0xED, 0b10_101_011]
		addop 'otir', [0xED, 0b10_110_011]
		addop 'otdr', [0xED, 0b10_111_011]

		addop 'unk_ed', [0xED], :i8

		addop 'unk_nop', [], :i8	# undefined opcode = nop
		@unknown_opcode = @opcode_list.last
	end

	# gameboy processor
	# from http://nocash.emubase.de/pandocs.htm#cpucomparisionwithz80
	def init_gb
		init_z80_common

		addop 'ld',   [0x08], :m16, :r_sp
		addop 'stop', [0x10]

		addop 'ldi',  [0x22], :m_hl, :r_a	# (hl++) <- a
		addop 'ldi',  [0x2A], :r_a, :m_hl
		addop 'ldd',  [0x32], :m_hl, :r_a	# (hl--) <- a
		addop 'ldd',  [0x3A], :r_a, :m_hl

		addop 'reti', [0xD9], :setip, :stopexec

		# override retpo/jpo
		@opcode_list.delete_if { |op| op.bin[0] & 0xE5 == 0xE0 }	# rm E0 E2 E8 EA F0 F2 F8 FA
		addop 'ld',  [0xE0], :mf8, :r_a	# (0xff00 + :i8)
		addop 'ld',  [0xE2], :mfc, :r_a	# (0xff00 + :r_c)
		addop 'add', [0xE8], :r_sp, :i8
		addop 'ld',  [0xEA], :m16, :r_a
		addop 'ld',  [0xF0], :r_a, :mf8
		addop 'ld',  [0xF2], :r_a, :mfc
		addop 'ld',  [0xF8], :r_hl, :r_sp, :i8	# hl <- sp+:i8
		addop 'ld',  [0xFA], :r_a, :m16

		addop 'swap', [0xCB, 0x30], :rz

		addop 'inv_dd', [0xDD], :stopexec	# invalid prefixes
		addop 'inv_ed', [0xED], :stopexec
		addop 'inv_fd', [0xFD], :stopexec

		addop 'unk_nop', [], :i8	# undefined opcode = nop
		@unknown_opcode = @opcode_list.last
	end

	alias init_latest init_z80
end
end
