From ac9cdb1e981032b4a6995f3c33dc49ba7a2b567e Mon Sep 17 00:00:00 2001 From: Apprentice Alf Date: Thu, 17 Feb 2011 11:35:51 +0000 Subject: [PATCH] tools v3.5 --- Calibre_Plugins/k4mobidedrm_plugin.zip | Bin 44083 -> 44145 bytes .../k4mobidedrm_plugin/k4mobidedrm_plugin.py | 4 +- .../k4mobidedrm_plugin/mobidedrm.py | 27 +- .../DeDRM.app/Contents/Info.plist | 4 +- .../Contents/Resources/k4mobidedrm.py | 4 +- .../DeDRM.app/Contents/Resources/mobidedrm.py | 27 +- .../DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py | 4 +- .../DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py | 27 +- .../ReadMe_DeDRM_WinApp.txt | 2 +- KindleBooks_Tools/FindTopazEbooks.pyw | 216 ---------- .../KindleBooks/lib/k4mobidedrm.py | 4 +- .../KindleBooks/lib/mobidedrm.py | 27 +- .../Kindle_4_Mac_Unswindle/lib/mobidedrm.py | 27 +- .../Kindle_4_PC_Unswindle/mobidedrm.py | 315 +++++--------- KindleBooks_Tools/MobiDeDRM.py | 406 ------------------ Mobi_Additional_Tools/lib/mobidedrm.py | 27 +- 16 files changed, 189 insertions(+), 932 deletions(-) delete mode 100644 KindleBooks_Tools/FindTopazEbooks.pyw delete mode 100644 KindleBooks_Tools/MobiDeDRM.py diff --git a/Calibre_Plugins/k4mobidedrm_plugin.zip b/Calibre_Plugins/k4mobidedrm_plugin.zip index 14af1cc742060e9319345a819063fb8b86b51324..0e5c337ee882fdd3f2e0f1e62a65e48ec263b8c0 100644 GIT binary patch delta 9086 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', 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) - content = "\144" elif type == 404 and size == 9: # make sure text to speech is enabled self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content + # print type, size, content, content.encode('hex') pos += size except: self.meta_array = {} @@ -244,13 +242,14 @@ class MobiBook: if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] + token = '' + # 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): diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist index 1eef22d..d1feae2 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist @@ -24,7 +24,7 @@ CFBundleExecutable droplet CFBundleGetInfoString - DeDRM 2.2, Copyright © 2010–2011 by Apprentice Alf and others. + DeDRM 2.3, Copyright © 2010–2011 by Apprentice Alf and others. CFBundleIconFile droplet CFBundleInfoDictionaryVersion @@ -34,7 +34,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.2 + 2.3 CFBundleSignature dplt LSMinimumSystemVersion diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py index 6d37a5b..0255a3c 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py @@ -29,7 +29,7 @@ from __future__ import with_statement # and import that ZIP into Calibre using its plugin configuration GUI. -__version__ = '2.3' +__version__ = '2.4' class Unbuffered: def __init__(self, stream): @@ -250,7 +250,7 @@ if not __name__ == "__main__" and inCalibre: 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, 3) # The version number of this plugin + version = (0, 2, 4) # 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 diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py index 2266329..ec756b9 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py @@ -46,8 +46,9 @@ # 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!) -__version__ = '0.26' +__version__ = '0.27' import sys @@ -207,19 +208,16 @@ class MobiBook: 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) - content = "\144" elif type == 404 and size == 9: # make sure text to speech is enabled self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content + # print type, size, content, content.encode('hex') pos += size except: self.meta_array = {} @@ -244,13 +242,14 @@ class MobiBook: if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] + token = '' + # 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): 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 6d37a5b..0255a3c 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py @@ -29,7 +29,7 @@ from __future__ import with_statement # and import that ZIP into Calibre using its plugin configuration GUI. -__version__ = '2.3' +__version__ = '2.4' class Unbuffered: def __init__(self, stream): @@ -250,7 +250,7 @@ if not __name__ == "__main__" and inCalibre: 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, 3) # The version number of this plugin + version = (0, 2, 4) # 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 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 2266329..ec756b9 100644 --- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py +++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py @@ -46,8 +46,9 @@ # 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!) -__version__ = '0.26' +__version__ = '0.27' import sys @@ -207,19 +208,16 @@ class MobiBook: 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) - content = "\144" elif type == 404 and size == 9: # make sure text to speech is enabled self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content + # print type, size, content, content.encode('hex') pos += size except: self.meta_array = {} @@ -244,13 +242,14 @@ class MobiBook: if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] + token = '' + # 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): diff --git a/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt b/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt index 8e1cfea..111d2b9 100644 --- a/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt +++ b/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt @@ -1,4 +1,4 @@ -ReadMe_DeDRM_WinApp_v1.2 +ReadMe_DeDRM_WinApp_v1.5 ----------------------- DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program. diff --git a/KindleBooks_Tools/FindTopazEbooks.pyw b/KindleBooks_Tools/FindTopazEbooks.pyw deleted file mode 100644 index 6a0df30..0000000 --- a/KindleBooks_Tools/FindTopazEbooks.pyw +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python - -# This is a simple tool to identify all Amazon Topaz ebooks in a specific directory. -# There always seems to be confusion since Topaz books downloaded to K4PC/Mac can have -# almost any extension (.azw, .azw1, .prc, tpz). While the .azw1 and .tpz extensions -# are fairly easy to indentify, the others are not (without opening the files in an editor). - -# To run the tool with the GUI frontend, just double-click on the 'FindTopazFiles.pyw' file -# and select the folder where all of the ebooks in question are located. Then click 'Search'. -# The program will list the file names of the ebooks that are indentified as being Topaz. -# You can then isolate those books and use the Topaz tools to decrypt and convert them. - -# You can also run the script from a command line... supplying the folder to search -# as a parameter: python FindTopazEbooks.pyw "C:\My Folder" (change appropriately for -# your particular O.S.) - -# ** NOTE: This program does NOT decrypt or modify Topaz files in any way. It simply identifies them. - -# PLEASE DO NOT PIRATE EBOOKS! - -# We want all authors and publishers, and eBook stores to live -# long and prosperous lives but at the same time we just want to -# be able to read OUR books on whatever device we want and to keep -# readable for a long, long time - -# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, -# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates -# and many many others - -# Revision history: -# 1 - Initial release. - -from __future__ import with_statement - -__license__ = 'GPL v3' - -import sys -import os -import re -import shutil -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - - -class ScrolledText(Tkinter.Text): - def __init__(self, master=None, **kw): - self.frame = Tkinter.Frame(master) - self.vbar = Tkinter.Scrollbar(self.frame) - self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) - kw.update({'yscrollcommand': self.vbar.set}) - Tkinter.Text.__init__(self, self.frame, **kw) - self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) - self.vbar['command'] = self.yview - # Copy geometry methods of self.frame without overriding Text - # methods = hack! - text_meths = vars(Tkinter.Text).keys() - methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() - methods = set(methods).difference(text_meths) - for m in methods: - if m[0] != '_' and m != 'config' and m != 'configure': - setattr(self, m, getattr(self.frame, m)) - - def __str__(self): - return str(self.frame) - - -def cli_main(argv=sys.argv, obj=None): - progname = os.path.basename(argv[0]) - if len(argv) != 2: - print "usage: %s DIRECTORY" % (progname,) - return 1 - - if obj == None: - print "\nTopaz search results:\n" - else: - obj.stext.insert(Tkconstants.END,"Topaz search results:\n\n") - - inpath = argv[1] - files = os.listdir(inpath) - filefilter = re.compile("(\.azw$)|(\.azw1$)|(\.prc$)|(\.tpz$)", re.IGNORECASE) - files = filter(filefilter.search, files) - - if files: - topazcount = 0 - totalcount = 0 - for filename in files: - with open(os.path.join(inpath, filename), 'rb') as f: - try: - if f.read().startswith('TPZ'): - f.close() - basename, extension = os.path.splitext(filename) - if obj == None: - print " %s is a Topaz formatted ebook." % filename - """ - if extension == '.azw' or extension == '.prc': - print " renaming to %s" % (basename + '.tpz') - shutil.move(os.path.join(inpath, filename), - os.path.join(inpath, basename + '.tpz')) - """ - else: - msg1 = " %s is a Topaz formatted ebook.\n" % filename - obj.stext.insert(Tkconstants.END,msg1) - """ - if extension == '.azw' or extension == '.prc': - msg2 = " renaming to %s\n" % (basename + '.tpz') - obj.stext.insert(Tkconstants.END,msg2) - shutil.move(os.path.join(inpath, filename), - os.path.join(inpath, basename + '.tpz')) - """ - topazcount += 1 - except: - if obj == None: - print " Error reading %s." % filename - else: - msg = " Error reading or %s.\n" % filename - obj.stext.insert(Tkconstants.END,msg) - pass - totalcount += 1 - if topazcount == 0: - if obj == None: - print "\nNo Topaz books found in %s." % inpath - else: - msg = "\nNo Topaz books found in %s.\n\n" % inpath - obj.stext.insert(Tkconstants.END,msg) - else: - if obj == None: - print "\n%i Topaz books found in %s\n%i total books checked.\n" % (topazcount, inpath, totalcount) - else: - msg = "\n%i Topaz books found in %s\n%i total books checked.\n\n" %(topazcount, inpath, totalcount) - obj.stext.insert(Tkconstants.END,msg) - else: - if obj == None: - print "No typical Topaz file extensions found in %s.\n" % inpath - else: - msg = "No typical Topaz file extensions found in %s.\n\n" % inpath - obj.stext.insert(Tkconstants.END,msg) - - return 0 - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - ltext='Search a directory for Topaz eBooks\n' - self.status = Tkinter.Label(self, text=ltext) - self.status.pack(fill=Tkconstants.X, expand=1) - body = Tkinter.Frame(self) - body.pack(fill=Tkconstants.X, expand=1) - sticky = Tkconstants.E + Tkconstants.W - body.grid_columnconfigure(1, weight=2) - Tkinter.Label(body, text='Directory to Search').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - msg1 = 'Topaz search results \n\n' - self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, - height=15, width=60, wrap=Tkconstants.WORD) - self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky) - #self.stext.insert(Tkconstants.END,msg1) - buttons = Tkinter.Frame(self) - buttons.pack() - - - self.botton = Tkinter.Button( - buttons, text="Search", width=10, command=self.search) - self.botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - self.button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - self.button.pack(side=Tkconstants.RIGHT) - - def get_inpath(self): - cwd = os.getcwdu() - cwd = cwd.encode('utf-8') - inpath = tkFileDialog.askdirectory( - parent=None, title='Directory to search', - initialdir=cwd, initialfile=None) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - - def search(self): - inpath = self.inpath.get() - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified directory does not exist' - return - argv = [sys.argv[0], inpath] - self.status['text'] = 'Searching...' - self.botton.configure(state='disabled') - cli_main(argv, self) - self.status['text'] = 'Search a directory for Topaz files' - self.botton.configure(state='normal') - - return - - -def gui_main(): - root = Tkinter.Tk() - root.title('Topaz eBook Finder') - root.resizable(True, False) - root.minsize(370, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) \ No newline at end of file diff --git a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py index 6d37a5b..0255a3c 100644 --- a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py +++ b/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py @@ -29,7 +29,7 @@ from __future__ import with_statement # and import that ZIP into Calibre using its plugin configuration GUI. -__version__ = '2.3' +__version__ = '2.4' class Unbuffered: def __init__(self, stream): @@ -250,7 +250,7 @@ if not __name__ == "__main__" and inCalibre: 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, 3) # The version number of this plugin + version = (0, 2, 4) # 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 diff --git a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py index 2266329..ec756b9 100644 --- a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py +++ b/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py @@ -46,8 +46,9 @@ # 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!) -__version__ = '0.26' +__version__ = '0.27' import sys @@ -207,19 +208,16 @@ class MobiBook: 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) - content = "\144" elif type == 404 and size == 9: # make sure text to speech is enabled self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content + # print type, size, content, content.encode('hex') pos += size except: self.meta_array = {} @@ -244,13 +242,14 @@ class MobiBook: if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] + token = '' + # 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): diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py index 2266329..ec756b9 100644 --- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py +++ b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py @@ -46,8 +46,9 @@ # 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!) -__version__ = '0.26' +__version__ = '0.27' import sys @@ -207,19 +208,16 @@ class MobiBook: 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) - content = "\144" elif type == 404 and size == 9: # make sure text to speech is enabled self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content + # print type, size, content, content.encode('hex') pos += size except: self.meta_array = {} @@ -244,13 +242,14 @@ class MobiBook: if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] + token = '' + # 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): diff --git a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py b/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py index 2266329..183432c 100644 --- a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py +++ b/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py @@ -24,7 +24,7 @@ # 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 +# files reveals that a confusin 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 @@ -39,17 +39,13 @@ # 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.20 - Corretion: It seems that multibyte entries are encrypted in a v6 file. -__version__ = '0.26' +__version__ = '0.20' import sys +import struct +import binascii class Unbuffered: def __init__(self, stream): @@ -59,20 +55,10 @@ class Unbuffered: 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; @@ -84,6 +70,7 @@ def PC1(key, src, decryption=True): 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; @@ -144,9 +131,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags): num += (ord(ptr[size - num - 1]) & 0x3) + 1 return num - - -class MobiBook: +class DrmStripper: def loadSection(self, section): if (section + 1 == self.num_sections): endoff = len(self.data_file) @@ -155,104 +140,6 @@ class MobiBook: off = self.sections[section][0] return self.data_file[off:endoff] - def __init__(self, infile): - # initial sanity check on file - self.data_file = file(infile, 'rb').read() - self.header = self.data_file[0:78] - if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") - self.magic = self.header[0x3C:0x3C+8] - self.crypto_type = -1 - - # build up section offset and flag info - self.num_sections, = struct.unpack('>H', self.header[76:78]) - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - - # parse information from section 0 - self.sect = self.loadSection(0) - self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) - - if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_version = -1 - self.meta_array = {} - return - self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) - self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 - if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): - self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags - if self.mobi_version < 7: - # multibyte utf8 data is included in the encryption for mobi_version 6 and below - # so clear that byte so that we leave it to be decrypted. - self.extra_data_flags &= 0xFFFE - - # if exth region exists parse it for metadata array - self.meta_array = {} - try: - exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' - if exth_flag & 0x40: - exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - # reset the text to speech flag and clipping limit, if present - if type == 401 and size == 9: - # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) - content = "\144" - elif type == 404 and size == 9: - # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content - pos += size - except: - self.meta_array = {} - pass - - def getBookTitle(self): - title = '' - if 503 in self.meta_array: - title = self.meta_array[503] - else : - toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) - tend = toff + tlen - title = self.sect[toff:tend] - if title == '': - title = self.header[:32] - title = title.split("\0")[0] - return title - - def getPIDMetaInfo(self): - rec209 = None - token = None - if 209 in self.meta_array: - rec209 = self.meta_array[209] - data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] - return rec209, token - def patch(self, off, new): self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] @@ -265,136 +152,134 @@ class MobiBook: assert off + in_off + len(new) <= endoff self.patch(off + in_off, new) - def parseDRM(self, data, count, pidlist): - found_key = None + def parseDRM(self, data, count, pid): + pid = pid.ljust(16,'\0') 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: + temp_key = PC1(keyvec1, pid, 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]) + cookie = PC1(temp_key, cookie) + ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) + if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1: + found_key = finalkey 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] + cookie = PC1(temp_key, cookie) + ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) + if verification == ver and cksum == temp_key_sum: + found_key = finalkey + break + return found_key - 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 + def __init__(self, data_file, pid): + if len(pid)==10: + if checksumPid(pid[0:-2]) != pid: + raise DrmException("invalid PID checksum") + pid = pid[0:-2] + elif len(pid)==8: + print "PID without checksum given. With checksum PID is "+checksumPid(pid) + else: + raise DrmException("Invalid PID length") + + self.data_file = data_file + header = data_file[0:72] + if header[0x3C:0x3C+8] != 'BOOKMOBI': + raise DrmException("invalid file format") + self.num_sections, = struct.unpack('>H', data_file[76:78]) + + self.sections = [] + for i in xrange(self.num_sections): + offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8]) + flags, val = a1, a2<<16|a3<<8|a4 + self.sections.append( (offset, flags, val) ) + + sect = self.loadSection(0) + records, = struct.unpack('>H', sect[0x8:0x8+2]) + mobi_length, = struct.unpack('>L',sect[0x14:0x18]) + mobi_version, = struct.unpack('>L',sect[0x68:0x6C]) + extra_data_flags = 0 + print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length) + if (mobi_length >= 0xE4) and (mobi_version >= 5): + extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) + print "Extra Data Flags = %d" %extra_data_flags + if mobi_version < 7: + # multibyte utf8 data is included in the encryption for mobi_version 6 and below + # so clear that byte so that we leave it to be decrypted. + extra_data_flags &= 0xFFFE + + crypto_type, = struct.unpack('>H', sect[0xC:0xC+2]) 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) + else: + if crypto_type == 1: + raise DrmException("cannot decode Mobipocket encryption type 1") + if crypto_type != 2: + raise DrmException("unknown 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]) + drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', 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) + raise DrmException("no PIDs found in this file") + found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid) if not found_key: - raise DrmException("No key found. Most likely the correct PID has not been given.") + raise DrmException("no key found. maybe the PID is incorrect") + # 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) - # 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, records+1): + data = self.loadSection(i) + extra_size = getSizeOfTrailingDataEntries(data, len(data), 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:] + #self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size])) + if self.num_sections > records+1: + new_data += self.data_file[self.sections[records+1][0]:] + self.data_file = new_data + print "done" - # 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" + def getResult(self): 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) + sys.stdout=Unbuffered(sys.stdout) + data_file = file(infile, 'rb').read() + strippedFile = DrmStripper(data_file, pid) + return strippedFile.getResult() def main(argv=sys.argv): + sys.stdout=Unbuffered(sys.stdout) print ('MobiDeDrm v%(__version__)s. ' 'Copyright 2008-2010 The Dark Reverser.' % globals()) - if len(argv)<3 or len(argv)>4: + if len(argv)<4: print "Removes protection from Mobipocket books" print "Usage:" - print " %s []" % sys.argv[0] + 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 = {} + pid = argv[3] try: - stripped_file = getUnencryptedBookWithList(infile, pidlist) + stripped_file = getUnencryptedBook(infile, pid) file(outfile, 'wb').write(stripped_file) except DrmException, e: print "Error: %s" % e diff --git a/KindleBooks_Tools/MobiDeDRM.py b/KindleBooks_Tools/MobiDeDRM.py deleted file mode 100644 index 2266329..0000000 --- a/KindleBooks_Tools/MobiDeDRM.py +++ /dev/null @@ -1,406 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 0.01 - Initial version -# 0.02 - Huffdic compressed books were not properly decrypted -# 0.03 - Wasn't checking MOBI header length -# 0.04 - Wasn't sanity checking size of data record -# 0.05 - It seems that the extra data flags take two bytes not four -# 0.06 - And that low bit does mean something after all :-) -# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size -# 0.08 - ...and also not in Mobi header version < 6 -# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4! -# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre -# import filter it works when importing unencrypted files. -# Also now handles encrypted files that don't need a specific PID. -# 0.11 - use autoflushed stdout and proper return values -# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors -# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace -# and extra blank lines, converted CR/LF pairs at ends of each line, -# and other cosmetic fixes. -# 0.14 - Working out when the extra data flags are present has been problematic -# Versions 7 through 9 have tried to tweak the conditions, but have been -# only partially successful. Closer examination of lots of sample -# files reveals that a confusion has arisen because trailing data entries -# are not encrypted, but it turns out that the multibyte entries -# in utf8 file are encrypted. (Although neither kind gets compressed.) -# This knowledge leads to a simplification of the test for the -# trailing data byte flags - version 5 and higher AND header size >= 0xE4. -# 0.15 - Now outputs 'heartbeat', and is also quicker for long files. -# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility. -# 0.17 - added modifications to support its use as an imported python module -# both inside calibre and also in other places (ie K4DeDRM tools) -# 0.17a- disabled the standalone plugin feature since a plugin can not import -# a plugin -# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... -# Removed the disabled Calibre plug-in code -# Permit use of 8-digit PIDs -# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. -# 0.21 - Added support for multiple pids -# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface -# 0.23 - fixed problem with older files with no EXTH section -# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well -# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption -# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% - -__version__ = '0.26' - -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii - -class DrmException(Exception): - pass - - -# -# MobiBook Utility Routines -# - -# Implementation of Pukall Cipher 1 -def PC1(key, src, decryption=True): - sum1 = 0; - sum2 = 0; - keyXorVal = 0; - if len(key)!=16: - print "Bad key length!" - return None - wkey = [] - for i in xrange(8): - wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) - dst = "" - for i in xrange(len(src)): - temp1 = 0; - byteXorVal = 0; - for j in xrange(8): - temp1 ^= wkey[j] - sum2 = (sum2+j)*20021 + sum1 - sum1 = (temp1*346)&0xFFFF - sum2 = (sum2+sum1)&0xFFFF - temp1 = (temp1*20021+1)&0xFFFF - byteXorVal ^= temp1 ^ sum2 - curByte = ord(src[i]) - if not decryption: - keyXorVal = curByte * 257; - curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF - if decryption: - keyXorVal = curByte * 257; - for j in xrange(8): - wkey[j] ^= keyXorVal; - dst+=chr(curByte) - return dst - -def checksumPid(s): - letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - crc = (~binascii.crc32(s,-1))&0xFFFFFFFF - crc = crc ^ (crc >> 16) - res = s - l = len(letters) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += letters[pos%l] - crc >>= 8 - return res - -def getSizeOfTrailingDataEntries(ptr, size, flags): - def getSizeOfTrailingDataEntry(ptr, size): - bitpos, result = 0, 0 - if size <= 0: - return result - while True: - v = ord(ptr[size-1]) - result |= (v & 0x7F) << bitpos - bitpos += 7 - size -= 1 - if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0): - return result - num = 0 - testflags = flags >> 1 - while testflags: - if testflags & 1: - num += getSizeOfTrailingDataEntry(ptr, size - num) - testflags >>= 1 - # Check the low bit to see if there's multibyte data present. - # if multibyte data is included in the encryped data, we'll - # have already cleared this flag. - if flags & 1: - num += (ord(ptr[size - num - 1]) & 0x3) + 1 - return num - - - -class MobiBook: - def loadSection(self, section): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - return self.data_file[off:endoff] - - def __init__(self, infile): - # initial sanity check on file - self.data_file = file(infile, 'rb').read() - self.header = self.data_file[0:78] - if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") - self.magic = self.header[0x3C:0x3C+8] - self.crypto_type = -1 - - # build up section offset and flag info - self.num_sections, = struct.unpack('>H', self.header[76:78]) - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - - # parse information from section 0 - self.sect = self.loadSection(0) - self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) - - if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_version = -1 - self.meta_array = {} - return - self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) - self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 - if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): - self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags - if self.mobi_version < 7: - # multibyte utf8 data is included in the encryption for mobi_version 6 and below - # so clear that byte so that we leave it to be decrypted. - self.extra_data_flags &= 0xFFFE - - # if exth region exists parse it for metadata array - self.meta_array = {} - try: - exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' - if exth_flag & 0x40: - exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - # reset the text to speech flag and clipping limit, if present - if type == 401 and size == 9: - # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) - content = "\144" - elif type == 404 and size == 9: - # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content - pos += size - except: - self.meta_array = {} - pass - - def getBookTitle(self): - title = '' - if 503 in self.meta_array: - title = self.meta_array[503] - else : - toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) - tend = toff + tlen - title = self.sect[toff:tend] - if title == '': - title = self.header[:32] - title = title.split("\0")[0] - return title - - def getPIDMetaInfo(self): - rec209 = None - token = None - if 209 in self.meta_array: - rec209 = self.meta_array[209] - data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] - return rec209, token - - def patch(self, off, new): - self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] - - def patchSection(self, section, new, in_off = 0): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - assert off + in_off + len(new) <= endoff - self.patch(off + in_off, new) - - def parseDRM(self, data, count, pidlist): - found_key = None - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" - for pid in pidlist: - bigpid = pid.ljust(16,'\0') - temp_key = PC1(keyvec1, bigpid, False) - temp_key_sum = sum(map(ord,temp_key)) & 0xff - found_key = None - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver and (flags & 0x1F) == 1: - found_key = finalkey - break - if found_key != None: - break - if not found_key: - # Then try the default encoding that doesn't require a PID - pid = "00000000" - temp_key = keyvec1 - temp_key_sum = sum(map(ord,temp_key)) & 0xff - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver: - found_key = finalkey - break - return [found_key,pid] - - def processBook(self, pidlist): - crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) - print 'Crypto Type is: ', crypto_type - self.crypto_type = crypto_type - if crypto_type == 0: - print "This book is not encrypted." - return self.data_file - if crypto_type != 2 and crypto_type != 1: - raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) - - goodpids = [] - for pid in pidlist: - if len(pid)==10: - if checksumPid(pid[0:-2]) != pid: - print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) - goodpids.append(pid[0:-2]) - elif len(pid)==8: - goodpids.append(pid) - - if self.crypto_type == 1: - t1_keyvec = "QDCVEPMU675RUBSZ" - if self.magic == 'TEXtREAd': - bookkey_data = self.sect[0x0E:0x0E+16] - elif self.mobi_version < 0: - bookkey_data = self.sect[0x90:0x90+16] - else: - bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] - pid = "00000000" - found_key = PC1(t1_keyvec, bookkey_data) - else : - # calculate the keys - drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) - if drm_count == 0: - raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") - found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) - if not found_key: - raise DrmException("No key found. Most likely the correct PID has not been given.") - # kill the drm keys - self.patchSection(0, "\0" * drm_size, drm_ptr) - # kill the drm pointers - self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) - - if pid=="00000000": - print "File has default encryption, no specific PID." - else: - print "File is encoded with PID "+checksumPid(pid)+"." - - # clear the crypto type - self.patchSection(0, "\0" * 2, 0xC) - - # decrypt sections - print "Decrypting. Please wait . . .", - new_data = self.data_file[:self.sections[1][0]] - for i in xrange(1, self.records+1): - data = self.loadSection(i) - extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) - if i%100 == 0: - print ".", - # print "record %d, extra_size %d" %(i,extra_size) - new_data += PC1(found_key, data[0:len(data) - extra_size]) - if extra_size > 0: - new_data += data[-extra_size:] - if self.num_sections > self.records+1: - new_data += self.data_file[self.sections[self.records+1][0]:] - self.data_file = new_data - print "done" - return self.data_file - -def getUnencryptedBook(infile,pid): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile) - return book.processBook([pid]) - -def getUnencryptedBookWithList(infile,pidlist): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile) - return book.processBook(pidlist) - -def main(argv=sys.argv): - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2010 The Dark Reverser.' % globals()) - if len(argv)<3 or len(argv)>4: - print "Removes protection from Mobipocket books" - print "Usage:" - print " %s []" % 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/Mobi_Additional_Tools/lib/mobidedrm.py b/Mobi_Additional_Tools/lib/mobidedrm.py index 2266329..ec756b9 100644 --- a/Mobi_Additional_Tools/lib/mobidedrm.py +++ b/Mobi_Additional_Tools/lib/mobidedrm.py @@ -46,8 +46,9 @@ # 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!) -__version__ = '0.26' +__version__ = '0.27' import sys @@ -207,19 +208,16 @@ class MobiBook: 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) - content = "\144" elif type == 404 and size == 9: # make sure text to speech is enabled self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - content = "\0" - else: - content = exth[pos + 8: pos + size] - #print type, size, content - self.meta_array[type] = content + # print type, size, content, content.encode('hex') pos += size except: self.meta_array = {} @@ -244,13 +242,14 @@ class MobiBook: if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 - # Parse the 209 data to find the the exth record with the token data. - # The last character of the 209 data points to the record with the token. - # Always 208 from my experience, but I'll leave the logic in case that changes. - for i in xrange(len(data)): - if ord(data[i]) != 0: - if self.meta_array[ord(data[i])] != None: - token = self.meta_array[ord(data[i])] + token = '' + # 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):