Hi,

On Tue, 29 Sep 2009, Eric House wrote:

We've discussed before that the free-software tools for building .cab
files on Linux produce files that Smartphone (but not PocketPC)
doesn't recognize.  But I wonder if anybody's found a way around this.

I don't know what has been discussed before, but I assume you're referring to pocketpc-cab, which makes .cab files that only work on PocketPC, not on Smartphone.

I've made a few changes/improvements to this tool (that I haven't had time to feed upstream to get properly integrated yet, unfortunately), so that it should be able to create .cab files that work on Smartphone, too. Additionally, I've added support for creating shortcuts and for adding registry entries.

Attached here is my version of this script, I hope you find it useful!

// Martin
#!/usr/bin/perl -w
# Build an installable Pocket PC cabinet file.
# Copyright 2006 Shaun Jackman

use strict;
use Getopt::Long;
use Pod::Usage;
use Text::ParseWords;
use MIME::Base64;


# Constants.
my $architecture = 0;
my @version_requirements = (4, 0, 6, 99, 0, -536870912); # 4.0.0 to 
6.99.0xE0000000 (-536870912)
my $verbose = 0;

# Strings.
my $string_count = 0;
my %string_ids;

# Directories.
my $directory_count = 0;
my %directory_ids;
my @directories;

# Files.
my $file_count = 0;
my %file_ids;
my %file_dirs;
my @files;

# RegHives.
my $reghive_count = 0;
my %reghive_ids;
my @reghive_roots;
my @reghives;

# RegKeys.
my $regkey_count = 0;
my %regkey_ids;
my @regkey_hives;
my @regkey_keys;
my @regkey_names;
my @regkey_values;
my @regkey_xmlvalues;
my @regkey_types;
my @regkey_typeids;

# Links.
my $links_count = 0;
my %links;
my %link_targets;
my %link_dirs;


# Returns the ID of the specified string.
sub get_string_id($)
{
        my $string = shift;
        my $id = $string_ids{$string};
        if( defined $id) {
                return $id;
        } else {
                $id = ++$string_count;
                $string_ids{$string} = $id;
                return $id;
        }
}


# Returns the ID of the specified directory.
sub get_directory_id($)
{
        my $directory = shift;
        my $id = $directory_ids{$directory};
        if( defined $id) {
                return $id;
        } else {
                $id = ++$directory_count;
                $directory_ids{$directory} = $id;

                my @strings = split '/', $directory;
                my @ids;
                foreach my $string (@strings) {
                        next if length $string == 0;
                        push @ids, get_string_id( $string);
                }
                push @ids, 0;
                $directories[$id] = \...@ids;

                return $id;
        }
}


# Returns the ID of the specified registry key
sub get_reghive_id($$)
{
        my $root = shift;
        my $key = shift;
        my $hive_name = "$root/$key";
        my $id = $reghive_ids{$hive_name};
        if( defined $id) {
                return $id;
        } else {
                $id = ++$reghive_count;
                $reghive_ids{$hive_name} = $id;

                my @strings = split '/', $key;
                my @ids;
                foreach my $string (@strings) {
                        next if length $string == 0;
                        push @ids, get_string_id( $string);
                }
                push @ids, 0;
                $reghives[$id] = \...@ids;
                $reghive_roots[$id] = $root;

                return $id;
        }
}

sub parseint($)
{
        my $str = shift;
        if ($str =~ /^0x/i) {
                return hex($str);
        } elsif ($str =~ /^0b/i) {
                return oct($str);
        } else {
                return int($str);
        }
}


# Returns a list of keys sorted by value.
sub get_value_sorted_keys(%)
{
        my %hash = @_;
        return sort { $hash{$a} <=> $hash{$b} } keys %hash;
}


