2010-12-31 04:41:07 +06:00
#!/usr/bin/env python
class Unbuffered :
def __init__ ( self , stream ) :
self . stream = stream
def write ( self , data ) :
self . stream . write ( data )
self . stream . flush ( )
def __getattr__ ( self , attr ) :
return getattr ( self . stream , attr )
import sys
2011-06-16 11:59:20 +06:00
if ' calibre ' in sys . modules :
inCalibre = True
else :
inCalibre = False
2012-05-16 22:15:43 +06:00
buildXML = False
2010-12-31 04:41:07 +06:00
import os , csv , getopt
import zlib , zipfile , tempfile , shutil
from struct import pack
from struct import unpack
2012-03-07 00:24:28 +06:00
from alfcrypto import Topaz_Cipher
2010-12-31 04:41:07 +06:00
class TpzDRMError ( Exception ) :
pass
2011-06-16 11:59:20 +06:00
2012-03-07 00:24:28 +06:00
2010-12-31 04:41:07 +06:00
# local support routines
2011-06-16 11:59:20 +06:00
if inCalibre :
from calibre_plugins . k4mobidedrm import kgenpids
else :
import kgenpids
# recursive zip creation support routine
def zipUpDir ( myzip , tdir , localname ) :
currentdir = tdir
if localname != " " :
currentdir = os . path . join ( currentdir , localname )
list = os . listdir ( currentdir )
for file in list :
afilename = file
localfilePath = os . path . join ( localname , afilename )
realfilePath = os . path . join ( currentdir , file )
if os . path . isfile ( realfilePath ) :
myzip . write ( realfilePath , localfilePath )
elif os . path . isdir ( realfilePath ) :
zipUpDir ( myzip , tdir , localfilePath )
2010-12-31 04:41:07 +06:00
#
# Utility routines
#
# Get a 7 bit encoded number from file
def bookReadEncodedNumber ( fo ) :
flag = False
data = ord ( fo . read ( 1 ) )
if data == 0xFF :
2012-03-07 00:24:28 +06:00
flag = True
data = ord ( fo . read ( 1 ) )
2010-12-31 04:41:07 +06:00
if data > = 0x80 :
datax = ( data & 0x7F )
while data > = 0x80 :
data = ord ( fo . read ( 1 ) )
datax = ( datax << 7 ) + ( data & 0x7F )
2012-03-07 00:24:28 +06:00
data = datax
2010-12-31 04:41:07 +06:00
if flag :
2012-03-07 00:24:28 +06:00
data = - data
2010-12-31 04:41:07 +06:00
return data
2012-03-07 00:24:28 +06:00
# Get a length prefixed string from file
2010-12-31 04:41:07 +06:00
def bookReadString ( fo ) :
stringLength = bookReadEncodedNumber ( fo )
2012-03-07 00:24:28 +06:00
return unpack ( str ( stringLength ) + " s " , fo . read ( stringLength ) ) [ 0 ]
2010-12-31 04:41:07 +06:00
#
# crypto routines
#
# Context initialisation for the Topaz Crypto
def topazCryptoInit ( key ) :
2012-03-07 00:24:28 +06:00
return Topaz_Cipher ( ) . ctx_init ( key )
# ctx1 = 0x0CAFFE19E
# for keyChar in key:
# keyByte = ord(keyChar)
# ctx2 = ctx1
# ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
# return [ctx1,ctx2]
2010-12-31 04:41:07 +06:00
# decrypt data with the context prepared by topazCryptoInit()
def topazCryptoDecrypt ( data , ctx ) :
2012-03-07 00:24:28 +06:00
return Topaz_Cipher ( ) . decrypt ( data , ctx )
# ctx1 = ctx[0]
# ctx2 = ctx[1]
# plainText = ""
# for dataChar in data:
# dataByte = ord(dataChar)
# m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
# ctx2 = ctx1
# ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
# plainText += chr(m)
# return plainText
2010-12-31 04:41:07 +06:00
# Decrypt data with the PID
def decryptRecord ( data , PID ) :
ctx = topazCryptoInit ( PID )
return topazCryptoDecrypt ( data , ctx )
# Try to decrypt a dkey record (contains the bookPID)
def decryptDkeyRecord ( data , PID ) :
record = decryptRecord ( data , PID )
fields = unpack ( " 3sB8sB8s3s " , record )
if fields [ 0 ] != " PID " or fields [ 5 ] != " pid " :
raise TpzDRMError ( " Didn ' t find PID magic numbers in record " )
elif fields [ 1 ] != 8 or fields [ 3 ] != 8 :
raise TpzDRMError ( " Record didn ' t contain correct length fields " )
elif fields [ 2 ] != PID :
raise TpzDRMError ( " Record didn ' t contain PID " )
return fields [ 4 ]
# Decrypt all dkey records (contain the book PID)
def decryptDkeyRecords ( data , PID ) :
nbKeyRecords = ord ( data [ 0 ] )
records = [ ]
data = data [ 1 : ]
for i in range ( 0 , nbKeyRecords ) :
length = ord ( data [ 0 ] )
try :
key = decryptDkeyRecord ( data [ 1 : length + 1 ] , PID )
records . append ( key )
except TpzDRMError :
pass
data = data [ 1 + length : ]
if len ( records ) == 0 :
raise TpzDRMError ( " BookKey Not Found " )
return records
class TopazBook :
2011-06-16 11:59:20 +06:00
def __init__ ( self , filename ) :
2010-12-31 04:41:07 +06:00
self . fo = file ( filename , ' rb ' )
2011-06-16 11:59:20 +06:00
self . outdir = tempfile . mkdtemp ( )
2011-10-28 12:24:15 +06:00
# self.outdir = 'rawdat'
2010-12-31 04:41:07 +06:00
self . bookPayloadOffset = 0
self . bookHeaderRecords = { }
self . bookMetadata = { }
self . bookKey = None
magic = unpack ( " 4s " , self . fo . read ( 4 ) ) [ 0 ]
if magic != ' TPZ0 ' :
raise TpzDRMError ( " Parse Error : Invalid Header, not a Topaz file " )
self . parseTopazHeaders ( )
self . parseMetadata ( )
def parseTopazHeaders ( self ) :
def bookReadHeaderRecordData ( ) :
2012-03-07 00:24:28 +06:00
# Read and return the data of one header record at the current book file position
2010-12-31 04:41:07 +06:00
# [[offset,decompressedLength,compressedLength],...]
nbValues = bookReadEncodedNumber ( self . fo )
values = [ ]
for i in range ( 0 , nbValues ) :
values . append ( [ bookReadEncodedNumber ( self . fo ) , bookReadEncodedNumber ( self . fo ) , bookReadEncodedNumber ( self . fo ) ] )
return values
def parseTopazHeaderRecord ( ) :
# Read and parse one header record at the current book file position and return the associated data
# [[offset,decompressedLength,compressedLength],...]
if ord ( self . fo . read ( 1 ) ) != 0x63 :
raise TpzDRMError ( " Parse Error : Invalid Header " )
tag = bookReadString ( self . fo )
record = bookReadHeaderRecordData ( )
return [ tag , record ]
nbRecords = bookReadEncodedNumber ( self . fo )
for i in range ( 0 , nbRecords ) :
result = parseTopazHeaderRecord ( )
# print result[0], result[1]
self . bookHeaderRecords [ result [ 0 ] ] = result [ 1 ]
if ord ( self . fo . read ( 1 ) ) != 0x64 :
raise TpzDRMError ( " Parse Error : Invalid Header " )
self . bookPayloadOffset = self . fo . tell ( )
def parseMetadata ( self ) :
# Parse the metadata record from the book payload and return a list of [key,values]
self . fo . seek ( self . bookPayloadOffset + self . bookHeaderRecords [ " metadata " ] [ 0 ] [ 0 ] )
tag = bookReadString ( self . fo )
if tag != " metadata " :
raise TpzDRMError ( " Parse Error : Record Names Don ' t Match " )
flags = ord ( self . fo . read ( 1 ) )
nbRecords = ord ( self . fo . read ( 1 ) )
2011-02-18 17:37:27 +06:00
# print nbRecords
2010-12-31 04:41:07 +06:00
for i in range ( 0 , nbRecords ) :
2011-02-18 17:37:27 +06:00
keyval = bookReadString ( self . fo )
content = bookReadString ( self . fo )
# print keyval
# print content
self . bookMetadata [ keyval ] = content
2010-12-31 04:41:07 +06:00
return self . bookMetadata
def getPIDMetaInfo ( self ) :
2011-02-18 17:37:27 +06:00
keysRecord = self . bookMetadata . get ( ' keys ' , ' ' )
keysRecordRecord = ' '
if keysRecord != ' ' :
keylst = keysRecord . split ( ' , ' )
for keyval in keylst :
keysRecordRecord + = self . bookMetadata . get ( keyval , ' ' )
2010-12-31 04:41:07 +06:00
return keysRecord , keysRecordRecord
def getBookTitle ( self ) :
title = ' '
if ' Title ' in self . bookMetadata :
title = self . bookMetadata [ ' Title ' ]
return title
def setBookKey ( self , key ) :
self . bookKey = key
def getBookPayloadRecord ( self , name , index ) :
2012-03-07 00:24:28 +06:00
# Get a record in the book payload, given its name and index.
# decrypted and decompressed if necessary
2010-12-31 04:41:07 +06:00
encrypted = False
compressed = False
2012-03-07 00:24:28 +06:00
try :
2010-12-31 04:41:07 +06:00
recordOffset = self . bookHeaderRecords [ name ] [ index ] [ 0 ]
except :
raise TpzDRMError ( " Parse Error : Invalid Record, record not found " )
self . fo . seek ( self . bookPayloadOffset + recordOffset )
tag = bookReadString ( self . fo )
if tag != name :
raise TpzDRMError ( " Parse Error : Invalid Record, record name doesn ' t match " )
recordIndex = bookReadEncodedNumber ( self . fo )
if recordIndex < 0 :
encrypted = True
recordIndex = - recordIndex - 1
if recordIndex != index :
raise TpzDRMError ( " Parse Error : Invalid Record, index doesn ' t match " )
if ( self . bookHeaderRecords [ name ] [ index ] [ 2 ] > 0 ) :
compressed = True
record = self . fo . read ( self . bookHeaderRecords [ name ] [ index ] [ 2 ] )
else :
record = self . fo . read ( self . bookHeaderRecords [ name ] [ index ] [ 1 ] )
if encrypted :
if self . bookKey :
ctx = topazCryptoInit ( self . bookKey )
record = topazCryptoDecrypt ( record , ctx )
else :
raise TpzDRMError ( " Error: Attempt to decrypt without bookKey " )
if compressed :
record = zlib . decompress ( record )
return record
def processBook ( self , pidlst ) :
raw = 0
fixedimage = True
try :
keydata = self . getBookPayloadRecord ( ' dkey ' , 0 )
except TpzDRMError , e :
print " no dkey record found, book may not be encrypted "
print " attempting to extrct files without a book key "
self . createBookDirectory ( )
self . extractFiles ( )
print " Successfully Extracted Topaz contents "
2012-09-09 06:45:24 +06:00
if inCalibre :
from calibre_plugins . k4mobidedrm import genbook
else :
import genbook
2012-11-20 19:28:12 +06:00
2010-12-31 04:41:07 +06:00
rv = genbook . generateBook ( self . outdir , raw , fixedimage )
if rv == 0 :
print " \n Book Successfully generated "
2012-03-07 00:24:28 +06:00
return rv
2010-12-31 04:41:07 +06:00
# try each pid to decode the file
bookKey = None
for pid in pidlst :
# use 8 digit pids here
pid = pid [ 0 : 8 ]
print " \n Trying: " , pid
bookKeys = [ ]
data = keydata
try :
bookKeys + = decryptDkeyRecords ( data , pid )
except TpzDRMError , e :
pass
else :
bookKey = bookKeys [ 0 ]
print " Book Key Found! "
break
if not bookKey :
2012-11-20 19:28:12 +06:00
raise TpzDRMError ( " Topaz Book. No key found in " + str ( len ( pidlst ) ) + " keys tried. Read the FAQs at Alf ' s blog. Only if none apply, report this failure for help. " )
2010-12-31 04:41:07 +06:00
self . setBookKey ( bookKey )
self . createBookDirectory ( )
self . extractFiles ( )
print " Successfully Extracted Topaz contents "
2012-09-09 06:45:24 +06:00
if inCalibre :
from calibre_plugins . k4mobidedrm import genbook
else :
import genbook
2012-11-20 19:28:12 +06:00
2010-12-31 04:41:07 +06:00
rv = genbook . generateBook ( self . outdir , raw , fixedimage )
if rv == 0 :
print " \n Book Successfully generated "
2012-03-07 00:24:28 +06:00
return rv
2010-12-31 04:41:07 +06:00
def createBookDirectory ( self ) :
outdir = self . outdir
# create output directory structure
if not os . path . exists ( outdir ) :
os . makedirs ( outdir )
destdir = os . path . join ( outdir , ' img ' )
if not os . path . exists ( destdir ) :
os . makedirs ( destdir )
destdir = os . path . join ( outdir , ' color_img ' )
if not os . path . exists ( destdir ) :
os . makedirs ( destdir )
destdir = os . path . join ( outdir , ' page ' )
if not os . path . exists ( destdir ) :
os . makedirs ( destdir )
destdir = os . path . join ( outdir , ' glyphs ' )
if not os . path . exists ( destdir ) :
os . makedirs ( destdir )
def extractFiles ( self ) :
outdir = self . outdir
for headerRecord in self . bookHeaderRecords :
name = headerRecord
if name != " dkey " :
ext = ' .dat '
if name == ' img ' : ext = ' .jpg '
if name == ' color ' : ext = ' .jpg '
print " \n Processing Section: %s " % name
for index in range ( 0 , len ( self . bookHeaderRecords [ name ] ) ) :
fnum = " %04d " % index
fname = name + fnum + ext
destdir = outdir
if name == ' img ' :
destdir = os . path . join ( outdir , ' img ' )
if name == ' color ' :
destdir = os . path . join ( outdir , ' color_img ' )
if name == ' page ' :
destdir = os . path . join ( outdir , ' page ' )
if name == ' glyphs ' :
destdir = os . path . join ( outdir , ' glyphs ' )
outputFile = os . path . join ( destdir , fname )
print " . " ,
record = self . getBookPayloadRecord ( name , index )
if record != ' ' :
file ( outputFile , ' wb ' ) . write ( record )
print " "
2011-06-16 11:59:20 +06:00
def getHTMLZip ( self , zipname ) :
htmlzip = zipfile . ZipFile ( zipname , ' w ' , zipfile . ZIP_DEFLATED , False )
htmlzip . write ( os . path . join ( self . outdir , ' book.html ' ) , ' book.html ' )
htmlzip . write ( os . path . join ( self . outdir , ' book.opf ' ) , ' book.opf ' )
if os . path . isfile ( os . path . join ( self . outdir , ' cover.jpg ' ) ) :
htmlzip . write ( os . path . join ( self . outdir , ' cover.jpg ' ) , ' cover.jpg ' )
htmlzip . write ( os . path . join ( self . outdir , ' style.css ' ) , ' style.css ' )
zipUpDir ( htmlzip , self . outdir , ' img ' )
htmlzip . close ( )
def getSVGZip ( self , zipname ) :
svgzip = zipfile . ZipFile ( zipname , ' w ' , zipfile . ZIP_DEFLATED , False )
svgzip . write ( os . path . join ( self . outdir , ' index_svg.xhtml ' ) , ' index_svg.xhtml ' )
zipUpDir ( svgzip , self . outdir , ' svg ' )
zipUpDir ( svgzip , self . outdir , ' img ' )
svgzip . close ( )
2012-11-20 19:28:12 +06:00
2011-06-16 11:59:20 +06:00
def getXMLZip ( self , zipname ) :
xmlzip = zipfile . ZipFile ( zipname , ' w ' , zipfile . ZIP_DEFLATED , False )
targetdir = os . path . join ( self . outdir , ' xml ' )
zipUpDir ( xmlzip , targetdir , ' ' )
zipUpDir ( xmlzip , self . outdir , ' img ' )
xmlzip . close ( )
def cleanup ( self ) :
if os . path . isdir ( self . outdir ) :
2012-05-16 22:15:43 +06:00
shutil . rmtree ( self . outdir , True )
2010-12-31 04:41:07 +06:00
def usage ( progname ) :
print " Removes DRM protection from Topaz ebooks and extract the contents "
print " Usage: "
print " %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir> " % progname
2012-03-07 00:24:28 +06:00
2010-12-31 04:41:07 +06:00
# Main
def main ( argv = sys . argv ) :
2012-05-16 22:15:43 +06:00
global buildXML
2010-12-31 04:41:07 +06:00
progname = os . path . basename ( argv [ 0 ] )
k4 = False
pids = [ ]
serials = [ ]
kInfoFiles = [ ]
2012-03-07 00:24:28 +06:00
2010-12-31 04:41:07 +06:00
try :
opts , args = getopt . getopt ( sys . argv [ 1 : ] , " k:p:s: " )
except getopt . GetoptError , err :
print str ( err )
usage ( progname )
return 1
if len ( args ) < 2 :
usage ( progname )
return 1
2012-03-07 00:24:28 +06:00
2010-12-31 04:41:07 +06:00
for o , a in opts :
if o == " -k " :
if a == None :
print " Invalid parameter for -k "
return 1
kInfoFiles . append ( a )
if o == " -p " :
if a == None :
print " Invalid parameter for -p "
return 1
pids = a . split ( ' , ' )
if o == " -s " :
if a == None :
print " Invalid parameter for -s "
return 1
serials = a . split ( ' , ' )
k4 = True
infile = args [ 0 ]
outdir = args [ 1 ]
if not os . path . isfile ( infile ) :
print " Input File Does Not Exist "
return 1
bookname = os . path . splitext ( os . path . basename ( infile ) ) [ 0 ]
2011-06-16 11:59:20 +06:00
tb = TopazBook ( infile )
2010-12-31 04:41:07 +06:00
title = tb . getBookTitle ( )
print " Processing Book: " , title
keysRecord , keysRecordRecord = tb . getPIDMetaInfo ( )
2012-11-20 19:28:12 +06:00
pids . extend ( kgenpids . getPidList ( keysRecord , keysRecordRecord , k4 , serials , kInfoFiles ) )
2010-12-31 04:41:07 +06:00
try :
2011-06-16 11:59:20 +06:00
print " Decrypting Book "
2012-11-20 19:28:12 +06:00
tb . processBook ( pids )
2011-06-16 11:59:20 +06:00
print " Creating HTML ZIP Archive "
zipname = os . path . join ( outdir , bookname + ' _nodrm ' + ' .htmlz ' )
tb . getHTMLZip ( zipname )
print " Creating SVG ZIP Archive "
2011-10-28 12:24:15 +06:00
zipname = os . path . join ( outdir , bookname + ' _SVG ' + ' .zip ' )
2011-06-16 11:59:20 +06:00
tb . getSVGZip ( zipname )
2012-05-16 22:15:43 +06:00
if buildXML :
print " Creating XML ZIP Archive "
zipname = os . path . join ( outdir , bookname + ' _XML ' + ' .zip ' )
tb . getXMLZip ( zipname )
2011-06-16 11:59:20 +06:00
# removing internal temporary directory of pieces
tb . cleanup ( )
2010-12-31 04:41:07 +06:00
except TpzDRMError , e :
print str ( e )
2011-10-28 12:24:15 +06:00
# tb.cleanup()
2010-12-31 04:41:07 +06:00
return 1
2011-06-16 11:59:20 +06:00
except Exception , e :
print str ( e )
2011-10-28 12:24:15 +06:00
# tb.cleanup
2011-06-16 11:59:20 +06:00
return 1
2010-12-31 04:41:07 +06:00
return 0
2012-03-07 00:24:28 +06:00
2010-12-31 04:41:07 +06:00
if __name__ == ' __main__ ' :
2011-06-16 11:59:20 +06:00
sys . stdout = Unbuffered ( sys . stdout )
2010-12-31 04:41:07 +06:00
sys . exit ( main ( ) )