From 4f34a9a1960a1670be0d0bf7b6f7511e9dd0d7dd Mon Sep 17 00:00:00 2001 From: Apprentice Alf Date: Thu, 16 Jun 2011 06:59:20 +0100 Subject: [PATCH] tools v4.0 New calibre plugin interface (0.7.55) Dropped unswindle.pyw Added Android patch --- Adobe_PDF_Tools/ineptpdf.pyw | 36 +- .../K4MobiDeDRM_plugin/__init__.py | 116 +++ Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py | 21 +- .../K4MobiDeDRM_plugin/k4mobidedrm_orig.py | 199 ++++ .../K4MobiDeDRM_plugin/kgenpids.py | 79 +- .../plugin-import-name-k4mobidedrm.txt | 0 .../K4MobiDeDRM_plugin/topazextract.py | 135 +-- Calibre_Plugins/README-K4MobiDeDRM-plugin.txt | 7 +- Calibre_Plugins/eReaderPDB2PML_plugin.zip | Bin 13165 -> 13530 bytes .../{eReaderPDB2PML_plugin.py => __init__.py} | 11 +- .../eReaderPDB2PML_plugin/erdr2pml.py | 36 +- .../plugin-import-name-erdrpdb2pml.txt | 0 Calibre_Plugins/ignobleepub_plugin.zip | Bin 6820 -> 6993 bytes .../ignobleepub_plugin/ignobleepub_plugin.py | 14 +- Calibre_Plugins/ineptepub_plugin.zip | Bin 11178 -> 11351 bytes .../{ineptepub_plugin.py => __init__.py} | 16 +- .../plugin-import-name-ineptepub.txt | 0 Calibre_Plugins/ineptpdf_plugin.zip | Bin 22059 -> 22223 bytes .../{ineptpdf_plugin.py => __init__.py} | 16 +- .../plugin-import-name-ineptpdf.txt | 0 Calibre_Plugins/k4mobidedrm_plugin.zip | Bin 44419 -> 47877 bytes .../k4mobidedrm_plugin/k4mobidedrm_plugin.py | 374 -------- .../k4mobidedrm_plugin/k4mutils.py | 225 ++++- .../k4mobidedrm_plugin/k4pcutils.py | 219 ++++- .../k4mobidedrm_plugin/mobidedrm.py | 28 +- DeDRM_Macintosh_Application/DeDRM.app.txt | Bin 106676 -> 106962 bytes .../DeDRM.app/Contents/Info.plist | 4 +- .../Contents/Resources/Scripts/main.scpt | Bin 234456 -> 234956 bytes .../DeDRM.app/Contents/Resources/erdr2pml.py | 36 +- .../DeDRM.app/Contents/Resources/genbook.py | 21 +- .../DeDRM.app/Contents/Resources/ineptpdf.py | 1 + .../Contents/Resources/k4mobidedrm.py | 235 +---- .../DeDRM.app/Contents/Resources/k4mutils.py | 225 ++++- .../DeDRM.app/Contents/Resources/k4pcutils.py | 219 ++++- .../DeDRM.app/Contents/Resources/kgenpids.py | 79 +- .../DeDRM.app/Contents/Resources/mobidedrm.py | 28 +- .../Contents/Resources/topazextract.py | 135 +-- .../DeDRM_WinApp/DeDRM_lib/DeDRM_app.pyw | 27 +- .../DeDRM_WinApp/DeDRM_lib/lib/decryptepub.py | 5 +- .../DeDRM_WinApp/DeDRM_lib/lib/decryptpdf.py | 7 +- .../DeDRM_WinApp/DeDRM_lib/lib/erdr2pml.py | 36 +- .../DeDRM_WinApp/DeDRM_lib/lib/genbook.py | 21 +- .../DeDRM_WinApp/DeDRM_lib/lib/ineptpdf.py | 1 + .../DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py | 235 +---- .../DeDRM_WinApp/DeDRM_lib/lib/k4mutils.py | 225 ++++- .../DeDRM_WinApp/DeDRM_lib/lib/k4pcutils.py | 219 ++++- .../DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py | 79 +- .../DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py | 28 +- .../DeDRM_lib/lib/topazextract.py | 135 +-- .../KindleBooks.pyw | 0 .../README_KindleBooks.txt | 0 .../lib/cmbtc_v2.2.py | 0 .../lib/convert2xml.py | 0 .../lib/flatxml2html.py | 0 .../lib/flatxml2svg.py | 0 .../lib/genbook.py | 21 +- .../KindleBooks => KindleBooks}/lib/genxml.py | 0 .../lib/k4mdumpkinfo.py | 0 KindleBooks/lib/k4mobidedrm.py | 199 ++++ KindleBooks/lib/k4mutils.py | 357 +++++++ KindleBooks/lib/k4pcutils.py | 296 ++++++ .../lib/kgenpids.py | 79 +- .../lib}/mobidedrm.py | 28 +- .../lib/scrolltextwidget.py | 0 .../lib/stylexml2css.py | 0 .../lib/subasyncio.py | 0 .../lib/topazextract.py | 135 +-- .../KindleBooks/lib/k4mobidedrm.py | 374 -------- KindleBooks_Tools/KindleBooks/lib/k4mutils.py | 200 ---- .../KindleBooks/lib/k4pcutils.py | 117 --- .../KindleBooks/lib/mobidedrm.py | 409 -------- .../Kindle_4_Mac_Unswindle/K4Munswindle.pyw | 299 ------ .../README_K4Munswindle.txt | 61 -- .../gdb_kindle_cmds_r1.txt | 13 - .../gdb_kindle_cmds_r2.txt | 18 - .../gdb_kindle_cmds_r3.txt | 18 - .../gdb_kindle_cmds_r4.txt | 18 - .../Kindle_4_Mac_Unswindle/lib/mobidedrm.py | 409 -------- .../lib/scrolltextwidget.py | 27 - .../Kindle_4_Mac_Unswindle/lib/subasyncio.py | 149 --- .../README-unswindlev7.txt | 5 - .../Kindle_4_PC_Unswindle/unswindle.pyw | 883 ------------------ Kindle_for_Android_Patch/ReadMe_K4Android.txt | 50 + Kindle_for_Android_Patch/kindle3.patch | 100 ++ Mobi_Additional_Tools/lib/mobidedrm.py | 28 +- ReadMe_First.txt | 33 +- eReader_PDB_Tools/lib/erdr2pml.py | 36 +- 87 files changed, 3336 insertions(+), 4559 deletions(-) create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt rename Calibre_Plugins/eReaderPDB2PML_plugin/{eReaderPDB2PML_plugin.py => __init__.py} (94%) create mode 100644 Calibre_Plugins/eReaderPDB2PML_plugin/plugin-import-name-erdrpdb2pml.txt rename Calibre_Plugins/ineptepub_plugin/{ineptepub_plugin.py => __init__.py} (97%) create mode 100644 Calibre_Plugins/ineptepub_plugin/plugin-import-name-ineptepub.txt rename Calibre_Plugins/ineptpdf_plugin/{ineptpdf_plugin.py => __init__.py} (99%) create mode 100644 Calibre_Plugins/ineptpdf_plugin/plugin-import-name-ineptpdf.txt delete mode 100644 Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py rename {KindleBooks_Tools/KindleBooks => KindleBooks}/KindleBooks.pyw (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/README_KindleBooks.txt (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/cmbtc_v2.2.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/convert2xml.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/flatxml2html.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/flatxml2svg.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/genbook.py (97%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/genxml.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/k4mdumpkinfo.py (100%) create mode 100644 KindleBooks/lib/k4mobidedrm.py create mode 100644 KindleBooks/lib/k4mutils.py create mode 100644 KindleBooks/lib/k4pcutils.py rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/kgenpids.py (74%) rename {KindleBooks_Tools/Kindle_4_PC_Unswindle => KindleBooks/lib}/mobidedrm.py (96%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/scrolltextwidget.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/stylexml2css.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/subasyncio.py (100%) rename {KindleBooks_Tools/KindleBooks => KindleBooks}/lib/topazextract.py (84%) delete mode 100644 KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py delete mode 100644 KindleBooks_Tools/KindleBooks/lib/k4mutils.py delete mode 100644 KindleBooks_Tools/KindleBooks/lib/k4pcutils.py delete mode 100644 KindleBooks_Tools/KindleBooks/lib/mobidedrm.py delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/K4Munswindle.pyw delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/README_K4Munswindle.txt delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r1.txt delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r2.txt delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r3.txt delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r4.txt delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/scrolltextwidget.py delete mode 100644 KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/subasyncio.py delete mode 100644 KindleBooks_Tools/Kindle_4_PC_Unswindle/README-unswindlev7.txt delete mode 100644 KindleBooks_Tools/Kindle_4_PC_Unswindle/unswindle.pyw create mode 100644 Kindle_for_Android_Patch/ReadMe_K4Android.txt create mode 100644 Kindle_for_Android_Patch/kindle3.patch diff --git a/Adobe_PDF_Tools/ineptpdf.pyw b/Adobe_PDF_Tools/ineptpdf.pyw index ccdd9e4..c9a419b 100644 --- a/Adobe_PDF_Tools/ineptpdf.pyw +++ b/Adobe_PDF_Tools/ineptpdf.pyw @@ -1,5 +1,5 @@ #! /usr/bin/env python -# ineptpdf.pyw, version 7.7 +# ineptpdf.pyw, version 7.9 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.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. @@ -156,6 +157,7 @@ def _load_crypto_libcrypto(): return out.raw class AES(object): + MODE_CBC = 0 @classmethod def new(cls, userkey, mode, iv): self = AES() @@ -1531,16 +1533,30 @@ class PDFDocument(object): bookkey = bookkey[index:] ebx_V = int_value(param.get('V', 4)) ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6)) - # added because of the booktype / decryption book session key error - if ebx_V == 3: - V = 3 - elif ebx_V < 4 or ebx_type < 6: - V = ord(bookkey[0]) - bookkey = bookkey[1:] + # added because of improper booktype / decryption book session key errors + if length > 0: + if len(bookkey) == length: + if ebx_V == 3: + V = 3 + else: + V = 2 + elif len(bookkey) == length + 1: + V = ord(bookkey[0]) + 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: - V = 2 - if length and len(bookkey) != length: - raise ADEPTError('error decrypting book session key') + # 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: + V = 2 self.decrypt_key = bookkey self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2 self.decipher = self.decrypt_rc4 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py b/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py new file mode 100644 index 0000000..971c02b --- /dev/null +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +from __future__ import with_statement + +from calibre.customize import FileTypePlugin +from calibre.gui2 import is_ok_to_use_qt +# from calibre.ptempfile import PersistentTemporaryDirectory + +from calibre_plugins.k4mobidedrm import kgenpids +from calibre_plugins.k4mobidedrm import topazextract +from calibre_plugins.k4mobidedrm import mobidedrm + +import sys +import os +import re + +class K4DeDRM(FileTypePlugin): + name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin + description = 'Removes DRM from K4PC and Mac, Kindle Mobi and Topaz files. 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 + author = 'DiapDealer, SomeUpdates' # The author of this plugin + version = (0, 3, 1) # The version number of this plugin + 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 + priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm + minimum_calibre_version = (0, 7, 55) + + def run(self, path_to_ebook): + + k4 = True + if sys.platform.startswith('linux'): + k4 = False + pids = [] + serials = [] + kInfoFiles = [] + + # Get supplied list of PIDs to try from plugin customization. + customvalues = self.site_customization.split(',') + for customvalue in customvalues: + customvalue = str(customvalue) + customvalue = customvalue.strip() + if len(customvalue) == 10 or len(customvalue) == 8: + pids.append(customvalue) + else : + if len(customvalue) == 16 and customvalue[0] == 'B': + serials.append(customvalue) + else: + print "%s is not a valid Kindle serial number or PID." % str(customvalue) + + # Load any kindle info files (*.info) included Calibre's config directory. + try: + # Find Calibre's configuration directory. + confpath = os.path.split(os.path.split(self.plugin_path)[0])[0] + print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath + files = os.listdir(confpath) + filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + fpath = os.path.join(confpath, filename) + kInfoFiles.append(fpath) + print 'K4MobiDeDRM: Kindle info/kinf file %s found in config folder.' % filename + except IOError: + print 'K4MobiDeDRM: Error reading kindle info/kinf files from config directory.' + pass + + mobi = True + magic3 = file(path_to_ebook,'rb').read(3) + if magic3 == 'TPZ': + mobi = False + + bookname = os.path.splitext(os.path.basename(path_to_ebook))[0] + + if mobi: + mb = mobidedrm.MobiBook(path_to_ebook) + else: + mb = topazextract.TopazBook(path_to_ebook) + + title = mb.getBookTitle() + md1, md2 = mb.getPIDMetaInfo() + pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) + + try: + mb.processBook(pidlst) + + except mobidedrm.DrmException: + #if you reached here then no luck raise and exception + if is_ok_to_use_qt(): + from PyQt4.Qt import QMessageBox + d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) + d.show() + d.raise_() + d.exec_() + raise Exception("K4MobiDeDRM plugin could not decode the file") + except topazextract.TpzDRMError: + #if you reached here then no luck raise and exception + if is_ok_to_use_qt(): + from PyQt4.Qt import QMessageBox + d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) + d.show() + d.raise_() + d.exec_() + raise Exception("K4MobiDeDRM plugin could not decode the file") + + print "Success!" + if mobi: + of = self.temporary_file(bookname+'.mobi') + mb.getMobiFile(of.name) + else : + of = self.temporary_file(bookname+'.htmlz') + mb.getHTMLZip(of.name) + mb.cleanup() + return of.name + + def customization_help(self, gui=False): + return 'Enter 10 character PIDs and/or Kindle serial numbers, use a comma (no spaces) to separate each PID or SerialNumber from the next.' diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py b/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py index 11258d1..f779cee 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py @@ -21,10 +21,21 @@ from struct import unpack # local support routines -import convert2xml -import flatxml2html -import flatxml2svg -import stylexml2css +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre : + from calibre_plugins.k4mobidedrm import convert2xml + from calibre_plugins.k4mobidedrm import flatxml2html + from calibre_plugins.k4mobidedrm import flatxml2svg + from calibre_plugins.k4mobidedrm import stylexml2css +else : + import convert2xml + import flatxml2html + import flatxml2svg + import stylexml2css # Get a 7 bit encoded number from a file @@ -504,7 +515,7 @@ def generateBook(bookDir, raw, fixedimage): opfstr += ' \n' opfstr += '\n' opfstr += ' \n' - opfstr += ' \n' + opfstr += ' \n' # adding image files to manifest filenames = os.listdir(imgDir) filenames = sorted(filenames) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py b/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py new file mode 100644 index 0000000..14556db --- /dev/null +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python + +from __future__ import with_statement + +# engine to remove drm from Kindle for Mac and Kindle for PC books +# for personal use for archiving and converting your ebooks + +# PLEASE DO NOT PIRATE EBOOKS! + +# We want all authors and publishers, and eBook stores to live +# long and prosperous lives but at the same time we just want to +# be able to read OUR books on whatever device we want and to keep +# readable for a long, long time + +# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, +# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates +# and many many others + + +__version__ = '3.1' + +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 +import os, csv, getopt +import string +import re + +class DrmException(Exception): + pass + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + from calibre_plugins.k4mobidedrm import mobidedrm + from calibre_plugins.k4mobidedrm import topazextract + from calibre_plugins.k4mobidedrm import kgenpids +else: + import mobidedrm + import topazextract + import kgenpids + + +# cleanup bytestring filenames +# borrowed from calibre from calibre/src/calibre/__init__.py +# added in removal of non-printing chars +# and removal of . at start +# convert spaces to underscores +def cleanup_name(name): + _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') + substitute='_' + one = ''.join(char for char in name if char in string.printable) + one = _filename_sanitize.sub(substitute, one) + one = re.sub(r'\s', ' ', one).strip() + one = re.sub(r'^\.+$', '_', one) + one = one.replace('..', substitute) + # Windows doesn't like path components that end with a period + if one.endswith('.'): + one = one[:-1]+substitute + # Mac and Unix don't like file names that begin with a full stop + if len(one) > 0 and one[0] == '.': + one = substitute+one[1:] + one = one.replace(' ','_') + return one + +def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): + # handle the obvious cases at the beginning + if not os.path.isfile(infile): + print "Error: Input file does not exist" + return 1 + + mobi = True + magic3 = file(infile,'rb').read(3) + if magic3 == 'TPZ': + mobi = False + + bookname = os.path.splitext(os.path.basename(infile))[0] + + if mobi: + mb = mobidedrm.MobiBook(infile) + else: + mb = topazextract.TopazBook(infile) + + title = mb.getBookTitle() + print "Processing Book: ", title + filenametitle = cleanup_name(title) + outfilename = bookname + if len(bookname)>4 and len(filenametitle)>4 and bookname[:4] != filenametitle[:4]: + outfilename = outfilename + "_" + filenametitle + + # build pid list + md1, md2 = mb.getPIDMetaInfo() + pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) + + try: + mb.processBook(pidlst) + + except mobidedrm.DrmException, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 + except topazextract.TpzDRMError, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 + except Exception, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 + + if mobi: + outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') + mb.getMobiFile(outfile) + return 0 + + # topaz: + print " Creating NoDRM HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz') + mb.getHTMLZip(zipname) + + print " Creating SVG HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_SVG' + '.htmlz') + mb.getSVGZip(zipname) + + print " Creating XML ZIP Archive" + zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') + mb.getXMLZip(zipname) + + # remove internal temporary directory of Topaz pieces + mb.cleanup() + + return 0 + + +def usage(progname): + print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks" + print "Usage:" + print " %s [-k ] [-p ] [-s ] " % progname + +# +# Main +# +def main(argv=sys.argv): + progname = os.path.basename(argv[0]) + + k4 = False + kInfoFiles = [] + serials = [] + pids = [] + + print ('K4MobiDeDrm v%(__version__)s ' + 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) + + print ' ' + try: + opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") + except getopt.GetoptError, err: + print str(err) + usage(progname) + sys.exit(2) + if len(args)<2: + usage(progname) + sys.exit(2) + + for o, a in opts: + if o == "-k": + if a == None : + raise DrmException("Invalid parameter for -k") + kInfoFiles.append(a) + if o == "-p": + if a == None : + raise DrmException("Invalid parameter for -p") + pids = a.split(',') + if o == "-s": + if a == None : + raise DrmException("Invalid parameter for -s") + serials = a.split(',') + + # try with built in Kindle Info files + k4 = True + if sys.platform.startswith('linux'): + k4 = False + kInfoFiles = None + infile = args[0] + outdir = args[1] + return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) + + +if __name__ == '__main__': + sys.stdout=Unbuffered(sys.stdout) + sys.exit(main()) + diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/kgenpids.py b/Calibre_Plugins/K4MobiDeDRM_plugin/kgenpids.py index c10f105..c9d8944 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/kgenpids.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/kgenpids.py @@ -11,16 +11,28 @@ from struct import pack, unpack, unpack_from class DrmException(Exception): pass -global kindleDatabase global charMap1 -global charMap2 global charMap3 global charMap4 -if sys.platform.startswith('win'): - from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 -if sys.platform.startswith('darwin'): - from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + if sys.platform.startswith('win'): + from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber +else: + if sys.platform.startswith('win'): + from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" @@ -66,50 +78,7 @@ def decode(data,map): value = (((high * len(map)) ^ 0x80) & 0xFF) + low result += pack("B",value) return result - -# 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 - global charMap1 - global charMap2 - encryptedValue = decode(kindleDatabase[hashedKey],charMap2) - if sys.platform.startswith('win'): - return CryptUnprotectData(encryptedValue,"") - else: - 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): - global charMap2 - 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): - global charMap2 - 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 # @@ -222,15 +191,15 @@ def getKindlePid(pidlst, rec209, token, serialnum): return pidlst -# Parse the EXTH header records and parse the Kindleinfo -# file to calculate the book pid. +# parse the Kindleinfo file to calculate the book pid. + +keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] def getK4Pids(pidlst, rec209, token, kInfoFile): - global kindleDatabase global charMap1 kindleDatabase = None try: - kindleDatabase = parseKindleInfo(kInfoFile) + kindleDatabase = getDBfromFile(kInfoFile) except Exception, message: print(message) kindleDatabase = None @@ -241,10 +210,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): try: # Get the Mazama Random number - MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") + MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] # Get the kindle account token - kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") + kindleAccountToken = kindleDatabase["kindle.account.tokens"] except KeyError: print "Keys not found in " + kInfoFile return pidlst diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt b/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt new file mode 100644 index 0000000..e69de29 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/topazextract.py b/Calibre_Plugins/K4MobiDeDRM_plugin/topazextract.py index 59bc5fa..12b92e2 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/topazextract.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/topazextract.py @@ -10,7 +10,12 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + import os, csv, getopt import zlib, zipfile, tempfile, shutil from struct import pack @@ -18,10 +23,32 @@ from struct import unpack class TpzDRMError(Exception): pass + # local support routines -import kgenpids -import genbook +if inCalibre: + from calibre_plugins.k4mobidedrm import kgenpids + from calibre_plugins.k4mobidedrm import genbook +else: + import kgenpids + import genbook + + +# recursive zip creation support routine +def zipUpDir(myzip, tdir, localname): + currentdir = tdir + if localname != "": + currentdir = os.path.join(currentdir,localname) + list = os.listdir(currentdir) + for file in list: + afilename = file + localfilePath = os.path.join(localname, afilename) + realfilePath = os.path.join(currentdir,file) + if os.path.isfile(realfilePath): + myzip.write(realfilePath, localfilePath) + elif os.path.isdir(realfilePath): + zipUpDir(myzip, tdir, localfilePath) + # # Utility routines # @@ -110,9 +137,9 @@ def decryptDkeyRecords(data,PID): class TopazBook: - def __init__(self, filename, outdir): + def __init__(self, filename): self.fo = file(filename, 'rb') - self.outdir = outdir + self.outdir = tempfile.mkdtemp() self.bookPayloadOffset = 0 self.bookHeaderRecords = {} self.bookMetadata = {} @@ -317,21 +344,33 @@ class TopazBook: file(outputFile, 'wb').write(record) print " " + def getHTMLZip(self, zipname): + htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html') + htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf') + if os.path.isfile(os.path.join(self.outdir,'cover.jpg')): + htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg') + htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css') + zipUpDir(htmlzip, self.outdir, 'img') + htmlzip.close() -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) + def getSVGZip(self, zipname): + svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml') + zipUpDir(svgzip, self.outdir, 'svg') + zipUpDir(svgzip, self.outdir, 'img') + svgzip.close() + def getXMLZip(self, zipname): + xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + targetdir = os.path.join(self.outdir,'xml') + zipUpDir(xmlzip, targetdir, '') + zipUpDir(xmlzip, self.outdir, 'img') + xmlzip.close() + + def cleanup(self): + if os.path.isdir(self.outdir): + shutil.rmtree(self.outdir, True) def usage(progname): print "Removes DRM protection from Topaz ebooks and extract the contents" @@ -383,58 +422,46 @@ def main(argv=sys.argv): return 1 bookname = os.path.splitext(os.path.basename(infile))[0] - tempdir = tempfile.mkdtemp() - tb = TopazBook(infile, tempdir) + tb = TopazBook(infile) title = tb.getBookTitle() print "Processing Book: ", title keysRecord, keysRecordRecord = tb.getPIDMetaInfo() pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) try: + print "Decrypting Book" tb.processBook(pidlst) + + print " Creating HTML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz') + tb.getHTMLZip(zipname) + + print " Creating SVG ZIP Archive" + zipname = os.path.join(outdir, bookname + '_SVG' + '.htmlz') + tb.getSVGZip(zipname) + + print " Creating XML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_XML' + '.zip') + tb.getXMLZip(zipname) + + # removing internal temporary directory of pieces + tb.cleanup() + except TpzDRMError, e: print str(e) - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) + tb.cleanup() return 1 - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() - - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() - - shutil.rmtree(tempdir, True) + except Exception, e: + print str(e) + tb.cleanup + return 1 return 0 if __name__ == '__main__': + sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) diff --git a/Calibre_Plugins/README-K4MobiDeDRM-plugin.txt b/Calibre_Plugins/README-K4MobiDeDRM-plugin.txt index d0909f5..1086890 100644 --- a/Calibre_Plugins/README-K4MobiDeDRM-plugin.txt +++ b/Calibre_Plugins/README-K4MobiDeDRM-plugin.txt @@ -5,12 +5,11 @@ This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If yo This plugin is meant to remove the DRM from .prc, .azw, .azw1, and .tpz ebooks. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books. Installation: -Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the 'Add' button. You're done. +Go to Calibre's Preferences page... click on the Plugins button. Click on the "Add a new plugin" button at the bottom of the screen. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the "Add" (or it may say "Open" button. Then click on the "Yes" button in the warning dialog that appears. A Confirmation dialog appears that says the plugin has been installed. + -Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added. - Configuration: -Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter a comma separated list of your 10 digit PIDs. Include in this list (again separated by commas) any 16 digit serial numbers the standalone Kindles you may have (these typically begin "B0...") This is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books. +Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your 10 digit PID. If you have more than one PID separate them with a comma (no spaces). If you have a standalone Kindle include the 16 digit serial number (these typically begin "B0...") in this list (again separated from the PIDs or other serial numbers with a comma (no spaces). This configuration is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books. Troubleshooting: diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin.zip b/Calibre_Plugins/eReaderPDB2PML_plugin.zip index c852068c1061b61563ad845de6619ac29dd76024..4fe2084343003226c0379febeb1d6162da652385 100644 GIT binary patch delta 9039 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 J-`zl<{{_Q&m^1(Y delta 8703 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 diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML_plugin.py b/Calibre_Plugins/eReaderPDB2PML_plugin/__init__.py similarity index 94% rename from Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML_plugin.py rename to Calibre_Plugins/eReaderPDB2PML_plugin/__init__.py index 5585cf5..3fb750c 100644 --- a/Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML_plugin.py +++ b/Calibre_Plugins/eReaderPDB2PML_plugin/__init__.py @@ -31,10 +31,15 @@ # 0.0.1 - Initial release # 0.0.2 - updated to distinguish it from earlier non-openssl version # 0.0.3 - removed added psyco code as it is not supported under Calibre's Python 2.7 +# 0.0.4 - minor typos fixed +# 0.0.5 - updated to the new calibre plugin interface import sys, os from calibre.customize import FileTypePlugin +from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.constants import iswindows, isosx +from calibre_plugins.erdrpdb2pml import erdr2pml class eRdrDeDRM(FileTypePlugin): name = 'eReader PDB 2 PML' # Name of the plugin @@ -42,16 +47,14 @@ class eRdrDeDRM(FileTypePlugin): Credit given to The Dark Reverser for the original standalone script.' supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on author = 'DiapDealer' # The author of this plugin - version = (0, 0, 4) # The version number of this plugin + version = (0, 0, 5) # The version number of this plugin file_types = set(['pdb']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import + minimum_calibre_version = (0, 7, 55) def run(self, path_to_ebook): - from calibre.ptempfile import PersistentTemporaryDirectory - from calibre.constants import iswindows, isosx global bookname, erdr2pml - import erdr2pml infile = path_to_ebook bookname = os.path.splitext(os.path.basename(infile))[0] diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/erdr2pml.py b/Calibre_Plugins/eReaderPDB2PML_plugin/erdr2pml.py index 6df9e13..0e59d8d 100644 --- a/Calibre_Plugins/eReaderPDB2PML_plugin/erdr2pml.py +++ b/Calibre_Plugins/eReaderPDB2PML_plugin/erdr2pml.py @@ -58,8 +58,9 @@ # 0.17 - added support for pycrypto's DES as well # 0.18 - on Windows try PyCrypto first and OpenSSL next # 0.19 - Modify the interface to allow use of import +# 0.20 - modify to allow use inside new interface for calibre plugins -__version__='0.19' +__version__='0.20' class Unbuffered: def __init__(self, stream): @@ -71,32 +72,50 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) - import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + Des = None if sys.platform.startswith('win'): # first try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() if Des == None: # they try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() else: # first try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() if Des == None: # then try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() # if that did not work then use pure python implementation # of DES and try to speed it up with Psycho if Des == None: - import python_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import python_des + else: + import python_des Des = python_des.Des # Import Psyco if available try: @@ -480,5 +499,6 @@ def main(argv=None): if __name__ == "__main__": + sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/plugin-import-name-erdrpdb2pml.txt b/Calibre_Plugins/eReaderPDB2PML_plugin/plugin-import-name-erdrpdb2pml.txt new file mode 100644 index 0000000..e69de29 diff --git a/Calibre_Plugins/ignobleepub_plugin.zip b/Calibre_Plugins/ignobleepub_plugin.zip index 3e0bfe1caf6f149542e41d753faa0bfe0be966dc..c03ca38b68346be132b2741dae7fdafe7cd9fb9c 100644 GIT binary patch delta 5387 zcma*rWlWr1m{mAC|=x(yHlLOI)fG{?(XiqKymL3?(Xhx#kD|@qJ>hN;uI*d z{l48~v)OF2Kc175oIH2#oS#=A-wZ#X@(vjV9|QuSffCEB#Q2Srr2<}oKxnbcO957pz3W{n-};7#^swcL``WM*?Y)59lFF~^mCA$9Mi zzxZ=zechq~UDNv&J}_^?QNX2G-qi!8xTt(uDNK65#XB|&q1S!?r!OZsg(FB=ucX2@ z*Yp&@`pTIe(MVWvDZ7);KG1e^CKSd&ID_Gp99rB}s#g0#M}z#QLLxyMK6CYKy{1H{ zI%{0K>9yNL`jH6e6DD&W$-2!BJBz<%w4>3#P((enWjL(?)JW!t=w7dk)Wv zncjOHmi`Gx9T;k3DtDyOkLt*$Mp4tN*Eg<4Nhy(Wq|UE|tGevw>jla}{pI|24cJ^r z-WBB^D2<;rj->3Dm(iFUpb20XCscj4QO%`pr(HKeX3%!dT0N8B;S{2&!1HcQ?x&0p zi|SJ|K;{$8=I$Fx*eX?&S+69L_};MJ`=0b1PJ& zvcMxrR-YO>OIsp=P@c~<5f<9V5ZZ*;X>A5xo@g`Fam8d@l$RbvDlg4C4dmpu9O*{E zm>J|`VgRuDSl2)G4dpFyAc0DLQ2~~>er;TSrujZp*tW#9eb!I=6pdR8ipA08cu?@t zuKONS+^`*hF3go|b%jVWYy}<#Pv(s@h^W?5YLCM(>Lu$px&-sb%SU}BKO%>`)_`tP zZSts{s+2e*AW0wO&fL>KYh{a>lVX?13Rh9q0tJ2u&v9Pla)MzMxIlEdK)huEPcBk; zZzqzh=xvO1ut7xTQJD(%>{q_1r7MO{RGmZaNLsG?QGVJY^LWi;6T=Wl!W!s@PDMX@ zpnt|(aae1AF*q|cx!|{$M%_d`V+sVhI%fZlBtt$qpg^J>uF98%-uw=8Oym#II%)_T zAVTlGC@4qhOvO(79APrw%1!MWWbue$7@W;nG22H=^C(17=KyybC7VBDjAF_MRh@n* zjV!|;tTFJCT3;A3V!DvdtflU-vDJ{p6|<%)vCdTwWznhcXqPz8V~Thv*3(ek8ZZ+C zZ+T;VqLo{anSzHPnNz86$g}ZInq-gxy1!C1>Y6QpQlb7Ibmg?k4CYSWEBvL{R(m5UwT|kS4kdd7 z)-<@riH(N-rg$iJPm{JZ5G`743PbI)S^Zel z^RI5)PuqGFN(?8vT5mQ7NB`WuIX&{#)L+NUBZ|+?GVJ_TQF^Lk=GnF4kGL#GL@S>t zvj^>*7|6Jnc+$O9Z`oxAB1-A7jHrF@HtlBHV^N{loCLQSyLvw;f1OYs9rr9D2lT$Z ze&k9U&@N0A1bojnuR3l!%cI6}HiqE)sT5qZcsK|JniQIeD8|3v_H0>yk7c9RIt{mD zZ0zvzXssiy_1-DKneR#Pm9v+6p~8n*f+&QdVITWGm~Y62A;22Ay?L(%gEGjzeEon= z06qe1YD$7P^YbnPJLI{gF`l7>O~56~68EZ9mXwI`kkDQb_mbO&M7o1Is%#((N4|`B zqMz~nb;R;8h(4WcO38XozszHKmY4?L;DK)Nf0U3%jfA=d^;b%(L71LdDvKDPD%F`s z9td%@t}-c{2lxV}yeUqyjIq3H zGRL0#9l`s%n7qPl$kC70ZdB1C6UU;INz;xOyRQ+BoSy{9NK{lP!F@&=QHhA%0tK`l zh6zQpSH_h-{=lUbwnqUScjFo~(<#>@uG@H!TK}Ms;CjPxVQ+0QZTaCcB;w_faUnKA@q>^p?02 z-zi#*lPm&n6QYr2D18V+qAMeMm~;dv3ZN7I;1y&D?`imVAh4!;7CcjD8P4G$?intB7cQLuN7_-p;d1nH>sHu#tIZDF^{3P7iLeLS&^Pc`cQD?rY>Dm zKDc!BJfT6c8XKi$;(qc>k-#dS52#NFdrxzIh91R7?%&+_{*QvwFF{{J+>PSO? zhF)$pCw9;=y4jqw`h!vC&xt1?*4R1B@zEXO7QFuytuO_{A>)D(<~4-_1@@0(z6~xp z)PUU6xaOip*M_}2y0AImVmL!AW4%S^{RK5=VuJO$4+ z77isd(MB3n;z*U>0dgKoNhCDYm}s_v8X42vJBBCXnUkOA7E6l9>a8d-)xwSmeU0Qe z?Ot9=o{;aFmi!PK0mmO*T;v)%p8ZHaxCD?gYTw03=_#VI7{1tpHB3HR(Vneg;whL=Ui3agis)ucCp(XqS8r4t0y8iwa1$&-bQ4D zWzrpw$ww<>H**?lYQv{}9>r9cYnb(81$jsXCaHMeU+WI;_*>>uJz(dkvi_U zJXQDiZ~?E$)s|}aB4Ic+e)1V`!Y>(iAl%P0cZGI(C}D~QcMDAl5r1%TJ3Xj?@Vrn$ ziIq^7b0qTkGV^*6xiQTZlapss2f!=I8l1x$a_;p=r>td0cB~7hYm0Y8SV0d~Y0_Va zptP2AOIt6m!)(WX!DS(icsQjw5s0uB45PSY-!cszBoFsl`9` z^u~9dBZXb1qBPGA)}0xD)mPEJ7-d;w)!~54f1Npy_X2?Y$1@r4t;o|h@Kva~|iJW~(HNrgY zY9n_K!{3CZxo*tuudcRx0>|Z%Ewp0Gu>{jhxs31;Xf^7w*};{J_RcGJUFw={hB!{f z4?zSc7sIZOLqg`lFXRoYD0#2WzvXmQ1xHJ&4|e4SoLJ?eq@k$7Z%`b`roQ40HCC3b z&UcB)OEpXSFXbYKw%tx+8+bIHQuWf;A^)^}AJ^VY)tMuTk=)ugp-Jj~yhoThx+2Em zf%ou$oc%I`@xE|4LtP8!hweOtrPt5O`=QO-cgIT$a`p53{LCVk*WI1VWo0Sei~IAV z?}u*q3AiG``$RTjqU~h}gu`C=A>(8Rq7D-uct@N?)Iu@wv>%JMc6|-=Tr06B+nCzb z0Jjb%GXLN;^l&LSGPWT+*L@2jL>WiM#_-Z7V8R+# zy8YT8NPrN$chO%><=tS(Mmg!4n|jw0Z>Na*2_NoBu&7FI*n&#ZVqbRu`=J~S3%-d#3C3~ezfr1CdEsCr{32Xxw=IxQ_`fIs&t=dj-`_m0#Nwq-qyE&-VBfsg+Gd+@=(F=H8FoTyTOA|5Y`DVy?VS!S_u6uaGwK`u zH>P$eIUS=oj6T|}8_i2}WRSVmYkNiz@TIN;;43)ZGC@}MyV|;eC6-AZEFIS{u=g4Z zfX5=%hvu&2@*96YGiu8c3wS39XKP=@sxpANtRA=63t*u;*OsZt?6ym7^1BW!(_}it z(@r;TdatJG)*W&#=g(_9VAd-~D`04I5x{qUfbdTEpNi0 zltFGT&$F-Ea*>=QbvM1&9P)}3DRrdkQp=^*nsq&V%EpHi@5N+2L%Gj|92Q5X6y8i1 z!u%zIV*t)=S~;=zS(F=xgpqJA@@3*9in%jLmk{~o=c~QlOHVQqzXfrgQgpdG*eqPZ zisX?O_U_UyOkyyOY<^T8_T$+T-KvVeSTZ%8~y@>ktqxiNa=S>-C*le^=n!rYo*&2I@)(mfv$L5;m9+y!)F=ZL&)5bV2(DjPE z<=Y`6vc*j+k8>GHVGjrFn_Q@9gcYw(g&O@Y9MNJz|gGm)-azY0I z-<}kKhpP#B$3HA&3kdGpYsf^ZdZEK@;YplWw$h6B=8{FLtO4l73`6Rp@?==K9; zyoDR$>%DV;&$)HC=!dgwQR-6>y`W&0p%EO+%|@QstMlVyCP0bo1%nL3|-KxU{ffw0pFW);dkhl=NXVu|qjgEGc(dwC;>O znl)%iJ}3DyU~*nW*m;AFDAYAE&H{a4zbU3iwPG>T^?>qrez5Mxdau@9bB=zIuIY#_s>Hq$X0%|xxi+{tOW?Jd1yQ~WmZ4VZ1_Zey}l|= zCIZ&@!}a3cP<3Gh_jPklxJD6MM-1~iBem|9Fb1D&f76KyjBSVfpgw1K$3M}hLU_9& z0h$KU-C$*5Z?^OLEDIeP_dzxwp5 z!-c5EBh~@SeZmwvq@D}}uPT866a1;sG&5$Be`c;(s5OQBi^!OS$;0eru{$^84?dLv*VJ87*CT zAIh{XtJp@VS-yZIIPheHOJY3^QqCL>-x1PTs9wyel`i%1=7G zwQSFhW%%)zoyPax!>aRu=z`J3=VR||Psv-e+Plu20-iGG_l389qb48Q%qMK^T-MrlX)j)=Cld!*o;2y<{BPi2A_Nr%{d4^v zmiYW%OZ>Y@j^vGik&@wm{;Q5Y)=n1IUjJI--}mi&1Uo5_+W*z^KjZ%iE(}^E0sX%^ Z|4sd;ZvZJVHZlkqg!Z=_59vP<{{eONJ#7F0 delta 5338 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 diff --git a/Calibre_Plugins/ignobleepub_plugin/ignobleepub_plugin.py b/Calibre_Plugins/ignobleepub_plugin/ignobleepub_plugin.py index 04ffc33..b63c949 100644 --- a/Calibre_Plugins/ignobleepub_plugin/ignobleepub_plugin.py +++ b/Calibre_Plugins/ignobleepub_plugin/ignobleepub_plugin.py @@ -4,7 +4,7 @@ # Released under the terms of the GNU General Public Licence, version 3 or # later. # -# Requires Calibre version 0.6.44 or higher. +# Requires Calibre version 0.7.55 or higher. # # All credit given to I <3 Cabbages for the original standalone scripts. # I had the much easier job of converting them to Calibre a plugin. @@ -48,6 +48,7 @@ # 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 +# 0.1.6 - update ot the new calibre plugin interface """ Decrypt Barnes & Noble ADEPT encrypted EPUB books. @@ -266,6 +267,7 @@ def plugin_main(userkey, inpath, outpath): return 0 from calibre.customize import FileTypePlugin +from calibre.constants import iswindows, isosx class IgnobleDeDRM(FileTypePlugin): name = 'Ignoble Epub DeDRM' @@ -273,8 +275,8 @@ class IgnobleDeDRM(FileTypePlugin): Credit given to I <3 Cabbages for the original stand-alone scripts.' supported_platforms = ['linux', 'osx', 'windows'] author = 'DiapDealer' - version = (0, 1, 5) - minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions. + version = (0, 1, 6) + minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions. file_types = set(['epub']) on_import = True @@ -282,10 +284,6 @@ class IgnobleDeDRM(FileTypePlugin): global AES global AES2 - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - from calibre.constants import iswindows, isosx - AES, AES2 = _load_crypto() if AES == None or AES2 == None: @@ -341,7 +339,7 @@ class IgnobleDeDRM(FileTypePlugin): for userkey in userkeys: # Create a TemporaryPersistent file to work with. # Check original epub archive for zip errors. - import zipfix + from calibre_plugins.ignobleepub import zipfix inf = self.temporary_file('.epub') try: fr = zipfix.fixZip(path_to_ebook, inf.name) diff --git a/Calibre_Plugins/ineptepub_plugin.zip b/Calibre_Plugins/ineptepub_plugin.zip index 2ce5bce5732527b47f2125871b3b3378bf43c5cf..b62a6255a83f164ed74d647ecb39f96ce1c410aa 100644 GIT binary patch delta 6165 zcmZ{obyO5w)b59H7(zlAN=l@2=pItKbLfy78fNGi5eaD+x=T`|8$={W8YBb-LAsPK z!O!=-_gm}UFVmy-xzoy}JQ7vG^HnY0D@yOde?Z$QsWwm6P^`ppmhsq6$G>pYo zNqc%p^0oSFWS=ZFFO-bYp7|6)xxFcz=(zq|7>&_P9OxM{N@H73S4AYpD%t=Qy1RH| z4{ZGXd#vzA%ZO$|Iv%6Q6H{F->ms(L_fVO7eCguEKb&($V?SH{5)C*7o(;G+cR!E- z%Emqy7~PcS8W8Nve9fWn&9%~leS@UslXpsSS(%l7>HOUYY%56eLpViY{Dgy>LKSOQ zG*9Szb>24uRR;MT*;&rlQX1s-dP!XoBhfNZOURqipv}cD=UXIa2QKQux~%DjS)0S z9kx^v*cG(P*%wCjl20p0m$Q;3ujl0xJ|FiE)cmdB;LX>6U8>Q?&VyJpPSPvoh zsPSbrR?dyO+FHv;xl|)%CKdJ{Q9;<=*`j0n04DjeAUg0nQkk^_%4?-pM!5aGa=E&% z4aV6Inlw=@2|6OC+*s&nwiaBC34YK6dQig11#*SC)+x!AeDtZ%XChD&d>qAYRwa7Fp>?dk16`o3&?i9X{Me{8na+z)%}?&I zGm{Nv-h@1G%Oo4KT?a<6a;h8aLk*t+Ip4{e#}hFz3z!}&ea+UhSoI#T9wos3iL)BGAQh1>;od+nND)>^ z6L74M7y6!d_A+W6-5|b7O!k8A^V?sYp79}Dr9bDbXArtR3I>;S0$!OFE(^_2cwv^l&jXzMFM=LAB zoT?^7GI!O(FigO-JCRS6(-ALZ^zI#W8;&2TQu(B(ZZk$-r){)8J5PC2f&KCAgLPEs z*@rdk+Nznd3lTy4onK5$Ra`$_X0;9aN4@M8eqhn&y3V>(6#Lr5#?FlIfw!unx(Cam zY^v}V{5hW37}W{A3FvT&Ia$C%Y(xhLZV|8dL8RKJcGS%h%v~lX$7L#GEcF6dfv7fO zB}{&!-X>ZDTKSVDp?Lor_kzdLp@9cw{~M(AM%70{F0(oI({c{M6nTg8kpHs4NL&Vj zY+}Ra*TI*jKDAOT#cRplC4mGAB5~-S6K*LfE^c`e%D~P?XiN!DGKig=toHlk2d;kD zf8;Bf2#JB{*Sl6&J{;X7+l+&E+@27~_dUHYqs^T+1fn&r`f@ynIvZXLi(gw&rS}XD zKaUYCVX`uX7FMC}sS9`Z(Z{z*DkeY36Cz>!$wYE1%oFQrNN0@f+jTk0jnF~TE8+K&IeGM5$}!#=WZ1h*%z{iw-=fU!QoFz!=l?h&}kE~;s-g~pm)m;@N(7E6ypK|&(55RoWoh{cZOCNa}JpJBXK6@OjFD@eD^l(86W}YsEr@(flO9gWq*EWy5vXH>d_(V5W+DchJ#Fge%!6|qJqrS5Q}>C-x4OXuy#2z98m1dtp~p!uUWBY0=b^F2d=ymRTd--}zHZBQ zXC3&on*`U1ff@`l~aX?VLM008A;Be%u` z2z;#lid951BT=@eaSX*0vytF7O58Sh@C(aa2mGL`fxfY~`a_$%I%-|4}dG(w5q7(9hcJN~&kJ$+8J`yR&yHPT5n<(>2D*@Pt zCO|VzM=OxFqDczvfYGIFQP zbKvV+Ha^#0D^$&3ZoSM<}dK%iWaZ6p@T6uV>kZFei0I%NmR1CazCH_Syb+(c#|F!!tLb0 z{0vA!y8v5Wt|Hz6GOvF5Q2cdd)h}-sW&MXLj{-jL_@;OLN_%Aqo+Qjt=XQGC>pw%> zWvk-3I(X3K9yk|v0gX>T0E}EMNj=mTr;K_6n&+@)mq<=aVqq&nJ|PVtR~=i<^T08Xw%W4D!K&fSGwqjk(tF+p$H zNNSY^%fgm^9@iRWt>)(d`4HIqxh}<%f0Vev#l|g%`B>sI9*$<{*lC>pEv>@G!lqL} zV`g@zaUDq+=T>W^AmYzm{dbw3!CT^v5-mWt7iYm8xq;U*a^s`c11tT@m$^}xphuVf zZ|U63L_i&=5`3NHk(NmW^!^;Rdx{D0`MuvjGoE_4#WH``5EEo0K;3$$@cnikZ(LID zkY@o7y=07Pxy9LbT#P_yEn8Iw1XJe()c#^XemzJU*k99P4ErW*{l;n2CARP9dHdxn zlFD2L-hCs+98O@9u-@;JkK1$6QQx&Xa}CT=W*7*Ybr_}kbiNOON`1efrtEHR*1t&| zkCKw}zx?~xRSX?gSMJAW3K_)*+0WvxYR|V7t}erS&K;ayC*&uD+kK1b^)XMn-9O2oqz?v^w9>s{y5z#jRl zitgCHNWYD@i_L^dmaprhP?ga_fiS$&pL%{dJo>BM5hP=tinZ+3)~~J~y|U_VH=j)x z;yF8hP5?M3ZqBA9$w||HAU{^#l`Azm}i7vNNIt_*W;;e>cf6;pFxKV=gyRQs& z$II_!#mw1e<`p!p+fvK@^St&i?-FTQdgD>XYGU4_&_ca$E;$FFMPxFds;+TwfehYl~vpFXN@aY-W*?D$FS*=&?KhXU6L=q zF{a-q(EwlmOyHfzKY1Z$j|tVO>&rIpxhp>PhVUII%TJn8Zr({ZSfosf-*W5>22&41 zy%)T_`7o~rTIO$kyit1z;^9QMfL(6DE|M>_j)zjJHz1$yrF>zCgDxiow2dhIt|b7$ zpKqU!;)1ZR7V!$mKP2!!^}ksC+`SlCa6K08a@qS8OY}+istgxFdq<3h@BSiXYB8BB z89UI-c-9jk>AO5;lZuKMR4{AFi7aDNW30*8M2&BZgZG^-YcTVX|PP^1iS|w@S+wWHb0M^`WaSsF-LIR^c!J@#?{=% zR+Mde$qI1QQ5@&QRcxuO;Eis*DbJ&*9SS*KWoxddyi*s>y(U}pT@)+Q=ZEn%m#`KD zU+CU|6M~`t#%i;tImu4k1?-fck3UU#uel}}_3RU@SwO4et(K|>!r`cKSl=TjO8x$K zC+{}GJ25_D;2~AXg16VlKopIp9)`fhfkKXETM8Eo|t-P zyuBH2`t#^vPF3e6;x~PCk6%tjk8tl!p_;r8w1J%2;i~7tZfRdf%Cs1ABgl1z^nN(H zPWj`kYKw)SiJWFga3Nw5@xW3S<;en(Le<YkddD^tPaapXwvsrIE2j7HP; z3N!Wei`b{nB4a6x#0S?`oj<~`W7%g7BNNA`48jZY!UvRoHoAI0W2G7w=ULBoh!)PK z)^X`K)+o)wQd}K;ZZdKwvz^c7OBt9gem8@As;q_?hiB($jntZ-1ud;q^Bo{_wnh`G zkW>_`FRbDi_c%pFfbsr|vkOqF^8yg6WbZ)fLC&`)I(R|Ng!?!lkUmC++z%UAX9HG3 zU!~p*nu0BGAac$UGILo_*+4W56z+tCUZS~~>n`a?C{)&H0ke&M|$Q)3rZW^eqHF)xcv1}X_ELxIV!8oBio0!Bm z+)wPAaIk>Hj`Yee_JX`>DjTZl@D8SVJ1Z;beZ}6lG~bL@hwTo}4;JO#t}L0c#_Q1e z{@!No?DRA6>m3U^#$X!O6g)gpr+I4osxiv+SVGK3}eZfDfPa)DwtNZT&mmRX90Jj?ns@0$VjjbBA8(}um zA(D>LPnWcRNdIbM9!VC-KNqtbK^E!=Eujemg;u&Hix)3Ml@gtMR^I4XPu>hL!D%Kt zge|GZFN6GanjtdKwC5;PpHx+0!v$Pr($vgdN)+=Vc7uX7&41P1+;@;z&nE_SSFlov zG3mFAKCX2=(7S}N`ggb?Xl7QaDxF@xWoGDZXt!ahSX?T5^&A1Pv`{0+f}o@;pgLfr zk_n@d*7>U{rJMH5cV9cNe7*EQj)^Y#Cvt=+B7$`_7gT{6SBCKQG%#QGUCi9=KZ3%j4jf%Y!Se zHp(`KY?2hr&HP{=1^zJW8mIZf@NMAnyPIIr+JLETO%k!%Q~2HVYOphBHp>j7l^}^I6WtP7N(N;*d%#96w zhW>)@vY)l71rL$+7PloH71(Q47z+!LnGmx)KGB2L*O~ELWG!IMkuVFXs~iRI{Mjcm z+Gpn;#~;EM>bP@jC>$Eqr!CB^>-wZ}0y{H8U&lFvPp)^LiR3mrjV=-1NA&dq{u4Xn zf&g8x6rv}1>lrLstqbD~!XRSosX%lZ3lknknV#0%!gx~`%Z~@DIJg9VDK!A#|Cdt# z+x*{)=`W@Ba`Sb9d+@^Dy*z!8ydJjh4!m#=2QQ?9m#-ZkG64B6x}KRI*ZN!NuUvE@ z{~dKx|7-V`X9LQ@|7rg}@crOl@coYxy9{u3_!Ugy*;pP6P^nbVeC;fjL^8omd4esA#{5SdjUo!yUKLBSnn}7fS delta 5974 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 diff --git a/Calibre_Plugins/ineptepub_plugin/ineptepub_plugin.py b/Calibre_Plugins/ineptepub_plugin/__init__.py similarity index 97% rename from Calibre_Plugins/ineptepub_plugin/ineptepub_plugin.py rename to Calibre_Plugins/ineptepub_plugin/__init__.py index d0541b5..264b9ee 100644 --- a/Calibre_Plugins/ineptepub_plugin/ineptepub_plugin.py +++ b/Calibre_Plugins/ineptepub_plugin/__init__.py @@ -4,7 +4,7 @@ # Released under the terms of the GNU General Public Licence, version 3 or # later. # -# Requires Calibre version 0.6.44 or higher. +# Requires Calibre version 0.7.55 or higher. # # All credit given to I <3 Cabbages for the original standalone scripts. # I had the much easier job of converting them to a Calibre plugin. @@ -49,6 +49,7 @@ # 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 +# 0.1.7 - update to new calibre plugin interface """ Decrypt Adobe ADEPT-encrypted EPUB books. @@ -365,6 +366,7 @@ def plugin_main(userkey, inpath, outpath): return 0 from calibre.customize import FileTypePlugin +from calibre.constants import iswindows, isosx class IneptDeDRM(FileTypePlugin): name = 'Inept Epub DeDRM' @@ -372,8 +374,8 @@ class IneptDeDRM(FileTypePlugin): Credit given to I <3 Cabbages for the original stand-alone scripts.' supported_platforms = ['linux', 'osx', 'windows'] author = 'DiapDealer' - version = (0, 1, 6) - minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions. + version = (0, 1, 7) + minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions. file_types = set(['epub']) on_import = True priority = 100 @@ -382,10 +384,6 @@ class IneptDeDRM(FileTypePlugin): global AES global RSA - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - from calibre.constants import iswindows, isosx - AES, RSA = _load_crypto() if AES == None or RSA == None: @@ -418,7 +416,7 @@ class IneptDeDRM(FileTypePlugin): # Calibre's configuration directory for future use. if iswindows or isosx: # ADE key retrieval script included in respective OS folder. - from ade_key import retrieve_key + from calibre_plugins.ineptepub.ade_key import retrieve_key try: keydata = retrieve_key() userkeys.append(keydata) @@ -439,7 +437,7 @@ class IneptDeDRM(FileTypePlugin): for userkey in userkeys: # Create a TemporaryPersistent file to work with. # Check original epub archive for zip errors. - import zipfix + from calibre_plugins.ineptepub import zipfix inf = self.temporary_file('.epub') try: fr = zipfix.fixZip(path_to_ebook, inf.name) diff --git a/Calibre_Plugins/ineptepub_plugin/plugin-import-name-ineptepub.txt b/Calibre_Plugins/ineptepub_plugin/plugin-import-name-ineptepub.txt new file mode 100644 index 0000000..e69de29 diff --git a/Calibre_Plugins/ineptpdf_plugin.zip b/Calibre_Plugins/ineptpdf_plugin.zip index 3174938628833546c32afdfed67b70ad449d8707..83dd0aee79726e60f7181653f746c159f25e49b9 100644 GIT binary patch delta 18668 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 delta 18590 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 R{JH|`(Wp9FgB^dJ{slBE&Nu)7 diff --git a/Calibre_Plugins/ineptpdf_plugin/ineptpdf_plugin.py b/Calibre_Plugins/ineptpdf_plugin/__init__.py similarity index 99% rename from Calibre_Plugins/ineptpdf_plugin/ineptpdf_plugin.py rename to Calibre_Plugins/ineptpdf_plugin/__init__.py index 5e3caf3..e1b6041 100644 --- a/Calibre_Plugins/ineptpdf_plugin/ineptpdf_plugin.py +++ b/Calibre_Plugins/ineptpdf_plugin/__init__.py @@ -1,6 +1,6 @@ #! /usr/bin/env python -# ineptpdf_plugin.py +# ineptpdf plugin __init__.py # Released under the terms of the GNU General Public Licence, version 3 or # later. @@ -11,7 +11,7 @@ # be able to read OUR books on whatever device we want and to keep # readable for a long, long time -# Requires Calibre version 0.6.44 or higher. +# Requires Calibre version 0.7.55 or higher. # # All credit given to I <3 Cabbages for the original standalone scripts. # I had the much easier job of converting them to a Calibre plugin. @@ -51,6 +51,7 @@ # 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 +# 0.1.4 - update to the new calibre plugin interface """ Decrypts Adobe ADEPT-encrypted PDF files. @@ -174,6 +175,7 @@ def _load_crypto_libcrypto(): return out.raw class AES(object): + MODE_CBC = 0 @classmethod def new(cls, userkey, mode, iv): self = AES() @@ -2126,6 +2128,7 @@ def plugin_main(keypath, inpath, outpath): from calibre.customize import FileTypePlugin +from calibre.constants import iswindows, isosx class IneptPDFDeDRM(FileTypePlugin): name = 'Inept PDF DeDRM' @@ -2133,17 +2136,14 @@ class IneptPDFDeDRM(FileTypePlugin): Credit given to I <3 Cabbages for the original stand-alone scripts.' supported_platforms = ['linux', 'osx', 'windows'] author = 'DiapDealer' - version = (0, 1, 3) - minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions. + version = (0, 1, 4) + minimum_calibre_version = (0, 7, 55) # for the new plugin interface file_types = set(['pdf']) on_import = True def run(self, path_to_ebook): global ARC4, RSA, AES - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - from calibre.constants import iswindows, isosx ARC4, RSA, AES = _load_crypto() @@ -2177,7 +2177,7 @@ class IneptPDFDeDRM(FileTypePlugin): # Calibre's configuration directory for future use. if iswindows or isosx: # ADE key retrieval script. - from ade_key import retrieve_key + from calibre_plugins.ineptpdf.ade_key import retrieve_key try: keydata = retrieve_key() userkeys.append(keydata) diff --git a/Calibre_Plugins/ineptpdf_plugin/plugin-import-name-ineptpdf.txt b/Calibre_Plugins/ineptpdf_plugin/plugin-import-name-ineptpdf.txt new file mode 100644 index 0000000..e69de29 diff --git a/Calibre_Plugins/k4mobidedrm_plugin.zip b/Calibre_Plugins/k4mobidedrm_plugin.zip index 295d06fe0e6b1c587d9e886e9f5547353815a156..00782f0029b3e05b332b73112a3d95cb1cfdcb29 100644 GIT binary patch delta 29713 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 delta 26269 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 diff --git a/Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py b/Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py deleted file mode 100644 index 64a998a..0000000 --- a/Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py +++ /dev/null @@ -1,374 +0,0 @@ -#!/usr/bin/env python - -from __future__ import with_statement - -# engine to remove drm from Kindle for Mac and Kindle for PC books -# for personal use for archiving and converting your ebooks - -# PLEASE DO NOT PIRATE EBOOKS! - -# We want all authors and publishers, and eBook stores to live -# long and prosperous lives but at the same time we just want to -# be able to read OUR books on whatever device we want and to keep -# readable for a long, long time - -# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, -# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates -# and many many others - -# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that importing -# K4 or Mobi with DRM is no londer a multi-step process. -# -# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook -# then calibre must be installed on the same machine and in the same account as K4PC or K4M -# for the plugin version to function properly. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines -# and import that ZIP into Calibre using its plugin configuration GUI. - - -__version__ = '2.8' - -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 -import os, csv, getopt -import string -import binascii -import zlib -import re -import zlib, zipfile, tempfile, shutil -from struct import pack, unpack, unpack_from - -class DrmException(Exception): - pass - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) - -# cleanup bytestring filenames -# borrowed from calibre from calibre/src/calibre/__init__.py -# added in removal of non-printing chars -# and removal of . at start -# convert spaces to underscores -def cleanup_name(name): - _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') - substitute='_' - one = ''.join(char for char in name if char in string.printable) - one = _filename_sanitize.sub(substitute, one) - one = re.sub(r'\s', ' ', one).strip() - one = re.sub(r'^\.+$', '_', one) - one = one.replace('..', substitute) - # Windows doesn't like path components that end with a period - if one.endswith('.'): - one = one[:-1]+substitute - # Mac and Unix don't like file names that begin with a full stop - if len(one) > 0 and one[0] == '.': - one = substitute+one[1:] - one = one.replace(' ','_') - return one - -def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): - import mobidedrm - import topazextract - import kgenpids - - # handle the obvious cases at the beginning - if not os.path.isfile(infile): - print "Error: Input file does not exist" - return 1 - - mobi = True - magic3 = file(infile,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(infile))[0] - - if mobi: - mb = mobidedrm.MobiBook(infile) - else: - tempdir = tempfile.mkdtemp() - mb = topazextract.TopazBook(infile, tempdir) - - title = mb.getBookTitle() - print "Processing Book: ", title - filenametitle = cleanup_name(title) - outfilename = bookname - if len(bookname)>4 and len(filenametitle)>4 and bookname[:4] != filenametitle[:4]: - outfilename = outfilename + "_" + filenametitle - - # build pid list - md1, md2 = mb.getPIDMetaInfo() - pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) - - try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) - - except mobidedrm.DrmException, e: - print "Error: " + str(e) + "\nDRM Removal Failed.\n" - return 1 - except Exception, e: - if not mobi: - print "Error: " + str(e) + "\nDRM Removal Failed.\n" - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) - return 1 - pass - - if mobi: - outfile = os.path.join(outdir,outfilename + '_nodrm' + '.mobi') - file(outfile, 'wb').write(unlocked_file) - return 0 - - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() - - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() - - shutil.rmtree(tempdir, True) - return 0 - - -def usage(progname): - print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - -# -# Main -# -def main(argv=sys.argv): - progname = os.path.basename(argv[0]) - - k4 = False - kInfoFiles = [] - serials = [] - pids = [] - - print ('K4MobiDeDrm v%(__version__)s ' - 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) - - print ' ' - try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") - except getopt.GetoptError, err: - print str(err) - usage(progname) - sys.exit(2) - if len(args)<2: - usage(progname) - sys.exit(2) - - for o, a in opts: - if o == "-k": - if a == None : - raise DrmException("Invalid parameter for -k") - kInfoFiles.append(a) - if o == "-p": - if a == None : - raise DrmException("Invalid parameter for -p") - pids = a.split(',') - if o == "-s": - if a == None : - raise DrmException("Invalid parameter for -s") - serials = a.split(',') - - # try with built in Kindle Info files - k4 = True - if sys.platform.startswith('linux'): - k4 = False - kInfoFiles = None - infile = args[0] - outdir = args[1] - - return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) - -if not __name__ == "__main__" and inCalibre: - from calibre.customize import FileTypePlugin - - class K4DeDRM(FileTypePlugin): - name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin - description = 'Removes DRM from K4PC and Mac, Kindle Mobi and Topaz files. \ - 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 - author = 'DiapDealer, SomeUpdates' # The author of this plugin - version = (0, 2, 8) # The version number of this plugin - file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to - on_import = True # Run this plugin during the import - priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm - - def run(self, path_to_ebook): - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - from calibre.ptempfile import PersistentTemporaryDirectory - - import kgenpids - import zlib - import zipfile - import topazextract - import mobidedrm - - k4 = True - if sys.platform.startswith('linux'): - k4 = False - pids = [] - serials = [] - kInfoFiles = [] - - # Get supplied list of PIDs to try from plugin customization. - customvalues = self.site_customization.split(',') - for customvalue in customvalues: - customvalue = str(customvalue) - customvalue = customvalue.strip() - if len(customvalue) == 10 or len(customvalue) == 8: - pids.append(customvalue) - else : - if len(customvalue) == 16 and customvalue[0] == 'B': - serials.append(customvalue) - else: - print "%s is not a valid Kindle serial number or PID." % str(customvalue) - - # Load any kindle info files (*.info) included Calibre's config directory. - try: - # Find Calibre's configuration directory. - confpath = os.path.split(os.path.split(self.plugin_path)[0])[0] - print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath - files = os.listdir(confpath) - filefilter = re.compile("\.info$", re.IGNORECASE) - files = filter(filefilter.search, files) - - if files: - for filename in files: - fpath = os.path.join(confpath, filename) - kInfoFiles.append(fpath) - print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename - except IOError: - print 'K4MobiDeDRM: Error reading kindle info files from config directory.' - pass - - - mobi = True - magic3 = file(path_to_ebook,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(path_to_ebook))[0] - - if mobi: - mb = mobidedrm.MobiBook(path_to_ebook) - else: - tempdir = PersistentTemporaryDirectory() - mb = topazextract.TopazBook(path_to_ebook, tempdir) - - title = mb.getBookTitle() - md1, md2 = mb.getPIDMetaInfo() - pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) - - try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) - - except mobidedrm.DrmException: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - except topazextract.TpzDRMError: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - - print "Success!" - if mobi: - of = self.temporary_file(bookname+'.mobi') - of.write(unlocked_file) - of.close() - return of.name - - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - of = self.temporary_file(bookname + '.zip') - myzip = zipfile.ZipFile(of.name,'w',zipfile.ZIP_DEFLATED, False) - myzip.write(os.path.join(tempdir,'book.html'),'book.html') - myzip.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip, tempdir, 'img') - myzip.close() - return of.name - - def customization_help(self, gui=False): - return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' \ No newline at end of file diff --git a/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py b/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py index 539723d..534c389 100644 --- a/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py +++ b/Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py @@ -1,10 +1,12 @@ # standlone set of Mac OSX specific routines needed for K4DeDRM from __future__ import with_statement + import sys import os import subprocess +from struct import pack, unpack, unpack_from class DrmException(Exception): pass @@ -66,9 +68,8 @@ def _load_crypto_libcrypto(): raise DrmException('AES decryption failed') return out.raw - def keyivgen(self, passwd): - salt = '16743' - saltlen = 5 + def keyivgen(self, passwd, salt): + saltlen = len(salt) passlen = len(passwd) iter = 0x3e8 keylen = 80 @@ -91,12 +92,78 @@ LibCrypto = _load_crypto() # Utility Routines # +# crypto digestroutines +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() # Various character maps used to decrypt books. Probably supposed to act as obfuscation charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +# For Future Reference from .kinf approach of K4PC +charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" + + +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 + +# For Future Reference from .kinf approach of K4PC +# generate table of prime number less than or equal to int n +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + # + # # get the first item record + # item = items.pop(0) + # + # # the first 32 chars of the first record of a group + # # is the MD5 hash of the key name encoded by charMap5 + # keyhash = item[0:32] + # + # # the raw keyhash string is also used to create entropy for the actual + # # CryptProtectData Blob that represents that keys contents + # entropy = SHA1(keyhash) + # + # # the remainder of the first record when decoded with charMap5 + # # has the ':' split char followed by the string representation + # # of the number of records that follow + # # and make up the contents + # srcnt = decode(item[34:],charMap5) + # rcnt = int(srcnt) + # + # # read and store in rcnt records of data + # # that make up the contents value + # edlst = [] + # for i in xrange(rcnt): + # item = items.pop(0) + # edlst.append(item) + # + # keyname = "unknown" + # for name in names: + # if encodeHash(name,charMap5) == keyhash: + # keyname = name + # break + # if keyname == "unknown": + # keyname = keyhash + # + # # the charMap5 encoded contents data has had a length + # # of chars (always odd) cut off of the front and moved + # # to the end to prevent decoding using charMap5 from + # # working properly, and thereby preventing the ensuing + # # CryptUnprotectData call from succeeding. + # + # # The offset into the charMap5 encoded contents seems to be: + # # len(contents) - largest prime number less than or equal to int(len(content)/3) + # # (in other words split "about" 2/3rds of the way through) + # + # # move first offsets chars to end to align for decode by charMap5 + # encdata = "".join(edlst) + # contlen = len(encdata) + # noffset = contlen - primes(int(contlen/3))[-1] + # + # # now properly split and recombine + # # by moving noffset chars from the start of the + # # string to the end of the string + # pfx = encdata[0:noffset] + # encdata = encdata[noffset:] + # encdata = encdata + pfx + # + # # decode using Map5 to get the CryptProtect Data + # encryptedValue = decode(encdata,charMap5) + # DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + # cnt = cnt + 1 + + if cnt == 0: + DB = None return DB diff --git a/Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py b/Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py index d460a70..690033b 100644 --- a/Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py +++ b/Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python # K4PC Windows specific routines from __future__ import with_statement import sys, os +from struct import pack, unpack, unpack_from from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ @@ -10,25 +12,86 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ import _winreg as winreg -import traceback - MAX_PATH = 255 kernel32 = windll.kernel32 advapi32 = windll.advapi32 crypt32 = windll.crypt32 +import traceback -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +# crypto digestroutines +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() + + +# simple primes table (<= n) calculator +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the raw keyhash string is also used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = "unknown" + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using Map5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + cnt = cnt + 1 + + if cnt == 0: + DB = None return DB + + diff --git a/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py b/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py index 7aef175..41bb12d 100644 --- a/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py +++ b/Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py @@ -51,8 +51,9 @@ # 0.29 - It seems that the ideas about when multibyte trailing characters were # included in the encryption were wrong. They aren't for DOC compressed # files, but they are for HUFF/CDIC compress files! +# 0.30 - Modified interface slightly to work better with new calibre plugin style -__version__ = '0.29' +__version__ = '0.30' import sys @@ -163,6 +164,7 @@ class MobiBook: def __init__(self, infile): # initial sanity check on file self.data_file = file(infile, 'rb').read() + self.mobi_data = '' self.header = self.data_file[0:78] if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': raise DrmException("invalid file format") @@ -301,13 +303,17 @@ class MobiBook: break return [found_key,pid] + def getMobiFile(self, outpath): + file(outpath,'wb').write(self.mobi_data) + def processBook(self, pidlist): crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) print 'Crypto Type is: ', crypto_type self.crypto_type = crypto_type if crypto_type == 0: print "This book is not encrypted." - return self.data_file + self.mobi_data = self.data_file + return if crypto_type != 2 and crypto_type != 1: raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) @@ -353,33 +359,35 @@ class MobiBook: # decrypt sections print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] + self.mobi_data = self.data_file[:self.sections[1][0]] for i in xrange(1, self.records+1): data = self.loadSection(i) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: print ".", # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) + self.mobi_data += PC1(found_key, data[0:len(data) - extra_size]) if extra_size > 0: - new_data += data[-extra_size:] + self.mobi_data += data[-extra_size:] if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data + self.mobi_data += self.data_file[self.sections[self.records+1][0]:] print "done" - return self.data_file + return def getUnencryptedBook(infile,pid): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook([pid]) + book.processBook([pid]) + return book.mobi_data def getUnencryptedBookWithList(infile,pidlist): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook(pidlist) + book.processBook(pidlist) + return book.mobi_data + def main(argv=sys.argv): print ('MobiDeDrm v%(__version__)s. ' diff --git a/DeDRM_Macintosh_Application/DeDRM.app.txt b/DeDRM_Macintosh_Application/DeDRM.app.txt index 26aa8fd627cc3f662fedb038543ae6a85c5c6e36..856958e029367525257ef53947476a5d888bbd9a 100644 GIT binary patch delta 404 zcmdmTknPf8whf0=6pI;B8A=!w7+ipKGDAK?3PUDC9z!~VD?zCBqv{}+%$QgfdXy|CL7jBPj-mpYW6eS?q|rDmJa}= C7-G2q delta 114 zcmV-&0FD3B!v?g$2C%{;lLiMAlMW@elctewm(U0S9+TW@7?U1U7L%Zn29pjl2$LXP z7?X}Bypxcq7_*Qk_y?Ctf&m(n5G7c%AV?GelMXTCFBundleExecutable droplet CFBundleGetInfoString - DeDRM 2.6, Written 2010–2011 by Apprentice Alf and others. + DeDRM 2.7, Written 2010–2011 by Apprentice Alf and others. CFBundleIconFile droplet CFBundleInfoDictionaryVersion @@ -34,7 +34,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.6 + 2.7 CFBundleSignature dplt LSMinimumSystemVersion diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt index 4d03f32e44585afe4236637e421a658811e537ce..13962a28485d9e7b0e8d033ce154cef303a565ad 100644 GIT binary patch literal 234956 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 literal 234456 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

(?`!~&W3_sK|J1u6}Z=a^)#EBWK;3AX(=`h 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 diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py index 6df9e13..0e59d8d 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py @@ -58,8 +58,9 @@ # 0.17 - added support for pycrypto's DES as well # 0.18 - on Windows try PyCrypto first and OpenSSL next # 0.19 - Modify the interface to allow use of import +# 0.20 - modify to allow use inside new interface for calibre plugins -__version__='0.19' +__version__='0.20' class Unbuffered: def __init__(self, stream): @@ -71,32 +72,50 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) - import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + Des = None if sys.platform.startswith('win'): # first try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() if Des == None: # they try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() else: # first try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() if Des == None: # then try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() # if that did not work then use pure python implementation # of DES and try to speed it up with Psycho if Des == None: - import python_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import python_des + else: + import python_des Des = python_des.Des # Import Psyco if available try: @@ -480,5 +499,6 @@ def main(argv=None): if __name__ == "__main__": + sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py index 11258d1..f779cee 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py @@ -21,10 +21,21 @@ from struct import unpack # local support routines -import convert2xml -import flatxml2html -import flatxml2svg -import stylexml2css +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre : + from calibre_plugins.k4mobidedrm import convert2xml + from calibre_plugins.k4mobidedrm import flatxml2html + from calibre_plugins.k4mobidedrm import flatxml2svg + from calibre_plugins.k4mobidedrm import stylexml2css +else : + import convert2xml + import flatxml2html + import flatxml2svg + import stylexml2css # Get a 7 bit encoded number from a file @@ -504,7 +515,7 @@ def generateBook(bookDir, raw, fixedimage): opfstr += ' \n' opfstr += '\n' opfstr += ' \n' - opfstr += ' \n' + opfstr += ' \n' # adding image files to manifest filenames = os.listdir(imgDir) filenames = sorted(filenames) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py index 40e97cd..c9a419b 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py @@ -157,6 +157,7 @@ def _load_crypto_libcrypto(): return out.raw class AES(object): + MODE_CBC = 0 @classmethod def new(cls, userkey, mode, iv): self = AES() diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py index 64a998a..14556db 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py @@ -16,20 +16,8 @@ from __future__ import with_statement # unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates # and many many others -# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that importing -# K4 or Mobi with DRM is no londer a multi-step process. -# -# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook -# then calibre must be installed on the same machine and in the same account as K4PC or K4M -# for the plugin version to function properly. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines -# and import that ZIP into Calibre using its plugin configuration GUI. - -__version__ = '2.8' +__version__ = '3.1' class Unbuffered: def __init__(self, stream): @@ -43,11 +31,7 @@ class Unbuffered: import sys import os, csv, getopt import string -import binascii -import zlib import re -import zlib, zipfile, tempfile, shutil -from struct import pack, unpack, unpack_from class DrmException(Exception): pass @@ -57,19 +41,15 @@ if 'calibre' in sys.modules: else: inCalibre = False -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) +if inCalibre: + from calibre_plugins.k4mobidedrm import mobidedrm + from calibre_plugins.k4mobidedrm import topazextract + from calibre_plugins.k4mobidedrm import kgenpids +else: + import mobidedrm + import topazextract + import kgenpids + # cleanup bytestring filenames # borrowed from calibre from calibre/src/calibre/__init__.py @@ -94,10 +74,6 @@ def cleanup_name(name): return one def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): - import mobidedrm - import topazextract - import kgenpids - # handle the obvious cases at the beginning if not os.path.isfile(infile): print "Error: Input file does not exist" @@ -113,8 +89,7 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): if mobi: mb = mobidedrm.MobiBook(infile) else: - tempdir = tempfile.mkdtemp() - mb = topazextract.TopazBook(infile, tempdir) + mb = topazextract.TopazBook(infile) title = mb.getBookTitle() print "Processing Book: ", title @@ -128,60 +103,39 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) + mb.processBook(pidlst) except mobidedrm.DrmException, e: print "Error: " + str(e) + "\nDRM Removal Failed.\n" return 1 + except topazextract.TpzDRMError, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 except Exception, e: - if not mobi: - print "Error: " + str(e) + "\nDRM Removal Failed.\n" - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) - return 1 - pass + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 if mobi: - outfile = os.path.join(outdir,outfilename + '_nodrm' + '.mobi') - file(outfile, 'wb').write(unlocked_file) + outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') + mb.getMobiFile(outfile) return 0 - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() + # topaz: + print " Creating NoDRM HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz') + mb.getHTMLZip(zipname) - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() + print " Creating SVG HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_SVG' + '.htmlz') + mb.getSVGZip(zipname) print " Creating XML ZIP Archive" zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() + mb.getXMLZip(zipname) + + # remove internal temporary directory of Topaz pieces + mb.cleanup() - shutil.rmtree(tempdir, True) return 0 @@ -236,7 +190,6 @@ def main(argv=sys.argv): kInfoFiles = None infile = args[0] outdir = args[1] - return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) @@ -244,131 +197,3 @@ if __name__ == '__main__': sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) -if not __name__ == "__main__" and inCalibre: - from calibre.customize import FileTypePlugin - - class K4DeDRM(FileTypePlugin): - name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin - description = 'Removes DRM from K4PC and Mac, Kindle Mobi and Topaz files. \ - 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 - author = 'DiapDealer, SomeUpdates' # The author of this plugin - version = (0, 2, 8) # The version number of this plugin - file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to - on_import = True # Run this plugin during the import - priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm - - def run(self, path_to_ebook): - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - from calibre.ptempfile import PersistentTemporaryDirectory - - import kgenpids - import zlib - import zipfile - import topazextract - import mobidedrm - - k4 = True - if sys.platform.startswith('linux'): - k4 = False - pids = [] - serials = [] - kInfoFiles = [] - - # Get supplied list of PIDs to try from plugin customization. - customvalues = self.site_customization.split(',') - for customvalue in customvalues: - customvalue = str(customvalue) - customvalue = customvalue.strip() - if len(customvalue) == 10 or len(customvalue) == 8: - pids.append(customvalue) - else : - if len(customvalue) == 16 and customvalue[0] == 'B': - serials.append(customvalue) - else: - print "%s is not a valid Kindle serial number or PID." % str(customvalue) - - # Load any kindle info files (*.info) included Calibre's config directory. - try: - # Find Calibre's configuration directory. - confpath = os.path.split(os.path.split(self.plugin_path)[0])[0] - print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath - files = os.listdir(confpath) - filefilter = re.compile("\.info$", re.IGNORECASE) - files = filter(filefilter.search, files) - - if files: - for filename in files: - fpath = os.path.join(confpath, filename) - kInfoFiles.append(fpath) - print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename - except IOError: - print 'K4MobiDeDRM: Error reading kindle info files from config directory.' - pass - - - mobi = True - magic3 = file(path_to_ebook,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(path_to_ebook))[0] - - if mobi: - mb = mobidedrm.MobiBook(path_to_ebook) - else: - tempdir = PersistentTemporaryDirectory() - mb = topazextract.TopazBook(path_to_ebook, tempdir) - - title = mb.getBookTitle() - md1, md2 = mb.getPIDMetaInfo() - pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) - - try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) - - except mobidedrm.DrmException: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - except topazextract.TpzDRMError: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - - print "Success!" - if mobi: - of = self.temporary_file(bookname+'.mobi') - of.write(unlocked_file) - of.close() - return of.name - - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - of = self.temporary_file(bookname + '.zip') - myzip = zipfile.ZipFile(of.name,'w',zipfile.ZIP_DEFLATED, False) - myzip.write(os.path.join(tempdir,'book.html'),'book.html') - myzip.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip, tempdir, 'img') - myzip.close() - return of.name - - def customization_help(self, gui=False): - return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' \ No newline at end of file diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py index 539723d..534c389 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py @@ -1,10 +1,12 @@ # standlone set of Mac OSX specific routines needed for K4DeDRM from __future__ import with_statement + import sys import os import subprocess +from struct import pack, unpack, unpack_from class DrmException(Exception): pass @@ -66,9 +68,8 @@ def _load_crypto_libcrypto(): raise DrmException('AES decryption failed') return out.raw - def keyivgen(self, passwd): - salt = '16743' - saltlen = 5 + def keyivgen(self, passwd, salt): + saltlen = len(salt) passlen = len(passwd) iter = 0x3e8 keylen = 80 @@ -91,12 +92,78 @@ LibCrypto = _load_crypto() # Utility Routines # +# crypto digestroutines +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() # Various character maps used to decrypt books. Probably supposed to act as obfuscation charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +# For Future Reference from .kinf approach of K4PC +charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" + + +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 + +# For Future Reference from .kinf approach of K4PC +# generate table of prime number less than or equal to int n +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + # + # # get the first item record + # item = items.pop(0) + # + # # the first 32 chars of the first record of a group + # # is the MD5 hash of the key name encoded by charMap5 + # keyhash = item[0:32] + # + # # the raw keyhash string is also used to create entropy for the actual + # # CryptProtectData Blob that represents that keys contents + # entropy = SHA1(keyhash) + # + # # the remainder of the first record when decoded with charMap5 + # # has the ':' split char followed by the string representation + # # of the number of records that follow + # # and make up the contents + # srcnt = decode(item[34:],charMap5) + # rcnt = int(srcnt) + # + # # read and store in rcnt records of data + # # that make up the contents value + # edlst = [] + # for i in xrange(rcnt): + # item = items.pop(0) + # edlst.append(item) + # + # keyname = "unknown" + # for name in names: + # if encodeHash(name,charMap5) == keyhash: + # keyname = name + # break + # if keyname == "unknown": + # keyname = keyhash + # + # # the charMap5 encoded contents data has had a length + # # of chars (always odd) cut off of the front and moved + # # to the end to prevent decoding using charMap5 from + # # working properly, and thereby preventing the ensuing + # # CryptUnprotectData call from succeeding. + # + # # The offset into the charMap5 encoded contents seems to be: + # # len(contents) - largest prime number less than or equal to int(len(content)/3) + # # (in other words split "about" 2/3rds of the way through) + # + # # move first offsets chars to end to align for decode by charMap5 + # encdata = "".join(edlst) + # contlen = len(encdata) + # noffset = contlen - primes(int(contlen/3))[-1] + # + # # now properly split and recombine + # # by moving noffset chars from the start of the + # # string to the end of the string + # pfx = encdata[0:noffset] + # encdata = encdata[noffset:] + # encdata = encdata + pfx + # + # # decode using Map5 to get the CryptProtect Data + # encryptedValue = decode(encdata,charMap5) + # DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + # cnt = cnt + 1 + + if cnt == 0: + DB = None return DB diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py index d460a70..690033b 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python # K4PC Windows specific routines from __future__ import with_statement import sys, os +from struct import pack, unpack, unpack_from from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ @@ -10,25 +12,86 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ import _winreg as winreg -import traceback - MAX_PATH = 255 kernel32 = windll.kernel32 advapi32 = windll.advapi32 crypt32 = windll.crypt32 +import traceback -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +# crypto digestroutines +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() + + +# simple primes table (<= n) calculator +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the raw keyhash string is also used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = "unknown" + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using Map5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + cnt = cnt + 1 + + if cnt == 0: + DB = None return DB + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py index c10f105..c9d8944 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py @@ -11,16 +11,28 @@ from struct import pack, unpack, unpack_from class DrmException(Exception): pass -global kindleDatabase global charMap1 -global charMap2 global charMap3 global charMap4 -if sys.platform.startswith('win'): - from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 -if sys.platform.startswith('darwin'): - from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + if sys.platform.startswith('win'): + from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber +else: + if sys.platform.startswith('win'): + from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" @@ -66,50 +78,7 @@ def decode(data,map): value = (((high * len(map)) ^ 0x80) & 0xFF) + low result += pack("B",value) return result - -# 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 - global charMap1 - global charMap2 - encryptedValue = decode(kindleDatabase[hashedKey],charMap2) - if sys.platform.startswith('win'): - return CryptUnprotectData(encryptedValue,"") - else: - 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): - global charMap2 - 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): - global charMap2 - 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 # @@ -222,15 +191,15 @@ def getKindlePid(pidlst, rec209, token, serialnum): return pidlst -# Parse the EXTH header records and parse the Kindleinfo -# file to calculate the book pid. +# parse the Kindleinfo file to calculate the book pid. + +keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] def getK4Pids(pidlst, rec209, token, kInfoFile): - global kindleDatabase global charMap1 kindleDatabase = None try: - kindleDatabase = parseKindleInfo(kInfoFile) + kindleDatabase = getDBfromFile(kInfoFile) except Exception, message: print(message) kindleDatabase = None @@ -241,10 +210,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): try: # Get the Mazama Random number - MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") + MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] # Get the kindle account token - kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") + kindleAccountToken = kindleDatabase["kindle.account.tokens"] except KeyError: print "Keys not found in " + kInfoFile return pidlst diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py index 7aef175..41bb12d 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py @@ -51,8 +51,9 @@ # 0.29 - It seems that the ideas about when multibyte trailing characters were # included in the encryption were wrong. They aren't for DOC compressed # files, but they are for HUFF/CDIC compress files! +# 0.30 - Modified interface slightly to work better with new calibre plugin style -__version__ = '0.29' +__version__ = '0.30' import sys @@ -163,6 +164,7 @@ class MobiBook: def __init__(self, infile): # initial sanity check on file self.data_file = file(infile, 'rb').read() + self.mobi_data = '' self.header = self.data_file[0:78] if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': raise DrmException("invalid file format") @@ -301,13 +303,17 @@ class MobiBook: break return [found_key,pid] + def getMobiFile(self, outpath): + file(outpath,'wb').write(self.mobi_data) + def processBook(self, pidlist): crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) print 'Crypto Type is: ', crypto_type self.crypto_type = crypto_type if crypto_type == 0: print "This book is not encrypted." - return self.data_file + self.mobi_data = self.data_file + return if crypto_type != 2 and crypto_type != 1: raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) @@ -353,33 +359,35 @@ class MobiBook: # decrypt sections print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] + self.mobi_data = self.data_file[:self.sections[1][0]] for i in xrange(1, self.records+1): data = self.loadSection(i) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: print ".", # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) + self.mobi_data += PC1(found_key, data[0:len(data) - extra_size]) if extra_size > 0: - new_data += data[-extra_size:] + self.mobi_data += data[-extra_size:] if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data + self.mobi_data += self.data_file[self.sections[self.records+1][0]:] print "done" - return self.data_file + return def getUnencryptedBook(infile,pid): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook([pid]) + book.processBook([pid]) + return book.mobi_data def getUnencryptedBookWithList(infile,pidlist): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook(pidlist) + book.processBook(pidlist) + return book.mobi_data + def main(argv=sys.argv): print ('MobiDeDrm v%(__version__)s. ' diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py index 59bc5fa..12b92e2 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py @@ -10,7 +10,12 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + import os, csv, getopt import zlib, zipfile, tempfile, shutil from struct import pack @@ -18,10 +23,32 @@ from struct import unpack class TpzDRMError(Exception): pass + # local support routines -import kgenpids -import genbook +if inCalibre: + from calibre_plugins.k4mobidedrm import kgenpids + from calibre_plugins.k4mobidedrm import genbook +else: + import kgenpids + import genbook + + +# recursive zip creation support routine +def zipUpDir(myzip, tdir, localname): + currentdir = tdir + if localname != "": + currentdir = os.path.join(currentdir,localname) + list = os.listdir(currentdir) + for file in list: + afilename = file + localfilePath = os.path.join(localname, afilename) + realfilePath = os.path.join(currentdir,file) + if os.path.isfile(realfilePath): + myzip.write(realfilePath, localfilePath) + elif os.path.isdir(realfilePath): + zipUpDir(myzip, tdir, localfilePath) + # # Utility routines # @@ -110,9 +137,9 @@ def decryptDkeyRecords(data,PID): class TopazBook: - def __init__(self, filename, outdir): + def __init__(self, filename): self.fo = file(filename, 'rb') - self.outdir = outdir + self.outdir = tempfile.mkdtemp() self.bookPayloadOffset = 0 self.bookHeaderRecords = {} self.bookMetadata = {} @@ -317,21 +344,33 @@ class TopazBook: file(outputFile, 'wb').write(record) print " " + def getHTMLZip(self, zipname): + htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html') + htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf') + if os.path.isfile(os.path.join(self.outdir,'cover.jpg')): + htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg') + htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css') + zipUpDir(htmlzip, self.outdir, 'img') + htmlzip.close() -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) + def getSVGZip(self, zipname): + svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml') + zipUpDir(svgzip, self.outdir, 'svg') + zipUpDir(svgzip, self.outdir, 'img') + svgzip.close() + def getXMLZip(self, zipname): + xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + targetdir = os.path.join(self.outdir,'xml') + zipUpDir(xmlzip, targetdir, '') + zipUpDir(xmlzip, self.outdir, 'img') + xmlzip.close() + + def cleanup(self): + if os.path.isdir(self.outdir): + shutil.rmtree(self.outdir, True) def usage(progname): print "Removes DRM protection from Topaz ebooks and extract the contents" @@ -383,58 +422,46 @@ def main(argv=sys.argv): return 1 bookname = os.path.splitext(os.path.basename(infile))[0] - tempdir = tempfile.mkdtemp() - tb = TopazBook(infile, tempdir) + tb = TopazBook(infile) title = tb.getBookTitle() print "Processing Book: ", title keysRecord, keysRecordRecord = tb.getPIDMetaInfo() pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) try: + print "Decrypting Book" tb.processBook(pidlst) + + print " Creating HTML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz') + tb.getHTMLZip(zipname) + + print " Creating SVG ZIP Archive" + zipname = os.path.join(outdir, bookname + '_SVG' + '.htmlz') + tb.getSVGZip(zipname) + + print " Creating XML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_XML' + '.zip') + tb.getXMLZip(zipname) + + # removing internal temporary directory of pieces + tb.cleanup() + except TpzDRMError, e: print str(e) - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) + tb.cleanup() return 1 - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() - - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() - - shutil.rmtree(tempdir, True) + except Exception, e: + print str(e) + tb.cleanup + return 1 return 0 if __name__ == '__main__': + sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/DeDRM_app.pyw b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/DeDRM_app.pyw index a319631..1b81ade 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/DeDRM_app.pyw +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/DeDRM_app.pyw @@ -124,14 +124,17 @@ class PrefsDialog(Toplevel): button = Tkinter.Button(body, text="...", command=self.get_bnkpath) button.grid(row=1, column=2) - Tkinter.Label(body, text='Additional kindle.info file').grid(row=2, sticky=Tkconstants.E) + Tkinter.Label(body, text='Additional kindle.info or .kinf file').grid(row=2, sticky=Tkconstants.E) self.altinfopath = Tkinter.Entry(body, width=50) self.altinfopath.grid(row=2, column=1, sticky=sticky) prefdir = self.prefs_array['dir'] - infofile = os.path.join(prefdir,'kindle.info') path = '' + infofile = os.path.join(prefdir,'kindle.info') + ainfofile = os.path.join(prefdir,'.kinf') if os.path.isfile(infofile): path = infofile + elif os.path.isfile(ainfofile): + path = ainfofile path = path.encode('utf-8') self.altinfopath.insert(0, path) button = Tkinter.Button(body, text="...", command=self.get_altinfopath) @@ -245,8 +248,8 @@ class PrefsDialog(Toplevel): def get_altinfopath(self): cpath = self.altinfopath.get() - altinfopath = tkFileDialog.askopenfilename(parent=None, title='Select Alternative kindle.info File', - defaultextension='.info', filetypes=[('Kindle Info', '.info'),('All Files', '.*')], + altinfopath = tkFileDialog.askopenfilename(parent=None, title='Select Alternative kindle.info or .kinf File', + defaultextension='.info', filetypes=[('Kindle Info', '.info'),('Kindle KInf','.kinf')('All Files', '.*')], initialdir=cpath) if altinfopath: altinfopath = os.path.normpath(altinfopath) @@ -457,8 +460,7 @@ class ConvDialog(Toplevel): name, ext = os.path.splitext(os.path.basename(infile)) ext = ext.lower() if ext == '.epub': - outfile = os.path.join(outdir, name + '_nodrm.epub') - self.p2 = processEPUB(apphome, infile, outfile, rscpath) + self.p2 = processEPUB(apphome, infile, outdir, rscpath) return 0 if ext == '.pdb': self.p2 = processPDB(apphome, infile, outdir, rscpath) @@ -467,8 +469,7 @@ class ConvDialog(Toplevel): self.p2 = processK4MOBI(apphome, infile, outdir, rscpath) return 0 if ext == '.pdf': - outfile = os.path.join(outdir, name + '_nodrm.pdf') - self.p2 = processPDF(apphome, infile, outfile, rscpath) + self.p2 = processPDF(apphome, infile, outdir, rscpath) return 0 return rv @@ -506,7 +507,7 @@ def processK4MOBI(apphome, infile, outdir, rscpath): parms += '-s "' + serialnums + '" ' files = os.listdir(rscpath) - filefilter = re.compile("\.info$", re.IGNORECASE) + filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE) files = filter(filefilter.search, files) if files: for filename in files: @@ -516,16 +517,16 @@ def processK4MOBI(apphome, infile, outdir, rscpath): p2 = runit(apphome, cmd, parms) return p2 -def processPDF(apphome, infile, outfile, rscpath): +def processPDF(apphome, infile, outdir, rscpath): cmd = os.path.join('lib','decryptpdf.py') - parms = '"' + infile + '" "' + outfile + '" "' + rscpath + '"' + parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"' p2 = runit(apphome, cmd, parms) return p2 -def processEPUB(apphome, infile, outfile, rscpath): +def processEPUB(apphome, infile, outdir, rscpath): # invoke routine to check both Adept and Barnes and Noble cmd = os.path.join('lib','decryptepub.py') - parms = '"' + infile + '" "' + outfile + '" "' + rscpath + '"' + parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"' p2 = runit(apphome, cmd, parms) return p2 diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptepub.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptepub.py index b9c9330..93713e4 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptepub.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptepub.py @@ -24,7 +24,7 @@ def main(argv=sys.argv): if len(args) != 3: return -1 infile = args[0] - outfile = args[1] + outdir = args[1] rscpath = args[2] errlog = '' @@ -37,6 +37,9 @@ def main(argv=sys.argv): print "Error while trying to fix epub" return rv + # determine a good name for the output file + outfile = os.path.join(outdir, name + '_nodrm.epub') + rv = 1 # first try with the Adobe adept epub # try with any keyfiles (*.der) in the rscpath diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptpdf.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptpdf.py index f18e75e..82d222a 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptpdf.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptpdf.py @@ -21,10 +21,15 @@ def main(argv=sys.argv): if len(args) != 3: return -1 infile = args[0] - outfile = args[1] + outdir = args[1] rscpath = args[2] errlog = '' rv = 1 + + # determine a good name for the output file + name, ext = os.path.splitext(os.path.basename(infile)) + outfile = os.path.join(outdir, name + '_nodrm.pdf') + # try with any keyfiles (*.der) in the rscpath files = os.listdir(rscpath) filefilter = re.compile("\.der$", re.IGNORECASE) diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/erdr2pml.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/erdr2pml.py index 6df9e13..0e59d8d 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/erdr2pml.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/erdr2pml.py @@ -58,8 +58,9 @@ # 0.17 - added support for pycrypto's DES as well # 0.18 - on Windows try PyCrypto first and OpenSSL next # 0.19 - Modify the interface to allow use of import +# 0.20 - modify to allow use inside new interface for calibre plugins -__version__='0.19' +__version__='0.20' class Unbuffered: def __init__(self, stream): @@ -71,32 +72,50 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) - import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + Des = None if sys.platform.startswith('win'): # first try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() if Des == None: # they try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() else: # first try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() if Des == None: # then try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() # if that did not work then use pure python implementation # of DES and try to speed it up with Psycho if Des == None: - import python_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import python_des + else: + import python_des Des = python_des.Des # Import Psyco if available try: @@ -480,5 +499,6 @@ def main(argv=None): if __name__ == "__main__": + sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/genbook.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/genbook.py index 11258d1..f779cee 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/genbook.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/genbook.py @@ -21,10 +21,21 @@ from struct import unpack # local support routines -import convert2xml -import flatxml2html -import flatxml2svg -import stylexml2css +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre : + from calibre_plugins.k4mobidedrm import convert2xml + from calibre_plugins.k4mobidedrm import flatxml2html + from calibre_plugins.k4mobidedrm import flatxml2svg + from calibre_plugins.k4mobidedrm import stylexml2css +else : + import convert2xml + import flatxml2html + import flatxml2svg + import stylexml2css # Get a 7 bit encoded number from a file @@ -504,7 +515,7 @@ def generateBook(bookDir, raw, fixedimage): opfstr += ' \n' opfstr += '\n' opfstr += ' \n' - opfstr += ' \n' + opfstr += ' \n' # adding image files to manifest filenames = os.listdir(imgDir) filenames = sorted(filenames) diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ineptpdf.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ineptpdf.py index 40e97cd..c9a419b 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ineptpdf.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ineptpdf.py @@ -157,6 +157,7 @@ def _load_crypto_libcrypto(): return out.raw class AES(object): + MODE_CBC = 0 @classmethod def new(cls, userkey, mode, iv): self = AES() diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py index 64a998a..14556db 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py @@ -16,20 +16,8 @@ from __future__ import with_statement # unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates # and many many others -# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that importing -# K4 or Mobi with DRM is no londer a multi-step process. -# -# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook -# then calibre must be installed on the same machine and in the same account as K4PC or K4M -# for the plugin version to function properly. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines -# and import that ZIP into Calibre using its plugin configuration GUI. - -__version__ = '2.8' +__version__ = '3.1' class Unbuffered: def __init__(self, stream): @@ -43,11 +31,7 @@ class Unbuffered: import sys import os, csv, getopt import string -import binascii -import zlib import re -import zlib, zipfile, tempfile, shutil -from struct import pack, unpack, unpack_from class DrmException(Exception): pass @@ -57,19 +41,15 @@ if 'calibre' in sys.modules: else: inCalibre = False -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) +if inCalibre: + from calibre_plugins.k4mobidedrm import mobidedrm + from calibre_plugins.k4mobidedrm import topazextract + from calibre_plugins.k4mobidedrm import kgenpids +else: + import mobidedrm + import topazextract + import kgenpids + # cleanup bytestring filenames # borrowed from calibre from calibre/src/calibre/__init__.py @@ -94,10 +74,6 @@ def cleanup_name(name): return one def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): - import mobidedrm - import topazextract - import kgenpids - # handle the obvious cases at the beginning if not os.path.isfile(infile): print "Error: Input file does not exist" @@ -113,8 +89,7 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): if mobi: mb = mobidedrm.MobiBook(infile) else: - tempdir = tempfile.mkdtemp() - mb = topazextract.TopazBook(infile, tempdir) + mb = topazextract.TopazBook(infile) title = mb.getBookTitle() print "Processing Book: ", title @@ -128,60 +103,39 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) + mb.processBook(pidlst) except mobidedrm.DrmException, e: print "Error: " + str(e) + "\nDRM Removal Failed.\n" return 1 + except topazextract.TpzDRMError, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 except Exception, e: - if not mobi: - print "Error: " + str(e) + "\nDRM Removal Failed.\n" - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) - return 1 - pass + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 if mobi: - outfile = os.path.join(outdir,outfilename + '_nodrm' + '.mobi') - file(outfile, 'wb').write(unlocked_file) + outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') + mb.getMobiFile(outfile) return 0 - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() + # topaz: + print " Creating NoDRM HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz') + mb.getHTMLZip(zipname) - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() + print " Creating SVG HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_SVG' + '.htmlz') + mb.getSVGZip(zipname) print " Creating XML ZIP Archive" zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() + mb.getXMLZip(zipname) + + # remove internal temporary directory of Topaz pieces + mb.cleanup() - shutil.rmtree(tempdir, True) return 0 @@ -236,7 +190,6 @@ def main(argv=sys.argv): kInfoFiles = None infile = args[0] outdir = args[1] - return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) @@ -244,131 +197,3 @@ if __name__ == '__main__': sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) -if not __name__ == "__main__" and inCalibre: - from calibre.customize import FileTypePlugin - - class K4DeDRM(FileTypePlugin): - name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin - description = 'Removes DRM from K4PC and Mac, Kindle Mobi and Topaz files. \ - 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 - author = 'DiapDealer, SomeUpdates' # The author of this plugin - version = (0, 2, 8) # The version number of this plugin - file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to - on_import = True # Run this plugin during the import - priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm - - def run(self, path_to_ebook): - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - from calibre.ptempfile import PersistentTemporaryDirectory - - import kgenpids - import zlib - import zipfile - import topazextract - import mobidedrm - - k4 = True - if sys.platform.startswith('linux'): - k4 = False - pids = [] - serials = [] - kInfoFiles = [] - - # Get supplied list of PIDs to try from plugin customization. - customvalues = self.site_customization.split(',') - for customvalue in customvalues: - customvalue = str(customvalue) - customvalue = customvalue.strip() - if len(customvalue) == 10 or len(customvalue) == 8: - pids.append(customvalue) - else : - if len(customvalue) == 16 and customvalue[0] == 'B': - serials.append(customvalue) - else: - print "%s is not a valid Kindle serial number or PID." % str(customvalue) - - # Load any kindle info files (*.info) included Calibre's config directory. - try: - # Find Calibre's configuration directory. - confpath = os.path.split(os.path.split(self.plugin_path)[0])[0] - print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath - files = os.listdir(confpath) - filefilter = re.compile("\.info$", re.IGNORECASE) - files = filter(filefilter.search, files) - - if files: - for filename in files: - fpath = os.path.join(confpath, filename) - kInfoFiles.append(fpath) - print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename - except IOError: - print 'K4MobiDeDRM: Error reading kindle info files from config directory.' - pass - - - mobi = True - magic3 = file(path_to_ebook,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(path_to_ebook))[0] - - if mobi: - mb = mobidedrm.MobiBook(path_to_ebook) - else: - tempdir = PersistentTemporaryDirectory() - mb = topazextract.TopazBook(path_to_ebook, tempdir) - - title = mb.getBookTitle() - md1, md2 = mb.getPIDMetaInfo() - pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) - - try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) - - except mobidedrm.DrmException: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - except topazextract.TpzDRMError: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - - print "Success!" - if mobi: - of = self.temporary_file(bookname+'.mobi') - of.write(unlocked_file) - of.close() - return of.name - - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - of = self.temporary_file(bookname + '.zip') - myzip = zipfile.ZipFile(of.name,'w',zipfile.ZIP_DEFLATED, False) - myzip.write(os.path.join(tempdir,'book.html'),'book.html') - myzip.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip, tempdir, 'img') - myzip.close() - return of.name - - def customization_help(self, gui=False): - return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' \ No newline at end of file diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mutils.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mutils.py index 539723d..534c389 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mutils.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mutils.py @@ -1,10 +1,12 @@ # standlone set of Mac OSX specific routines needed for K4DeDRM from __future__ import with_statement + import sys import os import subprocess +from struct import pack, unpack, unpack_from class DrmException(Exception): pass @@ -66,9 +68,8 @@ def _load_crypto_libcrypto(): raise DrmException('AES decryption failed') return out.raw - def keyivgen(self, passwd): - salt = '16743' - saltlen = 5 + def keyivgen(self, passwd, salt): + saltlen = len(salt) passlen = len(passwd) iter = 0x3e8 keylen = 80 @@ -91,12 +92,78 @@ LibCrypto = _load_crypto() # Utility Routines # +# crypto digestroutines +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() # Various character maps used to decrypt books. Probably supposed to act as obfuscation charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +# For Future Reference from .kinf approach of K4PC +charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" + + +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 + +# For Future Reference from .kinf approach of K4PC +# generate table of prime number less than or equal to int n +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + # + # # get the first item record + # item = items.pop(0) + # + # # the first 32 chars of the first record of a group + # # is the MD5 hash of the key name encoded by charMap5 + # keyhash = item[0:32] + # + # # the raw keyhash string is also used to create entropy for the actual + # # CryptProtectData Blob that represents that keys contents + # entropy = SHA1(keyhash) + # + # # the remainder of the first record when decoded with charMap5 + # # has the ':' split char followed by the string representation + # # of the number of records that follow + # # and make up the contents + # srcnt = decode(item[34:],charMap5) + # rcnt = int(srcnt) + # + # # read and store in rcnt records of data + # # that make up the contents value + # edlst = [] + # for i in xrange(rcnt): + # item = items.pop(0) + # edlst.append(item) + # + # keyname = "unknown" + # for name in names: + # if encodeHash(name,charMap5) == keyhash: + # keyname = name + # break + # if keyname == "unknown": + # keyname = keyhash + # + # # the charMap5 encoded contents data has had a length + # # of chars (always odd) cut off of the front and moved + # # to the end to prevent decoding using charMap5 from + # # working properly, and thereby preventing the ensuing + # # CryptUnprotectData call from succeeding. + # + # # The offset into the charMap5 encoded contents seems to be: + # # len(contents) - largest prime number less than or equal to int(len(content)/3) + # # (in other words split "about" 2/3rds of the way through) + # + # # move first offsets chars to end to align for decode by charMap5 + # encdata = "".join(edlst) + # contlen = len(encdata) + # noffset = contlen - primes(int(contlen/3))[-1] + # + # # now properly split and recombine + # # by moving noffset chars from the start of the + # # string to the end of the string + # pfx = encdata[0:noffset] + # encdata = encdata[noffset:] + # encdata = encdata + pfx + # + # # decode using Map5 to get the CryptProtect Data + # encryptedValue = decode(encdata,charMap5) + # DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + # cnt = cnt + 1 + + if cnt == 0: + DB = None return DB diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4pcutils.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4pcutils.py index d460a70..690033b 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4pcutils.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4pcutils.py @@ -1,8 +1,10 @@ +#!/usr/bin/env python # K4PC Windows specific routines from __future__ import with_statement import sys, os +from struct import pack, unpack, unpack_from from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ @@ -10,25 +12,86 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ import _winreg as winreg -import traceback - MAX_PATH = 255 kernel32 = windll.kernel32 advapi32 = windll.advapi32 crypt32 = windll.crypt32 +import traceback -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +# crypto digestroutines +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() + + +# simple primes table (<= n) calculator +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the raw keyhash string is also used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = "unknown" + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using Map5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + cnt = cnt + 1 + + if cnt == 0: + DB = None return DB + + diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py index c10f105..c9d8944 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py @@ -11,16 +11,28 @@ from struct import pack, unpack, unpack_from class DrmException(Exception): pass -global kindleDatabase global charMap1 -global charMap2 global charMap3 global charMap4 -if sys.platform.startswith('win'): - from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 -if sys.platform.startswith('darwin'): - from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + if sys.platform.startswith('win'): + from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber +else: + if sys.platform.startswith('win'): + from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" @@ -66,50 +78,7 @@ def decode(data,map): value = (((high * len(map)) ^ 0x80) & 0xFF) + low result += pack("B",value) return result - -# 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 - global charMap1 - global charMap2 - encryptedValue = decode(kindleDatabase[hashedKey],charMap2) - if sys.platform.startswith('win'): - return CryptUnprotectData(encryptedValue,"") - else: - 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): - global charMap2 - 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): - global charMap2 - 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 # @@ -222,15 +191,15 @@ def getKindlePid(pidlst, rec209, token, serialnum): return pidlst -# Parse the EXTH header records and parse the Kindleinfo -# file to calculate the book pid. +# parse the Kindleinfo file to calculate the book pid. + +keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] def getK4Pids(pidlst, rec209, token, kInfoFile): - global kindleDatabase global charMap1 kindleDatabase = None try: - kindleDatabase = parseKindleInfo(kInfoFile) + kindleDatabase = getDBfromFile(kInfoFile) except Exception, message: print(message) kindleDatabase = None @@ -241,10 +210,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): try: # Get the Mazama Random number - MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") + MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] # Get the kindle account token - kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") + kindleAccountToken = kindleDatabase["kindle.account.tokens"] except KeyError: print "Keys not found in " + kInfoFile return pidlst diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py index 7aef175..41bb12d 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py @@ -51,8 +51,9 @@ # 0.29 - It seems that the ideas about when multibyte trailing characters were # included in the encryption were wrong. They aren't for DOC compressed # files, but they are for HUFF/CDIC compress files! +# 0.30 - Modified interface slightly to work better with new calibre plugin style -__version__ = '0.29' +__version__ = '0.30' import sys @@ -163,6 +164,7 @@ class MobiBook: def __init__(self, infile): # initial sanity check on file self.data_file = file(infile, 'rb').read() + self.mobi_data = '' self.header = self.data_file[0:78] if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': raise DrmException("invalid file format") @@ -301,13 +303,17 @@ class MobiBook: break return [found_key,pid] + def getMobiFile(self, outpath): + file(outpath,'wb').write(self.mobi_data) + def processBook(self, pidlist): crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) print 'Crypto Type is: ', crypto_type self.crypto_type = crypto_type if crypto_type == 0: print "This book is not encrypted." - return self.data_file + self.mobi_data = self.data_file + return if crypto_type != 2 and crypto_type != 1: raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) @@ -353,33 +359,35 @@ class MobiBook: # decrypt sections print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] + self.mobi_data = self.data_file[:self.sections[1][0]] for i in xrange(1, self.records+1): data = self.loadSection(i) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: print ".", # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) + self.mobi_data += PC1(found_key, data[0:len(data) - extra_size]) if extra_size > 0: - new_data += data[-extra_size:] + self.mobi_data += data[-extra_size:] if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data + self.mobi_data += self.data_file[self.sections[self.records+1][0]:] print "done" - return self.data_file + return def getUnencryptedBook(infile,pid): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook([pid]) + book.processBook([pid]) + return book.mobi_data def getUnencryptedBookWithList(infile,pidlist): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook(pidlist) + book.processBook(pidlist) + return book.mobi_data + def main(argv=sys.argv): print ('MobiDeDrm v%(__version__)s. ' diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py index 59bc5fa..12b92e2 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py @@ -10,7 +10,12 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + import os, csv, getopt import zlib, zipfile, tempfile, shutil from struct import pack @@ -18,10 +23,32 @@ from struct import unpack class TpzDRMError(Exception): pass + # local support routines -import kgenpids -import genbook +if inCalibre: + from calibre_plugins.k4mobidedrm import kgenpids + from calibre_plugins.k4mobidedrm import genbook +else: + import kgenpids + import genbook + + +# recursive zip creation support routine +def zipUpDir(myzip, tdir, localname): + currentdir = tdir + if localname != "": + currentdir = os.path.join(currentdir,localname) + list = os.listdir(currentdir) + for file in list: + afilename = file + localfilePath = os.path.join(localname, afilename) + realfilePath = os.path.join(currentdir,file) + if os.path.isfile(realfilePath): + myzip.write(realfilePath, localfilePath) + elif os.path.isdir(realfilePath): + zipUpDir(myzip, tdir, localfilePath) + # # Utility routines # @@ -110,9 +137,9 @@ def decryptDkeyRecords(data,PID): class TopazBook: - def __init__(self, filename, outdir): + def __init__(self, filename): self.fo = file(filename, 'rb') - self.outdir = outdir + self.outdir = tempfile.mkdtemp() self.bookPayloadOffset = 0 self.bookHeaderRecords = {} self.bookMetadata = {} @@ -317,21 +344,33 @@ class TopazBook: file(outputFile, 'wb').write(record) print " " + def getHTMLZip(self, zipname): + htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html') + htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf') + if os.path.isfile(os.path.join(self.outdir,'cover.jpg')): + htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg') + htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css') + zipUpDir(htmlzip, self.outdir, 'img') + htmlzip.close() -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) + def getSVGZip(self, zipname): + svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml') + zipUpDir(svgzip, self.outdir, 'svg') + zipUpDir(svgzip, self.outdir, 'img') + svgzip.close() + def getXMLZip(self, zipname): + xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + targetdir = os.path.join(self.outdir,'xml') + zipUpDir(xmlzip, targetdir, '') + zipUpDir(xmlzip, self.outdir, 'img') + xmlzip.close() + + def cleanup(self): + if os.path.isdir(self.outdir): + shutil.rmtree(self.outdir, True) def usage(progname): print "Removes DRM protection from Topaz ebooks and extract the contents" @@ -383,58 +422,46 @@ def main(argv=sys.argv): return 1 bookname = os.path.splitext(os.path.basename(infile))[0] - tempdir = tempfile.mkdtemp() - tb = TopazBook(infile, tempdir) + tb = TopazBook(infile) title = tb.getBookTitle() print "Processing Book: ", title keysRecord, keysRecordRecord = tb.getPIDMetaInfo() pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) try: + print "Decrypting Book" tb.processBook(pidlst) + + print " Creating HTML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz') + tb.getHTMLZip(zipname) + + print " Creating SVG ZIP Archive" + zipname = os.path.join(outdir, bookname + '_SVG' + '.htmlz') + tb.getSVGZip(zipname) + + print " Creating XML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_XML' + '.zip') + tb.getXMLZip(zipname) + + # removing internal temporary directory of pieces + tb.cleanup() + except TpzDRMError, e: print str(e) - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) + tb.cleanup() return 1 - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() - - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() - - shutil.rmtree(tempdir, True) + except Exception, e: + print str(e) + tb.cleanup + return 1 return 0 if __name__ == '__main__': + sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) diff --git a/KindleBooks_Tools/KindleBooks/KindleBooks.pyw b/KindleBooks/KindleBooks.pyw similarity index 100% rename from KindleBooks_Tools/KindleBooks/KindleBooks.pyw rename to KindleBooks/KindleBooks.pyw diff --git a/KindleBooks_Tools/KindleBooks/README_KindleBooks.txt b/KindleBooks/README_KindleBooks.txt similarity index 100% rename from KindleBooks_Tools/KindleBooks/README_KindleBooks.txt rename to KindleBooks/README_KindleBooks.txt diff --git a/KindleBooks_Tools/KindleBooks/lib/cmbtc_v2.2.py b/KindleBooks/lib/cmbtc_v2.2.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/cmbtc_v2.2.py rename to KindleBooks/lib/cmbtc_v2.2.py diff --git a/KindleBooks_Tools/KindleBooks/lib/convert2xml.py b/KindleBooks/lib/convert2xml.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/convert2xml.py rename to KindleBooks/lib/convert2xml.py diff --git a/KindleBooks_Tools/KindleBooks/lib/flatxml2html.py b/KindleBooks/lib/flatxml2html.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/flatxml2html.py rename to KindleBooks/lib/flatxml2html.py diff --git a/KindleBooks_Tools/KindleBooks/lib/flatxml2svg.py b/KindleBooks/lib/flatxml2svg.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/flatxml2svg.py rename to KindleBooks/lib/flatxml2svg.py diff --git a/KindleBooks_Tools/KindleBooks/lib/genbook.py b/KindleBooks/lib/genbook.py similarity index 97% rename from KindleBooks_Tools/KindleBooks/lib/genbook.py rename to KindleBooks/lib/genbook.py index 11258d1..f779cee 100644 --- a/KindleBooks_Tools/KindleBooks/lib/genbook.py +++ b/KindleBooks/lib/genbook.py @@ -21,10 +21,21 @@ from struct import unpack # local support routines -import convert2xml -import flatxml2html -import flatxml2svg -import stylexml2css +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre : + from calibre_plugins.k4mobidedrm import convert2xml + from calibre_plugins.k4mobidedrm import flatxml2html + from calibre_plugins.k4mobidedrm import flatxml2svg + from calibre_plugins.k4mobidedrm import stylexml2css +else : + import convert2xml + import flatxml2html + import flatxml2svg + import stylexml2css # Get a 7 bit encoded number from a file @@ -504,7 +515,7 @@ def generateBook(bookDir, raw, fixedimage): opfstr += ' \n' opfstr += '\n' opfstr += ' \n' - opfstr += ' \n' + opfstr += ' \n' # adding image files to manifest filenames = os.listdir(imgDir) filenames = sorted(filenames) diff --git a/KindleBooks_Tools/KindleBooks/lib/genxml.py b/KindleBooks/lib/genxml.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/genxml.py rename to KindleBooks/lib/genxml.py diff --git a/KindleBooks_Tools/KindleBooks/lib/k4mdumpkinfo.py b/KindleBooks/lib/k4mdumpkinfo.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/k4mdumpkinfo.py rename to KindleBooks/lib/k4mdumpkinfo.py diff --git a/KindleBooks/lib/k4mobidedrm.py b/KindleBooks/lib/k4mobidedrm.py new file mode 100644 index 0000000..14556db --- /dev/null +++ b/KindleBooks/lib/k4mobidedrm.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python + +from __future__ import with_statement + +# engine to remove drm from Kindle for Mac and Kindle for PC books +# for personal use for archiving and converting your ebooks + +# PLEASE DO NOT PIRATE EBOOKS! + +# We want all authors and publishers, and eBook stores to live +# long and prosperous lives but at the same time we just want to +# be able to read OUR books on whatever device we want and to keep +# readable for a long, long time + +# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, +# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates +# and many many others + + +__version__ = '3.1' + +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 +import os, csv, getopt +import string +import re + +class DrmException(Exception): + pass + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + from calibre_plugins.k4mobidedrm import mobidedrm + from calibre_plugins.k4mobidedrm import topazextract + from calibre_plugins.k4mobidedrm import kgenpids +else: + import mobidedrm + import topazextract + import kgenpids + + +# cleanup bytestring filenames +# borrowed from calibre from calibre/src/calibre/__init__.py +# added in removal of non-printing chars +# and removal of . at start +# convert spaces to underscores +def cleanup_name(name): + _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') + substitute='_' + one = ''.join(char for char in name if char in string.printable) + one = _filename_sanitize.sub(substitute, one) + one = re.sub(r'\s', ' ', one).strip() + one = re.sub(r'^\.+$', '_', one) + one = one.replace('..', substitute) + # Windows doesn't like path components that end with a period + if one.endswith('.'): + one = one[:-1]+substitute + # Mac and Unix don't like file names that begin with a full stop + if len(one) > 0 and one[0] == '.': + one = substitute+one[1:] + one = one.replace(' ','_') + return one + +def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): + # handle the obvious cases at the beginning + if not os.path.isfile(infile): + print "Error: Input file does not exist" + return 1 + + mobi = True + magic3 = file(infile,'rb').read(3) + if magic3 == 'TPZ': + mobi = False + + bookname = os.path.splitext(os.path.basename(infile))[0] + + if mobi: + mb = mobidedrm.MobiBook(infile) + else: + mb = topazextract.TopazBook(infile) + + title = mb.getBookTitle() + print "Processing Book: ", title + filenametitle = cleanup_name(title) + outfilename = bookname + if len(bookname)>4 and len(filenametitle)>4 and bookname[:4] != filenametitle[:4]: + outfilename = outfilename + "_" + filenametitle + + # build pid list + md1, md2 = mb.getPIDMetaInfo() + pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) + + try: + mb.processBook(pidlst) + + except mobidedrm.DrmException, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 + except topazextract.TpzDRMError, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 + except Exception, e: + print "Error: " + str(e) + "\nDRM Removal Failed.\n" + return 1 + + if mobi: + outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') + mb.getMobiFile(outfile) + return 0 + + # topaz: + print " Creating NoDRM HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz') + mb.getHTMLZip(zipname) + + print " Creating SVG HTMLZ Archive" + zipname = os.path.join(outdir, outfilename + '_SVG' + '.htmlz') + mb.getSVGZip(zipname) + + print " Creating XML ZIP Archive" + zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') + mb.getXMLZip(zipname) + + # remove internal temporary directory of Topaz pieces + mb.cleanup() + + return 0 + + +def usage(progname): + print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks" + print "Usage:" + print " %s [-k ] [-p ] [-s ] " % progname + +# +# Main +# +def main(argv=sys.argv): + progname = os.path.basename(argv[0]) + + k4 = False + kInfoFiles = [] + serials = [] + pids = [] + + print ('K4MobiDeDrm v%(__version__)s ' + 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) + + print ' ' + try: + opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") + except getopt.GetoptError, err: + print str(err) + usage(progname) + sys.exit(2) + if len(args)<2: + usage(progname) + sys.exit(2) + + for o, a in opts: + if o == "-k": + if a == None : + raise DrmException("Invalid parameter for -k") + kInfoFiles.append(a) + if o == "-p": + if a == None : + raise DrmException("Invalid parameter for -p") + pids = a.split(',') + if o == "-s": + if a == None : + raise DrmException("Invalid parameter for -s") + serials = a.split(',') + + # try with built in Kindle Info files + k4 = True + if sys.platform.startswith('linux'): + k4 = False + kInfoFiles = None + infile = args[0] + outdir = args[1] + return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) + + +if __name__ == '__main__': + sys.stdout=Unbuffered(sys.stdout) + sys.exit(main()) + diff --git a/KindleBooks/lib/k4mutils.py b/KindleBooks/lib/k4mutils.py new file mode 100644 index 0000000..534c389 --- /dev/null +++ b/KindleBooks/lib/k4mutils.py @@ -0,0 +1,357 @@ +# standlone set of Mac OSX specific routines needed for K4DeDRM + +from __future__ import with_statement + +import sys +import os +import subprocess + +from struct import pack, unpack, unpack_from + +class DrmException(Exception): + pass + + +# 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 DrmException('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 DrmException('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 DrmException('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 DrmException('AES decryption failed') + return out.raw + + def keyivgen(self, passwd, salt): + saltlen = len(salt) + 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, DrmException): + pass + return LibCrypto + +LibCrypto = _load_crypto() + +# +# Utility Routines +# + +# crypto digestroutines +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() + +# Various character maps used to decrypt books. Probably supposed to act as obfuscation +charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" + +# For Future Reference from .kinf approach of K4PC +charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" + + +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 + +# For Future Reference from .kinf approach of K4PC +# generate table of prime number less than or equal to int n +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + # + # # get the first item record + # item = items.pop(0) + # + # # the first 32 chars of the first record of a group + # # is the MD5 hash of the key name encoded by charMap5 + # keyhash = item[0:32] + # + # # the raw keyhash string is also used to create entropy for the actual + # # CryptProtectData Blob that represents that keys contents + # entropy = SHA1(keyhash) + # + # # the remainder of the first record when decoded with charMap5 + # # has the ':' split char followed by the string representation + # # of the number of records that follow + # # and make up the contents + # srcnt = decode(item[34:],charMap5) + # rcnt = int(srcnt) + # + # # read and store in rcnt records of data + # # that make up the contents value + # edlst = [] + # for i in xrange(rcnt): + # item = items.pop(0) + # edlst.append(item) + # + # keyname = "unknown" + # for name in names: + # if encodeHash(name,charMap5) == keyhash: + # keyname = name + # break + # if keyname == "unknown": + # keyname = keyhash + # + # # the charMap5 encoded contents data has had a length + # # of chars (always odd) cut off of the front and moved + # # to the end to prevent decoding using charMap5 from + # # working properly, and thereby preventing the ensuing + # # CryptUnprotectData call from succeeding. + # + # # The offset into the charMap5 encoded contents seems to be: + # # len(contents) - largest prime number less than or equal to int(len(content)/3) + # # (in other words split "about" 2/3rds of the way through) + # + # # move first offsets chars to end to align for decode by charMap5 + # encdata = "".join(edlst) + # contlen = len(encdata) + # noffset = contlen - primes(int(contlen/3))[-1] + # + # # now properly split and recombine + # # by moving noffset chars from the start of the + # # string to the end of the string + # pfx = encdata[0:noffset] + # encdata = encdata[noffset:] + # encdata = encdata + pfx + # + # # decode using Map5 to get the CryptProtect Data + # encryptedValue = decode(encdata,charMap5) + # DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + # cnt = cnt + 1 + + if cnt == 0: + DB = None + return DB diff --git a/KindleBooks/lib/k4pcutils.py b/KindleBooks/lib/k4pcutils.py new file mode 100644 index 0000000..690033b --- /dev/null +++ b/KindleBooks/lib/k4pcutils.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +# K4PC Windows specific routines + +from __future__ import with_statement + +import sys, os +from struct import pack, unpack, unpack_from + +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 + +MAX_PATH = 255 + +kernel32 = windll.kernel32 +advapi32 = windll.advapi32 +crypt32 = windll.crypt32 + +import traceback + +# crypto digestroutines +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() + + +# simple primes table (<= n) calculator +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the raw keyhash string is also used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = "unknown" + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using Map5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + cnt = cnt + 1 + + if cnt == 0: + DB = None + return DB + + diff --git a/KindleBooks_Tools/KindleBooks/lib/kgenpids.py b/KindleBooks/lib/kgenpids.py similarity index 74% rename from KindleBooks_Tools/KindleBooks/lib/kgenpids.py rename to KindleBooks/lib/kgenpids.py index c10f105..c9d8944 100644 --- a/KindleBooks_Tools/KindleBooks/lib/kgenpids.py +++ b/KindleBooks/lib/kgenpids.py @@ -11,16 +11,28 @@ from struct import pack, unpack, unpack_from class DrmException(Exception): pass -global kindleDatabase global charMap1 -global charMap2 global charMap3 global charMap4 -if sys.platform.startswith('win'): - from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 -if sys.platform.startswith('darwin'): - from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + if sys.platform.startswith('win'): + from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber +else: + if sys.platform.startswith('win'): + from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + + if sys.platform.startswith('darwin'): + from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetVolumeSerialNumber + charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" @@ -66,50 +78,7 @@ def decode(data,map): value = (((high * len(map)) ^ 0x80) & 0xFF) + low result += pack("B",value) return result - -# 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 - global charMap1 - global charMap2 - encryptedValue = decode(kindleDatabase[hashedKey],charMap2) - if sys.platform.startswith('win'): - return CryptUnprotectData(encryptedValue,"") - else: - 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): - global charMap2 - 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): - global charMap2 - 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 # @@ -222,15 +191,15 @@ def getKindlePid(pidlst, rec209, token, serialnum): return pidlst -# Parse the EXTH header records and parse the Kindleinfo -# file to calculate the book pid. +# parse the Kindleinfo file to calculate the book pid. + +keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] def getK4Pids(pidlst, rec209, token, kInfoFile): - global kindleDatabase global charMap1 kindleDatabase = None try: - kindleDatabase = parseKindleInfo(kInfoFile) + kindleDatabase = getDBfromFile(kInfoFile) except Exception, message: print(message) kindleDatabase = None @@ -241,10 +210,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): try: # Get the Mazama Random number - MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") + MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] # Get the kindle account token - kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") + kindleAccountToken = kindleDatabase["kindle.account.tokens"] except KeyError: print "Keys not found in " + kInfoFile return pidlst diff --git a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py b/KindleBooks/lib/mobidedrm.py similarity index 96% rename from KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py rename to KindleBooks/lib/mobidedrm.py index 7aef175..41bb12d 100644 --- a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py +++ b/KindleBooks/lib/mobidedrm.py @@ -51,8 +51,9 @@ # 0.29 - It seems that the ideas about when multibyte trailing characters were # included in the encryption were wrong. They aren't for DOC compressed # files, but they are for HUFF/CDIC compress files! +# 0.30 - Modified interface slightly to work better with new calibre plugin style -__version__ = '0.29' +__version__ = '0.30' import sys @@ -163,6 +164,7 @@ class MobiBook: def __init__(self, infile): # initial sanity check on file self.data_file = file(infile, 'rb').read() + self.mobi_data = '' self.header = self.data_file[0:78] if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': raise DrmException("invalid file format") @@ -301,13 +303,17 @@ class MobiBook: break return [found_key,pid] + def getMobiFile(self, outpath): + file(outpath,'wb').write(self.mobi_data) + def processBook(self, pidlist): crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) print 'Crypto Type is: ', crypto_type self.crypto_type = crypto_type if crypto_type == 0: print "This book is not encrypted." - return self.data_file + self.mobi_data = self.data_file + return if crypto_type != 2 and crypto_type != 1: raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) @@ -353,33 +359,35 @@ class MobiBook: # decrypt sections print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] + self.mobi_data = self.data_file[:self.sections[1][0]] for i in xrange(1, self.records+1): data = self.loadSection(i) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: print ".", # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) + self.mobi_data += PC1(found_key, data[0:len(data) - extra_size]) if extra_size > 0: - new_data += data[-extra_size:] + self.mobi_data += data[-extra_size:] if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data + self.mobi_data += self.data_file[self.sections[self.records+1][0]:] print "done" - return self.data_file + return def getUnencryptedBook(infile,pid): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook([pid]) + book.processBook([pid]) + return book.mobi_data def getUnencryptedBookWithList(infile,pidlist): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook(pidlist) + book.processBook(pidlist) + return book.mobi_data + def main(argv=sys.argv): print ('MobiDeDrm v%(__version__)s. ' diff --git a/KindleBooks_Tools/KindleBooks/lib/scrolltextwidget.py b/KindleBooks/lib/scrolltextwidget.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/scrolltextwidget.py rename to KindleBooks/lib/scrolltextwidget.py diff --git a/KindleBooks_Tools/KindleBooks/lib/stylexml2css.py b/KindleBooks/lib/stylexml2css.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/stylexml2css.py rename to KindleBooks/lib/stylexml2css.py diff --git a/KindleBooks_Tools/KindleBooks/lib/subasyncio.py b/KindleBooks/lib/subasyncio.py similarity index 100% rename from KindleBooks_Tools/KindleBooks/lib/subasyncio.py rename to KindleBooks/lib/subasyncio.py diff --git a/KindleBooks_Tools/KindleBooks/lib/topazextract.py b/KindleBooks/lib/topazextract.py similarity index 84% rename from KindleBooks_Tools/KindleBooks/lib/topazextract.py rename to KindleBooks/lib/topazextract.py index 59bc5fa..12b92e2 100644 --- a/KindleBooks_Tools/KindleBooks/lib/topazextract.py +++ b/KindleBooks/lib/topazextract.py @@ -10,7 +10,12 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + import os, csv, getopt import zlib, zipfile, tempfile, shutil from struct import pack @@ -18,10 +23,32 @@ from struct import unpack class TpzDRMError(Exception): pass + # local support routines -import kgenpids -import genbook +if inCalibre: + from calibre_plugins.k4mobidedrm import kgenpids + from calibre_plugins.k4mobidedrm import genbook +else: + import kgenpids + import genbook + + +# recursive zip creation support routine +def zipUpDir(myzip, tdir, localname): + currentdir = tdir + if localname != "": + currentdir = os.path.join(currentdir,localname) + list = os.listdir(currentdir) + for file in list: + afilename = file + localfilePath = os.path.join(localname, afilename) + realfilePath = os.path.join(currentdir,file) + if os.path.isfile(realfilePath): + myzip.write(realfilePath, localfilePath) + elif os.path.isdir(realfilePath): + zipUpDir(myzip, tdir, localfilePath) + # # Utility routines # @@ -110,9 +137,9 @@ def decryptDkeyRecords(data,PID): class TopazBook: - def __init__(self, filename, outdir): + def __init__(self, filename): self.fo = file(filename, 'rb') - self.outdir = outdir + self.outdir = tempfile.mkdtemp() self.bookPayloadOffset = 0 self.bookHeaderRecords = {} self.bookMetadata = {} @@ -317,21 +344,33 @@ class TopazBook: file(outputFile, 'wb').write(record) print " " + def getHTMLZip(self, zipname): + htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html') + htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf') + if os.path.isfile(os.path.join(self.outdir,'cover.jpg')): + htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg') + htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css') + zipUpDir(htmlzip, self.outdir, 'img') + htmlzip.close() -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) + def getSVGZip(self, zipname): + svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml') + zipUpDir(svgzip, self.outdir, 'svg') + zipUpDir(svgzip, self.outdir, 'img') + svgzip.close() + def getXMLZip(self, zipname): + xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) + targetdir = os.path.join(self.outdir,'xml') + zipUpDir(xmlzip, targetdir, '') + zipUpDir(xmlzip, self.outdir, 'img') + xmlzip.close() + + def cleanup(self): + if os.path.isdir(self.outdir): + shutil.rmtree(self.outdir, True) def usage(progname): print "Removes DRM protection from Topaz ebooks and extract the contents" @@ -383,58 +422,46 @@ def main(argv=sys.argv): return 1 bookname = os.path.splitext(os.path.basename(infile))[0] - tempdir = tempfile.mkdtemp() - tb = TopazBook(infile, tempdir) + tb = TopazBook(infile) title = tb.getBookTitle() print "Processing Book: ", title keysRecord, keysRecordRecord = tb.getPIDMetaInfo() pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) try: + print "Decrypting Book" tb.processBook(pidlst) + + print " Creating HTML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz') + tb.getHTMLZip(zipname) + + print " Creating SVG ZIP Archive" + zipname = os.path.join(outdir, bookname + '_SVG' + '.htmlz') + tb.getSVGZip(zipname) + + print " Creating XML ZIP Archive" + zipname = os.path.join(outdir, bookname + '_XML' + '.zip') + tb.getXMLZip(zipname) + + # removing internal temporary directory of pieces + tb.cleanup() + except TpzDRMError, e: print str(e) - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) + tb.cleanup() return 1 - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() - - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() - - shutil.rmtree(tempdir, True) + except Exception, e: + print str(e) + tb.cleanup + return 1 return 0 if __name__ == '__main__': + sys.stdout=Unbuffered(sys.stdout) sys.exit(main()) diff --git a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py deleted file mode 100644 index 64a998a..0000000 --- a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py +++ /dev/null @@ -1,374 +0,0 @@ -#!/usr/bin/env python - -from __future__ import with_statement - -# engine to remove drm from Kindle for Mac and Kindle for PC books -# for personal use for archiving and converting your ebooks - -# PLEASE DO NOT PIRATE EBOOKS! - -# We want all authors and publishers, and eBook stores to live -# long and prosperous lives but at the same time we just want to -# be able to read OUR books on whatever device we want and to keep -# readable for a long, long time - -# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, -# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates -# and many many others - -# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a -# plugin for Calibre (http://calibre-ebook.com/about) so that importing -# K4 or Mobi with DRM is no londer a multi-step process. -# -# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook -# then calibre must be installed on the same machine and in the same account as K4PC or K4M -# for the plugin version to function properly. -# -# To create a Calibre plugin, rename this file so that the filename -# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines -# and import that ZIP into Calibre using its plugin configuration GUI. - - -__version__ = '2.8' - -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 -import os, csv, getopt -import string -import binascii -import zlib -import re -import zlib, zipfile, tempfile, shutil -from struct import pack, unpack, unpack_from - -class DrmException(Exception): - pass - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -def zipUpDir(myzip, tempdir,localname): - currentdir = tempdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tempdir, localfilePath) - -# cleanup bytestring filenames -# borrowed from calibre from calibre/src/calibre/__init__.py -# added in removal of non-printing chars -# and removal of . at start -# convert spaces to underscores -def cleanup_name(name): - _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') - substitute='_' - one = ''.join(char for char in name if char in string.printable) - one = _filename_sanitize.sub(substitute, one) - one = re.sub(r'\s', ' ', one).strip() - one = re.sub(r'^\.+$', '_', one) - one = one.replace('..', substitute) - # Windows doesn't like path components that end with a period - if one.endswith('.'): - one = one[:-1]+substitute - # Mac and Unix don't like file names that begin with a full stop - if len(one) > 0 and one[0] == '.': - one = substitute+one[1:] - one = one.replace(' ','_') - return one - -def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): - import mobidedrm - import topazextract - import kgenpids - - # handle the obvious cases at the beginning - if not os.path.isfile(infile): - print "Error: Input file does not exist" - return 1 - - mobi = True - magic3 = file(infile,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(infile))[0] - - if mobi: - mb = mobidedrm.MobiBook(infile) - else: - tempdir = tempfile.mkdtemp() - mb = topazextract.TopazBook(infile, tempdir) - - title = mb.getBookTitle() - print "Processing Book: ", title - filenametitle = cleanup_name(title) - outfilename = bookname - if len(bookname)>4 and len(filenametitle)>4 and bookname[:4] != filenametitle[:4]: - outfilename = outfilename + "_" + filenametitle - - # build pid list - md1, md2 = mb.getPIDMetaInfo() - pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) - - try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) - - except mobidedrm.DrmException, e: - print "Error: " + str(e) + "\nDRM Removal Failed.\n" - return 1 - except Exception, e: - if not mobi: - print "Error: " + str(e) + "\nDRM Removal Failed.\n" - print " Creating DeBug Full Zip Archive of Book" - zipname = os.path.join(outdir, bookname + '_debug' + '.zip') - myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - zipUpDir(myzip, tempdir, '') - myzip.close() - shutil.rmtree(tempdir, True) - return 1 - pass - - if mobi: - outfile = os.path.join(outdir,outfilename + '_nodrm' + '.mobi') - file(outfile, 'wb').write(unlocked_file) - return 0 - - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_nodrm' + '.zip') - myzip1 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip1.write(os.path.join(tempdir,'book.html'),'book.html') - myzip1.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip1.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip1.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip1, tempdir, 'img') - myzip1.close() - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') - myzip2 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - myzip2.write(os.path.join(tempdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(myzip2, tempdir, 'svg') - zipUpDir(myzip2, tempdir, 'img') - myzip2.close() - - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') - myzip3 = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(tempdir,'xml') - zipUpDir(myzip3, targetdir, '') - zipUpDir(myzip3, tempdir, 'img') - myzip3.close() - - shutil.rmtree(tempdir, True) - return 0 - - -def usage(progname): - print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - -# -# Main -# -def main(argv=sys.argv): - progname = os.path.basename(argv[0]) - - k4 = False - kInfoFiles = [] - serials = [] - pids = [] - - print ('K4MobiDeDrm v%(__version__)s ' - 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) - - print ' ' - try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") - except getopt.GetoptError, err: - print str(err) - usage(progname) - sys.exit(2) - if len(args)<2: - usage(progname) - sys.exit(2) - - for o, a in opts: - if o == "-k": - if a == None : - raise DrmException("Invalid parameter for -k") - kInfoFiles.append(a) - if o == "-p": - if a == None : - raise DrmException("Invalid parameter for -p") - pids = a.split(',') - if o == "-s": - if a == None : - raise DrmException("Invalid parameter for -s") - serials = a.split(',') - - # try with built in Kindle Info files - k4 = True - if sys.platform.startswith('linux'): - k4 = False - kInfoFiles = None - infile = args[0] - outdir = args[1] - - return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) - -if not __name__ == "__main__" and inCalibre: - from calibre.customize import FileTypePlugin - - class K4DeDRM(FileTypePlugin): - name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin - description = 'Removes DRM from K4PC and Mac, Kindle Mobi and Topaz files. \ - 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 - author = 'DiapDealer, SomeUpdates' # The author of this plugin - version = (0, 2, 8) # The version number of this plugin - file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to - on_import = True # Run this plugin during the import - priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm - - def run(self, path_to_ebook): - from calibre.gui2 import is_ok_to_use_qt - from PyQt4.Qt import QMessageBox - from calibre.ptempfile import PersistentTemporaryDirectory - - import kgenpids - import zlib - import zipfile - import topazextract - import mobidedrm - - k4 = True - if sys.platform.startswith('linux'): - k4 = False - pids = [] - serials = [] - kInfoFiles = [] - - # Get supplied list of PIDs to try from plugin customization. - customvalues = self.site_customization.split(',') - for customvalue in customvalues: - customvalue = str(customvalue) - customvalue = customvalue.strip() - if len(customvalue) == 10 or len(customvalue) == 8: - pids.append(customvalue) - else : - if len(customvalue) == 16 and customvalue[0] == 'B': - serials.append(customvalue) - else: - print "%s is not a valid Kindle serial number or PID." % str(customvalue) - - # Load any kindle info files (*.info) included Calibre's config directory. - try: - # Find Calibre's configuration directory. - confpath = os.path.split(os.path.split(self.plugin_path)[0])[0] - print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath - files = os.listdir(confpath) - filefilter = re.compile("\.info$", re.IGNORECASE) - files = filter(filefilter.search, files) - - if files: - for filename in files: - fpath = os.path.join(confpath, filename) - kInfoFiles.append(fpath) - print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename - except IOError: - print 'K4MobiDeDRM: Error reading kindle info files from config directory.' - pass - - - mobi = True - magic3 = file(path_to_ebook,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(path_to_ebook))[0] - - if mobi: - mb = mobidedrm.MobiBook(path_to_ebook) - else: - tempdir = PersistentTemporaryDirectory() - mb = topazextract.TopazBook(path_to_ebook, tempdir) - - title = mb.getBookTitle() - md1, md2 = mb.getPIDMetaInfo() - pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) - - try: - if mobi: - unlocked_file = mb.processBook(pidlst) - else: - mb.processBook(pidlst) - - except mobidedrm.DrmException: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - except topazextract.TpzDRMError: - #if you reached here then no luck raise and exception - if is_ok_to_use_qt(): - d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook) - d.show() - d.raise_() - d.exec_() - raise Exception("K4MobiDeDRM plugin could not decode the file") - return "" - - print "Success!" - if mobi: - of = self.temporary_file(bookname+'.mobi') - of.write(unlocked_file) - of.close() - return of.name - - # topaz: build up zip archives of results - print " Creating HTML ZIP Archive" - of = self.temporary_file(bookname + '.zip') - myzip = zipfile.ZipFile(of.name,'w',zipfile.ZIP_DEFLATED, False) - myzip.write(os.path.join(tempdir,'book.html'),'book.html') - myzip.write(os.path.join(tempdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(tempdir,'cover.jpg')): - myzip.write(os.path.join(tempdir,'cover.jpg'),'cover.jpg') - myzip.write(os.path.join(tempdir,'style.css'),'style.css') - zipUpDir(myzip, tempdir, 'img') - myzip.close() - return of.name - - def customization_help(self, gui=False): - return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' \ No newline at end of file diff --git a/KindleBooks_Tools/KindleBooks/lib/k4mutils.py b/KindleBooks_Tools/KindleBooks/lib/k4mutils.py deleted file mode 100644 index 539723d..0000000 --- a/KindleBooks_Tools/KindleBooks/lib/k4mutils.py +++ /dev/null @@ -1,200 +0,0 @@ -# standlone set of Mac OSX specific routines needed for K4DeDRM - -from __future__ import with_statement -import sys -import os -import subprocess - - -class DrmException(Exception): - pass - - -# 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 DrmException('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 DrmException('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 DrmException('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 DrmException('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, DrmException): - pass - return LibCrypto - -LibCrypto = _load_crypto() - -# -# Utility Routines -# - - -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - - - -# uses a sub process to get the Hard Drive Serial Number using ioreg -# returns with the serial number of drive whose BSD Name is "disk0" -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 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) - out1, out2 = p.communicate() - reslst = out1.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 - - -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 - -import hashlib - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# 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 the .kindle-info files -def getKindleInfoFiles(kInfoFiles): - home = os.getenv('HOME') - cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) - out1, out2 = p1.communicate() - reslst = out1.split('\n') - kinfopath = 'NONE' - found = False - cnt = len(reslst) - for resline in reslst: - if os.path.isfile(resline): - kInfoFiles.append(resline) - found = True - if not found: - print('No .kindle-info files have been found.') - return kInfoFiles - -# Parse the Kindle.info file and return the records as a list of key-values -def parseKindleInfo(kInfoFile): - DB = {} - infoReader = open(kInfoFile, 'r') - infoReader.read(1) - data = infoReader.read() - items = data.split('[') - for item in items: - splito = item.split(':') - DB[splito[0]] =splito[1] - return DB diff --git a/KindleBooks_Tools/KindleBooks/lib/k4pcutils.py b/KindleBooks_Tools/KindleBooks/lib/k4pcutils.py deleted file mode 100644 index d460a70..0000000 --- a/KindleBooks_Tools/KindleBooks/lib/k4pcutils.py +++ /dev/null @@ -1,117 +0,0 @@ -# K4PC Windows specific routines - -from __future__ import with_statement - -import sys, os - -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 traceback - -MAX_PATH = 255 - -kernel32 = windll.kernel32 -advapi32 = windll.advapi32 -crypt32 = windll.crypt32 - - -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - -class DrmException(Exception): - pass - - -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 = GetSystemDirectory().split('\\')[0] + '\\'): - vsn = c_uint(0) - GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0) - return str(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 DrmException("Failed to Unprotect Data") - return string_at(outdata.pbData, outdata.cbData) - return CryptUnprotectData -CryptUnprotectData = CryptUnprotectData() - -# Locate the .kindle-info files -def getKindleInfoFiles(kInfoFiles): - regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") - path = winreg.QueryValueEx(regkey, 'Local AppData')[0] - kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' - if not os.path.isfile(kinfopath): - print('The kindle.info files has not been found.') - else: - kInfoFiles.append(kinfopath) - return kInfoFiles - -# Parse the Kindle.info file and return the records as a list of key-values -def parseKindleInfo(kInfoFile): - DB = {} - infoReader = open(kInfoFile, 'r') - infoReader.read(1) - data = infoReader.read() - items = data.split('{') - for item in items: - splito = item.split(':') - DB[splito[0]] =splito[1] - return DB diff --git a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py deleted file mode 100644 index 7aef175..0000000 --- a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py +++ /dev/null @@ -1,409 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 0.01 - Initial version -# 0.02 - Huffdic compressed books were not properly decrypted -# 0.03 - Wasn't checking MOBI header length -# 0.04 - Wasn't sanity checking size of data record -# 0.05 - It seems that the extra data flags take two bytes not four -# 0.06 - And that low bit does mean something after all :-) -# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size -# 0.08 - ...and also not in Mobi header version < 6 -# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4! -# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre -# import filter it works when importing unencrypted files. -# Also now handles encrypted files that don't need a specific PID. -# 0.11 - use autoflushed stdout and proper return values -# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors -# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace -# and extra blank lines, converted CR/LF pairs at ends of each line, -# and other cosmetic fixes. -# 0.14 - Working out when the extra data flags are present has been problematic -# Versions 7 through 9 have tried to tweak the conditions, but have been -# only partially successful. Closer examination of lots of sample -# files reveals that a confusion has arisen because trailing data entries -# are not encrypted, but it turns out that the multibyte entries -# in utf8 file are encrypted. (Although neither kind gets compressed.) -# This knowledge leads to a simplification of the test for the -# trailing data byte flags - version 5 and higher AND header size >= 0xE4. -# 0.15 - Now outputs 'heartbeat', and is also quicker for long files. -# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility. -# 0.17 - added modifications to support its use as an imported python module -# both inside calibre and also in other places (ie K4DeDRM tools) -# 0.17a- disabled the standalone plugin feature since a plugin can not import -# a plugin -# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... -# Removed the disabled Calibre plug-in code -# Permit use of 8-digit PIDs -# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. -# 0.21 - Added support for multiple pids -# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface -# 0.23 - fixed problem with older files with no EXTH section -# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well -# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption -# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% -# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) -# 0.28 - slight additional changes to metadata token generation (None -> '') -# 0.29 - It seems that the ideas about when multibyte trailing characters were -# included in the encryption were wrong. They aren't for DOC compressed -# files, but they are for HUFF/CDIC compress files! - -__version__ = '0.29' - -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii - -class DrmException(Exception): - pass - - -# -# MobiBook Utility Routines -# - -# Implementation of Pukall Cipher 1 -def PC1(key, src, decryption=True): - sum1 = 0; - sum2 = 0; - keyXorVal = 0; - if len(key)!=16: - print "Bad key length!" - return None - wkey = [] - for i in xrange(8): - wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) - dst = "" - for i in xrange(len(src)): - temp1 = 0; - byteXorVal = 0; - for j in xrange(8): - temp1 ^= wkey[j] - sum2 = (sum2+j)*20021 + sum1 - sum1 = (temp1*346)&0xFFFF - sum2 = (sum2+sum1)&0xFFFF - temp1 = (temp1*20021+1)&0xFFFF - byteXorVal ^= temp1 ^ sum2 - curByte = ord(src[i]) - if not decryption: - keyXorVal = curByte * 257; - curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF - if decryption: - keyXorVal = curByte * 257; - for j in xrange(8): - wkey[j] ^= keyXorVal; - dst+=chr(curByte) - return dst - -def checksumPid(s): - letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - crc = (~binascii.crc32(s,-1))&0xFFFFFFFF - crc = crc ^ (crc >> 16) - res = s - l = len(letters) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += letters[pos%l] - crc >>= 8 - return res - -def getSizeOfTrailingDataEntries(ptr, size, flags): - def getSizeOfTrailingDataEntry(ptr, size): - bitpos, result = 0, 0 - if size <= 0: - return result - while True: - v = ord(ptr[size-1]) - result |= (v & 0x7F) << bitpos - bitpos += 7 - size -= 1 - if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0): - return result - num = 0 - testflags = flags >> 1 - while testflags: - if testflags & 1: - num += getSizeOfTrailingDataEntry(ptr, size - num) - testflags >>= 1 - # Check the low bit to see if there's multibyte data present. - # if multibyte data is included in the encryped data, we'll - # have already cleared this flag. - if flags & 1: - num += (ord(ptr[size - num - 1]) & 0x3) + 1 - return num - - - -class MobiBook: - def loadSection(self, section): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - return self.data_file[off:endoff] - - def __init__(self, infile): - # initial sanity check on file - self.data_file = file(infile, 'rb').read() - self.header = self.data_file[0:78] - if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") - self.magic = self.header[0x3C:0x3C+8] - self.crypto_type = -1 - - # build up section offset and flag info - self.num_sections, = struct.unpack('>H', self.header[76:78]) - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - - # parse information from section 0 - self.sect = self.loadSection(0) - self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) - self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2]) - - if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_version = -1 - self.meta_array = {} - return - self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) - self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 - if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): - self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags - if (self.mobi_version < 7) and (self.compression != 17480): - # multibyte utf8 data is included in the encryption for mobi_version 6 and below - # so clear that byte so that we leave it to be decrypted. - self.extra_data_flags &= 0xFFFE - - # if exth region exists parse it for metadata array - self.meta_array = {} - try: - exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' - if exth_flag & 0x40: - exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - content = exth[pos + 8: pos + size] - self.meta_array[type] = content - # reset the text to speech flag and clipping limit, if present - if type == 401 and size == 9: - # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) - elif type == 404 and size == 9: - # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - # print type, size, content, content.encode('hex') - pos += size - except: - self.meta_array = {} - pass - - def getBookTitle(self): - title = '' - if 503 in self.meta_array: - title = self.meta_array[503] - else : - toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) - tend = toff + tlen - title = self.sect[toff:tend] - if title == '': - title = self.header[:32] - title = title.split("\0")[0] - return title - - def getPIDMetaInfo(self): - rec209 = '' - token = '' - if 209 in self.meta_array: - rec209 = self.meta_array[209] - data = rec209 - # The 209 data comes in five byte groups. Interpret the last four bytes - # of each group as a big endian unsigned integer to get a key value - # if that key exists in the meta_array, append its contents to the token - for i in xrange(0,len(data),5): - val, = struct.unpack('>I',data[i+1:i+5]) - sval = self.meta_array.get(val,'') - token += sval - return rec209, token - - def patch(self, off, new): - self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] - - def patchSection(self, section, new, in_off = 0): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - assert off + in_off + len(new) <= endoff - self.patch(off + in_off, new) - - def parseDRM(self, data, count, pidlist): - found_key = None - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" - for pid in pidlist: - bigpid = pid.ljust(16,'\0') - temp_key = PC1(keyvec1, bigpid, False) - temp_key_sum = sum(map(ord,temp_key)) & 0xff - found_key = None - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver and (flags & 0x1F) == 1: - found_key = finalkey - break - if found_key != None: - break - if not found_key: - # Then try the default encoding that doesn't require a PID - pid = "00000000" - temp_key = keyvec1 - temp_key_sum = sum(map(ord,temp_key)) & 0xff - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver: - found_key = finalkey - break - return [found_key,pid] - - def processBook(self, pidlist): - crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) - print 'Crypto Type is: ', crypto_type - self.crypto_type = crypto_type - if crypto_type == 0: - print "This book is not encrypted." - return self.data_file - if crypto_type != 2 and crypto_type != 1: - raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) - - goodpids = [] - for pid in pidlist: - if len(pid)==10: - if checksumPid(pid[0:-2]) != pid: - print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) - goodpids.append(pid[0:-2]) - elif len(pid)==8: - goodpids.append(pid) - - if self.crypto_type == 1: - t1_keyvec = "QDCVEPMU675RUBSZ" - if self.magic == 'TEXtREAd': - bookkey_data = self.sect[0x0E:0x0E+16] - elif self.mobi_version < 0: - bookkey_data = self.sect[0x90:0x90+16] - else: - bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] - pid = "00000000" - found_key = PC1(t1_keyvec, bookkey_data) - else : - # calculate the keys - drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) - if drm_count == 0: - raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") - found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) - if not found_key: - raise DrmException("No key found. Most likely the correct PID has not been given.") - # kill the drm keys - self.patchSection(0, "\0" * drm_size, drm_ptr) - # kill the drm pointers - self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) - - if pid=="00000000": - print "File has default encryption, no specific PID." - else: - print "File is encoded with PID "+checksumPid(pid)+"." - - # clear the crypto type - self.patchSection(0, "\0" * 2, 0xC) - - # decrypt sections - print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] - for i in xrange(1, self.records+1): - data = self.loadSection(i) - extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) - if i%100 == 0: - print ".", - # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) - if extra_size > 0: - new_data += data[-extra_size:] - if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data - print "done" - return self.data_file - -def getUnencryptedBook(infile,pid): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile) - return book.processBook([pid]) - -def getUnencryptedBookWithList(infile,pidlist): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile) - return book.processBook(pidlist) - -def main(argv=sys.argv): - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2010 The Dark Reverser.' % globals()) - if len(argv)<3 or len(argv)>4: - print "Removes protection from Mobipocket books" - print "Usage:" - print " %s []" % sys.argv[0] - return 1 - else: - infile = argv[1] - outfile = argv[2] - if len(argv) is 4: - pidlist = argv[3].split(',') - else: - pidlist = {} - try: - stripped_file = getUnencryptedBookWithList(infile, pidlist) - file(outfile, 'wb').write(stripped_file) - except DrmException, e: - print "Error: %s" % e - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/K4Munswindle.pyw b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/K4Munswindle.pyw deleted file mode 100644 index 58a8e96..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/K4Munswindle.pyw +++ /dev/null @@ -1,299 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import sys -sys.path.append('lib') -import os, os.path, urllib -import subprocess -from subprocess import Popen, PIPE, STDOUT -import subasyncio -from subasyncio import Process -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox -from scrolltextwidget import ScrolledText -import binascii -import hashlib - - -# -# Returns the SHA1 digest of "message" -# -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.hexdigest() - - -class MainDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.root = root - self.interval = 2000 - self.p2 = None - self.status = Tkinter.Label(self, text='Remove Encryption from Kindle for Mac Mobi eBook') - self.status.pack(fill=Tkconstants.X, expand=1) - body = Tkinter.Frame(self) - body.pack(fill=Tkconstants.X, expand=1) - sticky = Tkconstants.E + Tkconstants.W - body.grid_columnconfigure(1, weight=2) - - Tkinter.Label(body, text='Locate your Kindle Applications').grid(row=0, sticky=Tkconstants.E) - self.k4mpath = Tkinter.Entry(body, width=50) - self.k4mpath.grid(row=0, column=1, sticky=sticky) - self.appname = '/Applications/Kindle for Mac.app' - if not os.path.exists(self.appname): - self.appname = '/Applications/Kindle.app' - cwd = self.appname - cwd = cwd.encode('utf-8') - self.k4mpath.insert(0, cwd) - button = Tkinter.Button(body, text="...", command=self.get_k4mpath) - button.grid(row=0, column=2) - - Tkinter.Label(body, text='Directory for Unencrypted Output File').grid(row=1, sticky=Tkconstants.E) - self.outpath = Tkinter.Entry(body, width=50) - self.outpath.grid(row=1, column=1, sticky=sticky) - desktoppath = os.getenv('HOME') + '/Desktop/' - desktoppath = desktoppath.encode('utf-8') - self.outpath.insert(0, desktoppath) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=1, column=2) - - msg1 = 'Conversion Log \n\n' - self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD) - self.stext.grid(row=3, column=0, columnspan=2,sticky=sticky) - self.stext.insert(Tkconstants.END,msg1) - - buttons = Tkinter.Frame(self) - buttons.pack() - self.sbotton = Tkinter.Button( - buttons, text="Start", width=10, command=self.convertit) - self.sbotton.pack(side=Tkconstants.LEFT) - - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - self.qbutton = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quitting) - self.qbutton.pack(side=Tkconstants.RIGHT) - - # read from subprocess pipe without blocking - # invoked every interval via the widget "after" - # option being used, so need to reset it for the next time - def processPipe(self): - poll = self.p2.wait('nowait') - if poll != None: - text = self.p2.readerr() - text += self.p2.read() - msg = text + '\n\n' + 'Encryption successfully removed\n' - if poll != 0: - msg = text + '\n\n' + 'Error: Encryption Removal Failed\n' - self.showCmdOutput(msg) - self.p2 = None - self.sbotton.configure(state='normal') - return - text = self.p2.readerr() - text += self.p2.read() - self.showCmdOutput(text) - # make sure we get invoked again by event loop after interval - self.stext.after(self.interval,self.processPipe) - return - - # post output from subprocess in scrolled text widget - def showCmdOutput(self, msg): - if msg and msg !='': - msg = msg.encode('utf-8') - self.stext.insert(Tkconstants.END,msg) - self.stext.yview_pickplace(Tkconstants.END) - return - - # run as a subprocess via pipes and collect stdout - def mobirdr(self, infile, outfile, pidnum): - cmdline = 'python ./lib/mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"' - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) - return p2 - - def get_k4mpath(self): - k4mpath = tkFileDialog.askopenfilename( - parent=None, title='Select Your Kindle Application', - defaultextension='.app', filetypes=[('Kindle for Mac Application', '.app')]) - - if k4mpath: - k4mpath = os.path.normpath(k4mpath) - self.k4mpath.delete(0, Tkconstants.END) - self.k4mpath.insert(0, k4mpath) - return - - def get_outpath(self): - cwd = os.getcwdu() - cwd = cwd.encode('utf-8') - outpath = tkFileDialog.askdirectory( - parent=None, title='Directory to Put Non-DRM eBook into', - initialdir=cwd, initialfile=None) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def quitting(self): - # kill any still running subprocess - if self.p2 != None: - if (self.p2.wait('nowait') == None): - self.p2.terminate() - self.root.destroy() - - # run as a gdb subprocess via pipes and collect stdout - def gdbrdr(self, k4mappfile, gdbcmds): - cmdline = '/usr/bin/gdb -q -silent -readnow -batch -x ' + gdbcmds + ' "' + k4mappfile + '"' - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p3 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) - poll = p3.wait('wait') - results = p3.read() - pidnum = 'NOTAPID+' - topazbook = 0 - bookpath = 'book not found' - # parse the gdb results to get the last pid and the last azw/prc file name in the gdb listing - reslst = results.split('\n') - cnt = len(reslst) - for j in xrange(cnt): - resline = reslst[j] - pp = resline.find('PID is ') - if pp == 0: - pidnum = resline[7:] - topazbook = 0 - if pp > 0: - pidnum = resline[13:] - topazbook = 1 - fp = resline.find('File is ') - if fp >= 0: - tp1 = resline.find('.azw') - tp2 = resline.find('.prc') - tp3 = resline.find('.mbp') - if tp1 >= 0 or tp2 >= 0: - bookpath = resline[8:] - if tp3 >= 0 and topazbook == 1: - bookpath = resline[8:-3] - bookpath += 'azw' - # put code here to get pid and file name - return pidnum, bookpath, topazbook - - # convert from 8 digit PID to proper 10 digit PID - def checksumPid(self, s): - letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - crc = (~binascii.crc32(s,-1))&0xFFFFFFFF - crc = crc ^ (crc >> 16) - res = s - l = len(letters) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += letters[pos%l] - crc >>= 8 - return res - - # start the process - def convertit(self): - # dictionary of all known Kindle for Mac Binaries - sha1_app_digests = { - 'e197ed2171ceb44a35c24bd30263b7253331694f' : 'gdb_kindle_cmds_r1.txt', - '4f702436171f84acc13bdf9f94fae91525aecef5' : 'gdb_kindle_cmds_r2.txt', - '4981b7eb37ccf0b8f63f56e8024b5ab593e8a97c' : 'gdb_kindle_cmds_r3.txt', - '82909f0545688f09343e2c8fd8521eeee37d2de6' : 'gdb_kindle_cmds_r4.txt', - 'e260e3515cd525cd085c70baa6e42e08079edbcd' : 'gdb_kindle_cmds_r4.txt', - 'no_sha1_digest_key_here_________________' : 'no_gdb_kindle_cmds.txt', - } - # now disable the button to prevent multiple launches - self.sbotton.configure(state='disabled') - - k4mpath = self.k4mpath.get() - outpath = self.outpath.get() - - # basic error checking - if not k4mpath or not os.path.exists(k4mpath): - self.status['text'] = 'Error: Specified Kindle for Mac Application does not exist' - self.sbotton.configure(state='normal') - return - if not outpath: - self.status['text'] = 'Error: No output directory specified' - self.sbotton.configure(state='normal') - return - if not os.path.isdir(outpath): - self.status['text'] = 'Error specified outputdirectory does not exist' - self.sbotton.configure(state='normal') - return - if not os.path.isfile('/usr/bin/gdb'): - self.status['text'] = 'Error: gdb does not exist, install the XCode Develoepr Tools' - self.sbotton.configure(state='normal') - return - - - # now check if the K4M app binary is known and if so which gdbcmds to use - binary_app_file = k4mpath + '/Contents/MacOS/Kindle for Mac' - if not os.path.exists(binary_app_file): - binary_app_file = k4mpath + '/Contents/MacOS/Kindle' - - k4mpath = binary_app_file - - digest = SHA1(file(binary_app_file, 'rb').read()) - - # print digest - gdbcmds = None - if digest in sha1_app_digests: - gdbcmds = sha1_app_digests[digest] - else : - self.status['text'] = 'Error: Kindle Application does not match any known version, sha1sum is ' + digest - self.sbotton.configure(state='normal') - return - - # run Kindle for Mac in gdb to get what we need - (pidnum, bookpath, topazbook) = self.gdbrdr(k4mpath, gdbcmds) - - if topazbook == 1: - log = 'Warning: ' + bookpath + ' is a Topaz book\n' - log += '\n\n' - log += 'To convert this book please use the Topaz Tools\n' - log += 'With the 8 digit PID: "' + pidnum + '"\n' - log += '\n\n' - log = log.encode('utf-8') - self.stext.insert(Tkconstants.END,log) - self.sbotton.configure(state='normal') - return - - pidnum = self.checksumPid(pidnum) - - # default output file name to be input file name + '_nodrm.mobi' - initname = os.path.splitext(os.path.basename(bookpath))[0] - initname += '_nodrm.mobi' - outpath += '/' + initname - - log = 'Command = "python mobidedrm.py"\n' - log += 'Mobi Path = "'+ bookpath + '"\n' - log += 'Output file = "' + outpath + '"\n' - log += 'PID = "' + pidnum + '"\n' - log += '\n\n' - log += 'Please Wait ...\n\n' - log = log.encode('utf-8') - self.stext.insert(Tkconstants.END,log) - self.p2 = self.mobirdr(bookpath, outpath, pidnum) - - # python does not seem to allow you to create - # your own eventloop which every other gui does - strange - # so need to use the widget "after" command to force - # event loop to run non-gui events every interval - self.stext.after(self.interval,self.processPipe) - return - - -def main(argv=None): - root = Tkinter.Tk() - root.title('Kindle for Mac eBook Encryption Removal') - root.resizable(True, False) - root.minsize(300, 0) - MainDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/README_K4Munswindle.txt b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/README_K4Munswindle.txt deleted file mode 100644 index 1f1907b..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/README_K4Munswindle.txt +++ /dev/null @@ -1,61 +0,0 @@ -K4Munswindle - -Prerequisites: - - - Kindle for Mac.app Version 1.0.0 Beta 1 (27214) - or - Kindle.app Version 1.2.0 (30689) - or - Kindle.app Version 1.2.1 (30781) - or - Kindle.app Version 1.2.2 (30814) - (this is now the current version) - - - A **recent** version of the XCode Developer Tools **must** be Installed - (see your latest Mac OS X Install Disk for the installer, and then use Apple System Updates) - -***PLEASE REMEMBER to UNCHECK the "auto updates" in the Kindle.app Preferences! -***otherwise it will always update and K4MUnswindle will stop working - - -The directions for use are: - -1. double-click on K4Munswindle.pyw - -In the window that opens: - -– hit the first '...' button to locate your Kindle Application - if it is not in /Applications - -– hit the second '...' button to select an output directory - (defaults to your Desktop) - -– hit the 'Start' button - -After a short delay, your Kindle application should open up automagically - -2. In Kindle for Mac: - - - hit the “Home” button to go home. - - - double-click on ONE of your books. - This should open the book. - -3. Once the book you want is open - -- hit the “Home” button and then exit the Kindle for Mac application - -4. Once you have exited the Kindle for Mac application you should see one of the following: - - - If the book you selected was a Topaz Book: - - A Warning message will appear in the Conversion Log indicating - that the book you opened was Topaz, along with the 8 digit PID - needed to convert it using Topaz_Tools - - - If the book you selected was a Mobi book: - - MobiDeDRM will be automagically started in the Conversion Log - window and if successful you should find your decoded book in - the output directory. - diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r1.txt b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r1.txt deleted file mode 100644 index dfcd5ab..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r1.txt +++ /dev/null @@ -1,13 +0,0 @@ -set verbose 0 -break * 0x00b2a56a -commands 1 -printf "PID is %s\n", $edx -continue -end -break * 0x014ca2af -commands 2 -printf "File is %s\n", $eax -continue -end -condition 2 $eax != 0 -run diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r2.txt b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r2.txt deleted file mode 100644 index d699d56..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r2.txt +++ /dev/null @@ -1,18 +0,0 @@ -set verbose 0 -break * 0x00e37be4 -commands 1 -printf "PID is %s\n", $eax -continue -end -break * 0x0142cd94 -commands 2 -printf "File is %s\n", $eax -continue -end -condition 2 $eax != 0 -break * 0x01009c88 -commands 3 -printf "TOPAZ PID is %s\n", *(long*)($esp+12) -continue -end -run diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r3.txt b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r3.txt deleted file mode 100644 index fcae6d6..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r3.txt +++ /dev/null @@ -1,18 +0,0 @@ -set verbose 0 -break * 0x00d3dac4 -commands 1 -printf "PID is %s\n", $eax -continue -end -break * 0x0130a384 -commands 2 -printf "File is %s\n", $eax -continue -end -condition 2 $eax != 0 -break * 0x00f0f306 -commands 3 -printf "TOPAZ PID is %s\n", *(long*)($esp+12) -continue -end -run diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r4.txt b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r4.txt deleted file mode 100644 index 2626fc5..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/gdb_kindle_cmds_r4.txt +++ /dev/null @@ -1,18 +0,0 @@ -set verbose 0 -break * 0x00d8f72a -commands 1 -printf "PID is %s\n", *(long*)($esp+4) -continue -end -break * 0x0127498c -commands 2 -printf "File is %s\n", $eax -continue -end -condition 2 $eax != 0 -break * 0x00e9aec0 -commands 3 -printf "TOPAZ PID is %s\n", **(long**)($esp+8) -continue -end -run diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py deleted file mode 100644 index 7aef175..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py +++ /dev/null @@ -1,409 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 0.01 - Initial version -# 0.02 - Huffdic compressed books were not properly decrypted -# 0.03 - Wasn't checking MOBI header length -# 0.04 - Wasn't sanity checking size of data record -# 0.05 - It seems that the extra data flags take two bytes not four -# 0.06 - And that low bit does mean something after all :-) -# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size -# 0.08 - ...and also not in Mobi header version < 6 -# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4! -# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre -# import filter it works when importing unencrypted files. -# Also now handles encrypted files that don't need a specific PID. -# 0.11 - use autoflushed stdout and proper return values -# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors -# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace -# and extra blank lines, converted CR/LF pairs at ends of each line, -# and other cosmetic fixes. -# 0.14 - Working out when the extra data flags are present has been problematic -# Versions 7 through 9 have tried to tweak the conditions, but have been -# only partially successful. Closer examination of lots of sample -# files reveals that a confusion has arisen because trailing data entries -# are not encrypted, but it turns out that the multibyte entries -# in utf8 file are encrypted. (Although neither kind gets compressed.) -# This knowledge leads to a simplification of the test for the -# trailing data byte flags - version 5 and higher AND header size >= 0xE4. -# 0.15 - Now outputs 'heartbeat', and is also quicker for long files. -# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility. -# 0.17 - added modifications to support its use as an imported python module -# both inside calibre and also in other places (ie K4DeDRM tools) -# 0.17a- disabled the standalone plugin feature since a plugin can not import -# a plugin -# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... -# Removed the disabled Calibre plug-in code -# Permit use of 8-digit PIDs -# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. -# 0.21 - Added support for multiple pids -# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface -# 0.23 - fixed problem with older files with no EXTH section -# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well -# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption -# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% -# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) -# 0.28 - slight additional changes to metadata token generation (None -> '') -# 0.29 - It seems that the ideas about when multibyte trailing characters were -# included in the encryption were wrong. They aren't for DOC compressed -# files, but they are for HUFF/CDIC compress files! - -__version__ = '0.29' - -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii - -class DrmException(Exception): - pass - - -# -# MobiBook Utility Routines -# - -# Implementation of Pukall Cipher 1 -def PC1(key, src, decryption=True): - sum1 = 0; - sum2 = 0; - keyXorVal = 0; - if len(key)!=16: - print "Bad key length!" - return None - wkey = [] - for i in xrange(8): - wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) - dst = "" - for i in xrange(len(src)): - temp1 = 0; - byteXorVal = 0; - for j in xrange(8): - temp1 ^= wkey[j] - sum2 = (sum2+j)*20021 + sum1 - sum1 = (temp1*346)&0xFFFF - sum2 = (sum2+sum1)&0xFFFF - temp1 = (temp1*20021+1)&0xFFFF - byteXorVal ^= temp1 ^ sum2 - curByte = ord(src[i]) - if not decryption: - keyXorVal = curByte * 257; - curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF - if decryption: - keyXorVal = curByte * 257; - for j in xrange(8): - wkey[j] ^= keyXorVal; - dst+=chr(curByte) - return dst - -def checksumPid(s): - letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - crc = (~binascii.crc32(s,-1))&0xFFFFFFFF - crc = crc ^ (crc >> 16) - res = s - l = len(letters) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += letters[pos%l] - crc >>= 8 - return res - -def getSizeOfTrailingDataEntries(ptr, size, flags): - def getSizeOfTrailingDataEntry(ptr, size): - bitpos, result = 0, 0 - if size <= 0: - return result - while True: - v = ord(ptr[size-1]) - result |= (v & 0x7F) << bitpos - bitpos += 7 - size -= 1 - if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0): - return result - num = 0 - testflags = flags >> 1 - while testflags: - if testflags & 1: - num += getSizeOfTrailingDataEntry(ptr, size - num) - testflags >>= 1 - # Check the low bit to see if there's multibyte data present. - # if multibyte data is included in the encryped data, we'll - # have already cleared this flag. - if flags & 1: - num += (ord(ptr[size - num - 1]) & 0x3) + 1 - return num - - - -class MobiBook: - def loadSection(self, section): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - return self.data_file[off:endoff] - - def __init__(self, infile): - # initial sanity check on file - self.data_file = file(infile, 'rb').read() - self.header = self.data_file[0:78] - if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") - self.magic = self.header[0x3C:0x3C+8] - self.crypto_type = -1 - - # build up section offset and flag info - self.num_sections, = struct.unpack('>H', self.header[76:78]) - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - - # parse information from section 0 - self.sect = self.loadSection(0) - self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) - self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2]) - - if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_version = -1 - self.meta_array = {} - return - self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) - self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 - if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): - self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags - if (self.mobi_version < 7) and (self.compression != 17480): - # multibyte utf8 data is included in the encryption for mobi_version 6 and below - # so clear that byte so that we leave it to be decrypted. - self.extra_data_flags &= 0xFFFE - - # if exth region exists parse it for metadata array - self.meta_array = {} - try: - exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' - if exth_flag & 0x40: - exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - content = exth[pos + 8: pos + size] - self.meta_array[type] = content - # reset the text to speech flag and clipping limit, if present - if type == 401 and size == 9: - # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) - elif type == 404 and size == 9: - # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - # print type, size, content, content.encode('hex') - pos += size - except: - self.meta_array = {} - pass - - def getBookTitle(self): - title = '' - if 503 in self.meta_array: - title = self.meta_array[503] - else : - toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) - tend = toff + tlen - title = self.sect[toff:tend] - if title == '': - title = self.header[:32] - title = title.split("\0")[0] - return title - - def getPIDMetaInfo(self): - rec209 = '' - token = '' - if 209 in self.meta_array: - rec209 = self.meta_array[209] - data = rec209 - # The 209 data comes in five byte groups. Interpret the last four bytes - # of each group as a big endian unsigned integer to get a key value - # if that key exists in the meta_array, append its contents to the token - for i in xrange(0,len(data),5): - val, = struct.unpack('>I',data[i+1:i+5]) - sval = self.meta_array.get(val,'') - token += sval - return rec209, token - - def patch(self, off, new): - self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] - - def patchSection(self, section, new, in_off = 0): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - assert off + in_off + len(new) <= endoff - self.patch(off + in_off, new) - - def parseDRM(self, data, count, pidlist): - found_key = None - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" - for pid in pidlist: - bigpid = pid.ljust(16,'\0') - temp_key = PC1(keyvec1, bigpid, False) - temp_key_sum = sum(map(ord,temp_key)) & 0xff - found_key = None - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver and (flags & 0x1F) == 1: - found_key = finalkey - break - if found_key != None: - break - if not found_key: - # Then try the default encoding that doesn't require a PID - pid = "00000000" - temp_key = keyvec1 - temp_key_sum = sum(map(ord,temp_key)) & 0xff - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver: - found_key = finalkey - break - return [found_key,pid] - - def processBook(self, pidlist): - crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) - print 'Crypto Type is: ', crypto_type - self.crypto_type = crypto_type - if crypto_type == 0: - print "This book is not encrypted." - return self.data_file - if crypto_type != 2 and crypto_type != 1: - raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) - - goodpids = [] - for pid in pidlist: - if len(pid)==10: - if checksumPid(pid[0:-2]) != pid: - print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) - goodpids.append(pid[0:-2]) - elif len(pid)==8: - goodpids.append(pid) - - if self.crypto_type == 1: - t1_keyvec = "QDCVEPMU675RUBSZ" - if self.magic == 'TEXtREAd': - bookkey_data = self.sect[0x0E:0x0E+16] - elif self.mobi_version < 0: - bookkey_data = self.sect[0x90:0x90+16] - else: - bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] - pid = "00000000" - found_key = PC1(t1_keyvec, bookkey_data) - else : - # calculate the keys - drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) - if drm_count == 0: - raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") - found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) - if not found_key: - raise DrmException("No key found. Most likely the correct PID has not been given.") - # kill the drm keys - self.patchSection(0, "\0" * drm_size, drm_ptr) - # kill the drm pointers - self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) - - if pid=="00000000": - print "File has default encryption, no specific PID." - else: - print "File is encoded with PID "+checksumPid(pid)+"." - - # clear the crypto type - self.patchSection(0, "\0" * 2, 0xC) - - # decrypt sections - print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] - for i in xrange(1, self.records+1): - data = self.loadSection(i) - extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) - if i%100 == 0: - print ".", - # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) - if extra_size > 0: - new_data += data[-extra_size:] - if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data - print "done" - return self.data_file - -def getUnencryptedBook(infile,pid): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile) - return book.processBook([pid]) - -def getUnencryptedBookWithList(infile,pidlist): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile) - return book.processBook(pidlist) - -def main(argv=sys.argv): - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2010 The Dark Reverser.' % globals()) - if len(argv)<3 or len(argv)>4: - print "Removes protection from Mobipocket books" - print "Usage:" - print " %s []" % sys.argv[0] - return 1 - else: - infile = argv[1] - outfile = argv[2] - if len(argv) is 4: - pidlist = argv[3].split(',') - else: - pidlist = {} - try: - stripped_file = getUnencryptedBookWithList(infile, pidlist) - file(outfile, 'wb').write(stripped_file) - except DrmException, e: - print "Error: %s" % e - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/scrolltextwidget.py b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/scrolltextwidget.py deleted file mode 100644 index 98b4147..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/scrolltextwidget.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import Tkinter -import Tkconstants - -# basic scrolled text widget -class ScrolledText(Tkinter.Text): - def __init__(self, master=None, **kw): - self.frame = Tkinter.Frame(master) - self.vbar = Tkinter.Scrollbar(self.frame) - self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) - kw.update({'yscrollcommand': self.vbar.set}) - Tkinter.Text.__init__(self, self.frame, **kw) - self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) - self.vbar['command'] = self.yview - # Copy geometry methods of self.frame without overriding Text - # methods = hack! - text_meths = vars(Tkinter.Text).keys() - methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() - methods = set(methods).difference(text_meths) - for m in methods: - if m[0] != '_' and m != 'config' and m != 'configure': - setattr(self, m, getattr(self.frame, m)) - - def __str__(self): - return str(self.frame) diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/subasyncio.py b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/subasyncio.py deleted file mode 100644 index ed13aa1..0000000 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/subasyncio.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import os, sys -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 " -# 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() - diff --git a/KindleBooks_Tools/Kindle_4_PC_Unswindle/README-unswindlev7.txt b/KindleBooks_Tools/Kindle_4_PC_Unswindle/README-unswindlev7.txt deleted file mode 100644 index 46dc417..0000000 --- a/KindleBooks_Tools/Kindle_4_PC_Unswindle/README-unswindlev7.txt +++ /dev/null @@ -1,5 +0,0 @@ -README - -unswindle can be used to find the book specific PID but it needs to be updated for each version of Kindle4PC that Amazon releases (and therefore is also useful for Linux users who have Wine). This program “patches” the Kindle4PC executable and therefore is very release specific. - -Unfortunately unswindle v7 the latest, has not been updated to work with the latest version of Kindle for PC. You will need to find one of the older versions of Kindle4PC and prevent later updates in order to use this tool. diff --git a/KindleBooks_Tools/Kindle_4_PC_Unswindle/unswindle.pyw b/KindleBooks_Tools/Kindle_4_PC_Unswindle/unswindle.pyw deleted file mode 100644 index 8eb0771..0000000 --- a/KindleBooks_Tools/Kindle_4_PC_Unswindle/unswindle.pyw +++ /dev/null @@ -1,883 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -# unswindle.pyw, version 7 -# Copyright © 2009-2010 i♥cabbages - -# Released under the terms of the GNU General Public Licence, version 3 or -# later. - -# Before running this program, you must first install Python 2.6 from -# . Save this script file as unswindle.pyw. -# Find and save in the same directory a copy of mobidedrm.py. Double-click on -# unswindle.pyw. It will run Kindle For PC. Open the book you want to -# decrypt. Close Kindle For PC. A dialog will open allowing you to select the -# output file. And you're done! - -# Revision history: -# 1 - Initial release -# 2 - Fixes to work properly on Windows versions >XP -# 3 - Fix minor bug in path extraction -# 4 - Fix error opening threads; detect Topaz books; -# detect unsupported versions of K4PC -# 5 - Work with new (20091222) version of K4PC -# 6 - Detect and just copy DRM-free books -# 7 - Work with new (20100629) version of K4PC - -""" -Decrypt Kindle For PC encrypted Mobipocket books. -""" - -from __future__ import with_statement - -__license__ = 'GPL v3' - -import sys -import os -import re -import tempfile -import shutil -import subprocess -import struct -import hashlib -import ctypes -from ctypes import * -from ctypes.wintypes import * -import binascii -import _winreg as winreg -import Tkinter -import Tkconstants -import tkMessageBox -import tkFileDialog -import traceback - -# -# _extrawintypes.py - -UBYTE = c_ubyte -ULONG_PTR = POINTER(ULONG) -PULONG = ULONG_PTR -PVOID = LPVOID -LPCTSTR = LPTSTR = c_wchar_p -LPBYTE = c_char_p -SIZE_T = c_uint -SIZE_T_p = POINTER(SIZE_T) - -# -# _ntdll.py - -NTSTATUS = DWORD - -ntdll = windll.ntdll - -class PROCESS_BASIC_INFORMATION(Structure): - _fields_ = [('Reserved1', PVOID), - ('PebBaseAddress', PVOID), - ('Reserved2', PVOID * 2), - ('UniqueProcessId', ULONG_PTR), - ('Reserved3', PVOID)] - -# NTSTATUS WINAPI NtQueryInformationProcess( -# __in HANDLE ProcessHandle, -# __in PROCESSINFOCLASS ProcessInformationClass, -# __out PVOID ProcessInformation, -# __in ULONG ProcessInformationLength, -# __out_opt PULONG ReturnLength -# ); -NtQueryInformationProcess = ntdll.NtQueryInformationProcess -NtQueryInformationProcess.argtypes = [HANDLE, DWORD, PVOID, ULONG, PULONG] -NtQueryInformationProcess.restype = NTSTATUS - -# -# _kernel32.py - -INFINITE = 0xffffffff - -CREATE_UNICODE_ENVIRONMENT = 0x00000400 -DEBUG_ONLY_THIS_PROCESS = 0x00000002 -DEBUG_PROCESS = 0x00000001 - -THREAD_GET_CONTEXT = 0x0008 -THREAD_QUERY_INFORMATION = 0x0040 -THREAD_SET_CONTEXT = 0x0010 -THREAD_SET_INFORMATION = 0x0020 - -EXCEPTION_BREAKPOINT = 0x80000003 -EXCEPTION_SINGLE_STEP = 0x80000004 -EXCEPTION_ACCESS_VIOLATION = 0xC0000005 - -DBG_CONTINUE = 0x00010002L -DBG_EXCEPTION_NOT_HANDLED = 0x80010001L - -EXCEPTION_DEBUG_EVENT = 1 -CREATE_THREAD_DEBUG_EVENT = 2 -CREATE_PROCESS_DEBUG_EVENT = 3 -EXIT_THREAD_DEBUG_EVENT = 4 -EXIT_PROCESS_DEBUG_EVENT = 5 -LOAD_DLL_DEBUG_EVENT = 6 -UNLOAD_DLL_DEBUG_EVENT = 7 -OUTPUT_DEBUG_STRING_EVENT = 8 -RIP_EVENT = 9 - -class DataBlob(Structure): - _fields_ = [('cbData', c_uint), - ('pbData', c_void_p)] -DataBlob_p = POINTER(DataBlob) - -class SECURITY_ATTRIBUTES(Structure): - _fields_ = [('nLength', DWORD), - ('lpSecurityDescriptor', LPVOID), - ('bInheritHandle', BOOL)] -LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES) - -class STARTUPINFO(Structure): - _fields_ = [('cb', DWORD), - ('lpReserved', LPTSTR), - ('lpDesktop', LPTSTR), - ('lpTitle', LPTSTR), - ('dwX', DWORD), - ('dwY', DWORD), - ('dwXSize', DWORD), - ('dwYSize', DWORD), - ('dwXCountChars', DWORD), - ('dwYCountChars', DWORD), - ('dwFillAttribute', DWORD), - ('dwFlags', DWORD), - ('wShowWindow', WORD), - ('cbReserved2', WORD), - ('lpReserved2', LPBYTE), - ('hStdInput', HANDLE), - ('hStdOutput', HANDLE), - ('hStdError', HANDLE)] -LPSTARTUPINFO = POINTER(STARTUPINFO) - -class PROCESS_INFORMATION(Structure): - _fields_ = [('hProcess', HANDLE), - ('hThread', HANDLE), - ('dwProcessId', DWORD), - ('dwThreadId', DWORD)] -LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) - -EXCEPTION_MAXIMUM_PARAMETERS = 15 -class EXCEPTION_RECORD(Structure): - pass -EXCEPTION_RECORD._fields_ = [ - ('ExceptionCode', DWORD), - ('ExceptionFlags', DWORD), - ('ExceptionRecord', POINTER(EXCEPTION_RECORD)), - ('ExceptionAddress', LPVOID), - ('NumberParameters', DWORD), - ('ExceptionInformation', ULONG_PTR * EXCEPTION_MAXIMUM_PARAMETERS)] - -class EXCEPTION_DEBUG_INFO(Structure): - _fields_ = [('ExceptionRecord', EXCEPTION_RECORD), - ('dwFirstChance', DWORD)] - -class CREATE_THREAD_DEBUG_INFO(Structure): - _fields_ = [('hThread', HANDLE), - ('lpThreadLocalBase', LPVOID), - ('lpStartAddress', LPVOID)] - -class CREATE_PROCESS_DEBUG_INFO(Structure): - _fields_ = [('hFile', HANDLE), - ('hProcess', HANDLE), - ('hThread', HANDLE), - ('dwDebugInfoFileOffset', DWORD), - ('nDebugInfoSize', DWORD), - ('lpThreadLocalBase', LPVOID), - ('lpStartAddress', LPVOID), - ('lpImageName', LPVOID), - ('fUnicode', WORD)] - -class EXIT_THREAD_DEBUG_INFO(Structure): - _fields_ = [('dwExitCode', DWORD)] - -class EXIT_PROCESS_DEBUG_INFO(Structure): - _fields_ = [('dwExitCode', DWORD)] - -class LOAD_DLL_DEBUG_INFO(Structure): - _fields_ = [('hFile', HANDLE), - ('lpBaseOfDll', LPVOID), - ('dwDebugInfoFileOffset', DWORD), - ('nDebugInfoSize', DWORD), - ('lpImageName', LPVOID), - ('fUnicode', WORD)] - -class UNLOAD_DLL_DEBUG_INFO(Structure): - _fields_ = [('lpBaseOfDll', LPVOID)] - -class OUTPUT_DEBUG_STRING_INFO(Structure): - _fields_ = [('lpDebugStringData', LPSTR), - ('fUnicode', WORD), - ('nDebugStringLength', WORD)] - -class RIP_INFO(Structure): - _fields_ = [('dwError', DWORD), - ('dwType', DWORD)] - -class _U(Union): - _fields_ = [('Exception', EXCEPTION_DEBUG_INFO), - ('CreateThread', CREATE_THREAD_DEBUG_INFO), - ('CreateProcessInfo', CREATE_PROCESS_DEBUG_INFO), - ('ExitThread', EXIT_THREAD_DEBUG_INFO), - ('ExitProcess', EXIT_PROCESS_DEBUG_INFO), - ('LoadDll', LOAD_DLL_DEBUG_INFO), - ('UnloadDll', UNLOAD_DLL_DEBUG_INFO), - ('DebugString', OUTPUT_DEBUG_STRING_INFO), - ('RipInfo', RIP_INFO)] - -class DEBUG_EVENT(Structure): - _anonymous_ = ('u',) - _fields_ = [('dwDebugEventCode', DWORD), - ('dwProcessId', DWORD), - ('dwThreadId', DWORD), - ('u', _U)] -LPDEBUG_EVENT = POINTER(DEBUG_EVENT) - -CONTEXT_X86 = 0x00010000 -CONTEXT_i386 = CONTEXT_X86 -CONTEXT_i486 = CONTEXT_X86 - -CONTEXT_CONTROL = (CONTEXT_i386 | 0x0001) # SS:SP, CS:IP, FLAGS, BP -CONTEXT_INTEGER = (CONTEXT_i386 | 0x0002) # AX, BX, CX, DX, SI, DI -CONTEXT_SEGMENTS = (CONTEXT_i386 | 0x0004) # DS, ES, FS, GS -CONTEXT_FLOATING_POINT = (CONTEXT_i386 | 0x0008L) # 387 state -CONTEXT_DEBUG_REGISTERS = (CONTEXT_i386 | 0x0010L) # DB 0-3,6,7 -CONTEXT_EXTENDED_REGISTERS = (CONTEXT_i386 | 0x0020L) -CONTEXT_FULL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS) -CONTEXT_ALL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | - CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | - CONTEXT_EXTENDED_REGISTERS) - -SIZE_OF_80387_REGISTERS = 80 -class FLOATING_SAVE_AREA(Structure): - _fields_ = [('ControlWord', DWORD), - ('StatusWord', DWORD), - ('TagWord', DWORD), - ('ErrorOffset', DWORD), - ('ErrorSelector', DWORD), - ('DataOffset', DWORD), - ('DataSelector', DWORD), - ('RegisterArea', BYTE * SIZE_OF_80387_REGISTERS), - ('Cr0NpxState', DWORD)] - -MAXIMUM_SUPPORTED_EXTENSION = 512 -class CONTEXT(Structure): - _fields_ = [('ContextFlags', DWORD), - ('Dr0', DWORD), - ('Dr1', DWORD), - ('Dr2', DWORD), - ('Dr3', DWORD), - ('Dr6', DWORD), - ('Dr7', DWORD), - ('FloatSave', FLOATING_SAVE_AREA), - ('SegGs', DWORD), - ('SegFs', DWORD), - ('SegEs', DWORD), - ('SegDs', DWORD), - ('Edi', DWORD), - ('Esi', DWORD), - ('Ebx', DWORD), - ('Edx', DWORD), - ('Ecx', DWORD), - ('Eax', DWORD), - ('Ebp', DWORD), - ('Eip', DWORD), - ('SegCs', DWORD), - ('EFlags', DWORD), - ('Esp', DWORD), - ('SegSs', DWORD), - ('ExtendedRegisters', BYTE * MAXIMUM_SUPPORTED_EXTENSION)] -LPCONTEXT = POINTER(CONTEXT) - -class LDT_ENTRY(Structure): - _fields_ = [('LimitLow', WORD), - ('BaseLow', WORD), - ('BaseMid', UBYTE), - ('Flags1', UBYTE), - ('Flags2', UBYTE), - ('BaseHi', UBYTE)] -LPLDT_ENTRY = POINTER(LDT_ENTRY) - -kernel32 = windll.kernel32 - -# BOOL WINAPI CloseHandle( -# __in HANDLE hObject -# ); -CloseHandle = kernel32.CloseHandle -CloseHandle.argtypes = [HANDLE] -CloseHandle.restype = BOOL - -# BOOL WINAPI CreateProcess( -# __in_opt LPCTSTR lpApplicationName, -# __inout_opt LPTSTR lpCommandLine, -# __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, -# __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, -# __in BOOL bInheritHandles, -# __in DWORD dwCreationFlags, -# __in_opt LPVOID lpEnvironment, -# __in_opt LPCTSTR lpCurrentDirectory, -# __in LPSTARTUPINFO lpStartupInfo, -# __out LPPROCESS_INFORMATION lpProcessInformation -# ); -CreateProcess = kernel32.CreateProcessW -CreateProcess.argtypes = [LPCTSTR, LPTSTR, LPSECURITY_ATTRIBUTES, - LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCTSTR, - LPSTARTUPINFO, LPPROCESS_INFORMATION] -CreateProcess.restype = BOOL - -# HANDLE WINAPI OpenThread( -# __in DWORD dwDesiredAccess, -# __in BOOL bInheritHandle, -# __in DWORD dwThreadId -# ); -OpenThread = kernel32.OpenThread -OpenThread.argtypes = [DWORD, BOOL, DWORD] -OpenThread.restype = HANDLE - -# BOOL WINAPI ContinueDebugEvent( -# __in DWORD dwProcessId, -# __in DWORD dwThreadId, -# __in DWORD dwContinueStatus -# ); -ContinueDebugEvent = kernel32.ContinueDebugEvent -ContinueDebugEvent.argtypes = [DWORD, DWORD, DWORD] -ContinueDebugEvent.restype = BOOL - -# BOOL WINAPI DebugActiveProcess( -# __in DWORD dwProcessId -# ); -DebugActiveProcess = kernel32.DebugActiveProcess -DebugActiveProcess.argtypes = [DWORD] -DebugActiveProcess.restype = BOOL - -# BOOL WINAPI GetThreadContext( -# __in HANDLE hThread, -# __inout LPCONTEXT lpContext -# ); -GetThreadContext = kernel32.GetThreadContext -GetThreadContext.argtypes = [HANDLE, LPCONTEXT] -GetThreadContext.restype = BOOL - -# BOOL WINAPI GetThreadSelectorEntry( -# __in HANDLE hThread, -# __in DWORD dwSelector, -# __out LPLDT_ENTRY lpSelectorEntry -# ); -GetThreadSelectorEntry = kernel32.GetThreadSelectorEntry -GetThreadSelectorEntry.argtypes = [HANDLE, DWORD, LPLDT_ENTRY] -GetThreadSelectorEntry.restype = BOOL - -# BOOL WINAPI ReadProcessMemory( -# __in HANDLE hProcess, -# __in LPCVOID lpBaseAddress, -# __out LPVOID lpBuffer, -# __in SIZE_T nSize, -# __out SIZE_T *lpNumberOfBytesRead -# ); -ReadProcessMemory = kernel32.ReadProcessMemory -ReadProcessMemory.argtypes = [HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T_p] -ReadProcessMemory.restype = BOOL - -# BOOL WINAPI SetThreadContext( -# __in HANDLE hThread, -# __in const CONTEXT *lpContext -# ); -SetThreadContext = kernel32.SetThreadContext -SetThreadContext.argtypes = [HANDLE, LPCONTEXT] -SetThreadContext.restype = BOOL - -# BOOL WINAPI WaitForDebugEvent( -# __out LPDEBUG_EVENT lpDebugEvent, -# __in DWORD dwMilliseconds -# ); -WaitForDebugEvent = kernel32.WaitForDebugEvent -WaitForDebugEvent.argtypes = [LPDEBUG_EVENT, DWORD] -WaitForDebugEvent.restype = BOOL - -# BOOL WINAPI WriteProcessMemory( -# __in HANDLE hProcess, -# __in LPVOID lpBaseAddress, -# __in LPCVOID lpBuffer, -# __in SIZE_T nSize, -# __out SIZE_T *lpNumberOfBytesWritten -# ); -WriteProcessMemory = kernel32.WriteProcessMemory -WriteProcessMemory.argtypes = [HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T_p] -WriteProcessMemory.restype = BOOL - -# BOOL WINAPI FlushInstructionCache( -# __in HANDLE hProcess, -# __in LPCVOID lpBaseAddress, -# __in SIZE_T dwSize -# ); -FlushInstructionCache = kernel32.FlushInstructionCache -FlushInstructionCache.argtypes = [HANDLE, LPCVOID, SIZE_T] -FlushInstructionCache.restype = BOOL - - -# -# debugger.py - -FLAG_TRACE_BIT = 0x100 - -class DebuggerError(Exception): - pass - -class Debugger(object): - def __init__(self, process_info): - self.process_info = process_info - self.pid = process_info.dwProcessId - self.tid = process_info.dwThreadId - self.hprocess = process_info.hProcess - self.hthread = process_info.hThread - self._threads = {self.tid: self.hthread} - self._processes = {self.pid: self.hprocess} - self._bps = {} - self._inactive = {} - - def read_process_memory(self, addr, size=None, type=str): - if issubclass(type, basestring): - buf = ctypes.create_string_buffer(size) - ref = buf - else: - size = ctypes.sizeof(type) - buf = type() - ref = byref(buf) - copied = SIZE_T(0) - rv = ReadProcessMemory(self.hprocess, addr, ref, size, byref(copied)) - if not rv: - addr = getattr(addr, 'value', addr) - raise DebuggerError("could not read memory @ 0x%08x" % (addr,)) - if copied.value != size: - raise DebuggerError("insufficient memory read") - if issubclass(type, basestring): - return buf.raw - return buf - - def set_bp(self, addr, callback, bytev=None): - hprocess = self.hprocess - if bytev is None: - byte = self.read_process_memory(addr, type=ctypes.c_byte) - bytev = byte.value - else: - byte = ctypes.c_byte(0) - self._bps[addr] = (bytev, callback) - byte.value = 0xcc - copied = SIZE_T(0) - rv = WriteProcessMemory(hprocess, addr, byref(byte), 1, byref(copied)) - if not rv: - addr = getattr(addr, 'value', addr) - raise DebuggerError("could not write memory @ 0x%08x" % (addr,)) - if copied.value != 1: - raise DebuggerError("insufficient memory written") - rv = FlushInstructionCache(hprocess, None, 0) - if not rv: - raise DebuggerError("could not flush instruction cache") - return - - def _restore_bps(self): - for addr, (bytev, callback) in self._inactive.items(): - self.set_bp(addr, callback, bytev=bytev) - self._inactive.clear() - - def _handle_bp(self, addr): - hprocess = self.hprocess - hthread = self.hthread - bytev, callback = self._inactive[addr] = self._bps.pop(addr) - byte = ctypes.c_byte(bytev) - copied = SIZE_T(0) - rv = WriteProcessMemory(hprocess, addr, byref(byte), 1, byref(copied)) - if not rv: - raise DebuggerError("could not write memory") - if copied.value != 1: - raise DebuggerError("insufficient memory written") - rv = FlushInstructionCache(hprocess, None, 0) - if not rv: - raise DebuggerError("could not flush instruction cache") - context = CONTEXT(ContextFlags=CONTEXT_FULL) - rv = GetThreadContext(hthread, byref(context)) - if not rv: - raise DebuggerError("could not get thread context") - context.Eip = addr - callback(self, context) - context.EFlags |= FLAG_TRACE_BIT - rv = SetThreadContext(hthread, byref(context)) - if not rv: - raise DebuggerError("could not set thread context") - return - - def _get_peb_address(self): - hthread = self.hthread - hprocess = self.hprocess - try: - pbi = PROCESS_BASIC_INFORMATION() - rv = NtQueryInformationProcess(hprocess, 0, byref(pbi), - sizeof(pbi), None) - if rv != 0: - raise DebuggerError("could not query process information") - return pbi.PebBaseAddress - except DebuggerError: - pass - try: - context = CONTEXT(ContextFlags=CONTEXT_FULL) - rv = GetThreadContext(hthread, byref(context)) - if not rv: - raise DebuggerError("could not get thread context") - entry = LDT_ENTRY() - rv = GetThreadSelectorEntry(hthread, context.SegFs, byref(entry)) - if not rv: - raise DebuggerError("could not get selector entry") - low, mid, high = entry.BaseLow, entry.BaseMid, entry.BaseHi - fsbase = low | (mid << 16) | (high << 24) - pebaddr = self.read_process_memory(fsbase + 0x30, type=c_voidp) - return pebaddr.value - except DebuggerError: - pass - return 0x7ffdf000 - - def get_base_address(self): - addr = self._get_peb_address() + (2 * 4) - baseaddr = self.read_process_memory(addr, type=c_voidp) - return baseaddr.value - - def main_loop(self): - event = DEBUG_EVENT() - finished = False - while not finished: - rv = WaitForDebugEvent(byref(event), INFINITE) - if not rv: - raise DebuggerError("could not get debug event") - self.pid = pid = event.dwProcessId - self.tid = tid = event.dwThreadId - self.hprocess = self._processes.get(pid, None) - self.hthread = self._threads.get(tid, None) - status = DBG_CONTINUE - evid = event.dwDebugEventCode - if evid == EXCEPTION_DEBUG_EVENT: - first = event.Exception.dwFirstChance - record = event.Exception.ExceptionRecord - exid = record.ExceptionCode - flags = record.ExceptionFlags - addr = record.ExceptionAddress - if exid == EXCEPTION_BREAKPOINT: - if addr in self._bps: - self._handle_bp(addr) - elif exid == EXCEPTION_SINGLE_STEP: - self._restore_bps() - else: - status = DBG_EXCEPTION_NOT_HANDLED - elif evid == LOAD_DLL_DEBUG_EVENT: - hfile = event.LoadDll.hFile - if hfile is not None: - rv = CloseHandle(hfile) - if not rv: - raise DebuggerError("error closing file handle") - elif evid == CREATE_THREAD_DEBUG_EVENT: - info = event.CreateThread - self.hthread = info.hThread - self._threads[tid] = self.hthread - elif evid == EXIT_THREAD_DEBUG_EVENT: - hthread = self._threads.pop(tid, None) - if hthread is not None: - rv = CloseHandle(hthread) - if not rv: - raise DebuggerError("error closing thread handle") - elif evid == CREATE_PROCESS_DEBUG_EVENT: - info = event.CreateProcessInfo - self.hprocess = info.hProcess - self._processes[pid] = self.hprocess - elif evid == EXIT_PROCESS_DEBUG_EVENT: - hprocess = self._processes.pop(pid, None) - if hprocess is not None: - rv = CloseHandle(hprocess) - if not rv: - raise DebuggerError("error closing process handle") - if pid == self.process_info.dwProcessId: - finished = True - rv = ContinueDebugEvent(pid, tid, status) - if not rv: - raise DebuggerError("could not continue debug") - return True - - -# -# unswindle.py - -KINDLE_REG_KEY = \ - r'Software\Classes\Amazon.KindleForPC.content\shell\open\command' - -class UnswindleError(Exception): - pass - -class PC1KeyGrabber(object): - HOOKS = { - 'b9f7e422094b8c8966a0e881e6358116e03e5b7b': { - 0x004a719d: '_no_debugger_here', - 0x005a795b: '_no_debugger_here', - 0x0054f7e0: '_get_pc1_pid', - 0x004f9c79: '_get_book_path', - }, - 'd5124ee20dab10e44b41a039363f6143725a5417': { - 0x0041150d: '_i_like_wine', - 0x004a681d: '_no_debugger_here', - 0x005a438b: '_no_debugger_here', - 0x0054c9e0: '_get_pc1_pid', - 0x004f8ac9: '_get_book_path', - }, - 'd791f52dd2ecc68722212d801ad52cb79d1b6fc9': { - 0x0041724d: '_i_like_wine', - 0x004bfe3d: '_no_debugger_here', - 0x005bd9db: '_no_debugger_here', - 0x00565920: '_get_pc1_pid', - 0x0050fde9: '_get_book_path', - }, - } - - MOBI_EXTENSIONS = set(['.prc', '.pdb', '.mobi', '.azw', '.az1', '.azw1']) - - @classmethod - def supported_version(cls, hexdigest): - return (hexdigest in cls.HOOKS) - - def _taddr(self, addr): - return (addr - 0x00400000) + self.baseaddr - - def __init__(self, debugger, hexdigest): - self.book_path = None - self.book_pid = None - self.baseaddr = debugger.get_base_address() - hooks = self.HOOKS[hexdigest] - for addr, mname in hooks.items(): - debugger.set_bp(self._taddr(addr), getattr(self, mname)) - - def _i_like_wine(self, debugger, context): - context.Eax = 1 - return - - def _no_debugger_here(self, debugger, context): - context.Eip += 2 - context.Eax = 0 - return - - def _get_book_path(self, debugger, context): - addr = debugger.read_process_memory(context.Esp, type=ctypes.c_voidp) - try: - path = debugger.read_process_memory(addr, 4096) - except DebuggerError: - pgrest = 0x1000 - (addr.value & 0xfff) - path = debugger.read_process_memory(addr, pgrest) - path = path.decode('utf-16', 'ignore') - if u'\0' in path: - path = path[:path.index(u'\0')] - root, ext = os.path.splitext(path) - if ext.lower() not in self.MOBI_EXTENSIONS: - return - self.book_path = path - - def _get_pc1_pid(self, debugger, context): - addr = context.Esp + ctypes.sizeof(ctypes.c_voidp) - addr = debugger.read_process_memory(addr, type=ctypes.c_char_p) - pid = debugger.read_process_memory(addr, 8) - pid = self._checksum_pid(pid) - self.book_pid = pid - - def _checksum_pid(self, s): - letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - crc = (~binascii.crc32(s,-1))&0xFFFFFFFF - crc = crc ^ (crc >> 16) - res = s - l = len(letters) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += letters[pos%l] - crc >>= 8 - return res - -class MobiParser(object): - def __init__(self, data): - self.data = data - header = data[0:72] - if header[0x3C:0x3C+8] != 'BOOKMOBI': - raise UnswindleError("invalid file format") - self.nsections = nsections = struct.unpack('>H', data[76:78])[0] - self.sections = sections = [] - for i in xrange(nsections): - offset, a1, a2, a3, a4 = \ - struct.unpack('>LBBBB', data[78+i*8:78+i*8+8]) - flags, val = a1, ((a2 << 16) | (a3 << 8) | a4) - sections.append((offset, flags, val)) - sect = self.load_section(0) - self.crypto_type = struct.unpack('>H', sect[0x0c:0x0c+2])[0] - - def load_section(self, snum): - if (snum + 1) == self.nsections: - endoff = len(self.data) - else: - endoff = self.sections[snum + 1][0] - off = self.sections[snum][0] - return self.data[off:endoff] - -class Unswindler(object): - def __init__(self): - self._exepath = self._get_exe_path() - self._hexdigest = self._get_hexdigest() - self._exedir = os.path.dirname(self._exepath) - self._mobidedrmpath = self._get_mobidedrm_path() - - def _get_mobidedrm_path(self): - basedir = sys.modules[self.__module__].__file__ - basedir = os.path.dirname(basedir) - for basename in ('mobidedrm', 'mobidedrm.py', 'mobidedrm.pyw'): - path = os.path.join(basedir, basename) - if os.path.isfile(path): - return path - raise UnswindleError("could not locate MobiDeDRM script") - - def _get_exe_path(self): - path = None - for root in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE): - try: - regkey = winreg.OpenKey(root, KINDLE_REG_KEY) - path = winreg.QueryValue(regkey, None) - break - except WindowsError: - pass - else: - raise UnswindleError("Kindle For PC installation not found") - if '"' in path: - path = re.search(r'"(.*?)"', path).group(1) - return path - - def _get_hexdigest(self): - path = self._exepath - sha1 = hashlib.sha1() - with open(path, 'rb') as f: - data = f.read(4096) - while data: - sha1.update(data) - data = f.read(4096) - hexdigest = sha1.hexdigest() - if not PC1KeyGrabber.supported_version(hexdigest): - raise UnswindleError("Unsupported version of Kindle For PC") - return hexdigest - - def _check_topaz(self, path): - with open(path, 'rb') as f: - magic = f.read(4) - if magic == 'TPZ0': - return True - return False - - def _check_drm_free(self, path): - with open(path, 'rb') as f: - crypto = MobiParser(f.read()).crypto_type - return (crypto == 0) - - def get_book(self): - creation_flags = (CREATE_UNICODE_ENVIRONMENT | - DEBUG_PROCESS | - DEBUG_ONLY_THIS_PROCESS) - startup_info = STARTUPINFO() - process_info = PROCESS_INFORMATION() - path = pid = None - try: - rv = CreateProcess(self._exepath, None, None, None, False, - creation_flags, None, self._exedir, - byref(startup_info), byref(process_info)) - if not rv: - raise UnswindleError("failed to launch Kindle For PC") - debugger = Debugger(process_info) - grabber = PC1KeyGrabber(debugger, self._hexdigest) - debugger.main_loop() - path = grabber.book_path - pid = grabber.book_pid - finally: - if process_info.hThread is not None: - CloseHandle(process_info.hThread) - if process_info.hProcess is not None: - CloseHandle(process_info.hProcess) - if path is None: - raise UnswindleError("failed to determine book path") - if self._check_topaz(path): - raise UnswindleError("cannot decrypt Topaz format book") - return (path, pid) - - def decrypt_book(self, inpath, outpath, pid): - if self._check_drm_free(inpath): - shutil.copy(inpath, outpath) - else: - self._mobidedrm(inpath, outpath, pid) - return - - def _mobidedrm(self, inpath, outpath, pid): - # darkreverser didn't protect mobidedrm's script execution to allow - # importing, so we have to just run it in a subprocess - if pid is None: - raise UnswindleError("failed to determine book PID") - with tempfile.NamedTemporaryFile(delete=False) as tmpf: - tmppath = tmpf.name - args = [sys.executable, self._mobidedrmpath, inpath, tmppath, pid] - mobidedrm = subprocess.Popen(args, stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - universal_newlines=True) - output = mobidedrm.communicate()[0] - if not output.endswith("done\n"): - try: - os.remove(tmppath) - except OSError: - pass - raise UnswindleError("problem running MobiDeDRM:\n" + output) - shutil.move(tmppath, outpath) - return - -class ExceptionDialog(Tkinter.Frame): - def __init__(self, root, text): - Tkinter.Frame.__init__(self, root, border=5) - label = Tkinter.Label(self, text="Unexpected error:", - anchor=Tkconstants.W, justify=Tkconstants.LEFT) - label.pack(fill=Tkconstants.X, expand=0) - self.text = Tkinter.Text(self) - self.text.pack(fill=Tkconstants.BOTH, expand=1) - self.text.insert(Tkconstants.END, text) - -def gui_main(argv=sys.argv): - root = Tkinter.Tk() - root.withdraw() - progname = os.path.basename(argv[0]) - try: - unswindler = Unswindler() - inpath, pid = unswindler.get_book() - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted Mobipocket file to produce', - defaultextension='.mobi', filetypes=[('MOBI files', '.mobi'), - ('All files', '.*')]) - if not outpath: - return 0 - unswindler.decrypt_book(inpath, outpath, pid) - except UnswindleError, e: - tkMessageBox.showerror("Unswindle For PC", "Error: " + str(e)) - return 1 - except Exception: - root.wm_state('normal') - root.title('Unswindle For PC') - text = traceback.format_exc() - ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) - root.mainloop() - return 1 - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - args = argv[1:] - if len(args) != 1: - sys.stderr.write("usage: %s OUTFILE\n" % (progname,)) - return 1 - outpath = args[0] - unswindler = Unswindler() - inpath, pid = unswindler.get_book() - unswindler.decrypt_book(inpath, outpath, pid) - return 0 - -if __name__ == '__main__': - sys.exit(gui_main()) diff --git a/Kindle_for_Android_Patch/ReadMe_K4Android.txt b/Kindle_for_Android_Patch/ReadMe_K4Android.txt new file mode 100644 index 0000000..f85e0fd --- /dev/null +++ b/Kindle_for_Android_Patch/ReadMe_K4Android.txt @@ -0,0 +1,50 @@ +Kindle for Android +------------------ + +Kindle for Android uses a different scheme to generate its books specific PIDs than Kindle for PC, Kindle for iPhone/iPad, and standalone Kindles. + +Unfortunately, K4Android uses an "account secrets" file that would only be available if the device were jail-broken and even then someone would have to figure out how to decode this secret information in order to reverse the process. + +Instead of trying to calculate the correct PIDs for each book from this primary data, "Me" (a commenter who posted to the ApprenticeAlf site) came up with a wonderful idea to simply modify the Kindle 3 for Android application to store the PIDs it uses to decode each book in its "about activity" window. This list of PIDS can then be provided to MobiDeDRM.py, which in its latest incarnations allows a comma separated list of pids to be passed in, to successfully remove the DRM from that book. Effectively "Me" has created an "Unswindle" for the Kindle for Android 3 application! + +Obviously, to use "Me"'s approach, requires an Android Developer's Certificate (to sign the modified application) and access to and knowledge of the developer tools, but does not require anything to be jail-broken. + +This is a copy the detailed instructions supplied by "Me" to the ApprenticeAlf blog in the comments. The kindle3.patch described below is included in this folder in the tools: + +From the ApprenticeAlf Comments: + +"Me" writes: + +A better solution seems to create a patched version of the Kindle apk which either logs or displays it’s PID. I created a patch to both log the pid list and show it in the Kindle application in the about activity screen. The pid list isn’t available until the DRMed book has been opened (and the list seem to differ for different books). + +To create the patched kindle apk a certificate must be created (http://developer.android.com/guide/publishing/app-signing.html#cert) and the apktool must be build from source (all subprojects) as long as version 1.4.2 isn’t released (http://code.google.com/p/android-apktool/wiki/BuildApktool). + +These are the steps to pull the original apk from the Android device, uninstall it, create a patched apk and install that (tested on a rooted device, but I think all steps should also work on non-rooted devices): + +adb pull /data/app/com.amazon.kindle-1.apk kindle3.apk +adb uninstall com.amazon.kindle +apktool d kindle3.apk kindle3 +cd kindle3 +patch -p1 < ..\kindle3.patch +cd .. +apktool b kindle3 kindle3_patched.apk +jarsigner -verbose -keystore kindle.keystore kindle3_patched.apk kindle +zipalign -v 4 kindle3_patched.apk kindle3_signed.apk +adb install kindle3_signed.apk + +kindle3.patch (based on kindle version 3.0.1.70) is available on pastebin: +http://pastebin.com/LNpgkcpP + +Have fun! + +Comment by me — June 9, 2011 @ 9:01 pm | Reply + +Hi me, +Wow! Great work!!!! + +With your patch, you have created the equivalent of Unswindle for the Kindle for Android app and it does not even require jailbreaking! + +Very nice work indeed! + +Comment by some_updates — June 10, 2011 @ 4:28 am | Reply + diff --git a/Kindle_for_Android_Patch/kindle3.patch b/Kindle_for_Android_Patch/kindle3.patch new file mode 100644 index 0000000..36f63bd --- /dev/null +++ b/Kindle_for_Android_Patch/kindle3.patch @@ -0,0 +1,100 @@ +diff -ru kindle3_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali kindle3/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali +--- kindle3_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali ++++ kindle3/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali +@@ -11,6 +11,8 @@ + + .field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity; + ++.field private pidList:Ljava/lang/String; ++ + + # direct methods + .method public constructor (Lcom/mobipocket/android/library/reader/AndroidSecurity;Lcom/amazon/kcp/application/AndroidDeviceType;)V +@@ -28,6 +30,10 @@ + .line 26 + iput-object p2, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->deviceType:Lcom/amazon/kcp/application/AndroidDeviceType; + ++ const-string v0, "Open DRMed book to show PID list." ++ ++ iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String; ++ + .line 27 + new-instance v0, Ljava/lang/StringBuilder; + +@@ -175,4 +181,26 @@ + move-result-object v0 + + return-object v0 ++.end method ++ ++.method public getPidList()Ljava/lang/String; ++ .locals 1 ++ ++ .prologue ++ .line 15 ++ iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String; ++ ++ return-object v0 ++.end method ++ ++.method public setPidList(Ljava/lang/String;)V ++ .locals 0 ++ .parameter "value" ++ ++ .prologue ++ .line 11 ++ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String; ++ ++ .line 12 ++ return-void + .end method +diff -ru kindle3_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali kindle3/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali +--- kindle3_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali ++++ kindle3/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali +@@ -27,3 +27,9 @@ + + .method public abstract getPid()Ljava/lang/String; + .end method ++ ++.method public abstract getPidList()Ljava/lang/String; ++.end method ++ ++.method public abstract setPidList(Ljava/lang/String;)V ++.end method +\ No newline at end of file +diff -ru kindle3_orig/smali/com/amazon/kcp/info/AboutActivity.smali kindle3/smali/com/amazon/kcp/info/AboutActivity.smali +--- kindle3_orig/smali/com/amazon/kcp/info/AboutActivity.smali ++++ kindle3/smali/com/amazon/kcp/info/AboutActivity.smali +@@ -32,9 +32,11 @@ + invoke-direct {v6, v1}, Ljava/util/ArrayList;->(I)V + + .line 36 +- const v1, 0x7f0b0005 ++ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider; + +- invoke-virtual {p0, v1}, Lcom/amazon/kcp/info/AboutActivity;->getString(I)Ljava/lang/String; ++ move-result-object v0 ++ ++ invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String; + + move-result-object v1 + +diff -ru kindle3_orig/smali/com/amazon/system/security/Security.smali kindle3/smali/com/amazon/system/security/Security.smali +--- kindle3_orig/smali/com/amazon/system/security/Security.smali ++++ kindle3/smali/com/amazon/system/security/Security.smali +@@ -884,6 +884,15 @@ + + .line 332 + :cond_1 ++ ++ const-string v1, "PID list" ++ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider; ++ move-result-object v0 ++ invoke-static {v7}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String; ++ move-result-object v2 ++ invoke-interface {v0, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V ++ invoke-static {v1, v2}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I ++ + return-object v7 + + :cond_2 \ No newline at end of file diff --git a/Mobi_Additional_Tools/lib/mobidedrm.py b/Mobi_Additional_Tools/lib/mobidedrm.py index 7aef175..41bb12d 100644 --- a/Mobi_Additional_Tools/lib/mobidedrm.py +++ b/Mobi_Additional_Tools/lib/mobidedrm.py @@ -51,8 +51,9 @@ # 0.29 - It seems that the ideas about when multibyte trailing characters were # included in the encryption were wrong. They aren't for DOC compressed # files, but they are for HUFF/CDIC compress files! +# 0.30 - Modified interface slightly to work better with new calibre plugin style -__version__ = '0.29' +__version__ = '0.30' import sys @@ -163,6 +164,7 @@ class MobiBook: def __init__(self, infile): # initial sanity check on file self.data_file = file(infile, 'rb').read() + self.mobi_data = '' self.header = self.data_file[0:78] if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': raise DrmException("invalid file format") @@ -301,13 +303,17 @@ class MobiBook: break return [found_key,pid] + def getMobiFile(self, outpath): + file(outpath,'wb').write(self.mobi_data) + def processBook(self, pidlist): crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) print 'Crypto Type is: ', crypto_type self.crypto_type = crypto_type if crypto_type == 0: print "This book is not encrypted." - return self.data_file + self.mobi_data = self.data_file + return if crypto_type != 2 and crypto_type != 1: raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) @@ -353,33 +359,35 @@ class MobiBook: # decrypt sections print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] + self.mobi_data = self.data_file[:self.sections[1][0]] for i in xrange(1, self.records+1): data = self.loadSection(i) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: print ".", # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) + self.mobi_data += PC1(found_key, data[0:len(data) - extra_size]) if extra_size > 0: - new_data += data[-extra_size:] + self.mobi_data += data[-extra_size:] if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data + self.mobi_data += self.data_file[self.sections[self.records+1][0]:] print "done" - return self.data_file + return def getUnencryptedBook(infile,pid): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook([pid]) + book.processBook([pid]) + return book.mobi_data def getUnencryptedBookWithList(infile,pidlist): if not os.path.isfile(infile): raise DrmException('Input File Not Found') book = MobiBook(infile) - return book.processBook(pidlist) + book.processBook(pidlist) + return book.mobi_data + def main(argv=sys.argv): print ('MobiDeDrm v%(__version__)s. ' diff --git a/ReadMe_First.txt b/ReadMe_First.txt index 09a9f8a..45d738e 100644 --- a/ReadMe_First.txt +++ b/ReadMe_First.txt @@ -1,5 +1,3 @@ - - Welcome to the tools! The set includes tools to remove DRM from eReader PDB books, Barnes and Noble ePubs, Adobe ePubs, Adobe PDFs, and Kindle/Mobi ebooks (including Topaz). @@ -10,7 +8,7 @@ This ReadMe_First.txt is meant to give users a quick overview of what is availab Calibre Users (Mac OS X, Linux, Windows) ------------- -If you are a calibre user, the quickest and easiest way to remove DRM form your ebooks is to open the Calibre_Plugins folder and install each of the plugins following the instructions and configuration directions provided in each plugins README file. +If you are a calibre user, the quickest and easiest way to remove DRM from your ebooks is to open the Calibre_Plugins folder and install each of the plugins following the instructions and configuration directions provided in each plugins README file. Once installed and configured, you can simply import a DRM book into Calibre and end up with the DeDRM version in the Calibre database. @@ -20,7 +18,7 @@ These plugins work for Windows, Mac OS X, and Linux Mac OS X Users (Mac OS X 10.5 and 10.6) -------------- -Drag the DeDRM X.X.app droplet to your Desktop. Double-click on it once and it will guide you through collecting the data it needs to remove the DRM. +From the DeDRM_for_Mac_and_Win folder, drag the DeDRM_X.X.app.zip droplet to your Desktop. Double-click on it once to unzip it to create the DeDRM X.X.app droplet. Double-click on the droplet once and it will guide you through collecting the data it needs to remove the DRM. To use it simply drag a book onto the droplet and it will process the book. This tools supports dragging and dropping of folders of ebooks as well. @@ -28,25 +26,27 @@ To use it simply drag a book onto the droplet and it will process the book. Thi Windows Users (Xp through Windows 7) -------------- -Fully extract the DeDRM_WinApp_vX.X.zip. Drag the resulting DeDRM_WinApp_vx.x folder to someplace out of the way on your machine. Open the folder and make a short-cut to DeDRM_Drop_Target and move that short-cut to your Desktop. Double-click on the short-cut and DeDRM will launch it will guide you through collecting the data it needs to remove the DRM. This progeam requires that Python and PyCrypto be properly installed. See below for details. +From the DeDRM_for_Mac_and_Win folder, fully extract the DeDRM_WinApp_vX.X.zip. Drag the resulting DeDRM_WinApp_vx.x folder to someplace out of the way on your machine. Open the folder and make a short-cut from the DeDRM_Drop_Target onto your Desktop. Double-click on the short-cut and DeDRM will launch it will guide you through collecting the data it needs to remove the DRM. -To use it simply drag ebooks or folders onto the DeDRM_Drop_Target short-cut, and it will remove process the ebooks. +***This program requires that Python and PyCrypto be properly installed***. See below for details on which versions are best. + +To use it simply drag ebooks or folders onto the DeDRM_Drop_Target short-cut, and it will process the ebooks. Linux Users ----------- -A version of the DeDRM drag and drop tool is coming for Linux. In the meantime, you should have no problems runnign the gui tools (or their command line equivalents) described next. +Since the state of the Linux Desktop is so jumbled and sad with so many different ways to set it up and different configuration files that depend on your version of Linux, making a DeDRM drag and drop tool for multiple versions of Linux is simply an exercise in futility. That said, you should have no problems running the gui tools (or their command line equivalents) described next. Not a Calibre or a DeDRM User? ------------------------------ There are a number of python based tools that have graphical user interfaces to make them easy to use. To use any of these tools, you need to have Python 2.5, 2.6, or 2.7 for 32 bits installed on your machine as well as a matching PyCrypto or OpenSSL for some tools. -On Mac OS X (10.5 and 10.6) and Linux (recent versions), your systems already have the proper Python and OpenSSL installed. So nothing need be done, you can already run these tools by double-clicking on the .pyw python scripts. +On Mac OS X (10.5 and 10.6) and Linux (recent versions), your systems already have the proper Python and OpenSSL installed. So nothing need be done, you can already run these tools by double-clicking on the .pyw python scripts. Users of Mac OS X 10.3 and 10.4, need to download and install the "32-bit Mac Installer disk Image (2.7.X) for OS X 10.3 and later from http://www.python.org/download/releases/2.7.1/ -On Windows, you need to install a 32 bit version of Python (even on Windows 64) plus a matching 32 bit version of PyCrypto *OR* OpenSSL. See the end of this document for details. +On Windows, you need to install a 32 bit version of Python (even on Windows 64) plus a matching 32 bit version of PyCrypto *OR* OpenSSL. We ***strongly*** recommend teh free ActiveState's Active Python version. See the end of this document for details. The scripts are organized by type of ebook you need to remove the DRM from. Choose from among: @@ -54,15 +54,16 @@ The scripts are organized by type of ebook you need to remove the DRM from. Cho "Adobe_PDF_Tools" "Barnes_and_Noble_ePub_Tools" "eReader_PDB_Tools" - "KindleBooks_Tools" + "KindleBooks_Tools" + "Kindle_for_Android_Patch" by simply opening that folder. -In the "KindleBooks_Tools" folder the primary tool is in the "KindleBooks" folder. +In the "KindleBooks_Tools" folder the primary tool is in the "KindleBooks" folder. If you are a Windows user, or a Linux platform using Wine, or Mac OS X or have trouble running the KindleBooks tools, there are two other tools provided. These are called "Kindle_4_Mac_Unswindle" and "Kindle_4_PC_Unswindle". -Look for a README inside of the relevant folder to get you started. +Look for a README inside of the relevant folder to get you started. @@ -70,7 +71,7 @@ Additional Tools ---------------------- Some additional tools are also provided in the "Mobi_Additional_Tools" folder. There are tools for working with "Kindle for iPhone/iPod_Touch/iPad", finding Topaz ebooks, unpacking Mobi ebooks (without DRM) to get to the Mobi markup language inside, and etc. -There is also an "ePub_Fixer" folder that can be used to fix broken DRM epubs that sometimes provided by Adobe and Barnes and Noble that actually violate the zip standard. +There is also an "ePub_Fixer" folder that can be used to fix broken DRM epubs that sometimes re provided by Adobe and Barnes and Noble that actually violate the zip/epub standard. Check out their readmes for more info. @@ -78,10 +79,12 @@ Check out their readmes for more info. Windows and Python Tools ------------------------ -We strongly recommend ActiveState's Active Python 2.6 or 2.7 Community Edition for Windows (x86) 32 bits. This can be downloaded for free from: +We **strongly** recommend ActiveState's Active Python 2.7 Community Edition for Windows (x86) 32 bits. This can be downloaded for free from: http://www.activestate.com/activepython/downloads +We do **NOT** recommend the version of Python from python.org. +The version from python.org is not as complete as most normal Python installations on Linux and even Mac OS X. It is missing various Windows specific libraries, does not install the default Tk Widget kit (for guis) unless you select it as an option in the installer, and does not properly update the system PATH environment variable. Therefore using the default python.org build on Windows is simply an exercise in frustration for most Windows users. In addition, Windows Users need one of PyCrypto OR OpenSSL. @@ -102,7 +105,7 @@ For PyCrypto: http://www.voidspace.org.uk/python/modules.shtml - Please get the latest PyCrypto meant for Windows 32 bit that matches the version of Python you installed (2.7, or 2.6) + Please get the latest PyCrypto meant for Windows 32 bit that matches the version of Python you installed (2.7) Once Windows users have installed Python 2.X for 32 bits, and the matching OpenSSL OR PyCrypto pieces, they too are ready to run the scripts. diff --git a/eReader_PDB_Tools/lib/erdr2pml.py b/eReader_PDB_Tools/lib/erdr2pml.py index 6df9e13..0e59d8d 100644 --- a/eReader_PDB_Tools/lib/erdr2pml.py +++ b/eReader_PDB_Tools/lib/erdr2pml.py @@ -58,8 +58,9 @@ # 0.17 - added support for pycrypto's DES as well # 0.18 - on Windows try PyCrypto first and OpenSSL next # 0.19 - Modify the interface to allow use of import +# 0.20 - modify to allow use inside new interface for calibre plugins -__version__='0.19' +__version__='0.20' class Unbuffered: def __init__(self, stream): @@ -71,32 +72,50 @@ class Unbuffered: return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) - import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + Des = None if sys.platform.startswith('win'): # first try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() if Des == None: # they try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() else: # first try with openssl - import openssl_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import openssl_des + else: + import openssl_des Des = openssl_des.load_libcrypto() if Des == None: # then try with pycrypto - import pycrypto_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import pycrypto_des + else: + import pycrypto_des Des = pycrypto_des.load_pycrypto() # if that did not work then use pure python implementation # of DES and try to speed it up with Psycho if Des == None: - import python_des + if inCalibre: + from calibre_plugins.erdrpdb2pml import python_des + else: + import python_des Des = python_des.Des # Import Psyco if available try: @@ -480,5 +499,6 @@ def main(argv=None): if __name__ == "__main__": + sys.stdout=Unbuffered(sys.stdout) sys.exit(main())