Compare commits

...

6 Commits
v3.3 ... v3.8

Author SHA1 Message Date
Apprentice Alf
529dd3f160 tools v3.8
version 2 - a minor change to one script.
2015-03-05 17:54:25 +00:00
Apprentice Alf
4163d5ccf4 tools v3.8 2015-03-05 17:48:25 +00:00
Apprentice Alf
867ac35b45 tools v3.7 2015-03-05 17:42:55 +00:00
Apprentice Alf
427137b0fe tools v3.6 2015-03-05 17:37:44 +00:00
Apprentice Alf
ac9cdb1e98 tools v3.5 2015-03-05 17:33:13 +00:00
Apprentice Alf
2bedd75005 tools v3.4 2015-03-05 17:22:23 +00:00
56 changed files with 795 additions and 1756 deletions

View File

@@ -192,6 +192,8 @@ class GParser(object):
argres[j] = int(argres[j]) argres[j] = int(argres[j])
return result return result
def getGlyphDim(self, gly): def getGlyphDim(self, gly):
if self.gdpi[gly] == 0:
return 0, 0
maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly] maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly]
maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly] maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly]
return maxh, maxw return maxh, maxw
@@ -320,6 +322,18 @@ def generateBook(bookDir, raw, fixedimage):
print 'Processing Meta Data and creating OPF' print 'Processing Meta Data and creating OPF'
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < >
title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;')
title = title.replace('<','&lt;')
title = title.replace('>','&gt;')
meta_array['Title'] = title
authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;')
meta_array['Authors'] = authors
xname = os.path.join(xmlDir, 'metadata.xml') xname = os.path.join(xmlDir, 'metadata.xml')
metastr = '' metastr = ''
for key in meta_array: for key in meta_array:
@@ -399,7 +413,9 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -416,7 +432,9 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -471,8 +489,11 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,8 +208,6 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
if rec209 != None and token != None:
# Compute book PID # Compute book PID
pidHash = SHA1(serialnum+rec209+token) pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -257,9 +239,16 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number # Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -1,26 +0,0 @@
Installing openssl on Windows 64-bit (Windows 2000 and higher)
Win64 OpenSSL v0.9.8o (8Mb)
http://www.slproweb.com/download/Win64OpenSSL-0_9_8o.exe
(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
Visual C++ 2008 Redistributables (x64) (1.7Mb)
http://www.microsoft.com/downloads/details.aspx?familyid=bd2a6171-e2d6-4230-b809-9a8d7548c1b6
Installing openssl on Windows 32-bit (Windows 2000 and higher)
Win32 OpenSSL v0.9.8o (8Mb)
http://www.slproweb.com/download/Win32OpenSSL-0_9_8o.exe
(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
Visual C++ 2008 Redistributables (1.7Mb)
http://www.microsoft.com/downloads/details.aspx?familyid=9B2DA534-3E03-4391-8A4D-074B9F2BC1BF
Other versions of OpenSSL (and versions for Windows older than Windows 2000) can be found on the following website.
Shining Light Productions
http://www.slproweb.com/products/Win32OpenSSL.html

View File

@@ -46,6 +46,8 @@
# - Incorporated SomeUpdates zipfix routine. # - Incorporated SomeUpdates zipfix routine.
# 0.1.2 - bug fix for non-ascii file names in encryption.xml # 0.1.2 - bug fix for non-ascii file names in encryption.xml
# 0.1.3 - Try PyCrypto on Windows first # 0.1.3 - Try PyCrypto on Windows first
# 0.1.4 - update zipfix to deal with mimetype not in correct place
# 0.1.5 - update zipfix to deal with completely missing mimetype files
""" """
Decrypt Barnes & Noble ADEPT encrypted EPUB books. Decrypt Barnes & Noble ADEPT encrypted EPUB books.
@@ -271,7 +273,7 @@ class IgnobleDeDRM(FileTypePlugin):
Credit given to I <3 Cabbages for the original stand-alone scripts.' Credit given to I <3 Cabbages for the original stand-alone scripts.'
supported_platforms = ['linux', 'osx', 'windows'] supported_platforms = ['linux', 'osx', 'windows']
author = 'DiapDealer' author = 'DiapDealer'
version = (0, 1, 4) version = (0, 1, 5)
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions. minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
file_types = set(['epub']) file_types = set(['epub'])
on_import = True on_import = True

View File

@@ -13,9 +13,20 @@ _FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28 _EXTRA_LEN_OFFSET = 28
_FILENAME_OFFSET = 30 _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024 _MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class fixZip: class fixZip:
def __init__(self, zinput, zoutput): def __init__(self, zinput, zoutput):
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r') self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w') self.outzip = zipfile.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file # open the input zip for reading only as a raw file
@@ -81,30 +92,15 @@ class fixZip:
# get the zipinfo for each member of the input archive # get the zipinfo for each member of the input archive
# and copy member over to output archive # and copy member over to output archive
# if problems exist with local vs central filename, fix them # if problems exist with local vs central filename, fix them
# also fix bad epub compression
# write mimetype file first, if present, and with no compression # if epub write mimetype file first, with no compression
for zinfo in self.inzip.infolist(): if self.ztype == 'epub':
if zinfo.filename == "mimetype": nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = zinfo self.outzip.writestr(nzinfo, _MIMETYPE)
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
nzinfo.date_time = zinfo.date_time
nzinfo.compress_type = zipfile.ZIP_STORED
nzinfo.flag_bits = 0
nzinfo.internal_attr = 0
nzinfo.extra = ""
self.outzip.writestr(nzinfo,data)
break
# write the rest of the files # write the rest of the files
for zinfo in self.inzip.infolist(): for zinfo in self.inzip.infolist():
if zinfo.filename != "mimetype": if zinfo.filename != "mimetype" or self.ztype == '.zip':
data = None data = None
nzinfo = zinfo nzinfo = zinfo
try: try:

Binary file not shown.

View File

@@ -47,7 +47,8 @@
# result of Calibre changing to python 2.7. # result of Calibre changing to python 2.7.
# 0.1.3 - bug fix for epubs with non-ascii chars in file names # 0.1.3 - bug fix for epubs with non-ascii chars in file names
# 0.1.4 - default to try PyCrypto first on Windows # 0.1.4 - default to try PyCrypto first on Windows
# 0.1.5 - update zipfix to handle out of position mimetypes
# 0.1.6 - update zipfix to handle completely missing mimetype files
""" """
Decrypt Adobe ADEPT-encrypted EPUB books. Decrypt Adobe ADEPT-encrypted EPUB books.
@@ -371,7 +372,7 @@ class IneptDeDRM(FileTypePlugin):
Credit given to I <3 Cabbages for the original stand-alone scripts.' Credit given to I <3 Cabbages for the original stand-alone scripts.'
supported_platforms = ['linux', 'osx', 'windows'] supported_platforms = ['linux', 'osx', 'windows']
author = 'DiapDealer' author = 'DiapDealer'
version = (0, 1, 5) version = (0, 1, 6)
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions. minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
file_types = set(['epub']) file_types = set(['epub'])
on_import = True on_import = True

View File

@@ -13,9 +13,20 @@ _FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28 _EXTRA_LEN_OFFSET = 28
_FILENAME_OFFSET = 30 _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024 _MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class fixZip: class fixZip:
def __init__(self, zinput, zoutput): def __init__(self, zinput, zoutput):
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r') self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w') self.outzip = zipfile.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file # open the input zip for reading only as a raw file
@@ -81,30 +92,15 @@ class fixZip:
# get the zipinfo for each member of the input archive # get the zipinfo for each member of the input archive
# and copy member over to output archive # and copy member over to output archive
# if problems exist with local vs central filename, fix them # if problems exist with local vs central filename, fix them
# also fix bad epub compression
# write mimetype file first, if present, and with no compression # if epub write mimetype file first, with no compression
for zinfo in self.inzip.infolist(): if self.ztype == 'epub':
if zinfo.filename == "mimetype": nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = zinfo self.outzip.writestr(nzinfo, _MIMETYPE)
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
nzinfo.date_time = zinfo.date_time
nzinfo.compress_type = zipfile.ZIP_STORED
nzinfo.flag_bits = 0
nzinfo.internal_attr = 0
nzinfo.extra = ""
self.outzip.writestr(nzinfo,data)
break
# write the rest of the files # write the rest of the files
for zinfo in self.inzip.infolist(): for zinfo in self.inzip.infolist():
if zinfo.filename != "mimetype": if zinfo.filename != "mimetype" or self.ztype == '.zip':
data = None data = None
nzinfo = zinfo nzinfo = zinfo
try: try:

