Compressed Macintosh resource data, Apple `'dcmp' (1)` format: Perl parsing library

Compressed resource data in 'dcmp' (1) format, as stored in compressed resources with header type 8 and decompressor ID 1.

The 'dcmp' (1) decompressor resource is included in the System file of System 7.0 and later. This compression format is used for a few compressed resources in System 7.0's files (such as the Finder Help file). This decompressor is also included with and used by some other Apple applications, such as ResEdit. (Note: ResEdit includes the 'dcmp' (1) resource, but none of its resources actually use this decompressor.)

This compression format supports some basic general-purpose compression schemes, including backreferences to previous data and run-length encoding. It also includes some types of compression tailored specifically to Mac OS resources, including a set of single-byte codes that correspond to entries in a hard-coded lookup table.

The 'dcmp' (0) compression format (see dcmp_0.ksy) is very similar to this format, with the main difference that it operates mostly on units of 2 or 4 bytes. This makes the ``dcmp' (0)format more suitable for word-aligned data, such as executable code, bitmaps, sounds, etc. The'dcmp' (0)` format also appears to be generally preferred over `'dcmp' (1)`, with the latter only being used in resource files that contain mostly unaligned data, such as text.

Application

Mac OS

KS implementation details

License: MIT
Minimal Kaitai Struct required: 0.8

This page hosts a formal specification of Compressed Macintosh resource data, Apple `'dcmp' (1)` format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Perl source code to parse Compressed Macintosh resource data, Apple `'dcmp' (1)` format

Dcmp1.pm

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

use strict;
use warnings;
use IO::KaitaiStruct 0.009_000;
use DcmpVariableLengthInteger;

########################################################################
package Dcmp1;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{chunks} = ();
    do {
        $_ = Dcmp1::Chunk->new($self->{_io}, $self, $self->{_root});
        push @{$self->{chunks}}, $_;
    } until ($_->tag() == 255);
}

sub chunks {
    my ($self) = @_;
    return $self->{chunks};
}

########################################################################
package Dcmp1::Chunk;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

our $TAG_KIND_INVALID = -1;
our $TAG_KIND_LITERAL = 0;
our $TAG_KIND_BACKREFERENCE = 1;
our $TAG_KIND_TABLE_LOOKUP = 2;
our $TAG_KIND_EXTENDED = 3;
our $TAG_KIND_END = 4;

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{tag} = $self->{_io}->read_u1();
    my $_on = ( (($self->tag() >= 0) && ($self->tag() <= 31))  ? $Dcmp1::Chunk::TAG_KIND_LITERAL : ( (($self->tag() >= 32) && ($self->tag() <= 207))  ? $Dcmp1::Chunk::TAG_KIND_BACKREFERENCE : ( (($self->tag() >= 208) && ($self->tag() <= 209))  ? $Dcmp1::Chunk::TAG_KIND_LITERAL : ($self->tag() == 210 ? $Dcmp1::Chunk::TAG_KIND_BACKREFERENCE : ( (($self->tag() >= 213) && ($self->tag() <= 253))  ? $Dcmp1::Chunk::TAG_KIND_TABLE_LOOKUP : ($self->tag() == 254 ? $Dcmp1::Chunk::TAG_KIND_EXTENDED : ($self->tag() == 255 ? $Dcmp1::Chunk::TAG_KIND_END : $Dcmp1::Chunk::TAG_KIND_INVALID)))))));
    if ($_on == $Dcmp1::Chunk::TAG_KIND_EXTENDED) {
        $self->{body} = Dcmp1::Chunk::ExtendedBody->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == $Dcmp1::Chunk::TAG_KIND_LITERAL) {
        $self->{body} = Dcmp1::Chunk::LiteralBody->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == $Dcmp1::Chunk::TAG_KIND_END) {
        $self->{body} = Dcmp1::Chunk::EndBody->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == $Dcmp1::Chunk::TAG_KIND_TABLE_LOOKUP) {
        $self->{body} = Dcmp1::Chunk::TableLookupBody->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == $Dcmp1::Chunk::TAG_KIND_BACKREFERENCE) {
        $self->{body} = Dcmp1::Chunk::BackreferenceBody->new($self->{_io}, $self, $self->{_root});
    }
}

sub tag {
    my ($self) = @_;
    return $self->{tag};
}

sub body {
    my ($self) = @_;
    return $self->{body};
}

########################################################################
package Dcmp1::Chunk::LiteralBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    if ($self->is_len_literal_separate()) {
        $self->{len_literal_separate} = $self->{_io}->read_u1();
    }
    $self->{literal} = $self->{_io}->read_bytes($self->len_literal());
}

sub do_store {
    my ($self) = @_;
    return $self->{do_store} if ($self->{do_store});
    $self->{do_store} = ($self->is_len_literal_separate() ? $self->tag() == 209 : ($self->tag() & 16) != 0);
    return $self->{do_store};
}

sub len_literal_m1_in_tag {
    my ($self) = @_;
    return $self->{len_literal_m1_in_tag} if ($self->{len_literal_m1_in_tag});
    if (!($self->is_len_literal_separate())) {
        $self->{len_literal_m1_in_tag} = ($self->tag() & 15);
    }
    return $self->{len_literal_m1_in_tag};
}

sub is_len_literal_separate {
    my ($self) = @_;
    return $self->{is_len_literal_separate} if ($self->{is_len_literal_separate});
    $self->{is_len_literal_separate} = $self->tag() >= 208;
    return $self->{is_len_literal_separate};
}

