[PSP][InternetRadioExt][PRS] A script that generates PRS formatted files (for PSP FW 3.80)

前回の記事の説明があまりに不親切だったので、コードにしてみた。以下のスクリーンショットは、このブログの favicon をアイコン画像にしてみた例。

f:id:moriyoshi:20080127043223j:image

use strict;
use IO::Handle;
use IO::Simple qw(slurp);

use constant {
    MAGIC => 'PRSF',
};
use constant {
    HEADER_SIZE => 64
};
use constant {
    PROP_ENTRY_STD => 1,
    PROP_ENTRY_INFO => 2,
    PROP_ENTRY_WHAT => 3,
    PORP_ENTRY_ICON => 4,
};
use vars qw(@PROPS %PROP_DESC);

@PROPS = (
    'title',
    'info',
    'comment',
    'copyright',
    'author',
    'radioplayer_url',
    'homepage_url',
    'version',
);

%PROP_DESC = (
    'title'           => { 'type' => PROP_ENTRY_STD  },
    'info'            => { 'type' => PROP_ENTRY_INFO },
    'comment'         => { 'type' => PROP_ENTRY_STD  },
    'copyright'       => { 'type' => PROP_ENTRY_STD  },
    'author'          => { 'type' => PROP_ENTRY_STD  },
    'radioplayer_url' => { 'type' => PROP_ENTRY_STD  },
    'homepage_url'    => { 'type' => PROP_ENTRY_STD  },
    'version'         => { 'type' => PROP_ENTRY_STD  },
);

sub align {
    return $_[0] + pad_len(@_);
}

sub pad_len {
    return ($_[1] - 1 - ($_[0] + $_[1] - 1) % $_[1]);
}

sub gen {
    my %params = @_;
    my $str_table_blk = '';
    my %str_table_info = ();
    my $icon_blk = $params{'icon'};

    # build string table
    $str_table_blk .= "radioplayer\0";
    $str_table_info{'icon'} = {
        'offset' => length($str_table_blk)
    };
    $str_table_blk .= "icon\0";

    for my $key (@PROPS) {
        my $offset = length($str_table_blk);
        $str_table_blk .= pack('A*x', $key);
        if ($PROP_DESC{$key}->{'type'} == PROP_ENTRY_STD) {
            my $offset_val = length($str_table_blk);
            $str_table_blk .= pack('A*x', $params{$key});
            $str_table_info{$key} = {
                'offset' => $offset,
                'offset_val' => $offset_val,
                'value_len' => length($params{$key})
            };
        } else {
            $str_table_info{$key} = {
                'offset' => $offset,
            };
        }
    }

    # compose descriptor blk
    my $desc_blk = pack('VVVVVVVVVVV',
        0, 2, -1, -1, -1, 0x3c, 0x3c, $str_table_info{'icon'}->{'offset'}, 0x6, 0x0, length($icon_blk)
    );

    for my $key (@PROPS) {
        my $type = $PROP_DESC{$key}->{'type'};
        if ($type == PROP_ENTRY_STD) {
            $desc_blk .= pack('VVVV',
                $str_table_info{$key}->{'offset'}, 3,
                $str_table_info{$key}->{'offset_val'},
                $str_table_info{$key}->{'value_len'}
            );
        } elsif ($type == PROP_ENTRY_INFO) {
            $desc_blk .= pack('VVVVVVV',
                $str_table_info{$key}->{'offset'}, 6,
                0, -1, -1, -1, -1
            );
        }
    }

    # compose header blk
    my $off_str_table = HEADER_SIZE + align(length($desc_blk), 16);
    my $off_icon = $off_str_table + align(length($str_table_blk), 16);

    my $header_blk = pack('A4VVVVx4VVVx4Vx4VV@'. HEADER_SIZE,
        MAGIC,
        $params{'file_version'},
        HEADER_SIZE,
        length($desc_blk),
        $off_str_table,
        $off_str_table,
        length($str_table_blk),
        $off_icon,
        $off_icon,
        $off_icon,
        align(length($icon_blk), 16)
    );
    return
        $header_blk
        . $desc_blk. pack('x'. pad_len(length($desc_blk), 16))
        . $str_table_blk. pack('x'. pad_len(length($str_table_blk), 16))
        . $icon_blk. pack('x'. pad_len(length($icon_blk), 16));
}

STDOUT->write(gen(
    'file_version' => 0x00000100,
    'title' => 'Internet Radio Player (1)',
    'comment' => 'Internet Radio Player (1). This player is using the SHOUTcast directory service.',
    'copyright' => 'C) 2007 SCEI ALL RIGHTS RESERVED.',
    'author' => 'SCEI',
    'radioplayer_url' => 'https://www.example.com/internetRadioPlayerV1/internetRadioPlayerV1.html',
    'homepage_url' => 'http://www.playstation.com/psp-app/radio.html',
    'version' => '1.0',
    'icon' => scalar(slurp('icon.png'))
));