Binary file not shown.

View File

@@ -48,6 +48,9 @@
# #
# Revision history: # Revision history:
# 0.1 - Initial release # 0.1 - Initial release
# 0.1.1 - back port ineptpdf 8.4.X support for increased number of encryption methods
# 0.1.2 - back port ineptpdf 8.4.X bug fixes
# 0.1.3 - add in fix for improper rejection of session bookkeys with len(bookkey) = length + 1
""" """
Decrypts Adobe ADEPT-encrypted PDF files. Decrypts Adobe ADEPT-encrypted PDF files.
@@ -1544,16 +1547,30 @@ class PDFDocument(object):
bookkey = bookkey[index:] bookkey = bookkey[index:]
ebx_V = int_value(param.get('V', 4)) ebx_V = int_value(param.get('V', 4))
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6)) ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
# added because of the booktype / decryption book session key error # added because of improper booktype / decryption book session key errors
if length > 0:
if len(bookkey) == length:
if ebx_V == 3: if ebx_V == 3:
V = 3 V = 3
elif ebx_V < 4 or ebx_type < 6: else:
V = 2
elif len(bookkey) == length + 1:
V = ord(bookkey[0]) V = ord(bookkey[0])
bookkey = bookkey[1:] bookkey = bookkey[1:]
else:
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
print "bookkey[0] is %d" % ord(bookkey[0])
raise ADEPTError('error decrypting book session key - mismatched length')
else:
# proper length unknown try with whatever you have
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
print "bookkey[0] is %d" % ord(bookkey[0])
if ebx_V == 3:
V = 3
else: else:
V = 2 V = 2
if length and len(bookkey) != length:
raise ADEPTError('error decrypting book session key')
self.decrypt_key = bookkey self.decrypt_key = bookkey
self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2 self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
self.decipher = self.decrypt_rc4 self.decipher = self.decrypt_rc4
@@ -2116,7 +2133,7 @@ class IneptPDFDeDRM(FileTypePlugin):
Credit given to I <3 Cabbages for the original stand-alone scripts.' Credit given to I <3 Cabbages for the original stand-alone scripts.'
supported_platforms = ['linux', 'osx', 'windows'] supported_platforms = ['linux', 'osx', 'windows']
author = 'DiapDealer' author = 'DiapDealer'
version = (0, 1, 2) version = (0, 1, 3)
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions. minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
file_types = set(['pdf']) file_types = set(['pdf'])
on_import = True on_import = True

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.2' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 2) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []

View File

@@ -168,9 +168,8 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None:
home = os.getenv('HOME') home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
@@ -178,17 +177,24 @@ def openKindleInfo(kInfoFile=None):
out1, out2 = p1.communicate() out1, out2 = p1.communicate()
reslst = out1.split('\n') reslst = out1.split('\n')
kinfopath = 'NONE' kinfopath = 'NONE'
found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
#
def openKindleInfo(kInfoFile=None):
if kInfoFile == None:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath): if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found') print('The kindle.info files has not been found.')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -46,8 +46,13 @@
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well # 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.26' __version__ = '0.29'
import sys import sys
@@ -175,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -190,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -207,19 +213,16 @@ class MobiBook:
pos = 12 pos = 12
for i in xrange(nitems): for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0" # print type, size, content, content.encode('hex')
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size pos += size
except: except:
self.meta_array = {} self.meta_array = {}
@@ -239,18 +242,18 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# Parse the 209 data to find the the exth record with the token data. # The 209 data comes in five byte groups. Interpret the last four bytes
# The last character of the 209 data points to the record with the token. # of each group as a big endian unsigned integer to get a key value
# Always 208 from my experience, but I'll leave the logic in case that changes. # if that key exists in the meta_array, append its contents to the token
for i in xrange(len(data)): for i in xrange(0,len(data),5):
if ord(data[i]) != 0: val, = struct.unpack('>I',data[i+1:i+5])
if self.meta_array[ord(data[i])] != None: sval = self.meta_array.get(val,'')
token = self.meta_array[ord(data[i])] token += sval
return rec209, token return rec209, token
def patch(self, off, new): def patch(self, off, new):

View File

@@ -24,7 +24,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>DeDRM 2.1, Copyright © 20102011 by Apprentice Alf and others.</string> <string>DeDRM 2.6, Written 20102011 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
@@ -34,7 +34,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.1</string> <string>2.6</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>dplt</string> <string>dplt</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
@@ -50,7 +50,7 @@
<key>name</key> <key>name</key>
<string>ScriptWindowState</string> <string>ScriptWindowState</string>
<key>positionOfDivider</key> <key>positionOfDivider</key>
<real>0.0</real> <real>0</real>
<key>savedFrame</key> <key>savedFrame</key>
<string>1578 27 862 788 1440 -150 1680 1050 </string> <string>1578 27 862 788 1440 -150 1680 1050 </string>
<key>selectedTabView</key> <key>selectedTabView</key>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

View File