# Creates the files.
sub parse_input($)
{
        my $destdir = shift;
        while(<>) {
                my ($file, $directory, $linkname, $linkdir) = &shellwords($_);
                next if length $directory == 0;

                my $id = ++$file_count;
                $file_ids{$file} = $id;
                $file_dirs{$file} = "$destdir$directory";
                $files[$id] = get_directory_id "$destdir$directory";


                if (defined $linkdir) {
                        $linkname .= '.lnk';

                        $link_targets{$linkname} = "$destdir$directory/$file";
                        $link_dirs{$linkname} = $linkdir;

                        my $linknameid = get_string_id $linkname;
                        my $dirid = 0;
                        my $basedir = 0;
                        if ($linkdir =~ /^%CE(\d+)%$/) {
                                $basedir = $1;
                        } else {
                                $dirid = get_directory_id $linkdir;
                        }

                        my @strings = split '/', $linkname;
                        my @stringids;
                        foreach my $string (@strings) {
                                next if length $string == 0;
                                push @stringids, get_string_id $string;
                        }

                        my $fileid = $id;
                        my $id = ++$links_count;
                        my $type = 1;
                        $links{$id} = [ $dirid, $basedir, $fileid, 1, [ 
@stringids ] ];
                }
        }
}


sub parse_regfile($)
{
        my $regfile = shift;
        open REGFILE, "<$regfile";
        while(<REGFILE>) {
                my @line = &shellwords($_);
                my $root = shift @line;
                my $key = shift @line;
                my $name = shift @line;
                my $type = shift @line;
                my $value = shift @line;
                next if length $root == 0;

                $root = uc($root);
                my $rootid;
                if ($root eq "HKCR") {
                        $rootid = 1;
                } elsif ($root eq "HKCU") {
                        $rootid = 2;
                } elsif ($root eq "HKLM") {
                        $rootid = 3;
                } elsif ($root eq "HKU") {
                        $rootid = 4;
                } else {
                        print STDERR "Unrecognised registry root $root\n";
                        next;
                }
                $type = lc($type);
                my $manifest_value = '';
                my $xml_value = '';
                my $typeid;
                if ($type eq "string") {
                        $manifest_value = $value . "\0";
                        $xml_value = $value;
                        $typeid = 0x00000000;
                        $type = "string";
                } elsif ($type eq "binary") {
                        $manifest_value = '';
                        while (length $value > 0) {
                                my $byte = hex(substr $value,0,2,'');
                                $manifest_value .= pack 'C',$byte;
                        }
                        $xml_value = encode_base64($manifest_value);
                        chomp $xml_value;
                        $typeid = 0x00000001;
                        $type = "binary";
                } elsif ($type eq "integer" || $type eq "int" || $type eq 
"dword") {
                        my $int = parseint($value);
                        $manifest_value = pack 'V', $int;
                        $xml_value = "$int";
                        $typeid = 0x00010001;
                        $type = "integer";
                } elsif ($type eq "multistring") {
                        my @strings;
                        push @strings, $value if defined $value;
                        push @strings, @line;
                        $manifest_value = '';
                        $xml_value = '';
                        foreach my $string (@strings) {
                                $manifest_value .= $string . "\0";
                                $xml_value .= $string . "\xEF\x80\x80"; # 
separate using EF8080, which is 0xF000 encoded as utf8
                        }
                        $manifest_value .= "\0";
                        $typeid = 0x00010000;
                        $type = "multiplestring";
                } else {
                        print STDERR "Unrecognised registry type $type\n";
                        next;
                }
                my $id = ++$regkey_count;
                $regkey_ids{$id} = $id;
                $regkey_hives[$id] = get_reghive_id $rootid, $key;
                $regkey_keys[$id] = "$root/$key";
                $regkey_names[$id] = $name;
                $regkey_values[$id] = $manifest_value;
                $regkey_xmlvalues[$id] = $xml_value;
                $regkey_typeids[$id] = $typeid;
                $regkey_types[$id] = $type;
        }
        close REGFILE;
}


