#!/usr/bin/perl

# resutil 0.1 - resource fork handling utility
#  by Yusuke Shinyama, 2001/7   * this software is public domain *

sub usage {
    print "usage:\n";
    print "  $0 ls rsrc                            (list the resource map)\n";
    print "  $0 type rsrc                          (list the resource types)\n";
    print "  $0 id rsrc type1 type2 ...            (list the resource ids)\n";
    print "  $0 info rsrc type1 id1 type2 id2 ...  (list the resource info)\n";
    print "  $0 cat rsrc type1 id1 type2 id2 ...   (extract the resource data)\n";
    print "  $0 unpack rsrc destdir\n";
    print "        (unpack all resource into a directory tree)\n";
    exit 1;
}

sub err { print STDERR "$0: $_[0]\n"; }

sub readerr { die("sysread: $rsrc " . &pos() . $!); }
sub uint8 { sysread(IN, $_, 1) || &readerr(); unpack('n', "\000".$_); }
sub uint16 { sysread(IN, $_, 2) || &readerr(); unpack('n', $_); }
sub uint24 { sysread(IN, $_, 3) || &readerr(); unpack('N', "\000".$_); }
sub uint32 { sysread(IN, $_, 4) || &readerr(); unpack('N', $_); }
sub str32 { sysread(IN, $_, 4) || &readerr(); $_; }
sub strn { sysread(IN, $_, $_[0]) || &readerr(); $_; }
sub pos { sysseek(IN, 0, 1); }
sub goto { sysseek(IN, $_[0], 0) || die("sysseek: $!"); }

sub read_map {
    $offset_data = &uint32();
    $offset_map  = &uint32();
    $length_data = &uint32();
    $length_map  = &uint32();

    &goto($offset_map);
    &uint32(); &uint32(); &uint32(); &uint32();	# reserved
    &uint32();					# reserved
    &uint16();					# reserved
    $resource_attr = &uint16();
    $offset_types = $offset_map + &uint16();
    $offset_names = $offset_map + &uint16();
    
    &goto($offset_types);
    $num_types = &uint16() + 1;
    for(my $i = 0; $i < $num_types; $i++) {
	push(@restype_name, &str32());
	push(@restype_num, &uint16()+1);
	push(@restype_offset, $offset_types+&uint16());
    }

    for(my $i = 0; $i < $num_types; $i++) {
	my $n = $restype_name[$i];
	for(my $j = 0; $j < $restype_num[$i]; $j++) {
	    &goto($restype_offset[$i] + $j * (2+2+1+3+4));
	    my $id = &uint16();
	    push(@{$res_ids{$n}}, $id);
	    my $of_name1 = &uint16();
	    my $attr = &uint8();
	    push(@{$res_attrs{$n}}, $attr);
	    my $ofst = &uint24() + $offset_data;
	    push(@{$res_offsets{$n}}, $ofst);
	    if ($of_name1 != 0xffff) {
		&goto($of_name1 + $offset_names);
		my $len = &uint8();
		push(@{$res_names{$n}}, &strn($len));
	    } else {
		push(@{$res_names{$n}}, '');
	    }
	}
    }
}

# list the resource map in a human-readale format
sub list_map {
    print "rsrc_attr: $resource_attr\n";
    foreach my $n (@restype_name) {
	print "$n\n";
	my $r_ids = $res_ids{$n};
	my $r_names = $res_names{$n};
	my $r_attrs = $res_attrs{$n};
	my $r_offsets = $res_offsets{$n};
	for(my $i = 0; $i < @{$r_ids}; $i++) {
	    printf " %6d (%s) : %08lx", $r_ids->[$i], unpack("B8",chr($r_attrs->[$i])), $r_offsets->[$i];
	    print ' "', $r_names->[$i], '"' if ($r_names->[$i]);
	    print "\n";
	}
    }
}

# list the resource types
sub list_type {
    foreach my $n (@restype_name) { print "$n\n"; }
}