@@ -192,6 +192,8 @@ class GParser(object):
argres[j] = int(argres[j]) argres[j] = int(argres[j])
return result return result
def getGlyphDim(self, gly): def getGlyphDim(self, gly):
if self.gdpi[gly] == 0:
return 0, 0
maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly] maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly]
maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly] maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly]
return maxh, maxw return maxh, maxw
@@ -320,6 +322,18 @@ def generateBook(bookDir, raw, fixedimage):
print 'Processing Meta Data and creating OPF' print 'Processing Meta Data and creating OPF'
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < >
title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;')
title = title.replace('<','&lt;')
title = title.replace('>','&gt;')
meta_array['Title'] = title
authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;')
meta_array['Authors'] = authors
xname = os.path.join(xmlDir, 'metadata.xml') xname = os.path.join(xmlDir, 'metadata.xml')
metastr = '' metastr = ''
for key in meta_array: for key in meta_array:
@@ -399,7 +413,9 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -416,7 +432,9 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -471,8 +489,11 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'

View File

@@ -1,5 +1,5 @@
#! /usr/bin/env python #! /usr/bin/env python
# ineptpdf.pyw, version 7.7 # ineptpdf.pyw, version 7.9
from __future__ import with_statement from __future__ import with_statement
@@ -33,6 +33,7 @@ from __future__ import with_statement
# 7.6 - backported AES and other fixes from version 8.4.48 # 7.6 - backported AES and other fixes from version 8.4.48
# 7.7 - On Windows try PyCrypto first and OpenSSL next # 7.7 - On Windows try PyCrypto first and OpenSSL next
# 7.8 - Modify interface to allow use of import # 7.8 - Modify interface to allow use of import
# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
""" """
Decrypts Adobe ADEPT-encrypted PDF files. Decrypts Adobe ADEPT-encrypted PDF files.
@@ -1531,16 +1532,30 @@ class PDFDocument(object):
bookkey = bookkey[index:] bookkey = bookkey[index:]
ebx_V = int_value(param.get('V', 4)) ebx_V = int_value(param.get('V', 4))
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6)) ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
# added because of the booktype / decryption book session key error # added because of improper booktype / decryption book session key errors
if length > 0:
if len(bookkey) == length:
if ebx_V == 3: if ebx_V == 3:
V = 3 V = 3
elif ebx_V < 4 or ebx_type < 6: else:
V = 2
elif len(bookkey) == length + 1:
V = ord(bookkey[0]) V = ord(bookkey[0])
bookkey = bookkey[1:] bookkey = bookkey[1:]
else:
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
print "bookkey[0] is %d" % ord(bookkey[0])
raise ADEPTError('error decrypting book session key - mismatched length')
else:
# proper length unknown try with whatever you have
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
print "bookkey[0] is %d" % ord(bookkey[0])
if ebx_V == 3:
V = 3
else: else:
V = 2 V = 2
if length and len(bookkey) != length:
raise ADEPTError('error decrypting book session key')
self.decrypt_key = bookkey self.decrypt_key = bookkey
self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2 self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
self.decipher = self.decrypt_rc4 self.decipher = self.decrypt_rc4

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.2' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 2) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []

View File

@@ -168,9 +168,8 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None:
home = os.getenv('HOME') home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
@@ -178,17 +177,24 @@ def openKindleInfo(kInfoFile=None):
out1, out2 = p1.communicate() out1, out2 = p1.communicate()
reslst = out1.split('\n') reslst = out1.split('\n')
kinfopath = 'NONE' kinfopath = 'NONE'
found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
#
def openKindleInfo(kInfoFile=None):
if kInfoFile == None:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath): if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found') print('The kindle.info files has not been found.')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,8 +208,6 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
if rec209 != None and token != None:
# Compute book PID # Compute book PID
pidHash = SHA1(serialnum+rec209+token) pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -257,9 +239,16 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number # Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -46,8 +46,13 @@
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well # 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.26' __version__ = '0.29'
import sys import sys
@@ -175,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -190,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -207,19 +213,16 @@ class MobiBook:
pos = 12 pos = 12
for i in xrange(nitems): for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0" # print type, size, content, content.encode('hex')
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size pos += size
except: except:
self.meta_array = {} self.meta_array = {}
@@ -239,18 +242,18 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# Parse the 209 data to find the the exth record with the token data. # The 209 data comes in five byte groups. Interpret the last four bytes
# The last character of the 209 data points to the record with the token. # of each group as a big endian unsigned integer to get a key value
# Always 208 from my experience, but I'll leave the logic in case that changes. # if that key exists in the meta_array, append its contents to the token
for i in xrange(len(data)): for i in xrange(0,len(data),5):
if ord(data[i]) != 0: val, = struct.unpack('>I',data[i+1:i+5])
if self.meta_array[ord(data[i])] != None: sval = self.meta_array.get(val,'')
token = self.meta_array[ord(data[i])] token += sval
return rec209, token return rec209, token
def patch(self, off, new): def patch(self, off, new):

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -13,9 +13,20 @@ _FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28 _EXTRA_LEN_OFFSET = 28
_FILENAME_OFFSET = 30 _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024 _MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class fixZip: class fixZip:
def __init__(self, zinput, zoutput): def __init__(self, zinput, zoutput):
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r') self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w') self.outzip = zipfile.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file # open the input zip for reading only as a raw file
@@ -81,30 +92,15 @@ class fixZip:
# get the zipinfo for each member of the input archive # get the zipinfo for each member of the input archive
# and copy member over to output archive # and copy member over to output archive
# if problems exist with local vs central filename, fix them # if problems exist with local vs central filename, fix them
# also fix bad epub compression
# write mimetype file first, if present, and with no compression # if epub write mimetype file first, with no compression
for zinfo in self.inzip.infolist(): if self.ztype == 'epub':
if zinfo.filename == "mimetype": nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = zinfo self.outzip.writestr(nzinfo, _MIMETYPE)
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
nzinfo.date_time = zinfo.date_time
nzinfo.compress_type = zipfile.ZIP_STORED
nzinfo.flag_bits = 0
nzinfo.internal_attr = 0
nzinfo.extra = ""
self.outzip.writestr(nzinfo,data)
break
# write the rest of the files # write the rest of the files
for zinfo in self.inzip.infolist(): for zinfo in self.inzip.infolist():
if zinfo.filename != "mimetype": if zinfo.filename != "mimetype" or self.ztype == '.zip':
data = None data = None
nzinfo = zinfo nzinfo = zinfo
try: try:

View File