# Returns the entire manifest.
sub get_manifest($$)
{
        my ($provider, $application) = @_;

# Header.
        my $offset = 100;

# Application.
        my $application_offset = $offset;
        $application .= "\0";
        while ((length $application) % 4 != 0) {
                $application .= "\0";
        }
        $offset += length $application;

# Provider.
        my $provider_offset = $offset;
        $provider .= "\0";
        while ((length $provider) % 4 != 0) {
                $provider .= "\0";
        }
        $offset += length $provider;

# Unsupported platforms.
        my $unsupported_offset = $offset;
        my $unsupported = '';
        $offset += length $unsupported;

# Strings.
        my $strings_offset = $offset;
        my $strings = '';
        foreach my $string (get_value_sorted_keys %string_ids) {
                my $string_id = $string_ids{$string};
                print "$string($string_id)\n" if $verbose;
                $string .= "\0";
                while ((length $string) % 4 != 0) {
                        $string .= "\0";
                }
                $strings .= pack 'vv', $string_id, length $string;
                $strings .= $string;
        }
        $offset += length $strings;

# Directories.
        my $directories_offset = $offset;
        my $directories = '';
        foreach my $directory (get_value_sorted_keys %directory_ids) {
                my $directory_id = $directory_ids{$directory};
                my @ids = @{$directories[$directory_id]};
                print "$directory($directory_id): @ids\n" if $verbose;
                $directories .= pack 'vv', $directory_id, 2 * scalar @ids;
                foreach my $id (@ids) {
                        $directories .= pack 'v', $id;
                }
        }
        $offset += length $directories;

# Files.
        my $files_offset = $offset;
        my $files = '';
        foreach my $path (get_value_sorted_keys %file_ids) {
                my $file_id = $file_ids{$path};
                my $directory_id = $files[$file_id];
                my $file = $path;
                $file =~ s/^.*\///;
                $file .= "\0";
                while ((length $file) % 4 != 0) {
                        $file .= "\0";
                }
                print "$file($file_id): $directory_id\n" if $verbose;
                $files .= pack 'vvvVv', $file_id, $directory_id, $file_id,
                        0, length $file;
                $files .= $file;
        }
        $offset += length $files;

# RegHives.
        my $reghives_offset = $offset;
        my $reghives = '';
        foreach my $reghive (get_value_sorted_keys %reghive_ids) {
                my $reghive_id = $reghive_ids{$reghive};
                my $reghive_root = $reghive_roots[$reghive_id];
                my @ids = @{$reghives[$reghive_id]};
                print "$reghive($reghive_id): @ids\n" if $verbose;
                $reghives .= pack 'vvvv', $reghive_id, $reghive_root, 0, 2 * 
scalar @ids;
                foreach my $id (@ids) {
                        $reghives .= pack 'v', $id;
                }
        }
        $offset += length $reghives;

# RegKeys.
        my $regkeys_offset = $offset;
        my $regkeys = '';
        foreach my $regkey_id (get_value_sorted_keys %regkey_ids) {
                my $hive = $regkey_hives[$regkey_id];
                my $name = $regkey_names[$regkey_id];
                my $value = $regkey_values[$regkey_id];
                my $len = length($value);
                my $typeid = $regkey_typeids[$regkey_id];
                print "$name($regkey_id): \n" if $verbose;
                $regkeys .= pack 'vvvVv', $regkey_id, $hive, 0, $typeid, 
length($name) + 1 + length($value);
                $regkeys .= $name . "\0" . $value;
        }
        $offset += length $regkeys;

# Links.
        my $links_offset = $offset;
        my $links = '';
        foreach my $link_id (keys %links) {
                my @link = @{ $links{$link_id} };
                my $dirid = $link[0];
                my $basedir = $link[1];
                my $fileid = $link[2];
                my $type = $link[3];
                my @stringids = @{ $link[4] };
                my $length = 2 * scalar @stringids;
                $links .= pack 'vvvvvv', $link_id, $dirid, $basedir, $fileid, 
$type, $length;
                foreach my $id (@stringids) {
                        $links .= pack 'v', $id;
                }
        }
        $offset += length $links;

# Header.
        my $length = $offset;
        my @fields = (
                0, $length, 0, 1, $architecture,
                @version_requirements,
                $string_count, $directory_count, $file_count,
                $reghive_count, $regkey_count, $links_count,
                $strings_offset, $directories_offset, $files_offset,
                $reghives_offset, $regkeys_offset, $links_offset,
                $application_offset, length $application,
                $provider_offset, length $provider,
                $unsupported_offset, length $unsupported,
                0, 0);
        my $header = 'MSCE';
        $header .= pack 'V11 v6 V6 v8', @fields;

        return $header . $application . $provider . $unsupported .
                $strings . $directories . $files .
                $reghives . $regkeys . $links;
}

