require "fiddle"

class Integer
  def m
    address = Intel::Address.new
    address.offset = self
    address
  end

  def inspect
    "0x#{to_s 16}"
  end if $DEBUG
end
class Array
  def second
    self[1]
  end

  def push_D integer
    self.push(*[integer].pack("V").unpack("C4"))
  end

  def push_B integer
    self << (integer & 255)
  end

  def push_W integer
    self.push((integer & 255), (integer >> 8 & 255))
  end
end

ASM = []


class Object
  def assemble arg_count = 0, &block
    asm = Intel::MachineCodeX86.new

    # TODO: enter?
    asm.ebp.push
    asm.ebp.mov asm.esp

    size = asm.stream.size

    asm.instance_eval(&block)

    if asm.stream.size == size  # return nil
      warn "returning nil for #{self}##{name}"
      asm.eax.mov 4
    end

    asm.leave
    asm.ret

    code = asm.stream.pack("C*")

    if $DEBUG then
      path = "#{$$}.obj"
      File.open path, "wb" do |f|
        f.write code
      end

      puts code.unpack("C*").map { |n| "%02X" % n }.join(' ')
      system "ndisasm -u #{path}"

      File.unlink path
    end

    code
  end

  @@asm = {}
  def asm(name, *args, &block)
    code = @@asm[name] ||= assemble(&block).to_ptr

    return execute_asm(code) # code is the function pointer, wrapped
  end

#  defasm :execute_asm, :code do
#    eax.mov ebp + 0x0c # grab code
#    eax.mov eax + 0x10 # unwrap function pointer field
#    eax.mov eax.m      # dereference the pointer
#    eax.call           # call the function pointer
#  end
end