2021-12-31 00:51:42 +02:00
# StoneD (Stone Depacker) - unpack Unisoc SC6531 stone images (part of UniFlash)
# Created by Luxferre in 2021, released into public domain
import os
import sys
import struct
import lzma
2021-12-31 00:56:58 +02:00
from custlzma.frenchlzma import DecodeurLZMASPD # (for LZMA_SPD decompression)
2021-12-31 00:51:42 +02:00
# common utils
def readFile ( fname ):
f = open ( fname , 'rb' )
fdata = f . read ()
f . close ()
return fdata
def writeFile ( fname , fdata ):
outf = open ( fname , 'wb' )
outf . write ( fdata )
outf . close ()
# unpack part
CMP_NONE = 0
CMP_LZMA_SPRD = 1
CMP_LZMA = 2
def getCompType ( data ):
if ( data [ 0 ] == 0x5d or data [ 0 ] == 0x67 ) and data [ 1 ] == 0 :
return CMP_LZMA
elif data [ 0 ] == 0x5a and data [ 1 ] == 0 :
return CMP_LZMA_SPRD
else :
return CMP_NONE
def getTblOffset ( blocksOffTbl , index ):
tind = index << 2
return struct . unpack ( '<L' , blocksOffTbl [ tind : tind + 4 ])[ 0 ]
def unpack_block ( blkData , blkPacSize , targetFile ):
print ( 'Extracting %s ...' % targetFile )
blocksOffTbl = None
compData = blkData
npacHdr = blkData [: 16 ]
( npacHdrMagic , npacHdrFlags , compDataSize , lzmaBlocksAmount ) = struct . unpack ( '<LLLL' , npacHdr )
# npacHdrMagic must be CAPN if using offsets
if npacHdrMagic == 0x4E504143 :
blocksOffTbl = blkData [ compDataSize :]
compData = blkData [ getTblOffset ( blocksOffTbl , 0 ):]
else :
lzmaBlocksAmount = 1
cType = getCompType ( compData )
assert cType == CMP_LZMA or cType == CMP_LZMA_SPRD , 'Only LZMA compression type is implemented as of now'
print ( 'Found LZMA blocks: %d , decompressing...' % lzmaBlocksAmount )
dest = b ''
inSizePure = blkPacSize * 2
2021-12-31 00:56:58 +02:00
lzmaDec = DecodeurLZMASPD ()
2021-12-31 00:51:42 +02:00
for i in range ( lzmaBlocksAmount ):
if blocksOffTbl is not None :
dataOffset = getTblOffset ( blocksOffTbl , i )
else :
dataOffset = 0
compData = blkData [ dataOffset :]
lzData = compData [ 0 : inSizePure ]
if cType == CMP_LZMA_SPRD :
outdata = lzmaDec . decode ( lzData )
dest += outdata
else :
dest += lzma . decompress ( lzData , format = lzma . FORMAT_ALONE )
sys . stdout . write ( '.' )
sys . stdout . flush ()
writeFile ( targetFile , dest )
print ( ' \n %s decompressed!' % targetFile )
def unpack_section ( sectionData , targetDir ):
bzpFileHdr = sectionData [: 16 ]
( bzpFileHdrMagic , bzpType , blocksOffset , blocksAmount ) = struct . unpack ( '<LLLL' , bzpFileHdr )
# bzpFileHdrMagic must be DRPS or RRPS
assert bzpFileHdrMagic == 0x53505244 or bzpFileHdrMagic == 0x53505252 , 'Invalid BZP header: 0x %X ' % bzpFileHdrMagic
bzpSize = blocksOffset + blocksAmount * 20
for i in range ( blocksAmount ):
blkHdrStart = blocksOffset + i * 20
blkHdr = sectionData [ blkHdrStart : blkHdrStart + 20 ]
( blkHdrMagic , blkId , blkDataOffset , blkPackedSize , blkPacSize ) = struct . unpack ( '<LLLLL' , blkHdr )
# blkHdrMagic must be COLB
assert blkHdrMagic == 0x424C4F43 , 'Invalid BZP block header: 0x %X ' % blkHdrMagic
if bzpSize < blkDataOffset + blkPackedSize :
bzpSize = blkDataOffset + blkPackedSize
if blkId == 0x494D4147 : # GAMI -> kernel image
targetFile = targetDir + '/kern.bin'
elif blkId == 0x75736572 : # resu -> user image
targetFile = targetDir + '/user.bin'
elif blkId == 0x7253736F : # resources
targetFile = targetDir + '/rsrc.bin'
else :
targetFile = targetDir + ( '/blk_ %X .bin' % blkId )
unpack_block ( sectionData [ blkDataOffset :], blkPacSize , targetFile )
def unpack_stone ( fname , targetDir ):
fdata = readFile ( fname )
flen = len ( fdata )
assert flen >= 0x10 , 'Input file %s is too small' % fname
# check for security header
sectionOffset = 0
if fdata [ 0 : 15 ] == b 'SPRD-SECUREFLAG' :
sectionOffset = 1024
print ( 'Signed image detected, using section offset %d ' % sectionOffset )
# look for TRAPGAMI header
startPos = - 1
for i in range ( flen ):
if fdata [ i : i + 8 ] == b 'TRAPGAMI' :
startPos = i
break
assert startPos > 0 , 'No stone header found in %s ' % fname
print ( 'Stone header found at 0x %X ' % startPos )
psImageEnd = 0xffffffff # PS (protocol station) image is the first in the flash backup and not compressed
dfcStruct = fdata [ startPos + 8 : startPos + 120 ]
for i in range ( 0 , 112 , 4 ):
targetAddr = struct . unpack ( '<L' , dfcStruct [ i : i + 4 ])[ 0 ]
if targetAddr < 0xffffffff :
if targetAddr < psImageEnd :
psImageEnd = targetAddr
print ( 'Target section address found: 0x %X ' % targetAddr )
unpack_section ( fdata [ sectionOffset + targetAddr :], targetDir )
print ( 'Section 0x %X unpacked!' % targetAddr )
if psImageEnd > 0 :
psPath = targetDir + '/ps.bin'
writeFile ( psPath , fdata [: psImageEnd ])
print ( 'Protocol station image %s written!' % psPath )
# main code start
if __name__ == '__main__' : # main app start
from argparse import ArgumentParser
rootdir = os . path . dirname ( os . path . realpath ( __file__ ))
parser = ArgumentParser ( description = 'StoneD: an opensource Unisoc/Spreadtrum stone image unpacker' , epilog = '(c) Luxferre 2021 --- No rights reserved <https://unlicense.org>' )
parser . add_argument ( 'file' , help = 'Stone image file to unpack' )
parser . add_argument ( '-d' , '--directory' , default = None , help = 'Directory where component files will be written to (defaults to the same where the main stone file resides)' )
args = parser . parse_args ()
imgfile = args . file
imgdir = os . path . dirname ( os . path . realpath ( imgfile ))
if args . directory is not None :
imgdir = os . path . realpath ( args . directory )
print ( 'Unpacking %s to %s ' % ( imgfile , imgdir ))
unpack_stone ( imgfile , imgdir )