# Returns a munged version of the specified filename.
# Removes the leading path. Removes the extension. Removes spaces.
# Truncates to eight characters. Pads to eight characters with leading
# zeros. Adds a numeric extension.
sub munge_filename($$)
{
        my $munged = shift;
        my $extension = shift;
        $munged =~ s/^.*\///;
        $munged =~ s/\..*$//;
        $munged =~ s/ //;
        $munged = substr $munged, 0, 8;
        $munged = sprintf '%08s.%03d', $munged, $extension;
        return $munged;
}

# Returns the _setup.xml file
sub get_setupxml($$)
{
        my ($provider, $application) = @_;

        my $output;

        $output .= "<wap-provisioningdoc>\n";
        $output .= "\t<characteristic type=\"Install\">\n";
        $output .= "\t\t<parm name=\"InstallPhase\" value=\"install\" />\n";
        $output .= "\t\t<parm name=\"OSVersionMin\" 
value=\"$version_requirements[0].$version_requirements[1]\" />\n";
        $output .= "\t\t<parm name=\"OSVersionMax\" 
value=\"$version_requirements[2].$version_requirements[3]\" />\n";
        $output .= "\t\t<parm name=\"BuildNumberMin\" 
value=\"$version_requirements[4]\" />\n";
        $output .= "\t\t<parm name=\"BuildNumberMax\" 
value=\"$version_requirements[5]\" />\n";
        $output .= "\t\t<parm name=\"AppName\" value=\"$provider $application\" 
/>\n";
        my @filenames = get_value_sorted_keys %file_ids;
        my $firstdir = $file_dirs{$filenames[0]};
        $firstdir =~ s/\//\\/g;
        $output .= "\t\t<parm name=\"InstallDir\" value=\"$firstdir\" 
translation=\"install\" />\n";
        my $dirs = scalar keys %directory_ids;
        my $files = scalar keys %file_ids;
        $output .= "\t\t<parm name=\"NumDirs\" value=\"$dirs\" />\n";
        $output .= "\t\t<parm name=\"NumFiles\" value=\"$files\" />\n";
        $output .= "\t\t<parm name=\"NumRegKeys\" value=\"$regkey_count\" />\n";
        $output .= "\t\t<parm name=\"NumRegVals\" value=\"$regkey_count\" />\n";
        my $shortcuts = scalar keys %links;
        $output .= "\t\t<parm name=\"NumShortcuts\" value=\"$shortcuts\" />\n";
        $output .= "\t</characteristic>\n";

        $output .= "\t<characteristic type=\"FileOperation\">\n";

        foreach my $path (get_value_sorted_keys %file_ids) {
                my $file_id = $file_ids{$path};
                my $dir = $file_dirs{$path};
                my $file = $path;
                $file =~ s/^.*\///g;
                $file =~ s/\//\\/g;
                $dir =~ s/\//\\/g;
                my $munged_file = munge_filename $file, $file_id;
                $output .= "\t\t<characteristic type=\"$dir\" 
translation=\"install\">\n";
                $output .= "\t\t\t<characteristic type=\"MakeDir\" />\n";
                $output .= "\t\t\t<characteristic type=\"$file\" 
translation=\"install\">\n";
                $output .= "\t\t\t\t<characteristic type=\"Extract\">\n";
                $output .= "\t\t\t\t\t<parm name=\"Source\" 
value=\"$munged_file\" />\n";
                $output .= "\t\t\t\t</characteristic>\n";
                $output .= "\t\t\t</characteristic>\n";
                $output .= "\t\t</characteristic>\n";
        }
        foreach my $link (get_value_sorted_keys %link_dirs) {
                my $link_dir = $link_dirs{$link};
                my $link_target = $link_targets{$link};
                $link =~ s/\//\\/g;
                $link_dir =~ s/\//\\/g;
                $link_target =~ s/\//\\/g;
                $output .= "\t\t<characteristic type=\"$link_dir\" 
translation=\"install\">\n";
                $output .= "\t\t\t<characteristic type=\"MakeDir\" />\n";
                $output .= "\t\t\t<characteristic type=\"$link\" 
translation=\"install\">\n";
                $output .= "\t\t\t\t<characteristic type=\"Shortcut\">\n";
                $output .= "\t\t\t\t\t<parm name=\"Source\" 
value=\"$link_target\" translation=\"install\" />\n";
                $output .= "\t\t\t\t</characteristic>\n";
                $output .= "\t\t\t</characteristic>\n";
                $output .= "\t\t</characteristic>\n";
        }
        $output .= "\t</characteristic>\n";
        $output .= "\t<characteristic type=\"Registry\">\n";
        foreach my $regkey_id (get_value_sorted_keys %regkey_ids) {
                my $key = $regkey_keys[$regkey_id];
                my $name = $regkey_names[$regkey_id];
                my $type = $regkey_types[$regkey_id];
                my $value = $regkey_xmlvalues[$regkey_id];
                $key =~ s/\//\\/g;
                $output .= "\t\t<characteristic type=\"$key\">\n";
                $output .= "\t\t\t<parm name=\"$name\" value=\"$value\" 
datatype=\"$type\" />\n";
                $output .= "\t\t</characteristic>\n";
        }
        $output .= "\t</characteristic>\n";
        $output .= "</wap-provisioningdoc>\n";

        return $output;
}