@@ -192,6 +192,8 @@ class GParser(object):
argres[j] = int(argres[j]) argres[j] = int(argres[j])
return result return result
def getGlyphDim(self, gly): def getGlyphDim(self, gly):
if self.gdpi[gly] == 0:
return 0, 0
maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly] maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly]
maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly] maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly]
return maxh, maxw return maxh, maxw
@@ -320,6 +322,18 @@ def generateBook(bookDir, raw, fixedimage):
print 'Processing Meta Data and creating OPF' print 'Processing Meta Data and creating OPF'
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < >
title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;')
title = title.replace('<','&lt;')
title = title.replace('>','&gt;')
meta_array['Title'] = title
authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;')
meta_array['Authors'] = authors
xname = os.path.join(xmlDir, 'metadata.xml') xname = os.path.join(xmlDir, 'metadata.xml')
metastr = '' metastr = ''
for key in meta_array: for key in meta_array:
@@ -399,7 +413,9 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -416,7 +432,9 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -471,8 +489,11 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'

View File

@@ -1,5 +1,5 @@
#! /usr/bin/env python #! /usr/bin/env python
# ineptpdf.pyw, version 7.7 # ineptpdf.pyw, version 7.9
from __future__ import with_statement from __future__ import with_statement
@@ -33,6 +33,7 @@ from __future__ import with_statement
# 7.6 - backported AES and other fixes from version 8.4.48 # 7.6 - backported AES and other fixes from version 8.4.48
# 7.7 - On Windows try PyCrypto first and OpenSSL next # 7.7 - On Windows try PyCrypto first and OpenSSL next
# 7.8 - Modify interface to allow use of import # 7.8 - Modify interface to allow use of import
# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
""" """
Decrypts Adobe ADEPT-encrypted PDF files. Decrypts Adobe ADEPT-encrypted PDF files.
@@ -1531,16 +1532,30 @@ class PDFDocument(object):
bookkey = bookkey[index:] bookkey = bookkey[index:]
ebx_V = int_value(param.get('V', 4)) ebx_V = int_value(param.get('V', 4))
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6)) ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
# added because of the booktype / decryption book session key error # added because of improper booktype / decryption book session key errors
if length > 0:
if len(bookkey) == length:
if ebx_V == 3: if ebx_V == 3:
V = 3 V = 3
elif ebx_V < 4 or ebx_type < 6: else:
V = 2
elif len(bookkey) == length + 1:
V = ord(bookkey[0]) V = ord(bookkey[0])
bookkey = bookkey[1:] bookkey = bookkey[1:]
else:
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
print "bookkey[0] is %d" % ord(bookkey[0])
raise ADEPTError('error decrypting book session key - mismatched length')
else:
# proper length unknown try with whatever you have
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
print "bookkey[0] is %d" % ord(bookkey[0])
if ebx_V == 3:
V = 3
else: else:
V = 2 V = 2
if length and len(bookkey) != length:
raise ADEPTError('error decrypting book session key')
self.decrypt_key = bookkey self.decrypt_key = bookkey
self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2 self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
self.decipher = self.decrypt_rc4 self.decipher = self.decrypt_rc4

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.2' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 2) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []

View File

@@ -168,9 +168,8 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None:
home = os.getenv('HOME') home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
@@ -178,17 +177,24 @@ def openKindleInfo(kInfoFile=None):
out1, out2 = p1.communicate() out1, out2 = p1.communicate()
reslst = out1.split('\n') reslst = out1.split('\n')
kinfopath = 'NONE' kinfopath = 'NONE'
found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
#
def openKindleInfo(kInfoFile=None):
if kInfoFile == None:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath): if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found') print('The kindle.info files has not been found.')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,8 +208,6 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
if rec209 != None and token != None:
# Compute book PID # Compute book PID
pidHash = SHA1(serialnum+rec209+token) pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -257,9 +239,16 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number # Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -46,8 +46,13 @@
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well # 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.26' __version__ = '0.29'
import sys import sys
@@ -175,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -190,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -207,19 +213,16 @@ class MobiBook:
pos = 12 pos = 12
for i in xrange(nitems): for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0" # print type, size, content, content.encode('hex')
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size pos += size
except: except:
self.meta_array = {} self.meta_array = {}
@@ -239,18 +242,18 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# Parse the 209 data to find the the exth record with the token data. # The 209 data comes in five byte groups. Interpret the last four bytes
# The last character of the 209 data points to the record with the token. # of each group as a big endian unsigned integer to get a key value
# Always 208 from my experience, but I'll leave the logic in case that changes. # if that key exists in the meta_array, append its contents to the token
for i in xrange(len(data)): for i in xrange(0,len(data),5):
if ord(data[i]) != 0: val, = struct.unpack('>I',data[i+1:i+5])
if self.meta_array[ord(data[i])] != None: sval = self.meta_array.get(val,'')
token = self.meta_array[ord(data[i])] token += sval
return rec209, token return rec209, token
def patch(self, off, new): def patch(self, off, new):

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -13,9 +13,20 @@ _FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28 _EXTRA_LEN_OFFSET = 28
_FILENAME_OFFSET = 30 _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024 _MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class fixZip: class fixZip:
def __init__(self, zinput, zoutput): def __init__(self, zinput, zoutput):
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r') self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w') self.outzip = zipfile.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file # open the input zip for reading only as a raw file
@@ -81,30 +92,15 @@ class fixZip:
# get the zipinfo for each member of the input archive # get the zipinfo for each member of the input archive
# and copy member over to output archive # and copy member over to output archive
# if problems exist with local vs central filename, fix them # if problems exist with local vs central filename, fix them
# also fix bad epub compression
# write mimetype file first, if present, and with no compression # if epub write mimetype file first, with no compression
for zinfo in self.inzip.infolist(): if self.ztype == 'epub':
if zinfo.filename == "mimetype": nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = zinfo self.outzip.writestr(nzinfo, _MIMETYPE)
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
nzinfo.date_time = zinfo.date_time
nzinfo.compress_type = zipfile.ZIP_STORED
nzinfo.flag_bits = 0
nzinfo.internal_attr = 0
nzinfo.extra = ""
self.outzip.writestr(nzinfo,data)
break
# write the rest of the files # write the rest of the files
for zinfo in self.inzip.infolist(): for zinfo in self.inzip.infolist():
if zinfo.filename != "mimetype": if zinfo.filename != "mimetype" or self.ztype == '.zip':
data = None data = None
nzinfo = zinfo nzinfo = zinfo
try: try:

View File

@@ -1,5 +1,5 @@
ReadMe_DeDRM_WinApp_v1.3 ReadMe_DeDRM_WinApp_vX.X
------------------------ -----------------------
DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program. DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program.

View File

