The reverse of encode→pack is unpack→decode. You have to remove "layers" in the opposite order that they've been applied, so you're doing it in the wrong order.
I don't see any trivial way of extracting the string before it's decoded, unfortunately.
sub extract_text {
$_[0] =~ s/^((?:..)*)\0\0//s or die;
return decode('UTF-16le', "$1");
}
my $first_name = extract_text($bytes);
my $middle_name = extract_text($bytes);
my $last_name = extract_text($bytes);
my ($x, $y) = unpack('VV', substr($bytes, 0, 8, ''));
my $address = extract_text($bytes);
my $zip_code = extract_text($bytes);
my ($z) = unpack('V', substr($bytes, 0, 4, ''));
or maybe
my @fields = $bytes =~ /
^
((?:..)*)\0\0
((?:..)*)\0\0
((?:..)*)\0\0
(.{4})
(.{4})
((?:..)*)\0\0
((?:..)*)\0\0
(.{4})
\z
/sx or die;
$_ = decode('UTF-16le', $_) for @fields[0,1,2,5,6];
$_ = unpack('V', $_) for @fields[3,4,7];