# Prints the version message and exits.
sub version()
{
        print
                "pocketpc-cab 1.0.1\n" .
                "Written by Shaun Jackman <sjackm...@gmail.com>.\n" .
                "\n" .
                "Copyright 2006 Shaun Jackman\n" .
                "This is free software; see the source for copying\n" .
                "conditions. There is NO warranty; not even for\n" .
                "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
        exit 0;
}


# Main.
sub main()
{
        # Parse the command line.
        my $provider = 'Provider';
        my $application = 'Application';
        my $sourcedir = '';
        my $destdir = '';
        my $regfile;

        GetOptions(
                        "provider=s" => \$provider,
                        "application=s" => \$application,
                        "source=s" => \$sourcedir,
                        "dest=s" => \$destdir,
                        "registry=s" => \$regfile,
                        "verbose|v!" => \$verbose,
                        "help|?" => sub { pod2usage(
                                -exitstatus => 0, -verbose => 1) },
                        "man" => sub { pod2usage(
                                -exitstatus => 0, -verbose => 2) },
                        "version" => \&version);

        $sourcedir .= '/' if length $sourcedir > 0;
        $destdir .= '/' if length $destdir > 0;

        if( scalar @ARGV < 1) {
                print
                        "pocketpc-cab: missing file arguments\n" .
                        "Try `pocketpc-cab --help' for more information.\n";
                exit 1;
        }
        my $cab_filename = pop @ARGV;

        # Parse the input file.
        parse_input( $destdir);
        parse_regfile($regfile) if defined $regfile;

        # Create the manifest.
        my $manifest = "manifest.000";
        open MANIFEST, ">$manifest";
        binmode MANIFEST;
        print MANIFEST get_manifest( $provider, $application);
        close MANIFEST;

        # Create the _setup.xml
        my $setupxml = "_setup.xml";
        open SETUPXML, ">$setupxml";
        print SETUPXML get_setupxml( $provider, $application);
        close SETUPXML;

        # Copy the data files.
        my $munged_files = " $setupxml";
        my $i = 0;
        foreach my $file (get_value_sorted_keys %file_ids) {
                my $munged_file = munge_filename $file, ++$i;
                print "$file: $munged_file\n" if $verbose;
                `cp "$sourcedir$file" "$munged_file"`;
                exit $? >> 8 if $? > 0;
                $munged_files = ' ' . $munged_file . $munged_files;
        }
        $munged_files = 'manifest.000' . $munged_files;

        # Create the cab.
        print "$cab_filename: $munged_files\n" if $verbose;
        my $lcab_output = `lcab $munged_files $cab_filename`;
        exit $? >> 8 if $? > 0;
        print $lcab_output if $verbose;
        `rm $munged_files`;
}