@@ -1,216 +0,0 @@
#!/usr/bin/env python
# This is a simple tool to identify all Amazon Topaz ebooks in a specific directory.
# There always seems to be confusion since Topaz books downloaded to K4PC/Mac can have
# almost any extension (.azw, .azw1, .prc, tpz). While the .azw1 and .tpz extensions
# are fairly easy to indentify, the others are not (without opening the files in an editor).
# To run the tool with the GUI frontend, just double-click on the 'FindTopazFiles.pyw' file
# and select the folder where all of the ebooks in question are located. Then click 'Search'.
# The program will list the file names of the ebooks that are indentified as being Topaz.
# You can then isolate those books and use the Topaz tools to decrypt and convert them.
# You can also run the script from a command line... supplying the folder to search
# as a parameter: python FindTopazEbooks.pyw "C:\My Folder" (change appropriately for
# your particular O.S.)
# ** NOTE: This program does NOT decrypt or modify Topaz files in any way. It simply identifies them.
# PLEASE DO NOT PIRATE EBOOKS!
# We want all authors and publishers, and eBook stores to live
# long and prosperous lives but at the same time we just want to
# be able to read OUR books on whatever device we want and to keep
# readable for a long, long time
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
# and many many others
# Revision history:
# 1 - Initial release.
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import re
import shutil
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
class ScrolledText(Tkinter.Text):
def __init__(self, master=None, **kw):
self.frame = Tkinter.Frame(master)
self.vbar = Tkinter.Scrollbar(self.frame)
self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
kw.update({'yscrollcommand': self.vbar.set})
Tkinter.Text.__init__(self, self.frame, **kw)
self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
self.vbar['command'] = self.yview
# Copy geometry methods of self.frame without overriding Text
# methods = hack!
text_meths = vars(Tkinter.Text).keys()
methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
methods = set(methods).difference(text_meths)
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(self, m, getattr(self.frame, m))
def __str__(self):
return str(self.frame)
def cli_main(argv=sys.argv, obj=None):
progname = os.path.basename(argv[0])
if len(argv) != 2:
print "usage: %s DIRECTORY" % (progname,)
return 1
if obj == None:
print "\nTopaz search results:\n"
else:
obj.stext.insert(Tkconstants.END,"Topaz search results:\n\n")
inpath = argv[1]
files = os.listdir(inpath)
filefilter = re.compile("(\.azw$)|(\.azw1$)|(\.prc$)|(\.tpz$)", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
topazcount = 0
totalcount = 0
for filename in files:
with open(os.path.join(inpath, filename), 'rb') as f:
try:
if f.read().startswith('TPZ'):
f.close()
basename, extension = os.path.splitext(filename)
if obj == None:
print " %s is a Topaz formatted ebook." % filename
"""
if extension == '.azw' or extension == '.prc':
print " renaming to %s" % (basename + '.tpz')
shutil.move(os.path.join(inpath, filename),
os.path.join(inpath, basename + '.tpz'))
"""
else:
msg1 = " %s is a Topaz formatted ebook.\n" % filename
obj.stext.insert(Tkconstants.END,msg1)
"""
if extension == '.azw' or extension == '.prc':
msg2 = " renaming to %s\n" % (basename + '.tpz')
obj.stext.insert(Tkconstants.END,msg2)
shutil.move(os.path.join(inpath, filename),
os.path.join(inpath, basename + '.tpz'))
"""
topazcount += 1
except:
if obj == None:
print " Error reading %s." % filename
else:
msg = " Error reading or %s.\n" % filename
obj.stext.insert(Tkconstants.END,msg)
pass
totalcount += 1
if topazcount == 0:
if obj == None:
print "\nNo Topaz books found in %s." % inpath
else:
msg = "\nNo Topaz books found in %s.\n\n" % inpath
obj.stext.insert(Tkconstants.END,msg)
else:
if obj == None:
print "\n%i Topaz books found in %s\n%i total books checked.\n" % (topazcount, inpath, totalcount)
else:
msg = "\n%i Topaz books found in %s\n%i total books checked.\n\n" %(topazcount, inpath, totalcount)
obj.stext.insert(Tkconstants.END,msg)
else:
if obj == None:
print "No typical Topaz file extensions found in %s.\n" % inpath
else:
msg = "No typical Topaz file extensions found in %s.\n\n" % inpath
obj.stext.insert(Tkconstants.END,msg)
return 0
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
ltext='Search a directory for Topaz eBooks\n'
self.status = Tkinter.Label(self, text=ltext)
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Directory to Search').grid(row=1)
self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2)
msg1 = 'Topaz search results \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE,
height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky)
#self.stext.insert(Tkconstants.END,msg1)
buttons = Tkinter.Frame(self)
buttons.pack()
self.botton = Tkinter.Button(
buttons, text="Search", width=10, command=self.search)
self.botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
self.button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
self.button.pack(side=Tkconstants.RIGHT)
def get_inpath(self):
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
inpath = tkFileDialog.askdirectory(
parent=None, title='Directory to search',
initialdir=cwd, initialfile=None)
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def search(self):
inpath = self.inpath.get()
if not inpath or not os.path.exists(inpath):
self.status['text'] = 'Specified directory does not exist'
return
argv = [sys.argv[0], inpath]
self.status['text'] = 'Searching...'
self.botton.configure(state='disabled')
cli_main(argv, self)
self.status['text'] = 'Search a directory for Topaz files'
self.botton.configure(state='normal')
return
def gui_main():
root = Tkinter.Tk()
root.title('Topaz eBook Finder')
root.resizable(True, False)
root.minsize(370, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.exit(cli_main())
sys.exit(gui_main())

View File

@@ -192,6 +192,8 @@ class GParser(object):
argres[j] = int(argres[j]) argres[j] = int(argres[j])
return result return result
def getGlyphDim(self, gly): def getGlyphDim(self, gly):
if self.gdpi[gly] == 0:
return 0, 0
maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly] maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly]
maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly] maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly]
return maxh, maxw return maxh, maxw
@@ -320,6 +322,18 @@ def generateBook(bookDir, raw, fixedimage):
print 'Processing Meta Data and creating OPF' print 'Processing Meta Data and creating OPF'
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < >
title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;')
title = title.replace('<','&lt;')
title = title.replace('>','&gt;')
meta_array['Title'] = title
authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;')
meta_array['Authors'] = authors
xname = os.path.join(xmlDir, 'metadata.xml') xname = os.path.join(xmlDir, 'metadata.xml')
metastr = '' metastr = ''
for key in meta_array: for key in meta_array:
@@ -399,7 +413,9 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -416,7 +432,9 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
if 'ASIN' in meta_array:
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -471,8 +489,11 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.2' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 2) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []

View File