sub len_literal {
    my ($self) = @_;
    return $self->{len_literal} if ($self->{len_literal});
    $self->{len_literal} = ($self->is_len_literal_separate() ? $self->len_literal_separate() : ($self->len_literal_m1_in_tag() + 1));
    return $self->{len_literal};
}

sub len_literal_separate {
    my ($self) = @_;
    return $self->{len_literal_separate};
}

sub literal {
    my ($self) = @_;
    return $self->{literal};
}

sub tag {
    my ($self) = @_;
    return $self->{tag};
}

########################################################################
package Dcmp1::Chunk::BackreferenceBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    if ($self->is_index_separate()) {
        $self->{index_separate_minus} = $self->{_io}->read_u1();
    }
}

sub is_index_separate {
    my ($self) = @_;
    return $self->{is_index_separate} if ($self->{is_index_separate});
    $self->{is_index_separate} = $self->tag() == 210;
    return $self->{is_index_separate};
}

sub index_in_tag {
    my ($self) = @_;
    return $self->{index_in_tag} if ($self->{index_in_tag});
    $self->{index_in_tag} = ($self->tag() - 32);
    return $self->{index_in_tag};
}

sub index_separate {
    my ($self) = @_;
    return $self->{index_separate} if ($self->{index_separate});
    if ($self->is_index_separate()) {
        $self->{index_separate} = ($self->index_separate_minus() + 176);
    }
    return $self->{index_separate};
}

sub index {
    my ($self) = @_;
    return $self->{index} if ($self->{index});
    $self->{index} = ($self->is_index_separate() ? $self->index_separate() : $self->index_in_tag());
    return $self->{index};
}

sub index_separate_minus {
    my ($self) = @_;
    return $self->{index_separate_minus};
}

sub tag {
    my ($self) = @_;
    return $self->{tag};
}

########################################################################
package Dcmp1::Chunk::TableLookupBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

}

sub lookup_table {
    my ($self) = @_;
    return $self->{lookup_table} if ($self->{lookup_table});
    $self->{lookup_table} = [pack('C*', (0, 0)), pack('C*', (0, 1)), pack('C*', (0, 2)), pack('C*', (0, 3)), pack('C*', (46, 1)), pack('C*', (62, 1)), pack('C*', (1, 1)), pack('C*', (30, 1)), pack('C*', (255, 255)), pack('C*', (14, 1)), pack('C*', (49, 0)), pack('C*', (17, 18)), pack('C*', (1, 7)), pack('C*', (51, 50)), pack('C*', (18, 57)), pack('C*', (237, 16)), pack('C*', (1, 39)), pack('C*', (35, 34)), pack('C*', (1, 55)), pack('C*', (7, 6)), pack('C*', (1, 23)), pack('C*', (1, 35)), pack('C*', (0, 255)), pack('C*', (0, 47)), pack('C*', (7, 14)), pack('C*', (253, 60)), pack('C*', (1, 53)), pack('C*', (1, 21)), pack('C*', (1, 2)), pack('C*', (0, 7)), pack('C*', (0, 62)), pack('C*', (5, 213)), pack('C*', (2, 1)), pack('C*', (6, 7)), pack('C*', (7, 8)), pack('C*', (48, 1)), pack('C*', (1, 51)), pack('C*', (0, 16)), pack('C*', (23, 22)), pack('C*', (55, 62)), pack('C*', (54, 55))];
    return $self->{lookup_table};
}

sub value {
    my ($self) = @_;
    return $self->{value} if ($self->{value});
    $self->{value} = @{$self->lookup_table()}[($self->tag() - 213)];
    return $self->{value};
}

sub tag {
    my ($self) = @_;
    return $self->{tag};
}

########################################################################
package Dcmp1::Chunk::EndBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

}

########################################################################
package Dcmp1::Chunk::ExtendedBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{tag} = $self->{_io}->read_u1();
    my $_on = $self->tag();
    if ($_on == 2) {
        $self->{body} = Dcmp1::Chunk::ExtendedBody::RepeatBody->new($self->{_io}, $self, $self->{_root});
    }
}

sub tag {
    my ($self) = @_;
    return $self->{tag};
}

sub body {
    my ($self) = @_;
    return $self->{body};
}

########################################################################
package Dcmp1::Chunk::ExtendedBody::RepeatBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{to_repeat_raw} = DcmpVariableLengthInteger->new($self->{_io});
    $self->{repeat_count_m1_raw} = DcmpVariableLengthInteger->new($self->{_io});
}

sub to_repeat {
    my ($self) = @_;
    return $self->{to_repeat} if ($self->{to_repeat});
    $self->{to_repeat} = $self->to_repeat_raw()->value();
    return $self->{to_repeat};
}

sub repeat_count_m1 {
    my ($self) = @_;
    return $self->{repeat_count_m1} if ($self->{repeat_count_m1});
    $self->{repeat_count_m1} = $self->repeat_count_m1_raw()->value();
    return $self->{repeat_count_m1};
}

sub repeat_count {
    my ($self) = @_;
    return $self->{repeat_count} if ($self->{repeat_count});
    $self->{repeat_count} = ($self->repeat_count_m1() + 1);
    return $self->{repeat_count};
}

sub to_repeat_raw {
    my ($self) = @_;
    return $self->{to_repeat_raw};
}

sub repeat_count_m1_raw {
    my ($self) = @_;
    return $self->{repeat_count_m1_raw};
}

1;