mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-21 21:38:56 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bedd75005 | ||
|
|
8b632e309f |
@@ -68,7 +68,7 @@ class DocParser(object):
|
|||||||
ys = []
|
ys = []
|
||||||
gdefs = []
|
gdefs = []
|
||||||
|
|
||||||
# get path defintions, positions, dimensions for ecah glyph
|
# get path defintions, positions, dimensions for each glyph
|
||||||
# that makes up the image, and find min x and min y to reposition origin
|
# that makes up the image, and find min x and min y to reposition origin
|
||||||
minx = -1
|
minx = -1
|
||||||
miny = -1
|
miny = -1
|
||||||
@@ -305,6 +305,15 @@ class DocParser(object):
|
|||||||
lastGlyph = firstglyphList[last]
|
lastGlyph = firstglyphList[last]
|
||||||
else :
|
else :
|
||||||
lastGlyph = len(gidList)
|
lastGlyph = len(gidList)
|
||||||
|
|
||||||
|
# handle case of white sapce paragraphs with no actual glyphs in them
|
||||||
|
# by reverting to text based paragraph
|
||||||
|
if firstGlyph >= lastGlyph:
|
||||||
|
# revert to standard text based paragraph
|
||||||
|
for wordnum in xrange(first, last):
|
||||||
|
result.append(('ocr', wordnum))
|
||||||
|
return pclass, result
|
||||||
|
|
||||||
for glyphnum in xrange(firstGlyph, lastGlyph):
|
for glyphnum in xrange(firstGlyph, lastGlyph):
|
||||||
glyphList.append(glyphnum)
|
glyphList.append(glyphnum)
|
||||||
# include any extratokens if they exist
|
# include any extratokens if they exist
|
||||||
|
|||||||
@@ -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['Title']
|
||||||
|
title = title.replace('&','&')
|
||||||
|
title = title.replace('<','<')
|
||||||
|
title = title.replace('>','>')
|
||||||
|
meta_array['Title'] = title
|
||||||
|
authors = meta_array['Authors']
|
||||||
|
authors = authors.replace('&','&')
|
||||||
|
authors = authors.replace('<','<')
|
||||||
|
authors = authors.replace('>','>')
|
||||||
|
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:
|
||||||
|
|||||||
@@ -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
|
|
||||||
Binary file not shown.
@@ -57,8 +57,9 @@
|
|||||||
# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
|
# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
|
||||||
# 0.17 - added support for pycrypto's DES as well
|
# 0.17 - added support for pycrypto's DES as well
|
||||||
# 0.18 - on Windows try PyCrypto first and OpenSSL next
|
# 0.18 - on Windows try PyCrypto first and OpenSSL next
|
||||||
|
# 0.19 - Modify the interface to allow use of import
|
||||||
|
|
||||||
__version__='0.18'
|
__version__='0.19'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -111,12 +112,14 @@ except ImportError:
|
|||||||
# older Python release
|
# older Python release
|
||||||
import sha
|
import sha
|
||||||
sha1 = lambda s: sha.new(s)
|
sha1 = lambda s: sha.new(s)
|
||||||
|
|
||||||
import cgi
|
import cgi
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
#logging.basicConfig(level=logging.DEBUG)
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
class Sectionizer(object):
|
class Sectionizer(object):
|
||||||
def __init__(self, filename, ident):
|
def __init__(self, filename, ident):
|
||||||
self.contents = file(filename, 'rb').read()
|
self.contents = file(filename, 'rb').read()
|
||||||
@@ -364,7 +367,7 @@ def cleanPML(pml):
|
|||||||
def convertEreaderToPml(infile, name, cc, outdir):
|
def convertEreaderToPml(infile, name, cc, outdir):
|
||||||
if not os.path.exists(outdir):
|
if not os.path.exists(outdir):
|
||||||
os.makedirs(outdir)
|
os.makedirs(outdir)
|
||||||
|
bookname = os.path.splitext(os.path.basename(infile))[0]
|
||||||
print " Decoding File"
|
print " Decoding File"
|
||||||
sect = Sectionizer(infile, 'PNRdPPrs')
|
sect = Sectionizer(infile, 'PNRdPPrs')
|
||||||
er = EreaderProcessor(sect.loadSection, name, cc)
|
er = EreaderProcessor(sect.loadSection, name, cc)
|
||||||
@@ -390,62 +393,14 @@ def convertEreaderToPml(infile, name, cc, outdir):
|
|||||||
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
|
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print "Converts DRMed eReader books to PML Source"
|
|
||||||
print "Usage:"
|
|
||||||
print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
|
|
||||||
print " "
|
|
||||||
print "Options: "
|
|
||||||
print " -h prints this message"
|
|
||||||
print " --make-pmlz create PMLZ instead of using output directory"
|
|
||||||
print " "
|
|
||||||
print "Note:"
|
|
||||||
print " if ommitted, outdir defaults based on 'infile.pdb'"
|
|
||||||
print " It's enough to enter the last 8 digits of the credit card number"
|
|
||||||
return
|
|
||||||
|
|
||||||
def main(argv=None):
|
|
||||||
global bookname
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
print str(err)
|
|
||||||
usage()
|
|
||||||
return 1
|
|
||||||
make_pmlz = False
|
|
||||||
zipname = None
|
|
||||||
for o, a in opts:
|
|
||||||
if o == "-h":
|
|
||||||
usage()
|
|
||||||
return 0
|
|
||||||
elif o == "--make-pmlz":
|
|
||||||
make_pmlz = True
|
|
||||||
zipname = ''
|
|
||||||
|
|
||||||
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
|
|
||||||
|
|
||||||
if len(args)!=3 and len(args)!=4:
|
|
||||||
usage()
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
if len(args)==3:
|
|
||||||
infile, name, cc = args[0], args[1], args[2]
|
|
||||||
outdir = infile[:-4] + '_Source'
|
|
||||||
elif len(args)==4:
|
|
||||||
infile, outdir, name, cc = args[0], args[1], args[2], args[3]
|
|
||||||
|
|
||||||
|
def decryptBook(infile, outdir, name, cc, make_pmlz):
|
||||||
if make_pmlz :
|
if make_pmlz :
|
||||||
# ignore specified outdir, use tempdir instead
|
# ignore specified outdir, use tempdir instead
|
||||||
outdir = tempfile.mkdtemp()
|
outdir = tempfile.mkdtemp()
|
||||||
|
|
||||||
bookname = os.path.splitext(os.path.basename(infile))[0]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print "Processing..."
|
print "Processing..."
|
||||||
import time
|
|
||||||
start_time = time.time()
|
|
||||||
convertEreaderToPml(infile, name, cc, outdir)
|
convertEreaderToPml(infile, name, cc, outdir)
|
||||||
|
|
||||||
if make_pmlz :
|
if make_pmlz :
|
||||||
import zipfile
|
import zipfile
|
||||||
import shutil
|
import shutil
|
||||||
@@ -469,11 +424,6 @@ def main(argv=None):
|
|||||||
myZipFile.close()
|
myZipFile.close()
|
||||||
# remove temporary directory
|
# remove temporary directory
|
||||||
shutil.rmtree(outdir, True)
|
shutil.rmtree(outdir, True)
|
||||||
|
|
||||||
end_time = time.time()
|
|
||||||
search_time = end_time - start_time
|
|
||||||
print 'elapsed time: %.2f seconds' % (search_time, )
|
|
||||||
if make_pmlz :
|
|
||||||
print 'output is %s' % zipname
|
print 'output is %s' % zipname
|
||||||
else :
|
else :
|
||||||
print 'output in %s' % outdir
|
print 'output in %s' % outdir
|
||||||
@@ -483,6 +433,52 @@ def main(argv=None):
|
|||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print "Converts DRMed eReader books to PML Source"
|
||||||
|
print "Usage:"
|
||||||
|
print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
|
||||||
|
print " "
|
||||||
|
print "Options: "
|
||||||
|
print " -h prints this message"
|
||||||
|
print " --make-pmlz create PMLZ instead of using output directory"
|
||||||
|
print " "
|
||||||
|
print "Note:"
|
||||||
|
print " if ommitted, outdir defaults based on 'infile.pdb'"
|
||||||
|
print " It's enough to enter the last 8 digits of the credit card number"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
|
||||||
|
except getopt.GetoptError, err:
|
||||||
|
print str(err)
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
make_pmlz = False
|
||||||
|
for o, a in opts:
|
||||||
|
if o == "-h":
|
||||||
|
usage()
|
||||||
|
return 0
|
||||||
|
elif o == "--make-pmlz":
|
||||||
|
make_pmlz = True
|
||||||
|
|
||||||
|
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
|
||||||
|
|
||||||
|
if len(args)!=3 and len(args)!=4:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if len(args)==3:
|
||||||
|
infile, name, cc = args[0], args[1], args[2]
|
||||||
|
outdir = infile[:-4] + '_Source'
|
||||||
|
elif len(args)==4:
|
||||||
|
infile, outdir, name, cc = args[0], args[1], args[2], args[3]
|
||||||
|
|
||||||
|
return decryptBook(infile, outdir, name, cc, make_pmlz)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -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, 3)
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -82,12 +93,18 @@ class fixZip:
|
|||||||
# 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
|
||||||
|
|
||||||
for i, zinfo in enumerate(self.inzip.infolist()):
|
# if epub write mimetype file first, with no compression
|
||||||
|
if self.ztype == 'epub':
|
||||||
|
nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
|
||||||
|
self.outzip.writestr(nzinfo, _MIMETYPE)
|
||||||
|
|
||||||
|
# write the rest of the files
|
||||||
|
for zinfo in self.inzip.infolist():
|
||||||
|
if zinfo.filename != "mimetype" or self.ztype == '.zip':
|
||||||
data = None
|
data = None
|
||||||
nzinfo = zinfo
|
nzinfo = zinfo
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.inzip.read(zinfo)
|
data = self.inzip.read(zinfo.filename)
|
||||||
except zipfile.BadZipfile or zipfile.error:
|
except zipfile.BadZipfile or zipfile.error:
|
||||||
local_name = self.getlocalname(zinfo)
|
local_name = self.getlocalname(zinfo)
|
||||||
data = self.getfiledata(zinfo)
|
data = self.getfiledata(zinfo)
|
||||||
@@ -111,14 +128,7 @@ def usage():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def repairBook(infile, outfile):
|
||||||
if len(argv)!=3:
|
|
||||||
usage()
|
|
||||||
return 1
|
|
||||||
infile = None
|
|
||||||
outfile = None
|
|
||||||
infile = argv[1]
|
|
||||||
outfile = argv[2]
|
|
||||||
if not os.path.exists(infile):
|
if not os.path.exists(infile):
|
||||||
print "Error: Input Zip File does not exist"
|
print "Error: Input Zip File does not exist"
|
||||||
return 1
|
return 1
|
||||||
@@ -130,6 +140,16 @@ def main(argv=sys.argv):
|
|||||||
print "Error Occurred ", e
|
print "Error Occurred ", e
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
if len(argv)!=3:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
infile = argv[1]
|
||||||
|
outfile = argv[2]
|
||||||
|
return repairBook(infile, outfile)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__' :
|
if __name__ == '__main__' :
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -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, 4)
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -82,12 +93,18 @@ class fixZip:
|
|||||||
# 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
|
||||||
|
|
||||||
for i, zinfo in enumerate(self.inzip.infolist()):
|
# if epub write mimetype file first, with no compression
|
||||||
|
if self.ztype == 'epub':
|
||||||
|
nzinfo = ZipInfo('mimetype', compress_type=zipfile.ZIP_STORED)
|
||||||
|
self.outzip.writestr(nzinfo, _MIMETYPE)
|
||||||
|
|
||||||
|
# write the rest of the files
|
||||||
|
for zinfo in self.inzip.infolist():
|
||||||
|
if zinfo.filename != "mimetype" or self.ztype == '.zip':
|
||||||
data = None
|
data = None
|
||||||
nzinfo = zinfo
|
nzinfo = zinfo
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.inzip.read(zinfo)
|
data = self.inzip.read(zinfo.filename)
|
||||||
except zipfile.BadZipfile or zipfile.error:
|
except zipfile.BadZipfile or zipfile.error:
|
||||||
local_name = self.getlocalname(zinfo)
|
local_name = self.getlocalname(zinfo)
|
||||||
data = self.getfiledata(zinfo)
|
data = self.getfiledata(zinfo)
|
||||||
@@ -111,14 +128,7 @@ def usage():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def repairBook(infile, outfile):
|
||||||
if len(argv)!=3:
|
|
||||||
usage()
|
|
||||||
return 1
|
|
||||||
infile = None
|
|
||||||
outfile = None
|
|
||||||
infile = argv[1]
|
|
||||||
outfile = argv[2]
|
|
||||||
if not os.path.exists(infile):
|
if not os.path.exists(infile):
|
||||||
print "Error: Input Zip File does not exist"
|
print "Error: Input Zip File does not exist"
|
||||||
return 1
|
return 1
|
||||||
@@ -130,6 +140,16 @@ def main(argv=sys.argv):
|
|||||||
print "Error Occurred ", e
|
print "Error Occurred ", e
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
if len(argv)!=3:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
infile = argv[1]
|
||||||
|
outfile = argv[2]
|
||||||
|
return repairBook(infile, outfile)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__' :
|
if __name__ == '__main__' :
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -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
|
||||||
|
|||||||
Binary file not shown.
@@ -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.1'
|
__version__ = '2.3'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -75,6 +75,7 @@ def zipUpDir(myzip, tempdir,localname):
|
|||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# and removal of . at start
|
||||||
|
# convert spaces to underscores
|
||||||
def cleanup_name(name):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
@@ -89,6 +90,7 @@ def cleanup_name(name):
|
|||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
|
one = one.replace(' ','_')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
@@ -248,7 +250,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, 1) # The version number of this plugin
|
version = (0, 2, 3) # 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
|
||||||
|
|||||||
@@ -189,6 +189,6 @@ def openKindleInfo(kInfoFile=None):
|
|||||||
raise DrmException('Error: .kindle-info file can not be found')
|
raise DrmException('Error: .kindle-info file can not be found')
|
||||||
return open(kinfopath,'r')
|
return open(kinfopath,'r')
|
||||||
else:
|
else:
|
||||||
if not os.path.isfile(kinfoFile):
|
if not os.path.isfile(kInfoFile):
|
||||||
raise DrmException('Error: kindle-info file can not be found')
|
raise DrmException('Error: kindle-info file can not be found')
|
||||||
return open(kInfoFile, 'r')
|
return open(kInfoFile, 'r')
|
||||||
|
|||||||
Binary file not shown.
@@ -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.0, Copyright © 2010–2011 by Apprentice Alf.</string>
|
<string>DeDRM 2.2, Copyright © 2010–2011 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.0</string>
|
<string>2.2</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>dplt</string>
|
<string>dplt</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
@@ -43,14 +43,18 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>WindowState</key>
|
<key>WindowState</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>dividerCollapsed</key>
|
||||||
|
<true/>
|
||||||
|
<key>eventLogLevel</key>
|
||||||
|
<integer>-1</integer>
|
||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>ScriptWindowState</string>
|
<string>ScriptWindowState</string>
|
||||||
<key>positionOfDivider</key>
|
<key>positionOfDivider</key>
|
||||||
<real>709</real>
|
<real>0.0</real>
|
||||||
<key>savedFrame</key>
|
<key>savedFrame</key>
|
||||||
<string>1617 62 862 788 1440 -150 1680 1050 </string>
|
<string>1578 27 862 788 1440 -150 1680 1050 </string>
|
||||||
<key>selectedTabView</key>
|
<key>selectedTabView</key>
|
||||||
<string>result</string>
|
<string>event log</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
@@ -1,900 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
Comprehensive Mazama Book DRM with Topaz Cryptography V2.2
|
|
||||||
|
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
|
|
||||||
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
|
|
||||||
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
|
|
||||||
y2/pHuYme7U1TsgSjwIDAQAB
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
import zlib
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
|
||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
|
||||||
string_at, Structure, c_void_p, cast
|
|
||||||
import _winreg as winreg
|
|
||||||
import Tkinter
|
|
||||||
import Tkconstants
|
|
||||||
import tkMessageBox
|
|
||||||
import traceback
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
MAX_PATH = 255
|
|
||||||
|
|
||||||
kernel32 = windll.kernel32
|
|
||||||
advapi32 = windll.advapi32
|
|
||||||
crypt32 = windll.crypt32
|
|
||||||
|
|
||||||
global kindleDatabase
|
|
||||||
global bookFile
|
|
||||||
global bookPayloadOffset
|
|
||||||
global bookHeaderRecords
|
|
||||||
global bookMetadata
|
|
||||||
global bookKey
|
|
||||||
global command
|
|
||||||
|
|
||||||
#
|
|
||||||
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
|
||||||
#
|
|
||||||
|
|
||||||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
|
||||||
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
|
||||||
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
||||||
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Exceptions for all the problems that might happen during the script
|
|
||||||
#
|
|
||||||
|
|
||||||
class CMBDTCError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class CMBDTCFatal(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
#
|
|
||||||
# Stolen stuff
|
|
||||||
#
|
|
||||||
|
|
||||||
class DataBlob(Structure):
|
|
||||||
_fields_ = [('cbData', c_uint),
|
|
||||||
('pbData', c_void_p)]
|
|
||||||
DataBlob_p = POINTER(DataBlob)
|
|
||||||
|
|
||||||
def GetSystemDirectory():
|
|
||||||
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
|
||||||
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
|
||||||
GetSystemDirectoryW.restype = c_uint
|
|
||||||
def GetSystemDirectory():
|
|
||||||
buffer = create_unicode_buffer(MAX_PATH + 1)
|
|
||||||
GetSystemDirectoryW(buffer, len(buffer))
|
|
||||||
return buffer.value
|
|
||||||
return GetSystemDirectory
|
|
||||||
GetSystemDirectory = GetSystemDirectory()
|
|
||||||
|
|
||||||
|
|
||||||
def GetVolumeSerialNumber():
|
|
||||||
GetVolumeInformationW = kernel32.GetVolumeInformationW
|
|
||||||
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
|
|
||||||
POINTER(c_uint), POINTER(c_uint),
|
|
||||||
POINTER(c_uint), c_wchar_p, c_uint]
|
|
||||||
GetVolumeInformationW.restype = c_uint
|
|
||||||
def GetVolumeSerialNumber(path):
|
|
||||||
vsn = c_uint(0)
|
|
||||||
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
|
|
||||||
return vsn.value
|
|
||||||
return GetVolumeSerialNumber
|
|
||||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
|
||||||
|
|
||||||
|
|
||||||
def GetUserName():
|
|
||||||
GetUserNameW = advapi32.GetUserNameW
|
|
||||||
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
|
|
||||||
GetUserNameW.restype = c_uint
|
|
||||||
def GetUserName():
|
|
||||||
buffer = create_unicode_buffer(32)
|
|
||||||
size = c_uint(len(buffer))
|
|
||||||
while not GetUserNameW(buffer, byref(size)):
|
|
||||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
|
||||||
size.value = len(buffer)
|
|
||||||
return buffer.value.encode('utf-16-le')[::2]
|
|
||||||
return GetUserName
|
|
||||||
GetUserName = GetUserName()
|
|
||||||
|
|
||||||
|
|
||||||
def CryptUnprotectData():
|
|
||||||
_CryptUnprotectData = crypt32.CryptUnprotectData
|
|
||||||
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
|
|
||||||
c_void_p, c_void_p, c_uint, DataBlob_p]
|
|
||||||
_CryptUnprotectData.restype = c_uint
|
|
||||||
def CryptUnprotectData(indata, entropy):
|
|
||||||
indatab = create_string_buffer(indata)
|
|
||||||
indata = DataBlob(len(indata), cast(indatab, c_void_p))
|
|
||||||
entropyb = create_string_buffer(entropy)
|
|
||||||
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
|
|
||||||
outdata = DataBlob()
|
|
||||||
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
|
||||||
None, None, 0, byref(outdata)):
|
|
||||||
raise CMBDTCFatal("Failed to Unprotect Data")
|
|
||||||
return string_at(outdata.pbData, outdata.cbData)
|
|
||||||
return CryptUnprotectData
|
|
||||||
CryptUnprotectData = CryptUnprotectData()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the MD5 digest of "message"
|
|
||||||
#
|
|
||||||
|
|
||||||
def MD5(message):
|
|
||||||
ctx = hashlib.md5()
|
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the MD5 digest of "message"
|
|
||||||
#
|
|
||||||
|
|
||||||
def SHA1(message):
|
|
||||||
ctx = hashlib.sha1()
|
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Open the book file at path
|
|
||||||
#
|
|
||||||
|
|
||||||
def openBook(path):
|
|
||||||
try:
|
|
||||||
return open(path,'rb')
|
|
||||||
except:
|
|
||||||
raise CMBDTCFatal("Could not open book file: " + path)
|
|
||||||
#
|
|
||||||
# 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),2):
|
|
||||||
high = map.find(data[i])
|
|
||||||
low = map.find(data[i+1])
|
|
||||||
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
|
|
||||||
result += pack("B",value)
|
|
||||||
return result
|
|
||||||
|
|
||||||
#
|
|
||||||
# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
|
|
||||||
#
|
|
||||||
|
|
||||||
def openKindleInfo():
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
|
||||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
|
||||||
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','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
|
|
||||||
|
|
||||||
#
|
|
||||||
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
|
|
||||||
#
|
|
||||||
|
|
||||||
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 name
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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 ("--------------------------\n")
|
|
||||||
else :
|
|
||||||
print ("Unknown Record")
|
|
||||||
print getKindleInfoValueForHash(record)
|
|
||||||
print "\n"
|
|
||||||
#
|
|
||||||
# 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)
|
|
||||||
return CryptUnprotectData(encryptedValue,"")
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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))
|
|
||||||
|
|
||||||
#
|
|
||||||
# Get a 7 bit encoded number from the book file
|
|
||||||
#
|
|
||||||
|
|
||||||
def bookReadEncodedNumber():
|
|
||||||
flag = False
|
|
||||||
data = ord(bookFile.read(1))
|
|
||||||
|
|
||||||
if data == 0xFF:
|
|
||||||
flag = True
|
|
||||||
data = ord(bookFile.read(1))
|
|
||||||
|
|
||||||
if data >= 0x80:
|
|
||||||
datax = (data & 0x7F)
|
|
||||||
while data >= 0x80 :
|
|
||||||
data = ord(bookFile.read(1))
|
|
||||||
datax = (datax <<7) + (data & 0x7F)
|
|
||||||
data = datax
|
|
||||||
|
|
||||||
if flag:
|
|
||||||
data = -data
|
|
||||||
return data
|
|
||||||
|
|
||||||
#
|
|
||||||
# Encode a number in 7 bit format
|
|
||||||
#
|
|
||||||
|
|
||||||
def encodeNumber(number):
|
|
||||||
result = ""
|
|
||||||
negative = False
|
|
||||||
flag = 0
|
|
||||||
|
|
||||||
if number < 0 :
|
|
||||||
number = -number + 1
|
|
||||||
negative = True
|
|
||||||
|
|
||||||
while True:
|
|
||||||
byte = number & 0x7F
|
|
||||||
number = number >> 7
|
|
||||||
byte += flag
|
|
||||||
result += chr(byte)
|
|
||||||
flag = 0x80
|
|
||||||
if number == 0 :
|
|
||||||
if (byte == 0xFF and negative == False) :
|
|
||||||
result += chr(0x80)
|
|
||||||
break
|
|
||||||
|
|
||||||
if negative:
|
|
||||||
result += chr(0xFF)
|
|
||||||
|
|
||||||
return result[::-1]
|
|
||||||
|
|
||||||
#
|
|
||||||
# Get a length prefixed string from the file
|
|
||||||
#
|
|
||||||
|
|
||||||
def bookReadString():
|
|
||||||
stringLength = bookReadEncodedNumber()
|
|
||||||
return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns a length prefixed string
|
|
||||||
#
|
|
||||||
|
|
||||||
def lengthPrefixString(data):
|
|
||||||
return encodeNumber(len(data))+data
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...]
|
|
||||||
#
|
|
||||||
|
|
||||||
def bookReadHeaderRecordData():
|
|
||||||
nbValues = bookReadEncodedNumber()
|
|
||||||
values = []
|
|
||||||
for i in range (0,nbValues):
|
|
||||||
values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
|
|
||||||
return values
|
|
||||||
|
|
||||||
#
|
|
||||||
# Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...]
|
|
||||||
#
|
|
||||||
|
|
||||||
def parseTopazHeaderRecord():
|
|
||||||
if ord(bookFile.read(1)) != 0x63:
|
|
||||||
raise CMBDTCFatal("Parse Error : Invalid Header")
|
|
||||||
|
|
||||||
tag = bookReadString()
|
|
||||||
record = bookReadHeaderRecordData()
|
|
||||||
return [tag,record]
|
|
||||||
|
|
||||||
#
|
|
||||||
# Parse the header of a Topaz file, get all the header records and the offset for the payload
|
|
||||||
#
|
|
||||||
|
|
||||||
def parseTopazHeader():
|
|
||||||
global bookHeaderRecords
|
|
||||||
global bookPayloadOffset
|
|
||||||
magic = unpack("4s",bookFile.read(4))[0]
|
|
||||||
|
|
||||||
if magic != 'TPZ0':
|
|
||||||
raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file")
|
|
||||||
|
|
||||||
nbRecords = bookReadEncodedNumber()
|
|
||||||
bookHeaderRecords = {}
|
|
||||||
|
|
||||||
for i in range (0,nbRecords):
|
|
||||||
result = parseTopazHeaderRecord()
|
|
||||||
bookHeaderRecords[result[0]] = result[1]
|
|
||||||
|
|
||||||
if ord(bookFile.read(1)) != 0x64 :
|
|
||||||
raise CMBDTCFatal("Parse Error : Invalid Header")
|
|
||||||
|
|
||||||
bookPayloadOffset = bookFile.tell()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
|
|
||||||
#
|
|
||||||
|
|
||||||
def getBookPayloadRecord(name, index):
|
|
||||||
encrypted = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
recordOffset = bookHeaderRecords[name][index][0]
|
|
||||||
except:
|
|
||||||
raise CMBDTCFatal("Parse Error : Invalid Record, record not found")
|
|
||||||
|
|
||||||
bookFile.seek(bookPayloadOffset + recordOffset)
|
|
||||||
|
|
||||||
tag = bookReadString()
|
|
||||||
if tag != name :
|
|
||||||
raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match")
|
|
||||||
|
|
||||||
recordIndex = bookReadEncodedNumber()
|
|
||||||
|
|
||||||
if recordIndex < 0 :
|
|
||||||
encrypted = True
|
|
||||||
recordIndex = -recordIndex -1
|
|
||||||
|
|
||||||
if recordIndex != index :
|
|
||||||
raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match")
|
|
||||||
|
|
||||||
if bookHeaderRecords[name][index][2] != 0 :
|
|
||||||
record = bookFile.read(bookHeaderRecords[name][index][2])
|
|
||||||
else:
|
|
||||||
record = bookFile.read(bookHeaderRecords[name][index][1])
|
|
||||||
|
|
||||||
if encrypted:
|
|
||||||
ctx = topazCryptoInit(bookKey)
|
|
||||||
record = topazCryptoDecrypt(record,ctx)
|
|
||||||
|
|
||||||
return record
|
|
||||||
|
|
||||||
#
|
|
||||||
# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
|
|
||||||
#
|
|
||||||
|
|
||||||
def extractBookPayloadRecord(name, index, filename):
|
|
||||||
compressed = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
compressed = bookHeaderRecords[name][index][2] != 0
|
|
||||||
record = getBookPayloadRecord(name,index)
|
|
||||||
except:
|
|
||||||
print("Could not find record")
|
|
||||||
|
|
||||||
if compressed:
|
|
||||||
try:
|
|
||||||
record = zlib.decompress(record)
|
|
||||||
except:
|
|
||||||
raise CMBDTCFatal("Could not decompress record")
|
|
||||||
|
|
||||||
if filename != "":
|
|
||||||
try:
|
|
||||||
file = open(filename,"wb")
|
|
||||||
file.write(record)
|
|
||||||
file.close()
|
|
||||||
except:
|
|
||||||
raise CMBDTCFatal("Could not write to destination file")
|
|
||||||
else:
|
|
||||||
print(record)
|
|
||||||
|
|
||||||
#
|
|
||||||
# return next record [key,value] from the book metadata from the current book position
|
|
||||||
#
|
|
||||||
|
|
||||||
def readMetadataRecord():
|
|
||||||
return [bookReadString(),bookReadString()]
|
|
||||||
|
|
||||||
#
|
|
||||||
# Parse the metadata record from the book payload and return a list of [key,values]
|
|
||||||
#
|
|
||||||
|
|
||||||
def parseMetadata():
|
|
||||||
global bookHeaderRecords
|
|
||||||
global bookPayloadAddress
|
|
||||||
global bookMetadata
|
|
||||||
bookMetadata = {}
|
|
||||||
bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0])
|
|
||||||
tag = bookReadString()
|
|
||||||
if tag != "metadata" :
|
|
||||||
raise CMBDTCFatal("Parse Error : Record Names Don't Match")
|
|
||||||
|
|
||||||
flags = ord(bookFile.read(1))
|
|
||||||
nbRecords = ord(bookFile.read(1))
|
|
||||||
|
|
||||||
for i in range (0,nbRecords) :
|
|
||||||
record =readMetadataRecord()
|
|
||||||
bookMetadata[record[0]] = record[1]
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
||||||
#
|
|
||||||
# Context initialisation for the Topaz Crypto
|
|
||||||
#
|
|
||||||
|
|
||||||
def topazCryptoInit(key):
|
|
||||||
ctx1 = 0x0CAFFE19E
|
|
||||||
|
|
||||||
for keyChar in key:
|
|
||||||
keyByte = ord(keyChar)
|
|
||||||
ctx2 = ctx1
|
|
||||||
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
|
|
||||||
return [ctx1,ctx2]
|
|
||||||
|
|
||||||
#
|
|
||||||
# decrypt data with the context prepared by topazCryptoInit()
|
|
||||||
#
|
|
||||||
|
|
||||||
def topazCryptoDecrypt(data, ctx):
|
|
||||||
ctx1 = ctx[0]
|
|
||||||
ctx2 = ctx[1]
|
|
||||||
|
|
||||||
plainText = ""
|
|
||||||
|
|
||||||
for dataChar in data:
|
|
||||||
dataByte = ord(dataChar)
|
|
||||||
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
|
|
||||||
ctx2 = ctx1
|
|
||||||
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
|
|
||||||
plainText += chr(m)
|
|
||||||
|
|
||||||
return plainText
|
|
||||||
|
|
||||||
#
|
|
||||||
# Decrypt a payload record with the PID
|
|
||||||
#
|
|
||||||
|
|
||||||
def decryptRecord(data,PID):
|
|
||||||
ctx = topazCryptoInit(PID)
|
|
||||||
return topazCryptoDecrypt(data, ctx)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Try to decrypt a dkey record (contains the book PID)
|
|
||||||
#
|
|
||||||
|
|
||||||
def decryptDkeyRecord(data,PID):
|
|
||||||
record = decryptRecord(data,PID)
|
|
||||||
fields = unpack("3sB8sB8s3s",record)
|
|
||||||
|
|
||||||
if fields[0] != "PID" or fields[5] != "pid" :
|
|
||||||
raise CMBDTCError("Didn't find PID magic numbers in record")
|
|
||||||
elif fields[1] != 8 or fields[3] != 8 :
|
|
||||||
raise CMBDTCError("Record didn't contain correct length fields")
|
|
||||||
elif fields[2] != PID :
|
|
||||||
raise CMBDTCError("Record didn't contain PID")
|
|
||||||
|
|
||||||
return fields[4]
|
|
||||||
|
|
||||||
#
|
|
||||||
# Decrypt all the book's dkey records (contain the book PID)
|
|
||||||
#
|
|
||||||
|
|
||||||
def decryptDkeyRecords(data,PID):
|
|
||||||
nbKeyRecords = ord(data[0])
|
|
||||||
records = []
|
|
||||||
data = data[1:]
|
|
||||||
for i in range (0,nbKeyRecords):
|
|
||||||
length = ord(data[0])
|
|
||||||
try:
|
|
||||||
key = decryptDkeyRecord(data[1:length+1],PID)
|
|
||||||
records.append(key)
|
|
||||||
except CMBDTCError:
|
|
||||||
pass
|
|
||||||
data = data[1+length:]
|
|
||||||
|
|
||||||
return records
|
|
||||||
|
|
||||||
#
|
|
||||||
# Encryption table used to generate the device PID
|
|
||||||
#
|
|
||||||
|
|
||||||
def generatePidEncryptionTable() :
|
|
||||||
table = []
|
|
||||||
for counter1 in range (0,0x100):
|
|
||||||
value = counter1
|
|
||||||
for counter2 in range (0,8):
|
|
||||||
if (value & 1 == 0) :
|
|
||||||
value = value >> 1
|
|
||||||
else :
|
|
||||||
value = value >> 1
|
|
||||||
value = value ^ 0xEDB88320
|
|
||||||
table.append(value)
|
|
||||||
return table
|
|
||||||
|
|
||||||
#
|
|
||||||
# Seed value used to generate the device PID
|
|
||||||
#
|
|
||||||
|
|
||||||
def generatePidSeed(table,dsn) :
|
|
||||||
value = 0
|
|
||||||
for counter in range (0,4) :
|
|
||||||
index = (ord(dsn[counter]) ^ value) &0xFF
|
|
||||||
value = (value >> 8) ^ table[index]
|
|
||||||
return value
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generate the device PID
|
|
||||||
#
|
|
||||||
|
|
||||||
def generateDevicePID(table,dsn,nbRoll):
|
|
||||||
seed = generatePidSeed(table,dsn)
|
|
||||||
pidAscii = ""
|
|
||||||
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
for counter in range (0,nbRoll):
|
|
||||||
pid[index] = pid[index] ^ ord(dsn[counter])
|
|
||||||
index = (index+1) %8
|
|
||||||
|
|
||||||
for counter in range (0,8):
|
|
||||||
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
|
||||||
pidAscii += charMap4[index]
|
|
||||||
return pidAscii
|
|
||||||
|
|
||||||
#
|
|
||||||
# Create decrypted book payload
|
|
||||||
#
|
|
||||||
|
|
||||||
def createDecryptedPayload(payload):
|
|
||||||
|
|
||||||
# store data to be able to create the header later
|
|
||||||
headerData= []
|
|
||||||
currentOffset = 0
|
|
||||||
|
|
||||||
# Add social DRM to decrypted files
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = getKindleInfoValueForKey("kindle.name.info")+":"+ getKindleInfoValueForKey("login")
|
|
||||||
if payload!= None:
|
|
||||||
payload.write(lengthPrefixString("sdrm"))
|
|
||||||
payload.write(encodeNumber(0))
|
|
||||||
payload.write(data)
|
|
||||||
else:
|
|
||||||
currentOffset += len(lengthPrefixString("sdrm"))
|
|
||||||
currentOffset += len(encodeNumber(0))
|
|
||||||
currentOffset += len(data)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for headerRecord in bookHeaderRecords:
|
|
||||||
name = headerRecord
|
|
||||||
newRecord = []
|
|
||||||
|
|
||||||
if name != "dkey" :
|
|
||||||
|
|
||||||
for index in range (0,len(bookHeaderRecords[name])) :
|
|
||||||
offset = currentOffset
|
|
||||||
|
|
||||||
if payload != None:
|
|
||||||
# write tag
|
|
||||||
payload.write(lengthPrefixString(name))
|
|
||||||
# write data
|
|
||||||
payload.write(encodeNumber(index))
|
|
||||||
payload.write(getBookPayloadRecord(name, index))
|
|
||||||
|
|
||||||
else :
|
|
||||||
currentOffset += len(lengthPrefixString(name))
|
|
||||||
currentOffset += len(encodeNumber(index))
|
|
||||||
currentOffset += len(getBookPayloadRecord(name, index))
|
|
||||||
newRecord.append([offset,bookHeaderRecords[name][index][1],bookHeaderRecords[name][index][2]])
|
|
||||||
|
|
||||||
headerData.append([name,newRecord])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return headerData
|
|
||||||
|
|
||||||
#
|
|
||||||
# Create decrypted book
|
|
||||||
#
|
|
||||||
|
|
||||||
def createDecryptedBook(outputFile):
|
|
||||||
outputFile = open(outputFile,"wb")
|
|
||||||
# Write the payload in a temporary file
|
|
||||||
headerData = createDecryptedPayload(None)
|
|
||||||
outputFile.write("TPZ0")
|
|
||||||
outputFile.write(encodeNumber(len(headerData)))
|
|
||||||
|
|
||||||
for header in headerData :
|
|
||||||
outputFile.write(chr(0x63))
|
|
||||||
outputFile.write(lengthPrefixString(header[0]))
|
|
||||||
outputFile.write(encodeNumber(len(header[1])))
|
|
||||||
for numbers in header[1] :
|
|
||||||
outputFile.write(encodeNumber(numbers[0]))
|
|
||||||
outputFile.write(encodeNumber(numbers[1]))
|
|
||||||
outputFile.write(encodeNumber(numbers[2]))
|
|
||||||
|
|
||||||
outputFile.write(chr(0x64))
|
|
||||||
createDecryptedPayload(outputFile)
|
|
||||||
outputFile.close()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Set the command to execute by the programm according to cmdLine parameters
|
|
||||||
#
|
|
||||||
|
|
||||||
def setCommand(name) :
|
|
||||||
global command
|
|
||||||
if command != "" :
|
|
||||||
raise CMBDTCFatal("Invalid command line parameters")
|
|
||||||
else :
|
|
||||||
command = name
|
|
||||||
|
|
||||||
#
|
|
||||||
# Program usage
|
|
||||||
#
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print("\nUsage:")
|
|
||||||
print("\nCMBDTC.py [options] bookFileName\n")
|
|
||||||
print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
|
|
||||||
print("-d Saves a decrypted copy of the book")
|
|
||||||
print("-r Prints or writes to disk a record indicated in the form name:index (e.g \"img:0\")")
|
|
||||||
print("-o Output file name to write records and decrypted books")
|
|
||||||
print("-v Verbose (can be used several times)")
|
|
||||||
print("-i Prints kindle.info database")
|
|
||||||
|
|
||||||
#
|
|
||||||
# Main
|
|
||||||
#
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
|
||||||
global kindleDatabase
|
|
||||||
global bookMetadata
|
|
||||||
global bookKey
|
|
||||||
global bookFile
|
|
||||||
global command
|
|
||||||
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
|
|
||||||
verbose = 0
|
|
||||||
recordName = ""
|
|
||||||
recordIndex = 0
|
|
||||||
outputFile = ""
|
|
||||||
PIDs = []
|
|
||||||
kindleDatabase = None
|
|
||||||
command = ""
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "vdir:o:p:")
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
# print help information and exit:
|
|
||||||
print str(err) # will print something like "option -a not recognized"
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if len(opts) == 0 and len(args) == 0 :
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
for o, a in opts:
|
|
||||||
if o == "-v":
|
|
||||||
verbose+=1
|
|
||||||
if o == "-i":
|
|
||||||
setCommand("printInfo")
|
|
||||||
if o =="-o":
|
|
||||||
if a == None :
|
|
||||||
raise CMBDTCFatal("Invalid parameter for -o")
|
|
||||||
outputFile = a
|
|
||||||
if o =="-r":
|
|
||||||
setCommand("printRecord")
|
|
||||||
try:
|
|
||||||
recordName,recordIndex = a.split(':')
|
|
||||||
except:
|
|
||||||
raise CMBDTCFatal("Invalid parameter for -r")
|
|
||||||
if o =="-p":
|
|
||||||
PIDs.append(a)
|
|
||||||
if o =="-d":
|
|
||||||
setCommand("doit")
|
|
||||||
|
|
||||||
if command == "" :
|
|
||||||
raise CMBDTCFatal("No action supplied on command line")
|
|
||||||
|
|
||||||
#
|
|
||||||
# Read the encrypted database
|
|
||||||
#
|
|
||||||
|
|
||||||
try:
|
|
||||||
kindleDatabase = parseKindleInfo()
|
|
||||||
except Exception, message:
|
|
||||||
if verbose>0:
|
|
||||||
print(message)
|
|
||||||
|
|
||||||
if kindleDatabase != None :
|
|
||||||
if command == "printInfo" :
|
|
||||||
printKindleInfo()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Compute the DSN
|
|
||||||
#
|
|
||||||
|
|
||||||
# Get the Mazama Random number
|
|
||||||
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
|
|
||||||
|
|
||||||
# Get the HDD serial
|
|
||||||
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
|
|
||||||
|
|
||||||
# Get the current user name
|
|
||||||
encodedUsername = encodeHash(GetUserName(),charMap1)
|
|
||||||
|
|
||||||
# concat, hash and encode
|
|
||||||
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
|
||||||
|
|
||||||
if verbose >1:
|
|
||||||
print("DSN: " + DSN)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Compute the device PID
|
|
||||||
#
|
|
||||||
|
|
||||||
table = generatePidEncryptionTable()
|
|
||||||
devicePID = generateDevicePID(table,DSN,4)
|
|
||||||
PIDs.append(devicePID)
|
|
||||||
|
|
||||||
if verbose > 0:
|
|
||||||
print("Device PID: " + devicePID)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Open book and parse metadata
|
|
||||||
#
|
|
||||||
|
|
||||||
if len(args) == 1:
|
|
||||||
|
|
||||||
bookFile = openBook(args[0])
|
|
||||||
parseTopazHeader()
|
|
||||||
parseMetadata()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Compute book PID
|
|
||||||
#
|
|
||||||
|
|
||||||
# Get the account token
|
|
||||||
|
|
||||||
if kindleDatabase != None:
|
|
||||||
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
|
|
||||||
|
|
||||||
if verbose >1:
|
|
||||||
print("Account Token: " + kindleAccountToken)
|
|
||||||
|
|
||||||
keysRecord = bookMetadata["keys"]
|
|
||||||
keysRecordRecord = bookMetadata[keysRecord]
|
|
||||||
|
|
||||||
pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
|
|
||||||
|
|
||||||
bookPID = encodePID(pidHash)
|
|
||||||
PIDs.append(bookPID)
|
|
||||||
|
|
||||||
if verbose > 0:
|
|
||||||
print ("Book PID: " + bookPID )
|
|
||||||
|
|
||||||
#
|
|
||||||
# Decrypt book key
|
|
||||||
#
|
|
||||||
|
|
||||||
dkey = getBookPayloadRecord('dkey', 0)
|
|
||||||
|
|
||||||
bookKeys = []
|
|
||||||
for PID in PIDs :
|
|
||||||
bookKeys+=decryptDkeyRecords(dkey,PID)
|
|
||||||
|
|
||||||
if len(bookKeys) == 0 :
|
|
||||||
if verbose > 0 :
|
|
||||||
print ("Book key could not be found. Maybe this book is not registered with this device.")
|
|
||||||
else :
|
|
||||||
bookKey = bookKeys[0]
|
|
||||||
if verbose > 0:
|
|
||||||
print("Book key: " + bookKey.encode('hex'))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if command == "printRecord" :
|
|
||||||
extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
|
|
||||||
if outputFile != "" and verbose>0 :
|
|
||||||
print("Wrote record to file: "+outputFile)
|
|
||||||
elif command == "doit" :
|
|
||||||
if outputFile!="" :
|
|
||||||
createDecryptedBook(outputFile)
|
|
||||||
if verbose >0 :
|
|
||||||
print ("Decrypted book saved. Don't pirate!")
|
|
||||||
elif verbose > 0:
|
|
||||||
print("Output file name was not supplied.")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540
|
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||||
{\fonttbl}
|
{\fonttbl}
|
||||||
{\colortbl;\red255\green255\blue255;}
|
{\colortbl;\red255\green255\blue255;}
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ class DocParser(object):
|
|||||||
ys = []
|
ys = []
|
||||||
gdefs = []
|
gdefs = []
|
||||||
|
|
||||||
# get path defintions, positions, dimensions for ecah glyph
|
# get path defintions, positions, dimensions for each glyph
|
||||||
# that makes up the image, and find min x and min y to reposition origin
|
# that makes up the image, and find min x and min y to reposition origin
|
||||||
minx = -1
|
minx = -1
|
||||||
miny = -1
|
miny = -1
|
||||||
@@ -305,6 +305,15 @@ class DocParser(object):
|
|||||||
lastGlyph = firstglyphList[last]
|
lastGlyph = firstglyphList[last]
|
||||||
else :
|
else :
|
||||||
lastGlyph = len(gidList)
|
lastGlyph = len(gidList)
|
||||||
|
|
||||||
|
# handle case of white sapce paragraphs with no actual glyphs in them
|
||||||
|
# by reverting to text based paragraph
|
||||||
|
if firstGlyph >= lastGlyph:
|
||||||
|
# revert to standard text based paragraph
|
||||||
|
for wordnum in xrange(first, last):
|
||||||
|
result.append(('ocr', wordnum))
|
||||||
|
return pclass, result
|
||||||
|
|
||||||
for glyphnum in xrange(firstGlyph, lastGlyph):
|
for glyphnum in xrange(firstGlyph, lastGlyph):
|
||||||
glyphList.append(glyphnum)
|
glyphList.append(glyphnum)
|
||||||
# include any extratokens if they exist
|
# include any extratokens if they exist
|
||||||
|
|||||||
@@ -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['Title']
|
||||||
|
title = title.replace('&','&')
|
||||||
|
title = title.replace('<','<')
|
||||||
|
title = title.replace('>','>')
|
||||||
|
meta_array['Title'] = title
|
||||||
|
authors = meta_array['Authors']
|
||||||
|
authors = authors.replace('&','&')
|
||||||
|
authors = authors.replace('<','<')
|
||||||
|
authors = authors.replace('>','>')
|
||||||
|
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:
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
# For use with Topaz Scripts Version 2.6
|
|
||||||
|
|
||||||
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, getopt
|
|
||||||
|
|
||||||
# local routines
|
|
||||||
import convert2xml
|
|
||||||
import flatxml2html
|
|
||||||
import decode_meta
|
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print 'Usage: '
|
|
||||||
print ' '
|
|
||||||
print ' genxml.py dict0000.dat unencryptedBookDir'
|
|
||||||
print ' '
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
|
||||||
bookDir = ''
|
|
||||||
|
|
||||||
if len(argv) == 0:
|
|
||||||
argv = sys.argv
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(argv[1:], "h:")
|
|
||||||
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
print str(err)
|
|
||||||
usage()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if len(opts) == 0 and len(args) == 0 :
|
|
||||||
usage()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
for o, a in opts:
|
|
||||||
if o =="-h":
|
|
||||||
usage()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
bookDir = args[0]
|
|
||||||
|
|
||||||
if not os.path.exists(bookDir) :
|
|
||||||
print "Can not find directory with unencrypted book"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
dictFile = os.path.join(bookDir,'dict0000.dat')
|
|
||||||
if not os.path.exists(dictFile) :
|
|
||||||
print "Can not find dict0000.dat file"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
pageDir = os.path.join(bookDir,'page')
|
|
||||||
if not os.path.exists(pageDir) :
|
|
||||||
print "Can not find page directory in unencrypted book"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
glyphsDir = os.path.join(bookDir,'glyphs')
|
|
||||||
if not os.path.exists(glyphsDir) :
|
|
||||||
print "Can not find glyphs directory in unencrypted book"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
otherFile = os.path.join(bookDir,'other0000.dat')
|
|
||||||
if not os.path.exists(otherFile) :
|
|
||||||
print "Can not find other0000.dat in unencrypted book"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
metaFile = os.path.join(bookDir,'metadata0000.dat')
|
|
||||||
if not os.path.exists(metaFile) :
|
|
||||||
print "Can not find metadata0000.dat in unencrypted book"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
xmlDir = os.path.join(bookDir,'xml')
|
|
||||||
if not os.path.exists(xmlDir):
|
|
||||||
os.makedirs(xmlDir)
|
|
||||||
|
|
||||||
|
|
||||||
print 'Processing ... '
|
|
||||||
|
|
||||||
print ' ', 'metadata0000.dat'
|
|
||||||
fname = os.path.join(bookDir,'metadata0000.dat')
|
|
||||||
xname = os.path.join(xmlDir, 'metadata.txt')
|
|
||||||
metastr = decode_meta.getMetaData(fname)
|
|
||||||
file(xname, 'wb').write(metastr)
|
|
||||||
|
|
||||||
print ' ', 'other0000.dat'
|
|
||||||
fname = os.path.join(bookDir,'other0000.dat')
|
|
||||||
xname = os.path.join(xmlDir, 'stylesheet.xml')
|
|
||||||
pargv=[]
|
|
||||||
pargv.append('convert2xml.py')
|
|
||||||
pargv.append(dictFile)
|
|
||||||
pargv.append(fname)
|
|
||||||
xmlstr = convert2xml.main(pargv)
|
|
||||||
file(xname, 'wb').write(xmlstr)
|
|
||||||
|
|
||||||
filenames = os.listdir(pageDir)
|
|
||||||
filenames = sorted(filenames)
|
|
||||||
|
|
||||||
for filename in filenames:
|
|
||||||
print ' ', filename
|
|
||||||
fname = os.path.join(pageDir,filename)
|
|
||||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
|
||||||
pargv=[]
|
|
||||||
pargv.append('convert2xml.py')
|
|
||||||
pargv.append(dictFile)
|
|
||||||
pargv.append(fname)
|
|
||||||
xmlstr = convert2xml.main(pargv)
|
|
||||||
file(xname, 'wb').write(xmlstr)
|
|
||||||
|
|
||||||
filenames = os.listdir(glyphsDir)
|
|
||||||
filenames = sorted(filenames)
|
|
||||||
|
|
||||||
for filename in filenames:
|
|
||||||
print ' ', filename
|
|
||||||
fname = os.path.join(glyphsDir,filename)
|
|
||||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
|
||||||
pargv=[]
|
|
||||||
pargv.append('convert2xml.py')
|
|
||||||
pargv.append(dictFile)
|
|
||||||
pargv.append(fname)
|
|
||||||
xmlstr = convert2xml.main(pargv)
|
|
||||||
file(xname, 'wb').write(xmlstr)
|
|
||||||
|
|
||||||
|
|
||||||
print 'Processing Complete'
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main(''))
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
|
|
||||||
# ignobleepub.pyw, version 3.3
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
# ignobleepub.pyw, version 3.4
|
||||||
|
|
||||||
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
||||||
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||||
@@ -14,10 +16,9 @@
|
|||||||
# 3.1 - Allow Windows versions of libcrypto to be found
|
# 3.1 - Allow Windows versions of libcrypto to be found
|
||||||
# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
|
# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
|
||||||
# 3.3 - On Windows try PyCrypto first and OpenSSL next
|
# 3.3 - On Windows try PyCrypto first and OpenSSL next
|
||||||
|
# 3.4 - Modify interace to allow use with import
|
||||||
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -170,49 +171,6 @@ class Decryptor(object):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cli_main(argv=sys.argv):
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
if AES is None:
|
|
||||||
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
|
||||||
"separately. Read the top-of-script comment for details." % \
|
|
||||||
(progname,)
|
|
||||||
return 1
|
|
||||||
if len(argv) != 4:
|
|
||||||
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
|
||||||
return 1
|
|
||||||
keypath, inpath, outpath = argv[1:]
|
|
||||||
with open(keypath, 'rb') as f:
|
|
||||||
keyb64 = f.read()
|
|
||||||
key = keyb64.decode('base64')[:16]
|
|
||||||
# aes = AES.new(key, AES.MODE_CBC)
|
|
||||||
aes = AES(key)
|
|
||||||
|
|
||||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
|
||||||
namelist = set(inf.namelist())
|
|
||||||
if 'META-INF/rights.xml' not in namelist or \
|
|
||||||
'META-INF/encryption.xml' not in namelist:
|
|
||||||
raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,))
|
|
||||||
for name in META_NAMES:
|
|
||||||
namelist.remove(name)
|
|
||||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
|
||||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
|
||||||
expr = './/%s' % (adept('encryptedKey'),)
|
|
||||||
bookkey = ''.join(rights.findtext(expr))
|
|
||||||
bookkey = aes.decrypt(bookkey.decode('base64'))
|
|
||||||
bookkey = bookkey[:-ord(bookkey[-1])]
|
|
||||||
encryption = inf.read('META-INF/encryption.xml')
|
|
||||||
decryptor = Decryptor(bookkey[-16:], encryption)
|
|
||||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
|
||||||
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
|
||||||
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
|
|
||||||
outf.writestr(zi, inf.read('mimetype'))
|
|
||||||
for path in namelist:
|
|
||||||
data = inf.read(path)
|
|
||||||
outf.writestr(path, decryptor.decrypt(path, data))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
Tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
@@ -308,6 +266,53 @@ class DecryptionDialog(Tkinter.Frame):
|
|||||||
return
|
return
|
||||||
self.status['text'] = 'File successfully decrypted'
|
self.status['text'] = 'File successfully decrypted'
|
||||||
|
|
||||||
|
|
||||||
|
def decryptBook(keypath, inpath, outpath):
|
||||||
|
with open(keypath, 'rb') as f:
|
||||||
|
keyb64 = f.read()
|
||||||
|
key = keyb64.decode('base64')[:16]
|
||||||
|
# aes = AES.new(key, AES.MODE_CBC)
|
||||||
|
aes = AES(key)
|
||||||
|
|
||||||
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
|
namelist = set(inf.namelist())
|
||||||
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
|
'META-INF/encryption.xml' not in namelist:
|
||||||
|
raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,))
|
||||||
|
for name in META_NAMES:
|
||||||
|
namelist.remove(name)
|
||||||
|
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||||
|
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||||
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
|
bookkey = aes.decrypt(bookkey.decode('base64'))
|
||||||
|
bookkey = bookkey[:-ord(bookkey[-1])]
|
||||||
|
encryption = inf.read('META-INF/encryption.xml')
|
||||||
|
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||||
|
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||||
|
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||||
|
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
|
||||||
|
outf.writestr(zi, inf.read('mimetype'))
|
||||||
|
for path in namelist:
|
||||||
|
data = inf.read(path)
|
||||||
|
outf.writestr(path, decryptor.decrypt(path, data))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
if AES is None:
|
||||||
|
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
||||||
|
"separately. Read the top-of-script comment for details." % \
|
||||||
|
(progname,)
|
||||||
|
return 1
|
||||||
|
if len(argv) != 4:
|
||||||
|
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
||||||
|
return 1
|
||||||
|
keypath, inpath, outpath = argv[1:]
|
||||||
|
return decryptBook(keypath, inpath, outpath)
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
root = Tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
if AES is None:
|
if AES is None:
|
||||||
@@ -324,6 +329,7 @@ def gui_main():
|
|||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
sys.exit(cli_main())
|
sys.exit(cli_main())
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
|
|
||||||
# ignoblekeygen.pyw, version 2.2
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
# ignoblekeygen.pyw, version 2.3
|
||||||
|
|
||||||
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
||||||
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||||
@@ -12,12 +14,12 @@
|
|||||||
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
||||||
# 2.1 - Allow Windows versions of libcrypto to be found
|
# 2.1 - Allow Windows versions of libcrypto to be found
|
||||||
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
|
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
|
||||||
|
# 2.3 - Modify interface to allow use of import
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -120,6 +122,7 @@ AES = _load_crypto()
|
|||||||
def normalize_name(name):
|
def normalize_name(name):
|
||||||
return ''.join(x for x in name.lower() if x != ' ')
|
return ''.join(x for x in name.lower() if x != ' ')
|
||||||
|
|
||||||
|
|
||||||
def generate_keyfile(name, ccn, outpath):
|
def generate_keyfile(name, ccn, outpath):
|
||||||
name = normalize_name(name) + '\x00'
|
name = normalize_name(name) + '\x00'
|
||||||
ccn = ccn + '\x00'
|
ccn = ccn + '\x00'
|
||||||
@@ -133,19 +136,6 @@ def generate_keyfile(name, ccn, outpath):
|
|||||||
f.write(userkey.encode('base64'))
|
f.write(userkey.encode('base64'))
|
||||||
return userkey
|
return userkey
|
||||||
|
|
||||||
def cli_main(argv=sys.argv):
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
if AES is None:
|
|
||||||
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
|
||||||
"separately. Read the top-of-script comment for details." % \
|
|
||||||
(progname,)
|
|
||||||
return 1
|
|
||||||
if len(argv) != 4:
|
|
||||||
print "usage: %s NAME CC# OUTFILE" % (progname,)
|
|
||||||
return 1
|
|
||||||
name, ccn, outpath = argv[1:]
|
|
||||||
generate_keyfile(name, ccn, outpath)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
@@ -211,6 +201,22 @@ class DecryptionDialog(Tkinter.Frame):
|
|||||||
return
|
return
|
||||||
self.status['text'] = 'Keyfile successfully generated'
|
self.status['text'] = 'Keyfile successfully generated'
|
||||||
|
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
if AES is None:
|
||||||
|
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
||||||
|
"separately. Read the top-of-script comment for details." % \
|
||||||
|
(progname,)
|
||||||
|
return 1
|
||||||
|
if len(argv) != 4:
|
||||||
|
print "usage: %s NAME CC# OUTFILE" % (progname,)
|
||||||
|
return 1
|
||||||
|
name, ccn, outpath = argv[1:]
|
||||||
|
generate_keyfile(name, ccn, outpath)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
root = Tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
if AES is None:
|
if AES is None:
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ineptepub.pyw, version 5.5
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
# ineptepub.pyw, version 5.6
|
||||||
# Copyright © 2009-2010 i♥cabbages
|
# Copyright © 2009-2010 i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
@@ -27,13 +29,11 @@
|
|||||||
# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
|
# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
|
||||||
# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
|
# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
|
||||||
# 5.5 - On Windows try PyCrypto first, OpenSSL next
|
# 5.5 - On Windows try PyCrypto first, OpenSSL next
|
||||||
|
# 5.6 - Modify interface to allow use with import
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe ADEPT-encrypted EPUB books.
|
Decrypt Adobe ADEPT-encrypted EPUB books.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -312,45 +312,6 @@ class Decryptor(object):
|
|||||||
data = self.decompress(data)
|
data = self.decompress(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def cli_main(argv=sys.argv):
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
if AES is None:
|
|
||||||
print "%s: This script requires OpenSSL or PyCrypto, which must be" \
|
|
||||||
" installed separately. Read the top-of-script comment for" \
|
|
||||||
" details." % (progname,)
|
|
||||||
return 1
|
|
||||||
if len(argv) != 4:
|
|
||||||
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
|
||||||
return 1
|
|
||||||
keypath, inpath, outpath = argv[1:]
|
|
||||||
with open(keypath, 'rb') as f:
|
|
||||||
keyder = f.read()
|
|
||||||
rsa = RSA(keyder)
|
|
||||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
|
||||||
namelist = set(inf.namelist())
|
|
||||||
if 'META-INF/rights.xml' not in namelist or \
|
|
||||||
'META-INF/encryption.xml' not in namelist:
|
|
||||||
raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
|
|
||||||
for name in META_NAMES:
|
|
||||||
namelist.remove(name)
|
|
||||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
|
||||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
|
||||||
expr = './/%s' % (adept('encryptedKey'),)
|
|
||||||
bookkey = ''.join(rights.findtext(expr))
|
|
||||||
bookkey = rsa.decrypt(bookkey.decode('base64'))
|
|
||||||
# Padded as per RSAES-PKCS1-v1_5
|
|
||||||
if bookkey[-17] != '\x00':
|
|
||||||
raise ADEPTError('problem decrypting session key')
|
|
||||||
encryption = inf.read('META-INF/encryption.xml')
|
|
||||||
decryptor = Decryptor(bookkey[-16:], encryption)
|
|
||||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
|
||||||
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
|
||||||
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
|
|
||||||
outf.writestr(zi, inf.read('mimetype'))
|
|
||||||
for path in namelist:
|
|
||||||
data = inf.read(path)
|
|
||||||
outf.writestr(path, decryptor.decrypt(path, data))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
@@ -446,6 +407,52 @@ class DecryptionDialog(Tkinter.Frame):
|
|||||||
return
|
return
|
||||||
self.status['text'] = 'File successfully decrypted'
|
self.status['text'] = 'File successfully decrypted'
|
||||||
|
|
||||||
|
|
||||||
|
def decryptBook(keypath, inpath, outpath):
|
||||||
|
with open(keypath, 'rb') as f:
|
||||||
|
keyder = f.read()
|
||||||
|
rsa = RSA(keyder)
|
||||||
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
|
namelist = set(inf.namelist())
|
||||||
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
|
'META-INF/encryption.xml' not in namelist:
|
||||||
|
raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
|
||||||
|
for name in META_NAMES:
|
||||||
|
namelist.remove(name)
|
||||||
|
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||||
|
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||||
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
|
bookkey = rsa.decrypt(bookkey.decode('base64'))
|
||||||
|
# Padded as per RSAES-PKCS1-v1_5
|
||||||
|
if bookkey[-17] != '\x00':
|
||||||
|
raise ADEPTError('problem decrypting session key')
|
||||||
|
encryption = inf.read('META-INF/encryption.xml')
|
||||||
|
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||||
|
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||||
|
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||||
|
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
|
||||||
|
outf.writestr(zi, inf.read('mimetype'))
|
||||||
|
for path in namelist:
|
||||||
|
data = inf.read(path)
|
||||||
|
outf.writestr(path, decryptor.decrypt(path, data))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
if AES is None:
|
||||||
|
print "%s: This script requires OpenSSL or PyCrypto, which must be" \
|
||||||
|
" installed separately. Read the top-of-script comment for" \
|
||||||
|
" details." % (progname,)
|
||||||
|
return 1
|
||||||
|
if len(argv) != 4:
|
||||||
|
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
||||||
|
return 1
|
||||||
|
keypath, inpath, outpath = argv[1:]
|
||||||
|
return decryptBook(keypath, inpath, outpath)
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
root = Tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
if AES is None:
|
if AES is None:
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ineptkey.pyw, version 5.3
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
# ineptkey.pyw, version 5.4
|
||||||
# Copyright © 2009-2010 i♥cabbages
|
# Copyright © 2009-2010 i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
@@ -33,13 +35,12 @@
|
|||||||
# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
|
# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
|
||||||
# 5.2 - added support for output of key to a particular file
|
# 5.2 - added support for output of key to a particular file
|
||||||
# 5.3 - On Windows try PyCrypto first, OpenSSL next
|
# 5.3 - On Windows try PyCrypto first, OpenSSL next
|
||||||
|
# 5.4 - Modify interface to allow use of import
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Adobe ADEPT user key.
|
Retrieve Adobe ADEPT user key.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -415,10 +416,11 @@ class ExceptionDialog(Tkinter.Frame):
|
|||||||
label.pack(fill=Tkconstants.X, expand=0)
|
label.pack(fill=Tkconstants.X, expand=0)
|
||||||
self.text = Tkinter.Text(self)
|
self.text = Tkinter.Text(self)
|
||||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
|
|
||||||
self.text.insert(Tkconstants.END, text)
|
self.text.insert(Tkconstants.END, text)
|
||||||
|
|
||||||
def cli_main(argv=sys.argv):
|
|
||||||
keypath = argv[1]
|
def extractKeyfile(keypath):
|
||||||
try:
|
try:
|
||||||
success = retrieve_key(keypath)
|
success = retrieve_key(keypath)
|
||||||
except ADEPTError, e:
|
except ADEPTError, e:
|
||||||
@@ -431,6 +433,12 @@ def cli_main(argv=sys.argv):
|
|||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
keypath = argv[1]
|
||||||
|
return extractKeyfile(keypath)
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
root = Tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#! /usr/bin/env python
|
#! /usr/bin/env python
|
||||||
# ineptpdf.pyw, version 7.7
|
# ineptpdf.pyw, version 7.9
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# To run this program install Python 2.6 from http://www.python.org/download/
|
# To run this program install Python 2.6 from http://www.python.org/download/
|
||||||
# and OpenSSL (already installed on Mac OS X and Linux) OR
|
# and OpenSSL (already installed on Mac OS X and Linux) OR
|
||||||
@@ -30,13 +32,13 @@
|
|||||||
# fixed minor typos
|
# fixed minor typos
|
||||||
# 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.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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -1530,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
|
||||||
@@ -2076,25 +2092,6 @@ class PDFSerializer(object):
|
|||||||
self.write('\n')
|
self.write('\n')
|
||||||
self.write('endobj\n')
|
self.write('endobj\n')
|
||||||
|
|
||||||
def cli_main(argv=sys.argv):
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
if RSA is None:
|
|
||||||
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
|
||||||
"separately. Read the top-of-script comment for details." % \
|
|
||||||
(progname,)
|
|
||||||
return 1
|
|
||||||
if len(argv) != 4:
|
|
||||||
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
|
||||||
return 1
|
|
||||||
keypath, inpath, outpath = argv[1:]
|
|
||||||
with open(inpath, 'rb') as inf:
|
|
||||||
serializer = PDFSerializer(inf, keypath)
|
|
||||||
# hope this will fix the 'bad file descriptor' problem
|
|
||||||
with open(outpath, 'wb') as outf:
|
|
||||||
# help construct to make sure the method runs to the end
|
|
||||||
serializer.dump(outf)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
@@ -2198,6 +2195,31 @@ class DecryptionDialog(Tkinter.Frame):
|
|||||||
'Close this window or decrypt another pdf file.'
|
'Close this window or decrypt another pdf file.'
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def decryptBook(keypath, inpath, outpath):
|
||||||
|
with open(inpath, 'rb') as inf:
|
||||||
|
serializer = PDFSerializer(inf, keypath)
|
||||||
|
# hope this will fix the 'bad file descriptor' problem
|
||||||
|
with open(outpath, 'wb') as outf:
|
||||||
|
# help construct to make sure the method runs to the end
|
||||||
|
serializer.dump(outf)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
if RSA is None:
|
||||||
|
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
||||||
|
"separately. Read the top-of-script comment for details." % \
|
||||||
|
(progname,)
|
||||||
|
return 1
|
||||||
|
if len(argv) != 4:
|
||||||
|
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
||||||
|
return 1
|
||||||
|
keypath, inpath, outpath = argv[1:]
|
||||||
|
return decryptBook(keypath, inpath, outpath)
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
root = Tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
if RSA is None:
|
if RSA is None:
|
||||||
@@ -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.1'
|
__version__ = '2.3'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -75,6 +75,7 @@ def zipUpDir(myzip, tempdir,localname):
|
|||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# and removal of . at start
|
||||||
|
# convert spaces to underscores
|
||||||
def cleanup_name(name):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
@@ -89,6 +90,7 @@ def cleanup_name(name):
|
|||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
|
one = one.replace(' ','_')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
@@ -134,8 +136,9 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
|||||||
except mobidedrm.DrmException, e:
|
except mobidedrm.DrmException, e:
|
||||||
print "Error: " + str(e) + "\nDRM Removal Failed.\n"
|
print "Error: " + str(e) + "\nDRM Removal Failed.\n"
|
||||||
return 1
|
return 1
|
||||||
except topazextract.TpzDRMError, e:
|
except Exception, e:
|
||||||
print str(e)
|
if not mobi:
|
||||||
|
print "Error: " + str(e) + "\nDRM Removal Failed.\n"
|
||||||
print " Creating DeBug Full Zip Archive of Book"
|
print " Creating DeBug Full Zip Archive of Book"
|
||||||
zipname = os.path.join(outdir, bookname + '_debug' + '.zip')
|
zipname = os.path.join(outdir, bookname + '_debug' + '.zip')
|
||||||
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
||||||
@@ -143,6 +146,7 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
|||||||
myzip.close()
|
myzip.close()
|
||||||
shutil.rmtree(tempdir, True)
|
shutil.rmtree(tempdir, True)
|
||||||
return 1
|
return 1
|
||||||
|
pass
|
||||||
|
|
||||||
if mobi:
|
if mobi:
|
||||||
outfile = os.path.join(outdir,outfilename + '_nodrm' + '.mobi')
|
outfile = os.path.join(outdir,outfilename + '_nodrm' + '.mobi')
|
||||||
@@ -246,7 +250,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, 1) # The version number of this plugin
|
version = (0, 2, 3) # 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
|
||||||
|
|||||||
@@ -189,6 +189,6 @@ def openKindleInfo(kInfoFile=None):
|
|||||||
raise DrmException('Error: .kindle-info file can not be found')
|
raise DrmException('Error: .kindle-info file can not be found')
|
||||||
return open(kinfopath,'r')
|
return open(kinfopath,'r')
|
||||||
else:
|
else:
|
||||||
if not os.path.isfile(kinfoFile):
|
if not os.path.isfile(kInfoFile):
|
||||||
raise DrmException('Error: kindle-info file can not be found')
|
raise DrmException('Error: kindle-info file can not be found')
|
||||||
return open(kInfoFile, 'r')
|
return open(kInfoFile, 'r')
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
sys.path.append('lib')
|
|
||||||
import os, os.path
|
import os, os.path
|
||||||
|
sys.path.append(sys.path[0]+os.sep+'lib')
|
||||||
import shutil
|
import shutil
|
||||||
import Tkinter
|
import Tkinter
|
||||||
from Tkinter import *
|
from Tkinter import *
|
||||||
@@ -22,10 +22,11 @@ class DrmException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class MainApp(Tk):
|
class MainApp(Tk):
|
||||||
def __init__(self, dnd=False, filenames=[]):
|
def __init__(self, apphome, dnd=False, filenames=[]):
|
||||||
Tk.__init__(self)
|
Tk.__init__(self)
|
||||||
self.withdraw()
|
self.withdraw()
|
||||||
self.dnd = dnd
|
self.dnd = dnd
|
||||||
|
self.apphome = apphome
|
||||||
# preference settings
|
# preference settings
|
||||||
# [dictionary key, file in preferences directory where info is stored]
|
# [dictionary key, file in preferences directory where info is stored]
|
||||||
description = [ ['pids' , 'pidlist.txt' ],
|
description = [ ['pids' , 'pidlist.txt' ],
|
||||||
@@ -312,6 +313,7 @@ class ConvDialog(Toplevel):
|
|||||||
self.protocol("WM_DELETE_WINDOW", self.withdraw)
|
self.protocol("WM_DELETE_WINDOW", self.withdraw)
|
||||||
self.title("DeDRM Processing")
|
self.title("DeDRM Processing")
|
||||||
self.master = master
|
self.master = master
|
||||||
|
self.apphome = self.master.apphome
|
||||||
self.prefs_array = prefs_array
|
self.prefs_array = prefs_array
|
||||||
self.filenames = filenames
|
self.filenames = filenames
|
||||||
self.interval = 50
|
self.interval = 50
|
||||||
@@ -328,11 +330,11 @@ class ConvDialog(Toplevel):
|
|||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
|
|
||||||
Tkinter.Label(body, text='Activity Bar').grid(row=0, sticky=Tkconstants.E)
|
Tkinter.Label(body, text='Activity Bar').grid(row=0, sticky=Tkconstants.E)
|
||||||
self.bar = ActivityBar(body, length=50, height=15, barwidth=5)
|
self.bar = ActivityBar(body, length=80, height=15, barwidth=5)
|
||||||
self.bar.grid(row=0, column=1, sticky=sticky)
|
self.bar.grid(row=0, column=1, sticky=sticky)
|
||||||
|
|
||||||
msg1 = ''
|
msg1 = ''
|
||||||
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=4, width=50, wrap=Tkconstants.WORD)
|
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=4, width=80, wrap=Tkconstants.WORD)
|
||||||
self.stext.grid(row=2, column=0, columnspan=2,sticky=sticky)
|
self.stext.grid(row=2, column=0, columnspan=2,sticky=sticky)
|
||||||
self.stext.insert(Tkconstants.END,msg1)
|
self.stext.insert(Tkconstants.END,msg1)
|
||||||
|
|
||||||
@@ -435,7 +437,6 @@ class ConvDialog(Toplevel):
|
|||||||
if poll != 0:
|
if poll != 0:
|
||||||
msg = 'Failed\n'
|
msg = 'Failed\n'
|
||||||
text = self.p2.read()
|
text = self.p2.read()
|
||||||
text = self.p2.read()
|
|
||||||
text += self.p2.readerr()
|
text += self.p2.readerr()
|
||||||
msg += text
|
msg += text
|
||||||
msg += '\n'
|
msg += '\n'
|
||||||
@@ -451,42 +452,43 @@ class ConvDialog(Toplevel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def decrypt_ebook(self, infile, outdir, rscpath):
|
def decrypt_ebook(self, infile, outdir, rscpath):
|
||||||
|
apphome = self.apphome
|
||||||
rv = 1
|
rv = 1
|
||||||
name, ext = os.path.splitext(os.path.basename(infile))
|
name, ext = os.path.splitext(os.path.basename(infile))
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
if ext == '.epub':
|
if ext == '.epub':
|
||||||
outfile = os.path.join(outdir, name + '_nodrm.epub')
|
outfile = os.path.join(outdir, name + '_nodrm.epub')
|
||||||
self.p2 = processEPUB(infile, outfile, rscpath)
|
self.p2 = processEPUB(apphome, infile, outfile, rscpath)
|
||||||
return 0
|
return 0
|
||||||
if ext == '.pdb':
|
if ext == '.pdb':
|
||||||
self.p2 = processPDB(infile, outdir, rscpath)
|
self.p2 = processPDB(apphome, infile, outdir, rscpath)
|
||||||
return 0
|
return 0
|
||||||
if ext in ['.azw', '.azw1', '.prc', '.mobi', '.tpz']:
|
if ext in ['.azw', '.azw1', '.prc', '.mobi', '.tpz']:
|
||||||
self.p2 = processK4MOBI(infile, outdir, rscpath)
|
self.p2 = processK4MOBI(apphome, infile, outdir, rscpath)
|
||||||
return 0
|
return 0
|
||||||
if ext == '.pdf':
|
if ext == '.pdf':
|
||||||
outfile = os.path.join(outdir, name + '_nodrm.pdf')
|
outfile = os.path.join(outdir, name + '_nodrm.pdf')
|
||||||
self.p2 = processPDF(infile, outfile, rscpath)
|
self.p2 = processPDF(apphome, infile, outfile, rscpath)
|
||||||
return 0
|
return 0
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
# run as a subprocess via pipes and collect stdout, stderr, and return value
|
# run as a subprocess via pipes and collect stdout, stderr, and return value
|
||||||
def runit(ncmd, nparms):
|
def runit(apphome, ncmd, nparms):
|
||||||
cmdline = 'python ' + ncmd
|
cmdline = 'python ' + '"' + os.path.join(apphome, ncmd) + '" '
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
search_path = os.environ['PATH']
|
search_path = os.environ['PATH']
|
||||||
search_path = search_path.lower()
|
search_path = search_path.lower()
|
||||||
if search_path.find('python') < 0:
|
if search_path.find('python') < 0:
|
||||||
# if no python hope that win registry finds what is associated with py extension
|
# if no python hope that win registry finds what is associated with py extension
|
||||||
cmdline = ncmd
|
cmdline = '"' + os.path.join(apphome, ncmd) + '" '
|
||||||
cmdline += nparms
|
cmdline += nparms
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
return p2
|
return p2
|
||||||
|
|
||||||
def processK4MOBI(infile, outdir, rscpath):
|
def processK4MOBI(apphome, infile, outdir, rscpath):
|
||||||
cmd = '"' + os.path.join('lib','k4mobidedrm.py') + '" '
|
cmd = os.path.join('lib','k4mobidedrm.py')
|
||||||
parms = ''
|
parms = ''
|
||||||
pidnums = ''
|
pidnums = ''
|
||||||
pidspath = os.path.join(rscpath,'pidlist.txt')
|
pidspath = os.path.join(rscpath,'pidlist.txt')
|
||||||
@@ -511,30 +513,33 @@ def processK4MOBI(infile, outdir, rscpath):
|
|||||||
dpath = os.path.join(rscpath,filename)
|
dpath = os.path.join(rscpath,filename)
|
||||||
parms += '-k "' + dpath + '" '
|
parms += '-k "' + dpath + '" '
|
||||||
parms += '"' + infile +'" "' + outdir + '"'
|
parms += '"' + infile +'" "' + outdir + '"'
|
||||||
p2 = runit(cmd, parms)
|
p2 = runit(apphome, cmd, parms)
|
||||||
return p2
|
return p2
|
||||||
|
|
||||||
def processPDF(infile, outfile, rscpath):
|
def processPDF(apphome, infile, outfile, rscpath):
|
||||||
cmd = '"' + os.path.join('lib','decryptpdf.py') + '" '
|
cmd = os.path.join('lib','decryptpdf.py')
|
||||||
parms = '"' + infile + '" "' + outfile + '" "' + rscpath + '"'
|
parms = '"' + infile + '" "' + outfile + '" "' + rscpath + '"'
|
||||||
p2 = runit(cmd, parms)
|
p2 = runit(apphome, cmd, parms)
|
||||||
return p2
|
return p2
|
||||||
|
|
||||||
def processEPUB(infile, outfile, rscpath):
|
def processEPUB(apphome, infile, outfile, rscpath):
|
||||||
# invoke routine to check both Adept and Barnes and Noble
|
# invoke routine to check both Adept and Barnes and Noble
|
||||||
cmd = '"' + os.path.join('lib','decryptepub.py') + '" '
|
cmd = os.path.join('lib','decryptepub.py')
|
||||||
parms = '"' + infile + '" "' + outfile + '" "' + rscpath + '"'
|
parms = '"' + infile + '" "' + outfile + '" "' + rscpath + '"'
|
||||||
p2 = runit(cmd, parms)
|
p2 = runit(apphome, cmd, parms)
|
||||||
return p2
|
return p2
|
||||||
|
|
||||||
def processPDB(infile, outdir, rscpath):
|
def processPDB(apphome, infile, outdir, rscpath):
|
||||||
cmd = '"' + os.path.join('lib','decryptpdb.py') + '" '
|
cmd = os.path.join('lib','decryptpdb.py')
|
||||||
parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"'
|
parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"'
|
||||||
p2 = runit(cmd, parms)
|
p2 = runit(apphome, cmd, parms)
|
||||||
return p2
|
return p2
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
|
apphome = os.path.dirname(sys.argv[0])
|
||||||
|
apphome = os.path.abspath(apphome)
|
||||||
|
|
||||||
# windows may pass a spurious quoted null string as argv[1] from bat file
|
# windows may pass a spurious quoted null string as argv[1] from bat file
|
||||||
# simply work around this until we can figure out a better way to handle things
|
# simply work around this until we can figure out a better way to handle things
|
||||||
if len(argv) == 2:
|
if len(argv) == 2:
|
||||||
@@ -571,7 +576,7 @@ def main(argv=sys.argv):
|
|||||||
filenames.append(infile)
|
filenames.append(infile)
|
||||||
|
|
||||||
# start up gui app
|
# start up gui app
|
||||||
app = MainApp(dnd, filenames)
|
app = MainApp(apphome, dnd, filenames)
|
||||||
app.mainloop()
|
app.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class DocParser(object):
|
|||||||
ys = []
|
ys = []
|
||||||
gdefs = []
|
gdefs = []
|
||||||
|
|
||||||
# get path defintions, positions, dimensions for ecah glyph
|
# get path defintions, positions, dimensions for each glyph
|
||||||
# that makes up the image, and find min x and min y to reposition origin
|
# that makes up the image, and find min x and min y to reposition origin
|
||||||
minx = -1
|
minx = -1
|
||||||
miny = -1
|
miny = -1
|
||||||
@@ -305,6 +305,15 @@ class DocParser(object):
|
|||||||
lastGlyph = firstglyphList[last]
|
lastGlyph = firstglyphList[last]
|
||||||
else :
|
else :
|
||||||
lastGlyph = len(gidList)
|
lastGlyph = len(gidList)
|
||||||
|
|
||||||
|
# handle case of white sapce paragraphs with no actual glyphs in them
|
||||||
|
# by reverting to text based paragraph
|
||||||
|
if firstGlyph >= lastGlyph:
|
||||||
|
# revert to standard text based paragraph
|
||||||
|
for wordnum in xrange(first, last):
|
||||||
|
result.append(('ocr', wordnum))
|
||||||
|
return pclass, result
|
||||||
|
|
||||||
for glyphnum in xrange(firstGlyph, lastGlyph):
|
for glyphnum in xrange(firstGlyph, lastGlyph):
|
||||||
glyphList.append(glyphnum)
|
glyphList.append(glyphnum)
|
||||||
# include any extratokens if they exist
|
# include any extratokens if they exist
|
||||||
|
|||||||
@@ -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['Title']
|
||||||
|
title = title.replace('&','&')
|
||||||
|
title = title.replace('<','<')
|
||||||
|
title = title.replace('>','>')
|
||||||
|
meta_array['Title'] = title
|
||||||
|
authors = meta_array['Authors']
|
||||||
|
authors = authors.replace('&','&')
|
||||||
|
authors = authors.replace('<','<')
|
||||||
|
authors = authors.replace('>','>')
|
||||||
|
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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.1'
|
__version__ = '2.3'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -75,6 +75,7 @@ def zipUpDir(myzip, tempdir,localname):
|
|||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# and removal of . at start
|
||||||
|
# convert spaces to underscores
|
||||||
def cleanup_name(name):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
@@ -89,6 +90,7 @@ def cleanup_name(name):
|
|||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
|
one = one.replace(' ','_')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
@@ -248,7 +250,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, 1) # The version number of this plugin
|
version = (0, 2, 3) # 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
|
||||||
|
|||||||
@@ -189,6 +189,6 @@ def openKindleInfo(kInfoFile=None):
|
|||||||
raise DrmException('Error: .kindle-info file can not be found')
|
raise DrmException('Error: .kindle-info file can not be found')
|
||||||
return open(kinfopath,'r')
|
return open(kinfopath,'r')
|
||||||
else:
|
else:
|
||||||
if not os.path.isfile(kinfoFile):
|
if not os.path.isfile(kInfoFile):
|
||||||
raise DrmException('Error: kindle-info file can not be found')
|
raise DrmException('Error: kindle-info file can not be found')
|
||||||
return open(kInfoFile, 'r')
|
return open(kInfoFile, 'r')
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ class MainDialog(Tkinter.Frame):
|
|||||||
|
|
||||||
tpz = False
|
tpz = False
|
||||||
# Identify any Topaz Files
|
# Identify any Topaz Files
|
||||||
with open(mobipath, 'rb') as f:
|
f = file(mobipath, 'rb')
|
||||||
raw = f.read(3)
|
raw = f.read(3)
|
||||||
if raw.startswith('TPZ'):
|
if raw.startswith('TPZ'):
|
||||||
tpz = True
|
tpz = True
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class DocParser(object):
|
|||||||
ys = []
|
ys = []
|
||||||
gdefs = []
|
gdefs = []
|
||||||
|
|
||||||
# get path defintions, positions, dimensions for ecah glyph
|
# get path defintions, positions, dimensions for each glyph
|
||||||
# that makes up the image, and find min x and min y to reposition origin
|
# that makes up the image, and find min x and min y to reposition origin
|
||||||
minx = -1
|
minx = -1
|
||||||
miny = -1
|
miny = -1
|
||||||
@@ -305,6 +305,15 @@ class DocParser(object):
|
|||||||
lastGlyph = firstglyphList[last]
|
lastGlyph = firstglyphList[last]
|
||||||
else :
|
else :
|
||||||
lastGlyph = len(gidList)
|
lastGlyph = len(gidList)
|
||||||
|
|
||||||
|
# handle case of white sapce paragraphs with no actual glyphs in them
|
||||||
|
# by reverting to text based paragraph
|
||||||
|
if firstGlyph >= lastGlyph:
|
||||||
|
# revert to standard text based paragraph
|
||||||
|
for wordnum in xrange(first, last):
|
||||||
|
result.append(('ocr', wordnum))
|
||||||
|
return pclass, result
|
||||||
|
|
||||||
for glyphnum in xrange(firstGlyph, lastGlyph):
|
for glyphnum in xrange(firstGlyph, lastGlyph):
|
||||||
glyphList.append(glyphnum)
|
glyphList.append(glyphnum)
|
||||||
# include any extratokens if they exist
|
# include any extratokens if they exist
|
||||||
|
|||||||
@@ -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['Title']
|
||||||
|
title = title.replace('&','&')
|
||||||
|
title = title.replace('<','<')
|
||||||
|
title = title.replace('>','>')
|
||||||
|
meta_array['Title'] = title
|
||||||
|
authors = meta_array['Authors']
|
||||||
|
authors = authors.replace('&','&')
|
||||||
|
authors = authors.replace('<','<')
|
||||||
|
authors = authors.replace('>','>')
|
||||||
|
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:
|
||||||
|
|||||||
@@ -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.1'
|
__version__ = '2.3'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -75,6 +75,7 @@ def zipUpDir(myzip, tempdir,localname):
|
|||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# and removal of . at start
|
||||||
|
# convert spaces to underscores
|
||||||
def cleanup_name(name):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
@@ -89,6 +90,7 @@ def cleanup_name(name):
|
|||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
|
one = one.replace(' ','_')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
@@ -248,7 +250,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, 1) # The version number of this plugin
|
version = (0, 2, 3) # 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
|
||||||
|
|||||||
@@ -189,6 +189,6 @@ def openKindleInfo(kInfoFile=None):
|
|||||||
raise DrmException('Error: .kindle-info file can not be found')
|
raise DrmException('Error: .kindle-info file can not be found')
|
||||||
return open(kinfopath,'r')
|
return open(kinfopath,'r')
|
||||||
else:
|
else:
|
||||||
if not os.path.isfile(kinfoFile):
|
if not os.path.isfile(kInfoFile):
|
||||||
raise DrmException('Error: kindle-info file can not be found')
|
raise DrmException('Error: kindle-info file can not be found')
|
||||||
return open(kInfoFile, 'r')
|
return open(kInfoFile, 'r')
|
||||||
|
|||||||
@@ -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())
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class eRdrDeDRM(FileTypePlugin):
|
|||||||
Credit given to The Dark Reverser for the original standalone script.'
|
Credit given to The Dark Reverser for the original standalone script.'
|
||||||
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
||||||
author = 'DiapDealer' # The author of this plugin
|
author = 'DiapDealer' # The author of this plugin
|
||||||
version = (0, 0, 3) # The version number of this plugin
|
version = (0, 0, 4) # The version number of this plugin
|
||||||
file_types = set(['pdb']) # The file types that this plugin will be applied to
|
file_types = set(['pdb']) # 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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user