#!/usr/bin/perl
use strict;
use warnings;

# pokeFile
# This script edits a binary file by replacing the bytes at specified offsets
# with the supplied values.
# The name 'pokeFile' is supposed to suggest the 'poke' command of BASIC
# The first command-line arg is the name of the file to be edited
# The remaining args supplies an array of alternating offsets and values
# For example:
# pokeFile myDataFile 53 0x67 105 0x72
# would replace whatever is at byte-offset 53 with the value 0x67
# and whatever is at byte-offset 105 with the value 0x72
# Offsets and values can be given in decimal, octal, or hexadecimal
# (in the above example, the offsets were given in decimal and the values in hex
#  but that was just for clarity)
#
# Cameron Hayne (macdev@hayne.net)  August 2006

my $scriptName = "pokeFile";
if (scalar(@ARGV) < 3)
{
    die "Usage: $scriptName filename offset1 value1 [offset2 value2 ...]\n";
}

my $verbose = 1;
my $filename = shift @ARGV;
die "Missing value\n" if (scalar(@ARGV) % 2 != 0);
my %values = @ARGV;

sub convertToDec($)
{
    my ($value) = @_;

    if ($value =~ /^0/)
    {
        return oct($value); # does both oct and hex
    }
    else
    {
        return $value;
    }
}

use Fcntl qw(:seek);
open(FILE, "+<$filename") or die "Can't open file $filename: $!\n";
foreach my $offset (sort keys(%values))
{
    my $value = $values{$offset};
    my $decOffset = convertToDec($offset);
    my $decValue = convertToDec($value);

    my $oldValue;
    if ($verbose)
    {
        my $numBytesToRead = 1;
        seek(FILE, $decOffset, SEEK_SET) or die "seek failed: $!";
        read(FILE, $oldValue, $numBytesToRead) == $numBytesToRead
             or die "Failed to read value from file: $!\n";
        $oldValue = unpack("C", $oldValue);
    }

    seek(FILE, $decOffset, SEEK_SET) or die "seek failed: $!";
    print FILE pack("C", $decValue)
        or die "Failed to write value into file: $!\n";

    if ($verbose)
    {
        printf("Offset $offset: 0x%x (was 0x%x)\n", $decValue, $oldValue);
    }
}
close(FILE);