# list the resource ids of the specified type
sub list_id {
    my $r_ids = $res_ids{$_[0]};
    if (! $r_ids) { &err("restype not found: $_[0]"); return; }
    foreach my $i (@{$r_ids}) { print "$i\n"; }
}

# list the resource info
sub list_info {
    my $r_ids = $res_ids{$_[0]};
    if (! $r_ids) { &err("restype not found: $_[0]"); return; }
    for(my $i = 0; $i < @{$r_ids}; $i++) {
	if ($r_ids->[$i] == $_[1]) {
	    print "type\t", $_[0], "\n";
	    print "id\t", $_[1], "\n";
	    print "attr\t", unpack("B8", chr($res_attrs{$_[0]}->[$i])), "\n";
	    print "name\t", $res_names{$_[0]}->[$i], "\n";
	    printf "offset\t%08x\n", $res_offsets{$_[0]}->[$i];
	    &goto($res_offsets{$_[0]}->[$i]);
	    print "size\t", &uint32(), "\n";
	    return;
	}
    }
    &err("resid not found: $_[0] $_[1]");
}

# output the resource content
$BUFSIZ = 1024;
sub cat1 {
    my $h = $_[0];
    &goto($_[1]);
    my $len = &uint32();
    while(0 < $len) {
	my $s = $len; $s = $BUFSIZ if ($BUFSIZ < $s);
	$len -= sysread(IN, $_, $s) || die("sysread: $!");
	syswrite($h, $_, length($_));
    }
}
sub res_cat {
    my $r_ids = $res_ids{$_[0]};
    if (! $r_ids) { &err("restype not found: $_[0]"); return; }
    for(my $i = 0; $i < @{$r_ids}; $i++) {
	if ($r_ids->[$i] == $_[1]) {
	    &cat1(STDOUT, $res_offsets{$_[0]}->[$i]);
	    return;
	}
    }
    &err("resid not found: $_[0] $_[1]");
}

# unpack all resources into a directory tree
sub res_unpack {
    open(OUT, ">rsrc_attr") || die("open: $!");
    print OUT unpack("B16", pack("n", $resource_attr)), "\n";
    close(OUT);
    foreach my $n (@restype_name) {
	mkdir($n, 0777);
	if (!chdir($n)) { &err("chdir: $!"); next; }
	my $r_ids = $res_ids{$n};
	my $r_names = $res_names{$n};
	my $r_attrs = $res_attrs{$n};
	my $r_offsets = $res_offsets{$n};
	for(my $i = 0; $i < @{$r_ids}; $i++) {
	    if (!open(OUT, ">$r_ids->[$i].data")) { &err("open: $!"); next; }
	    &cat1(OUT, $r_offsets->[$i]);
	    close(OUT);
	    if (!open(OUT, ">$r_ids->[$i].info")) { &err("open: $!"); next; }
	    print OUT "attr\t", unpack("B8", chr($r_attrs->[$i])), "\n";
	    print OUT "name\t", $r_names->[$i], "\n";
	    close(OUT);
	}
	chdir("..");
    }
}

$cmd = $ARGV[0] || &usage();
$rsrc = $ARGV[1] || &usage();
shift(@ARGV); shift(@ARGV);
open(IN, $rsrc) || die("open: $!");
binmode(IN);
&read_map();

if ($cmd eq 'ls') {
    &list_map();
} elsif ($cmd eq 'type') {
    &list_type();
} elsif ($cmd eq 'id') {
    foreach $n (@ARGV) { &list_id($n); }
} elsif ($cmd eq 'info') {
    for(my $i = 0; $i < @ARGV; $i += 2) {
	&list_info($ARGV[$i], $ARGV[$i+1]);
    }
} elsif ($cmd eq 'cat') {
    for(my $i = 0; $i < @ARGV; $i += 2) {
	&res_cat($ARGV[$i], $ARGV[$i+1]);
    }
} elsif ($cmd eq 'unpack') {
    $dir = $ARGV[0] || &usage();
    mkdir($dir, 0777);
    chdir($dir) || die("chdir: $!");
    &res_unpack();
} else {
    &usage();
}