@@ -168,9 +168,8 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None:
home = os.getenv('HOME') home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
@@ -178,17 +177,24 @@ def openKindleInfo(kInfoFile=None):
out1, out2 = p1.communicate() out1, out2 = p1.communicate()
reslst = out1.split('\n') reslst = out1.split('\n')
kinfopath = 'NONE' kinfopath = 'NONE'
found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
#
def openKindleInfo(kInfoFile=None):
if kInfoFile == None:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath): if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found') print('The kindle.info files has not been found.')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,8 +208,6 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
if rec209 != None and token != None:
# Compute book PID # Compute book PID
pidHash = SHA1(serialnum+rec209+token) pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -257,9 +239,16 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number # Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -46,8 +46,13 @@
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well # 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.26' __version__ = '0.29'
import sys import sys
@@ -175,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -190,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -207,19 +213,16 @@ class MobiBook:
pos = 12 pos = 12
for i in xrange(nitems): for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0" # print type, size, content, content.encode('hex')
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size pos += size
except: except:
self.meta_array = {} self.meta_array = {}
@@ -239,18 +242,18 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# Parse the 209 data to find the the exth record with the token data. # The 209 data comes in five byte groups. Interpret the last four bytes
# The last character of the 209 data points to the record with the token. # of each group as a big endian unsigned integer to get a key value
# Always 208 from my experience, but I'll leave the logic in case that changes. # if that key exists in the meta_array, append its contents to the token
for i in xrange(len(data)): for i in xrange(0,len(data),5):
if ord(data[i]) != 0: val, = struct.unpack('>I',data[i+1:i+5])
if self.meta_array[ord(data[i])] != None: sval = self.meta_array.get(val,'')
token = self.meta_array[ord(data[i])] token += sval
return rec209, token return rec209, token
def patch(self, off, new): def patch(self, off, new):

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -46,8 +46,13 @@
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well # 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.26' __version__ = '0.29'
import sys import sys
@@ -175,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -190,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -207,19 +213,16 @@ class MobiBook:
pos = 12 pos = 12
for i in xrange(nitems): for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0" # print type, size, content, content.encode('hex')
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size pos += size
except: except:
self.meta_array = {} self.meta_array = {}
@@ -239,18 +242,18 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# Parse the 209 data to find the the exth record with the token data. # The 209 data comes in five byte groups. Interpret the last four bytes
# The last character of the 209 data points to the record with the token. # of each group as a big endian unsigned integer to get a key value
# Always 208 from my experience, but I'll leave the logic in case that changes. # if that key exists in the meta_array, append its contents to the token
for i in xrange(len(data)): for i in xrange(0,len(data),5):
if ord(data[i]) != 0: val, = struct.unpack('>I',data[i+1:i+5])
if self.meta_array[ord(data[i])] != None: sval = self.meta_array.get(val,'')
token = self.meta_array[ord(data[i])] token += sval
return rec209, token return rec209, token
def patch(self, off, new): def patch(self, off, new):

View File

@@ -46,8 +46,13 @@
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well # 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.26' __version__ = '0.29'
import sys import sys
@@ -175,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -190,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -207,19 +213,16 @@ class MobiBook:
pos = 12 pos = 12
for i in xrange(nitems): for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0" # print type, size, content, content.encode('hex')
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size pos += size
except: except:
self.meta_array = {} self.meta_array = {}
@@ -239,18 +242,18 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# Parse the 209 data to find the the exth record with the token data. # The 209 data comes in five byte groups. Interpret the last four bytes
# The last character of the 209 data points to the record with the token. # of each group as a big endian unsigned integer to get a key value
# Always 208 from my experience, but I'll leave the logic in case that changes. # if that key exists in the meta_array, append its contents to the token
for i in xrange(len(data)): for i in xrange(0,len(data),5):
if ord(data[i]) != 0: val, = struct.unpack('>I',data[i+1:i+5])
if self.meta_array[ord(data[i])] != None: sval = self.meta_array.get(val,'')
token = self.meta_array[ord(data[i])] token += sval
return rec209, token return rec209, token
def patch(self, off, new): def patch(self, off, new):

View File

