1#!/bin/bash 2# 3############################################################################# 4# 5# 7z2lzma.bash is very primitive .7z to .lzma converter. The input file must 6# have exactly one LZMA compressed stream, which has been created with the 7# default lc, lp, and pb values. The CRC32 in the .7z archive is not checked, 8# and the script may seem to succeed while it actually created a corrupt .lzma 9# file. You should always try uncompressing both the original .7z and the 10# created .lzma and compare that the output is identical. 11# 12# This script requires basic GNU tools and 7z or 7za tool from p7zip. 13# 14# Last modified: 2009-01-15 14:25+0200 15# 16############################################################################# 17# 18# Author: Lasse Collin <lasse.collin@tukaani.org> 19# 20# This file has been put into the public domain. 21# You can do whatever you want with this file. 22# 23############################################################################# 24 25# You can use 7z or 7za, both will work. 26SEVENZIP=7za 27 28if [ $# != 2 -o -z "$1" -o -z "$2" ]; then 29 echo "Usage: $0 input.7z output.lzma" 30 exit 1 31fi 32 33# Converts an integer variable to little endian binary integer. 34int2bin() 35{ 36 local LEN=$1 37 local NUM=$2 38 local HEX=(0 1 2 3 4 5 6 7 8 9 A B C D E F) 39 local I 40 for ((I=0; I < "$LEN"; ++I)); do 41 printf "\\x${HEX[(NUM >> 4) & 0x0F]}${HEX[NUM & 0x0F]}" 42 NUM=$((NUM >> 8)) 43 done 44} 45 46# Make sure we get possible errors from pipes. 47set -o pipefail 48 49# Get information about the input file. At least older 7z and 7za versions 50# may return with zero exit status even when an error occurred, so check 51# if the output has any lines beginning with "Error". 52INFO=$("$SEVENZIP" l -slt "$1") 53if [ $? != 0 ] || printf '%s\n' "$INFO" | grep -q ^Error; then 54 printf '%s\n' "$INFO" 55 exit 1 56fi 57 58# Check if the input file has more than one compressed block. 59if printf '%s\n' "$INFO" | grep -q '^Block = 1'; then 60 echo "Cannot convert, because the input file has more than" 61 echo "one compressed block." 62 exit 1 63fi 64 65# Get compressed, uncompressed, and dictionary size. 66CSIZE=$(printf '%s\n' "$INFO" | sed -rn 's|^Packed Size = ([0-9]+$)|\1|p') 67USIZE=$(printf '%s\n' "$INFO" | sed -rn 's|^Size = ([0-9]+$)|\1|p') 68DICT=$(printf '%s\n' "$INFO" | sed -rn 's|^Method = LZMA:([0-9]+[bkm]?)$|\1|p') 69 70if [ -z "$CSIZE" -o -z "$USIZE" -o -z "$DICT" ]; then 71 echo "Parsing output of $SEVENZIP failed. Maybe the file uses some" 72 echo "other compression method than plain LZMA." 73 exit 1 74fi 75 76# The following assumes that the default lc, lp, and pb settings were used. 77# Otherwise the output will be corrupt. 78printf '\x5D' > "$2" 79 80# Dictionary size can be either was power of two, bytes, kibibytes, or 81# mebibytes. We need to convert it to bytes. 82case $DICT in 83 *b) 84 DICT=${DICT%b} 85 ;; 86 *k) 87 DICT=${DICT%k} 88 DICT=$((DICT << 10)) 89 ;; 90 *m) 91 DICT=${DICT%m} 92 DICT=$((DICT << 20)) 93 ;; 94 *) 95 DICT=$((1 << DICT)) 96 ;; 97esac 98int2bin 4 "$DICT" >> "$2" 99 100# Uncompressed size 101int2bin 8 "$USIZE" >> "$2" 102 103# Copy the actual compressed data. Using multiple dd commands to avoid 104# copying large amount of data with one-byte block size, which would be 105# annoyingly slow. 106BS=8192 107BIGSIZE=$((CSIZE / BS)) 108CSIZE=$((CSIZE % BS)) 109{ 110 dd of=/dev/null bs=32 count=1 \ 111 && dd bs="$BS" count="$BIGSIZE" \ 112 && dd bs=1 count="$CSIZE" 113} < "$1" >> "$2" 114 115exit $? 116