package Keystone;

use 5.014000;
use strict;
use warnings;
require Exporter;

our $VERSION = '1.2';

our @ISA = qw(Exporter);

# Generated by :
# perl -ne 'if(m/^\s*(\S+)\s*=>\s*.+/){$h{$1}++} END{ print "qw(" . join(" ", keys %h) . ")\n"}' < lib/Keystone_const.pm

our %EXPORT_TAGS = ( 'all' =>
                     [
                      qw(KS_ERR_ASM_MACRO_TOKEN KS_ERR_NOMEM KS_MODE_PPC64 KS_ERR_ASM_LABEL_INVALID KS_ARCH_PPC KS_MODE_SPARC32 KS_ERR_ASM_MNEMONICFAIL KS_ERR_ASM_ESC_OCTAL KS_ERR_ASM_FIXUP_INVALID KS_MODE_MIPS3 KS_MODE_BIG_ENDIAN KS_ERR_ASM_DIRECTIVE_STR KS_ERR_ASM_EXPR_TOKEN KS_MODE_MIPS32R6 KS_ERR_ASM_UNSUPPORTED KS_ERR_ASM_TOKEN_INVALID KS_ERR_ASM_EXPR_BRACKET KS_ERR_ASM_ESC_SEQUENCE KS_ERR_ASM_ESC_BACKSLASH KS_ERR_HANDLE KS_MODE_V8 KS_ERR_ASM_SYMBOL_MODIFIER KS_ARCH_SYSTEMZ KS_ERR_ASM_ARCH KS_ERR_ASM_MACRO_EQU KS_ERR_OK KS_ARCH_ARM64 KS_ERR_ASM_DIRECTIVE_FPOINT KS_ARCH_HEXAGON KS_OPT_SYNTAX KS_ERR_ASM_MACRO_LEVELS_EXCEED KS_OPT_SYNTAX_ATT KS_ARCH_MAX KS_ERR_ASM_MACRO_STR KS_ERR_ASM_FRAGMENT_INVALID KS_MODE_64 KS_ARCH_ARM KS_ERR_MODE KS_ARCH_MIPS KS_ERR_ASM_INVALIDOPERAND KS_MODE_16 KS_ERR_ASM_STAT_TOKEN KS_OPT_SYNTAX_NASM KS_ERR_ASM_ESC_STR KS_ERR_ASM_MACRO_INVALID KS_ERR_ASM_INSN_UNSUPPORTED KS_ERR_ASM_DIRECTIVE_INVALID KS_ERR_ASM_VARIANT_INVALID KS_ERR_ASM_DIRECTIVE_TOKEN KS_MODE_32 KS_OPT_SYNTAX_INTEL KS_ERR_ASM_DIRECTIVE_EQU KS_ERR_ASM_DIRECTIVE_RELOC_TOKEN KS_ERR_ASM_MACRO_PAREN KS_MODE_V9 KS_ERR_ARCH KS_OPT_SYNTAX_GAS KS_MODE_SPARC64 KS_ARCH_X86 KS_ERR_ASM KS_ERR_ASM_SYMBOL_MISSING KS_ERR_ASM_DIRECTIVE_RELOC_NAME KS_ERR_ASM_SYMBOL_REDEFINED KS_ERR_ASM_MACRO_ARGS KS_ARCH_SPARC KS_ERR_ASM_DIRECTIVE_UNKNOWN KS_ERR_ASM_RPAREN KS_ERR_ASM_MISSINGFEATURE KS_MODE_PPC32 KS_MODE_QPX KS_ERR_ASM_DIRECTIVE_COMMA KS_ERR_OPT_INVALID KS_ERR_VERSION KS_MODE_THUMB KS_ERR_ASM_DIRECTIVE_ID KS_MODE_MIPS64 KS_MODE_LITTLE_ENDIAN KS_MODE_MICRO KS_MODE_MIPS32 KS_MODE_ARM KS_ERR_ASM_DIRECTIVE_VALUE_RANGE KS_OPT_SYNTAX_MASM)
                     ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

require XSLoader;
XSLoader::load('Keystone', $VERSION);


# Preloaded methods go here.

require Keystone_const;

sub new {
    my ($class, $arch, $mode) = @_;
    my $this = {};

    bless($this, $class);

    $this->{_handle} = Keystone::ks_open($arch, $mode);

    undef $this if(!defined($this->{_handle}));

    return $this;
}

sub asm {
    my ($this, $code, $address) = @_;
    my @ops;

    $address = 0 if !defined($address);

    @ops =  Keystone::ks_asm($this->{_handle}, $code, $address);

    return @ops;
}

sub errno {
    my $this = shift;

    return Keystone::ks_errno($this->{_handle});
}

# Can be used as static and non static method
sub strerror {
    my $arg = shift;

    if(ref($arg)) {
        return Keystone::ks_strerror($arg->errno());
    }

    return Keystone::ks_strerror($arg);
}

# Static methods
sub version {
    return Keystone::ks_version();
}

sub arch_supported {
    return Keystone::ks_arch_supported($_[0]);
}

1;

__END__

=head1 NAME

Keystone - Perl extension for keystone-engine

=head1 SYNOPSIS

  use Keystone ':all';

  $ks = Keystone->new(KS_ARCH_X86, KS_MODE_64) || die "Can't init Keystone\n";
  @opcodes = $ks->asm("pop rax; inc rbx; ret");

  foreach(@opcodes) {
    printf "0x%.2x ", $_;
  }
  print "\n";


=head1 DESCRIPTION

This module is a Perl wrapper for the keystone-engine library.

Keystone is a lightweight multi-platform, multi-architecture assembler framework.

Further information are available at http://www.keystone-engine.org

=head2 METHODS

=over 4

=item new(arch, mode)

  $ks = Keystone->new(KS_ARCH_X86, KS_MODE_32);

Create a new keystone object.
Take two arguments, the arch (KS_ARCH_*) and the mode (KS_MODE_*).
See ks_open() in keystone-engine documentation

=back

=over 4

=item asm(code, address)

  @opcodes = $ks->asm("pop rax; ret");

Assemble code, and return a list of opcodes.

See ks_asm() in keystone-engine documentation.

=back

=over 4

=item strerror()

   printf "%s\n", $ks->strerror();

Get the last error string

See ks_strerror() in keystone-engine documentation.

=back

=over 4

=item errno()

   printf "%d\n", $ks->errno();

Get the last error code (KS_ERR_* constants)

See ks_errno() in keystone-engine documentation.

=back

=head2 FUNCTIONS

=over 4

=item version()

  ($maj, $min) = Keystone::version();

Get the major and the minor version of the Keystone engine.
See ks_version() in keystone-engine documentation

=back

=over 4

=item arch_supported(arch)

  printf "%d\n", Keystone::arch_supported(KS_ARCH_X86);

Return 1 if the KS_ARCH_* is supported, 0 overwise.
See ks_arch_supported() in keystone-engine documentation

=back

=head2 EXAMPLES

  #!/usr/bin/perl

  use ExtUtils::testlib;
  use Keystone ':all';

  use strict;
  use warnings;

  my @asm = ("push ebp",
             "mov rdx, rdi",
             "int 0x80",
             "inc rdx",
             "mov eax, 0x12345678",
             "mov bx, 5");

  # Open a Keystone object
  my $ks = Keystone->new(KS_ARCH_X86, KS_MODE_64) ||
      die "[-] Can't open Keystone\n";


  for my $ins(@asm) {

      # Assemble...
      my @opcodes = $ks->asm($ins);

      if(!scalar(@opcodes)) {
          printf "Assembly failed (\"$ins\") : %s\n", $ks->strerror();
      } else {
          # Print opcodes
          printf "%-20s %s\n", join(' ', map {sprintf "%.2x", $_} @opcodes), $ins;
      }
  }

=head1 SEE ALSO

http://keystone-engine.org/

https://github.com/t00sh/perl-keystone

=head1 AUTHOR

Tosh, E<lt>tosh@t0x0sh.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2016 by Tosh

This library 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 3 of the License, or
(at your option) any later version.

=cut
