#!/usr/bin/perl

use 5.010;
use warnings;
use strict;
use File::Basename;
use IO::Socket;
use Getopt::Long;
use Pod::Usage;

use constant VERSION => "0.2";

my ($help, $man) = (0, 0);

GetOptions(
    "help|h" => \$help,
    "man" => \$man
) or pod2usage(2);

pod2usage(1) if $help;
pod2usage(-verbose => 2) if $man;

my $remote_host = $ENV{PS2_IP} || "192.168.0.10";
my $remote_port = 4234;

use constant {
    NTPB_MAGIC => "\xff\x00NTPB",
    NTPB_HDR_SIZE => 6 + 2 + 2,
    NTPB_DUMP => 0x0100,
    NTPB_HALT => 0x0201,
    NTPB_RESUME => 0x0202,
    NTPB_SEND_DUMP => 0x0300,
    NTPB_EOT => 0xffff,
    NTPB_ACK => 0x0001,
    BUFSIZE => 65536
};

sub ntpb_connect {
    IO::Socket::INET->new(
        PeerAddr => $remote_host,
        PeerPort => $remote_port,
        Proto => "tcp",
        Timeout => 3
    )
}

sub ntpb_send_cmd {
    my ($sock, $cmd, $buf) = @_;
    $buf ||= "";
    $sock->send(NTPB_MAGIC . pack("S2", length $buf, $cmd) . $buf) or return undef;
    $sock->recv($buf, BUFSIZE) // return undef;
}

sub ntpb_recv_data {
    my ($sock, $file) = @_;
    my $eot = 0;

    if ($file) {
        open FILE, ">", $file or die $!;
        binmode FILE;
    }

    do {
        my $buf;
        $sock->recv($buf, BUFSIZE) // return undef;
        my ($magic, $bytes, $cmd) = unpack("a6 S S", $buf);
        my $size = $bytes + NTPB_HDR_SIZE;

        while (length($buf) < $size) {
            $sock->recv($_, BUFSIZE - length($buf)) // return undef;
            $buf .= $_;
        }

        if ($cmd == NTPB_SEND_DUMP) {
            $buf = substr($buf, NTPB_HDR_SIZE);
            print FILE $buf if $file;
        } elsif ($cmd == NTPB_EOT) {
            $eot = 1;
        }

        $sock->send(NTPB_MAGIC . pack("S3", length NTPB_ACK,
                    NTPB_EOT, NTPB_ACK)) or return undef;
    } while (!$eot);

    close(FILE) if $file;
}

sub ntpb_cmd_halt {
    my $sock = ntpb_connect $remote_host, $remote_port or die $!;
    ntpb_send_cmd $sock, NTPB_HALT;
    ntpb_recv_data $sock;
    close $sock;
}

sub ntpb_cmd_resume {
    my $sock = ntpb_connect $remote_host, $remote_port or die $!;
    ntpb_send_cmd $sock, NTPB_RESUME;
    ntpb_recv_data $sock;
    close $sock;
}

sub ntpb_cmd_dump {
    my ($start, $end, $file) = @_;
    my $sock = ntpb_connect $remote_host, $remote_port or die $!;
    ntpb_send_cmd $sock, NTPB_DUMP, pack("L2", $start, $end);
    ntpb_recv_data $sock, $file;
    close $sock;
}

my $cmd = shift || die pod2usage("$0: command missing");
given ($cmd) {
    when (/halt/i) { ntpb_cmd_halt }
    when (/resume/i) { ntpb_cmd_resume }
    when (/dump/i) {
        pod2usage("$0: not enough arguments") unless @ARGV == 3;
        my ($start, $end, $file) = @ARGV;
        ntpb_cmd_dump hex $start, hex $end, $file }
    default { pod2usage("$0: invalid command") }
}

__END__

=head1 NAME

ntpbclient - issue commands to PS2rd

=head1 SYNOPSIS

ntpbclient [options] <command> [args]

=head1 DESCRIPTION

ntpbclient is a client application to talk to the server side of PS2rd.

=head1 OPTIONS

=over

=item -h, --help

Show help text.

=item --man

Show man page.

=back

=head2 COMMANDS

=over

=item halt

Halt game execution.

=item resume

Resume game execution.

=item dump <start> <end> <file>

Dump memory from address <start> to address <end> to <file>.

=back

=head1 ENVIRONMENT

=over

=item B<PS2_IP>

By default, ntpbclient connects to the IP 192.168.0.10. You can change
the address by setting the B<PS2_IP> environment variable.

=back

=head1 EXAMPLES

=over

=item *

ntpbclient halt

=item *

ntpbclient resume

=item *

ntpbclient dump bfc00000 c0000000 bios.bin

=item *

ntpbclient dump 00100000 02000000 user.bin

=back

=head1 AUTHORS

=over

=item *

Written by Mathias Lafeldt <misfire@debugon.org>

=item *

Based on ntpbclient.c by jimmikaelkael <jimmikaelkael@wanadoo.fr>

=back

=head1 LICENSE

ntpbclient is part of PS2rd, the PS2 remote debugger.

PS2rd 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.