# Entry-point.
main;


__END__

=head1 NAME

pocketpc-cab - build an installable Pocket PC cabinet file

=head1 SYNOPSIS

B<pocketpc-cab> [I<OPTION>]... I<INPUTFILE> I<CABINET>

=head1 DESCRIPTION
                                                                                
                                                        
Read the INPUTFILE, which is a list of filenames and destination
directories, and create CABINET, a cabinet file, that will install
those files into the specified directories.

=head1 OPTIONS

=over

=item B<-p, --provider>=I<PROVIDER>

set the provider name

=item B<-a, --application>=I<APPLICATION>

set the application name

=item B<-s, --source>=I<SOURCE>

set the source directory

=item B<-d, --dest>=I<DEST>

set the destination directory

=item B<-r, --registry>=I<REGISTRYFILE>

use a registry specification file

=item B<-v, --verbose>

display verbose output

=item B<--help>

display a brief help message

=item B<--man>

display the full documentation

=back

=head1 EXAMPLES

 $ cat > foobar.files <<EOF
 foobar.exe /bin "Shortcut Name" %CE11%
 foobar.dll /windows
 EOF
 $ cat > foobar.regkeys <<EOF
 HKCU Software/Fooware/Foobar Name string "Some string"
 HKCU Software/Fooware/Foobar Data binary 00112233445566778899aabbccddeeff
 HKCU Software/Fooware/Foobar Value integer 42
 HKCU Software/Fooware/Foobar HexValue integer 0xff
 HKCU Software/Fooware/Foobar Names multistring "String 1" "String 2"
 EOF
 $ pocketpc-cab -p Fooware -a FooBar -r foobar.regkeys foobar.files foobar.cab

=head1 AUTHOR

Written by Shaun Jackman.

=head1 REPORTING BUGS

Report bugs to Shaun Jackman <sjack...@gmail.com>.

=head1 COPYRIGHT

Copyright 2006 Shaun Jackman

This is free software; see the source for copying conditions. There is
NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.

=head1 SEE ALSO

=over

=item B<lcab>(1)

create cabinet archives

=item B</usr/share/doc/pocketpc-cab/wince_cab_format.html>

Windows CE installation cabinet file format

=back

=cut


ChangeLog

2006-04-06  Shaun Jackman  <sjack...@gmail.com>

        * Release version 1.0.1.
        * Fix the CAB for WinCE5 by putting the manifest.000 file first in
        the cabinet file.
        Thanks to Rouven SchÃŒrch <rouven.schue...@tegonal.com>.

2004-09-17  Shaun Jackman  <sjack...@debian.org>

        * Initial release, version 1.0.0.
------------------------------------------------------------------------------
Come build with us! The BlackBerry&reg; Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay 
ahead of the curve. Join us from November 9&#45;12, 2009. Register now&#33;
http://p.sf.net/sfu/devconf
_______________________________________________
Cegcc-devel mailing list
Cegcc-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/cegcc-devel

Reply via email to