Archive-ByteBoozer
view release on metacpan or search on metacpan
lib/Archive/ByteBoozer.pm view on Meta::CPAN
my $byte = substr $crunched_data, 0, 1, '';
die "Error (P-4): cannot write undefined value, aborting" unless defined $byte;
my $num_bytes = $target->syswrite($byte, 1);
die "Error (B-2): cannot write output stream, aborting" if $num_bytes != 1;
}
unless (defined $target->flush) {
die "Error (B-3): cannot flush output stream, aborting";
}
return;
}
sub _attach_decruncher {
my ($params) = @_;
my $start_address = $params->{attach_decruncher} || $params->{make_executable} || 0;
$params->{_start_address} = $start_address;
return;
}
sub _precede_initial_address {
my ($params) = @_;
my $precede_initial_address = $params->{precede_initial_address};
return unless defined $precede_initial_address;
my @memory_address = _memory_address_bytes($precede_initial_address);
substr $params->{_source_data}, 0, 0, join '', @memory_address;
return;
}
sub _get_address_to_relocate_output_up_to {
my ($params) = @_;
my $data_length = length ($params->{_crunched_data}) - 0x02;
my $relocate_output_up_to = $params->{relocate_output_up_to};
my $address_to_relocate_data = $relocate_output_up_to - $data_length;
return $address_to_relocate_data;
}
sub _relocate_output {
my ($params) = @_;
my $relocate_output = $params->{relocate_output};
my $relocate_output_up_to = $params->{relocate_output_up_to};
return unless defined $relocate_output || defined $relocate_output_up_to;
my $address_to_relocate_data =
defined $relocate_output_up_to ? _get_address_to_relocate_output_up_to($params) : $relocate_output;
my @memory_address = _memory_address_bytes($address_to_relocate_data);
substr $params->{_crunched_data}, 0, 2, join '', @memory_address;
return;
}
sub _replace_initial_address {
my ($params) = @_;
my $replace_initial_address = $params->{replace_initial_address};
return unless defined $replace_initial_address;
my @memory_address = _memory_address_bytes($replace_initial_address);
substr $params->{_source_data}, 0, 2, join '', @memory_address;
return;
}
sub crunch {
my $params = { @_ };
validate(
@_, {
source => { type => HANDLE, isa => 'IO::Handle', callbacks => {
is_not_the_same_as_target => sub { exists $_[1]->{target} && refaddr $_[0] != refaddr $_[1]->{target} },
} },
target => { type => HANDLE, isa => 'IO::Handle', callbacks => {
is_not_the_same_as_source => sub { exists $_[1]->{source} && refaddr $_[0] != refaddr $_[1]->{source} },
} },
attach_decruncher => { type => SCALAR, optional => 1, callbacks => {
is_valid_memory_address => sub { looks_like_number $_[0] && $_[0] >= 0x0000 && $_[0] <= 0xffff },
} },
make_executable => { type => SCALAR, optional => 1, callbacks => {
is_valid_memory_address => sub { looks_like_number $_[0] && $_[0] >= 0x0000 && $_[0] <= 0xffff },
} },
precede_initial_address => { type => SCALAR, optional => 1, callbacks => {
is_valid_memory_address => sub { looks_like_number $_[0] && $_[0] >= 0x0000 && $_[0] <= 0xffff },
} },
relocate_output => { type => SCALAR, optional => 1, callbacks => {
is_valid_memory_address => sub { looks_like_number $_[0] && $_[0] >= 0x0000 && $_[0] <= 0xffff },
} },
relocate_output_up_to => { type => SCALAR, optional => 1, callbacks => {
is_valid_memory_address => sub { looks_like_number $_[0] && $_[0] >= 0x0000 && $_[0] <= 0xffff },
} },
replace_initial_address => { type => SCALAR, optional => 1, callbacks => {
is_valid_memory_address => sub { looks_like_number $_[0] && $_[0] >= 0x0000 && $_[0] <= 0xffff },
} },
verbose => { type => SCALAR, optional => 1, regex => qr/^\d+$/ },
}
);
my $pos = _seek_and_tell($params->{source}, $params->{target});
my $source_position = $pos->{source}->{get}->($params->{source});
my $target_position = $pos->{target}->{get}->($params->{target});
_read_file $params;
_precede_initial_address $params;
die "Error (I-3): no data to crunch in input stream, aborting" unless length $params->{_source_data} > 1;
_replace_initial_address $params;
_attach_decruncher $params;
_crunch_data $params;
_relocate_output $params;
_write_file $params;
$pos->{source}->{set}->($params->{source}, $source_position);
$pos->{target}->{set}->($params->{target}, $target_position);
if (defined $params->{verbose} && $params->{verbose} == 1) {
printf("[Archive::ByteBoozer] Compressed %u bytes into %u bytes.\n", length $params->{_source_data}, length $params->{_crunched_data});
}
return;
}
sub _seek_and_tell {
my ($source, $target) = @_;
my $source_getpos = $source->can('getpos') || \&IO::Scalar::getpos;
my $source_setpos = $source->can('setpos') || \&IO::Scalar::setpos;
my $target_getpos = $target->can('getpos') || \&IO::Scalar::getpos;
my $target_setpos = $target->can('setpos') || \&IO::Scalar::setpos;
return {
'source' => {
'get' => $source_getpos,
'set' => $source_setpos,
},
'target' => {
'get' => $target_getpos,
'set' => $target_setpos,
}
};
}
=head1 EXAMPLES
Compress a PRG file named "part-1.prg", replace its start address with $2000 (this is where the data will be uncompressed to), move all packed bytes to $f000 (this will be written into loading address of the output file), and save crunched data into ...
use Archive::ByteBoozer qw/crunch/;
my $source = new IO::File "part-1.prg", "r";
my $target = new IO::File "part-1.crunched.prg", "w";
my $unpacking_address = 0x2000;
my $relocate_address = 0xf000;
crunch(
source => $source,
target => $target,
replace_initial_address => $unpacking_address,
relocate_output => $relocate_address,
);
( run in 0.708 second using v1.01-cache-2.11-cpan-39bf76dae61 )