bzip2_decompressor.pl 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #!/usr/bin/perl
  2. # Author: Trizen
  3. # Date: 19 August 2024
  4. # https://github.com/trizen
  5. # A very basic Bzip2 decompressor.
  6. # References:
  7. # BZIP2: Format Specification, by Joe Tsai
  8. # https://github.com/dsnet/compress/blob/master/doc/bzip2-format.pdf
  9. #
  10. # Pyflate, by Paul Sladen
  11. # http://www.paul.sladen.org/projects/pyflate/
  12. use 5.036;
  13. use lib qw(../lib);
  14. use List::Util qw(max);
  15. use Compression::Util qw(:all);
  16. my $s = "BZh91AY&SY\xEA\xE0\x8D\xEB\0\0\0\xC1\0\0\x100\0 \0!\x98\31\x84aw\$S\x85\t\16\xAE\b\xDE\xB0"; # "ab\n"
  17. $s .= "BZh91AY&SY\x99\xAC\"V\0\0\2W\x80\0\20`\4\0@\0\x80\6\4\x90\0 \0\"\6\x81\x90\x80i\xA6\x89\30j\xCE\xA4\31o\x8B\xB9\"\x9C(HL\xD6\21+\0"; # "Hello, World!\n"
  18. $s .=
  19. "BZh91AY&SY\xE9\xA6L\xBE\0\0\20\xC9\x80\n\20\2\xE0?\xFB\x8B0"
  20. . " \0\x89\fE2i\xA3&\x9A\3A)\xEA\"'\xA8h\3\xD4\xD3gxRZ\4\x8C\xDA'g,\x88\xD5\xA6"
  21. . "\x9C\xEA\xC4\30wWy\xE4\xD7\xC0\x95\xF9L\x89\5\x936'\xED\x95a\22o%B\x90\x93"
  22. . "T\xAF\xFD\xE6\xEA)\x8D\x90\x82\xB5\x9E\x89Z\xD7X\xB19\x9D0\xC9\21s\x9E\x95"
  23. . "\1\xB2F\xE9\x98\xFD\x8A+O\xAD\xBDi\x96s\e\0\4\xA3G\xC0\xB2\4\xA6_\x8B\xB9\"\x9C(Ht\xD3&_\0"; # some bigger string
  24. local $| = 1;
  25. binmode(STDIN, ":raw");
  26. binmode(STDOUT, ":raw");
  27. my $fh;
  28. if (-t STDIN) {
  29. open $fh, "<:raw", \$s;
  30. }
  31. else {
  32. $fh = \*STDIN;
  33. }
  34. while (!eof($fh)) {
  35. my $buffer = '';
  36. (bytes2int($fh, 2) == 0x425a and getc($fh) eq 'h')
  37. or die "Not a valid Bzip2 archive";
  38. my $level = getc($fh) + 0;
  39. if (not $level) {
  40. die "invalid level";
  41. }
  42. say STDERR "Compression level: $level";
  43. my $stream_crc32 = 0;
  44. while (!eof($fh)) {
  45. my $block_magic = pack "B48", join('', map { read_bit($fh, \$buffer) } 1 .. 48);
  46. if ($block_magic eq "1AY&SY") { # BlockHeader
  47. say STDERR "Block header detected";
  48. my $crc32 = bits2int($fh, 32, \$buffer);
  49. say STDERR "CRC32 = $crc32";
  50. my $randomized = read_bit($fh, \$buffer);
  51. $randomized == 0 or die "randomized not supported";
  52. my $bwt_idx = bits2int($fh, 24, \$buffer);
  53. say STDERR "BWT index: $bwt_idx";
  54. my @alphabet;
  55. my $l1 = bits2int($fh, 16, \$buffer);
  56. for my $i (0 .. 15) {
  57. if ($l1 & (0x8000 >> $i)) {
  58. my $l2 = bits2int($fh, 16, \$buffer);
  59. for my $j (0 .. 15) {
  60. if ($l2 & (0x8000 >> $j)) {
  61. push @alphabet, 16 * $i + $j;
  62. }
  63. }
  64. }
  65. }
  66. say STDERR "MTF alphabet: (@alphabet)";
  67. my $num_trees = bits2int($fh, 3, \$buffer);
  68. say STDERR "Number or trees: $num_trees";
  69. my $num_sels = bits2int($fh, 15, \$buffer);
  70. say STDERR "Number of selectors: $num_sels";
  71. my @idxs;
  72. for (1 .. $num_sels) {
  73. my $i = 0;
  74. while (read_bit($fh, \$buffer)) {
  75. $i += 1;
  76. ($i < $num_trees) or die "error";
  77. }
  78. push @idxs, $i;
  79. }
  80. my $sels = mtf_decode(\@idxs, [0 .. $num_trees - 1]);
  81. say STDERR "Selectors: (@$sels)";
  82. my $MaxHuffmanBits = 20;
  83. my $num_syms = scalar(@alphabet) + 2;
  84. my @trees;
  85. for (1 .. $num_trees) {
  86. my @clens;
  87. my $clen = bits2int($fh, 5, \$buffer);
  88. for (1 .. $num_syms) {
  89. while (1) {
  90. ($clen > 0 and $clen <= $MaxHuffmanBits)
  91. or warn "Invalid code length: $clen!\n";
  92. if (not read_bit($fh, \$buffer)) {
  93. last;
  94. }
  95. $clen -= read_bit($fh, \$buffer) ? 1 : -1;
  96. }
  97. push @clens, $clen;
  98. }
  99. push @trees, \@clens;
  100. say STDERR "Code lengths: (@clens)";
  101. }
  102. foreach my $tree (@trees) {
  103. my $maxLen = max(@$tree);
  104. my $sum = 1 << $maxLen;
  105. for my $clen (@$tree) {
  106. $sum -= (1 << $maxLen) >> $clen;
  107. }
  108. $sum == 0 or warn "incomplete tree detected: (@$tree)\n";
  109. }
  110. my @huffman_trees = map { (huffman_from_code_lengths($_))[1] } @trees;
  111. my $eob = @alphabet + 1;
  112. my @zrle;
  113. my $code = '';
  114. my $sel_idx = 0;
  115. my $tree = $huffman_trees[$sels->[$sel_idx]];
  116. my $decoded = 50;
  117. while (!eof($fh)) {
  118. $code .= read_bit($fh, \$buffer);
  119. if (length($code) > $MaxHuffmanBits) {
  120. die "[!] Something went wrong: length of LL code `$code` is > $MaxHuffmanBits.\n";
  121. }
  122. if (exists($tree->{$code})) {
  123. my $sym = $tree->{$code};
  124. if ($sym == $eob) { # end of block marker
  125. say STDERR "EOB detected: $sym";
  126. last;
  127. }
  128. push @zrle, $sym;
  129. $code = '';
  130. if (--$decoded <= 0) {
  131. if (++$sel_idx <= $#$sels) {
  132. $tree = $huffman_trees[$sels->[$sel_idx]];
  133. }
  134. else {
  135. die "No more selectors"; # should not happen
  136. }
  137. $decoded = 50;
  138. }
  139. }
  140. }
  141. ##say STDERR "ZRLE: (@zrle)";
  142. my @mtf = reverse @{zrle_decode([reverse @zrle])};
  143. ##say STDERR "MTF: (@mtf)";
  144. my $bwt = symbols2string mtf_decode(\@mtf, \@alphabet);
  145. ## say "BWT: ($bwt, $bwt_idx)";
  146. my $rle4 = string2symbols bwt_decode($bwt, $bwt_idx);
  147. my $data = rle4_decode($rle4);
  148. my $dec = symbols2string($data);
  149. my $new_crc32 = oct('0b' . int2bits_lsb(crc32(pack('b*', unpack('B*', $dec))), 32));
  150. say STDERR "Computed CRC32: $new_crc32";
  151. if ($crc32 != $new_crc32) {
  152. warn "CRC32 error: $crc32 (stored) != $new_crc32 (actual)\n";
  153. }
  154. $stream_crc32 = ($new_crc32 ^ (0xffffffff & ((0xffffffff & ($stream_crc32 << 1)) | (($stream_crc32 >> 31) & 0x1)))) & 0xffffffff;
  155. print $dec;
  156. }
  157. elsif ($block_magic eq "\27rE8P\x90") { # BlockFooter
  158. say STDERR "Block footer detected";
  159. my $stored_stream_crc32 = bits2int($fh, 32, \$buffer);
  160. say STDERR "Stream CRC32: $stored_stream_crc32";
  161. if ($stream_crc32 != $stored_stream_crc32) {
  162. warn "Stream CRC32 error: $stored_stream_crc32 (stored) != $stream_crc32 (actual)\n";
  163. }
  164. $buffer = '';
  165. last;
  166. }
  167. else {
  168. die "Unknown block magic: $block_magic";
  169. }
  170. }
  171. say STDERR "End of container";
  172. }
  173. say STDERR "End of input";