@@ -1,406 +0,0 @@
#!/usr/bin/python
#
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# Changelog
# 0.01 - Initial version
# 0.02 - Huffdic compressed books were not properly decrypted
# 0.03 - Wasn't checking MOBI header length
# 0.04 - Wasn't sanity checking size of data record
# 0.05 - It seems that the extra data flags take two bytes not four
# 0.06 - And that low bit does mean something after all :-)
# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
# 0.08 - ...and also not in Mobi header version < 6
# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
# import filter it works when importing unencrypted files.
# Also now handles encrypted files that don't need a specific PID.
# 0.11 - use autoflushed stdout and proper return values
# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
# and extra blank lines, converted CR/LF pairs at ends of each line,
# and other cosmetic fixes.
# 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample
# files reveals that a confusion has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries
# in utf8 file are encrypted. (Although neither kind gets compressed.)
# This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
# 0.15 - Now outputs 'heartbeat', and is also quicker for long files.
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
# 0.17 - added modifications to support its use as an imported python module
# both inside calibre and also in other places (ie K4DeDRM tools)
# 0.17a- disabled the standalone plugin feature since a plugin can not import
# a plugin
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
# Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
__version__ = '0.26'
import sys
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)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
class DrmException(Exception):
pass
#
# MobiBook Utility Routines
#
# Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in xrange(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
def checksumPid(s):
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
crc = crc ^ (crc >> 16)
res = s
l = len(letters)
for i in (0,1):
b = crc & 0xff
pos = (b // l) ^ (b % l)
res += letters[pos%l]
crc >>= 8
return res
def getSizeOfTrailingDataEntries(ptr, size, flags):
def getSizeOfTrailingDataEntry(ptr, size):
bitpos, result = 0, 0
if size <= 0:
return result
while True:
v = ord(ptr[size-1])
result |= (v & 0x7F) << bitpos
bitpos += 7
size -= 1
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
return result
num = 0
testflags = flags >> 1
while testflags:
if testflags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num)
testflags >>= 1
# Check the low bit to see if there's multibyte data present.
# if multibyte data is included in the encryped data, we'll
# have already cleared this flag.
if flags & 1:
num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num
class MobiBook:
def loadSection(self, section):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
else:
endoff = self.sections[section + 1][0]
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile):
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
# parse information from section 0
self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
self.extra_data_flags = 0
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7:
# multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE
# if exth region exists parse it for metadata array
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = 'NONE'
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8])
# reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9:
# set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9:
# make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0"
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self):
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
return title
def getPIDMetaInfo(self):
rec209 = None
token = None
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
# Parse the 209 data to find the the exth record with the token data.
# The last character of the 209 data points to the record with the token.
# Always 208 from my experience, but I'll leave the logic in case that changes.
for i in xrange(len(data)):
if ord(data[i]) != 0:
if self.meta_array[ord(data[i])] != None:
token = self.meta_array[ord(data[i])]
return rec209, token
def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
def patchSection(self, section, new, in_off = 0):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
else:
endoff = self.sections[section + 1][0]
off = self.sections[section][0]
assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new)
def parseDRM(self, data, count, pidlist):
found_key = None
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
for pid in pidlist:
bigpid = pid.ljust(16,'\0')
temp_key = PC1(keyvec1, bigpid, False)
temp_key_sum = sum(map(ord,temp_key)) & 0xff
found_key = None
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and (flags & 0x1F) == 1:
found_key = finalkey
break
if found_key != None:
break
if not found_key:
# Then try the default encoding that doesn't require a PID
pid = "00000000"
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver:
found_key = finalkey
break
return [found_key,pid]
def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print 'Crypto Type is: ', crypto_type
self.crypto_type = crypto_type
if crypto_type == 0:
print "This book is not encrypted."
return self.data_file
if crypto_type != 2 and crypto_type != 1:
raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
goodpids = []
for pid in pidlist:
if len(pid)==10:
if checksumPid(pid[0:-2]) != pid:
print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
t1_keyvec = "QDCVEPMU675RUBSZ"
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
elif self.mobi_version < 0:
bookkey_data = self.sect[0x90:0x90+16]
else:
bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
if pid=="00000000":
print "File has default encryption, no specific PID."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)
# decrypt sections
print "Decrypting. Please wait . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0:
print ".",
# print "record %d, extra_size %d" %(i,extra_size)
new_data += PC1(found_key, data[0:len(data) - extra_size])
if extra_size > 0:
new_data += data[-extra_size:]
if self.num_sections > self.records+1:
new_data += self.data_file[self.sections[self.records+1][0]:]
self.data_file = new_data
print "done"
return self.data_file
def getUnencryptedBook(infile,pid):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
return book.processBook([pid])
def getUnencryptedBookWithList(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
return book.processBook(pidlist)
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(argv)<3 or len(argv)>4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1
else:
infile = argv[1]
outfile = argv[2]
if len(argv) is 4:
pidlist = argv[3].split(',')
else:
pidlist = {}
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,476 +0,0 @@
# engine to remove drm from Kindle for Mac books
# for personal use for archiving and converting your ebooks
# PLEASE DO NOT PIRATE!
# We want all authors and Publishers, and eBook stores to live long and prosperous lives
#
# it borrows heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DiapDealer, some_updates and many many others
from __future__ import with_statement
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
sys.stdout=Unbuffered(sys.stdout)
import os, csv, getopt
from struct import pack
from struct import unpack
import zlib
# for handling sub processes
import subprocess
from subprocess import Popen, PIPE, STDOUT
#Exception Handling
class K4MDEDRMError(Exception):
pass
class K4MDEDRMFatal(Exception):
pass
#
# crypto routines
#
import hashlib
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
def SHA256(message):
ctx = hashlib.sha256()
ctx.update(message)
return ctx.digest()
# interface to needed routines in openssl's libcrypto
def _load_crypto_libcrypto():
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
from ctypes.util import find_library
libcrypto = find_library('crypto')
if libcrypto is None:
raise K4MDEDRMError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
class LibCrypto(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self.iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise K4MDEDRMError('AES improper key used')
return
keyctx = self._keyctx = AES_KEY()
self.iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise K4MDEDRMError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0:
raise K4MDEDRMError('AES decryption failed')
return out.raw
def keyivgen(self, passwd):
salt = '16743'
saltlen = 5
passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw
return LibCrypto
def _load_crypto():
LibCrypto = None
try:
LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDEDRMError):
pass
return LibCrypto
LibCrypto = _load_crypto()
#
# Utility Routines
#
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the first found serial number in that class
def GetVolumeSerialNumber():
sernum = os.getenv('MYSERIALNUMBER')
if sernum != None:
return sernum
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p.wait('wait')
results = p.read()
reslst = results.split('\n')
cnt = len(reslst)
bsdname = None
sernum = None
foundIt = False
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
if pp >= 0:
sernum = resline[pp+19:-1]
sernum = sernum.strip()
bb = resline.find('"BSD Name" = "')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
if (bsdname == 'disk0') and (sernum != None):
foundIt = True
break
if not foundIt:
sernum = '9999999999'
return sernum
# uses unix env to get username instead of using sysctlbyname
def GetUserName():
username = os.getenv('USER')
return username
MAX_PATH = 255
#
# start of Kindle specific routines
#
global kindleDatabase
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
# Encode the bytes in data with the characters in map
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
# Hash the bytes in data and then encode the digest with the characters in map
def encodeHash(data,map):
return encode(MD5(data),map)
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
# implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData):
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
crp = LibCrypto()
key_iv = crp.keyivgen(passwdData)
key = key_iv[0:32]
iv = key_iv[32:48]
crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData)
return cleartext
# Locate and open the .kindle-info file
def openKindleInfo():
home = os.getenv('HOME')
kinfopath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
if not os.path.exists(kinfopath):
kinfopath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
if not os.path.exists(kinfopath):
raise K4MDEDRMError('Error: .kindle-info file can not be found')
return open(kinfopath,'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1)
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return result
# Print all the records from the kindle.info file (option -i)
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# PID generation routines
#
# Returns two bit at offset from a bit field
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
# Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
# 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
kindleDatabase = None
#
# Read the encrypted database
#
try:
kindleDatabase = parseKindleInfo()
except Exception, message:
print(message)
if kindleDatabase != None :
printKindleInfo()
return 0
import signal
import threading
import subprocess
from subprocess import Popen, PIPE, STDOUT
# **heavily** chopped up and modfied version of asyncproc.py
# to make it actually work on Windows as well as Mac/Linux
# For the original see:
# "http://www.lysator.liu.se/~bellman/download/"
# author is "Thomas Bellman <bellman@lysator.liu.se>"
# available under GPL version 3 or Later
# create an asynchronous subprocess whose output can be collected in
# a non-blocking manner
# What a mess! Have to use threads just to get non-blocking io
# in a cross-platform manner
# luckily all thread use is hidden within this class
class Process(object):
def __init__(self, *params, **kwparams):
if len(params) <= 3:
kwparams.setdefault('stdin', subprocess.PIPE)
if len(params) <= 4:
kwparams.setdefault('stdout', subprocess.PIPE)
if len(params) <= 5:
kwparams.setdefault('stderr', subprocess.PIPE)
self.__pending_input = []
self.__collected_outdata = []
self.__collected_errdata = []
self.__exitstatus = None
self.__lock = threading.Lock()
self.__inputsem = threading.Semaphore(0)
self.__quit = False
self.__process = subprocess.Popen(*params, **kwparams)
if self.__process.stdin:
self.__stdin_thread = threading.Thread(
name="stdin-thread",
target=self.__feeder, args=(self.__pending_input,
self.__process.stdin))
self.__stdin_thread.setDaemon(True)
self.__stdin_thread.start()
if self.__process.stdout:
self.__stdout_thread = threading.Thread(
name="stdout-thread",
target=self.__reader, args=(self.__collected_outdata,
self.__process.stdout))
self.__stdout_thread.setDaemon(True)
self.__stdout_thread.start()
if self.__process.stderr:
self.__stderr_thread = threading.Thread(
name="stderr-thread",
target=self.__reader, args=(self.__collected_errdata,
self.__process.stderr))
self.__stderr_thread.setDaemon(True)
self.__stderr_thread.start()
def pid(self):
return self.__process.pid
def kill(self, signal):
self.__process.send_signal(signal)
# check on subprocess (pass in 'nowait') to act like poll
def wait(self, flag):
if flag.lower() == 'nowait':
rc = self.__process.poll()
else:
rc = self.__process.wait()
if rc != None:
if self.__process.stdin:
self.closeinput()
if self.__process.stdout:
self.__stdout_thread.join()
if self.__process.stderr:
self.__stderr_thread.join()
return self.__process.returncode
def terminate(self):
if self.__process.stdin:
self.closeinput()
self.__process.terminate()
# thread gets data from subprocess stdout
def __reader(self, collector, source):
while True:
data = os.read(source.fileno(), 65536)
self.__lock.acquire()
collector.append(data)
self.__lock.release()
if data == "":
source.close()
break
return
# thread feeds data to subprocess stdin
def __feeder(self, pending, drain):
while True:
self.__inputsem.acquire()
self.__lock.acquire()
if not pending and self.__quit:
drain.close()
self.__lock.release()
break
data = pending.pop(0)
self.__lock.release()
drain.write(data)
# non-blocking read of data from subprocess stdout
def read(self):
self.__lock.acquire()
outdata = "".join(self.__collected_outdata)
del self.__collected_outdata[:]
self.__lock.release()
return outdata
# non-blocking read of data from subprocess stderr
def readerr(self):
self.__lock.acquire()
errdata = "".join(self.__collected_errdata)
del self.__collected_errdata[:]
self.__lock.release()
return errdata
# non-blocking write to stdin of subprocess
def write(self, data):
if self.__process.stdin is None:
raise ValueError("Writing to process with stdin not a pipe")
self.__lock.acquire()
self.__pending_input.append(data)
self.__inputsem.release()
self.__lock.release()
# close stdinput of subprocess
def closeinput(self):
self.__lock.acquire()
self.__quit = True
self.__inputsem.release()
self.__lock.release()
if __name__ == '__main__':
sys.exit(main())

View File

@@ -46,8 +46,13 @@
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well # 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.26' __version__ = '0.29'
import sys import sys
@@ -175,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -190,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -207,19 +213,16 @@ class MobiBook:
pos = 12 pos = 12
for i in xrange(nitems): for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
content = "\144"
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
content = "\0" # print type, size, content, content.encode('hex')
else:
content = exth[pos + 8: pos + size]
#print type, size, content
self.meta_array[type] = content
pos += size pos += size
except: except:
self.meta_array = {} self.meta_array = {}
@@ -239,18 +242,18 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# Parse the 209 data to find the the exth record with the token data. # The 209 data comes in five byte groups. Interpret the last four bytes
# The last character of the 209 data points to the record with the token. # of each group as a big endian unsigned integer to get a key value
# Always 208 from my experience, but I'll leave the logic in case that changes. # if that key exists in the meta_array, append its contents to the token
for i in xrange(len(data)): for i in xrange(0,len(data),5):
if ord(data[i]) != 0: val, = struct.unpack('>I',data[i+1:i+5])
if self.meta_array[ord(data[i])] != None: sval = self.meta_array.get(val,'')
token = self.meta_array[ord(data[i])] token += sval
return rec209, token return rec209, token
def patch(self, off, new): def patch(self, off, new):

View File

@@ -1,8 +1,8 @@
ePub_Fixer ePub_Fixer
ePubs are specially crafted zip archives whose first file is an uncompresssed "mimetype" file. Unfortunately, many of the DRM encoded Adobe Adept and Barnes & Noble ePubs are not "proper" zip archives in that the names of some files in the zip central directory do NOT match the local name given in archive itself, or they do not have an uncompressed mimetype file as the first file in the archive. These types of epubs are technically incorrect/corrupted and can not be read by many other programs. ePubs are specially crafted zip archives. Unfortunately, many of the DRM encoded Adobe Adept and Barnes & Noble ePubs are not "proper" zip archives in that the names of some files in the zip central directory do NOT match the local name given in archive itself. This type of zip archive is technically incorrect/corrupted and can not be read by many other programs.
ePub_Fixer was designed to fix improperly created zip archives of these types. ePub_Fixer was designed to fix improperly created zip archives of this type.
1. Simply double-click to launch ePub_Fixer.pyw. 1. Simply double-click to launch ePub_Fixer.pyw.

View File

@@ -13,9 +13,20 @@ _FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28 _EXTRA_LEN_OFFSET = 28
_FILENAME_OFFSET = 30 _FILENAME_OFFSET = 30
_MAX_SIZE = 64 * 1024 _MAX_SIZE = 64 * 1024
_MIMETYPE = 'application/epub+zip'
class ZipInfo(zipfile.ZipInfo):
def __init__(self, *args, **kwargs):
if 'compress_type' in kwargs:
compress_type = kwargs.pop('compress_type')
super(ZipInfo, self).__init__(*args, **kwargs)
self.compress_type = compress_type
class fixZip: class fixZip:
def __init__(self, zinput, zoutput): def __init__(self, zinput, zoutput):
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
self.inzip = zipfile.ZipFile(zinput,'r') self.inzip = zipfile.ZipFile(zinput,'r')
self.outzip = zipfile.ZipFile(zoutput,'w') self.outzip = zipfile.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file # open the input zip for reading only as a raw file
@@ -81,30 +92,15 @@ class fixZip:
# get the zipinfo for each member of the input archive # get the zipinfo for each member of the input archive
# and copy member over to output archive # and copy member over to output archive
# if problems exist with local vs central filename, fix them # if problems exist with local vs central filename, fix them
# also fix bad epub compression
# write mimetype file first, if present, and with no compression # if epub write mimetype file first, with no compression
for zinfo in self.inzip.infolist(): if self.ztype == 'epub':
if zinfo.filename == "mimetype": nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
nzinfo = zinfo self.outzip.writestr(nzinfo, _MIMETYPE)
try:
data = self.inzip.read(zinfo.filename)
except zipfile.BadZipfile or zipfile.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
nzinfo.date_time = zinfo.date_time
nzinfo.compress_type = zipfile.ZIP_STORED
nzinfo.flag_bits = 0
nzinfo.internal_attr = 0
nzinfo.extra = ""
self.outzip.writestr(nzinfo,data)
break
# write the rest of the files # write the rest of the files
for zinfo in self.inzip.infolist(): for zinfo in self.inzip.infolist():
if zinfo.filename != "mimetype": if zinfo.filename != "mimetype" or self.ztype == '.zip':
data = None data = None
nzinfo = zinfo nzinfo = zinfo
try: try: