]> xmof Git - DeDRM.git/commitdiff
tools v2.2
authorApprentice Alf <apprenticealf@gmail.com>
Thu, 11 Nov 2010 22:11:36 +0000 (22:11 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Wed, 4 Mar 2015 07:12:08 +0000 (07:12 +0000)
100 files changed:
.gitignore
Adobe_EPUB_Tools/README_ineptepub.txt
Adobe_EPUB_Tools/ineptepub.pyw
Adobe_EPUB_Tools/ineptkey.pyw
Barnes_and_Noble_EPUB_Tools/ignobleepub.pyw
Barnes_and_Noble_EPUB_Tools/ignoblekeygen.pyw
Calibre_Plugins/Win_OpenSSL_0.9.8o.txt [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin.zip
Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML-README.txt [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML_plugin.py
Calibre_Plugins/eReaderPDB2PML_plugin/erdr2pml.py
Calibre_Plugins/eReaderPDB2PML_plugin/openssl_des.py [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/all-wcprops [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/dir-prop-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/entries [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/__init__.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/classes.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/core.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/kdictproxy.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/logger.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/profiler.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/support.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/__init__.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/classes.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/core.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/kdictproxy.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/logger.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/profiler.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/support.py.svn-base [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/__init__.py [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/classes.py [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/core.py [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/kdictproxy.py [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/logger.py [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/profiler.py [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/support.py [deleted file]
Calibre_Plugins/eReaderPDB2PML_plugin/python_des.py [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/kdictproxy.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo [new file with mode: 0644]
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo [new file with mode: 0644]
Calibre_Plugins/ignobleepub_plugin.zip
Calibre_Plugins/ignobleepub_plugin/Ignobleepub-README.txt [deleted file]
Calibre_Plugins/ignobleepub_plugin/ignobleepub_plugin.py
Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd [new file with mode: 0644]
Calibre_Plugins/ignobleepub_plugin/zipfix.py [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin.zip
Calibre_Plugins/ineptepub_plugin/Ineptepub-README.txt [deleted file]
Calibre_Plugins/ineptepub_plugin/ade_key.py
Calibre_Plugins/ineptepub_plugin/ineptepub_plugin.py
Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Random/OSRNG/winrandom.pyd [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Util/_counter.pyd [new file with mode: 0644]
Calibre_Plugins/ineptepub_plugin/zipfix.py [new file with mode: 0644]
Calibre_Plugins/k4mobidedrm_plugin.zip
Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py
Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py
Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py
Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/K4MobiDeDRM.pyw
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/README_K4MobiDeDRM.txt
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mobidedrm.py
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mutils.py
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4pcutils.py
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/mobidedrm.py
Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/K4Munswindle.pyw
Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/README_K4Munswindle.txt
Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py
Kindle_Mobi_Tools/Kindle_4_PC_Unswindle/mobidedrm.py
Kindle_Mobi_Tools/MobiDeDRM.py [new file with mode: 0644]
Mobi_Additional_Tools/lib/mobidedrm.py
Topaz_Tools/README_Topaz_Tools.txt
Topaz_Tools/TopazExtract.pyw [moved from Topaz_Tools/TopazExtract_Kindle4PC.pyw with 81% similarity]
Topaz_Tools/lib/changes.txt [deleted file]
Topaz_Tools/lib/cmbtc_dump.py
Topaz_Tools/lib/cmbtc_dump_nonK4PC.py [deleted file]
Topaz_Tools/lib/k4mutils.py [new file with mode: 0644]
Topaz_Tools/lib/k4pcutils.py [new file with mode: 0644]
Topaz_Tools/lib/topaz-changes.txt [deleted file]
Topaz_Tools/lib/topaz-readme.txt
ePub_Fixer/README_ePub_Fixer.txt [new file with mode: 0644]
ePub_Fixer/ePub_Fixer.pyw [moved from Topaz_Tools/TopazExtract_iPhone_iPad_K4M.pyw with 62% similarity]
ePub_Fixer/lib/scrolltextwidget.py [new file with mode: 0644]
ePub_Fixer/lib/subasyncio.py [new file with mode: 0644]
ePub_Fixer/lib/zipfix.py [new file with mode: 0644]
eReader_PDB_Tools/lib/eReaderPDB2PML_plugin.py [new file with mode: 0644]
eReader_PDB_Tools/lib/erdr2pml.py
eReader_PDB_Tools/lib/openssl_des.py [new file with mode: 0644]
eReader_PDB_Tools/lib/python_des.py [new file with mode: 0644]

index b2d0d77e46d7320418fc4e0f2cff9e87f2dadde0..436624b3117154b43516bf812de12c972587aee5 100644 (file)
@@ -1,6 +1,6 @@
 # Byte-compiled / optimized / DLL files
 __pycache__/
-*.py[cod]
+*.pyc
 
 # C extensions
 *.so
index cab043d28f1471d2bd58faef5e617c629fd12f1b..d764357f422934ec1c6fe059fede62dabdba79ab 100644 (file)
@@ -2,17 +2,18 @@ From Apprentice Alf's Blog
 
 Adobe Adept ePub and PDF, .epub, .pdf
 
-The wonderful I♥CABBAGES has produced scripts that will remove the DRM from ePubs and PDFs encryped with Adobe’s DRM. Installing these scripts is a little more complex that the Mobipocket and eReader decryption tools, as they require installation of the PyCrypto package for Windows Boxes.  For Mac OS X and Linux boxes, these scripts use the already installed OpenSSL libcrypto so there is no additional requirements for these platforms.
+This directory includes modified versions of the I♥CABBAGES Adobe Adept inept scripts for epubs.  These scripts have been modified to work with OpenSSL on Windows as well as Linux and Mac OS X.  His original scripts can be found in the clearly labelled folder.  If a Windows User has OpenSSL installed, these scripts will make use of it in place of PyCrypto.
+
+The wonderful I♥CABBAGES has produced scripts that will remove the DRM from ePubs and PDFs encryped with Adobe’s DRM. These scripts require installation of the PyCrypto python package *or* the OpenSSL library on Windows.  For Mac OS X and Linux boxes, these scripts use the already installed OpenSSL libcrypto so there is no additional requirements for these platforms.
 
 For more info, see the author's blog:
 http://i-u2665-cabbages.blogspot.com/2009_02_01_archive.html
 
 There are two scripts:
 
-The first is called ineptkey_v5.pyw.  Simply double-click to launch it and it will create a key file that is needed later to actually remove the DRM.  This script need only be run once unless you change your ADE account information.
-
-The second is called in ineptepub_v5.pyw.  Simply double-click to launch it.  It will ask for your previously generated key file and the path to the book you want to remove the DRM from.
+The first is called ineptkey_v5.1.pyw.  Simply double-click to launch it and it will create a key file that is needed later to actually remove the DRM.  This script need only be run once unless you change your ADE account information.
 
+The second is called in ineptepub_v5.3.pyw.  Simply double-click to launch it.  It will ask for your previously generated key file and the path to the book you want to remove the DRM from.
 
 Both of these scripts are gui python programs.   Python 2.X (32 bit) is already installed in Mac OSX.  We recommend ActiveState's Active Python Version 2.X (32 bit) for Windows users.
 
index d6c5f7d269fb58d8cc8c868f616ecab8fb9ab5dc..701fc2e15b4bbf3e1ef1ff4a76a9cb61f3655b18 100644 (file)
@@ -24,7 +24,7 @@
 #       Improve OS X support by using OpenSSL when available
 #   5.1 - Improve OpenSSL error checking
 #   5.2 - Fix ctypes error causing segfaults on some systems
-
+#   5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
 """
 Decrypt Adobe ADEPT-encrypted EPUB books.
 """
@@ -53,7 +53,11 @@ def _load_crypto_libcrypto():
         Structure, c_ulong, create_string_buffer, cast
     from ctypes.util import find_library
 
-    libcrypto = find_library('crypto')
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
+
     if libcrypto is None:
         raise ADEPTError('libcrypto not found')
     libcrypto = CDLL(libcrypto)
@@ -116,6 +120,9 @@ def _load_crypto_libcrypto():
     class AES(object):
         def __init__(self, userkey):
             self._blocksize = len(userkey)
+            if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+                raise ADEPTError('AES improper key used')
+                return
             key = self._key = AES_KEY()
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
index 3756ae318ef11e65f7739ac0a1c3d8be9ddbb175..e0ac72e98849972e0388ce8a93820d1580255c66 100644 (file)
@@ -30,6 +30,7 @@
 #   4.4 - Make it working on 64-bit Python
 #   5 - Clean up and improve 4.x changes;
 #       Clean up and merge OS X support by unknown
+#   5.1 - add support for using OpenSSL on Windows in place of PyCrypto
 
 """
 Retrieve Adobe ADEPT user key.
@@ -53,14 +54,76 @@ class ADEPTError(Exception):
 if sys.platform.startswith('win'):
     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, c_size_t, memmove
+        string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
+        c_long, c_ulong
+
     from ctypes.wintypes import LPVOID, DWORD, BOOL
     import _winreg as winreg
 
-    try:
-        from Crypto.Cipher import AES
-    except ImportError:
+    def _load_crypto_libcrypto():
+        from ctypes.util import find_library
+        libcrypto = find_library('libeay32')
+        if libcrypto is None:
+            raise ADEPTError('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_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
+                                [c_char_p, c_int, AES_KEY_p])
+        AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
+                            [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
+                             c_int])
+        class AES(object):
+            def __init__(self, userkey):
+                self._blocksize = len(userkey)
+                if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+                    raise ADEPTError('AES improper key used')
+                key = self._key = AES_KEY()
+                rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
+                if rv < 0:
+                    raise ADEPTError('Failed to initialize AES key')
+            def decrypt(self, data):
+                out = create_string_buffer(len(data))
+                iv = ("\x00" * self._blocksize)
+                rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
+                if rv == 0:
+                    raise ADEPTError('AES decryption failed')
+                return out.raw
+        return AES
+
+    def _load_crypto_pycrypto():
+        from Crypto.Cipher import AES as _AES
+        class AES(object):
+            def __init__(self, key):
+                self._aes = _AES.new(key, _AES.MODE_CBC)
+            def decrypt(self, data):
+                return self._aes.decrypt(data)
+        return AES
+
+    def _load_crypto():
         AES = None
+        for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
+            try:
+                AES = loader()
+                break
+            except (ImportError, ADEPTError):
+                pass
+        return AES
+
+    AES = _load_crypto()
+
 
     DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
     PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
@@ -230,7 +293,7 @@ if sys.platform.startswith('win'):
         if AES is None:
             tkMessageBox.showerror(
                 "ADEPT Key",
-                "This script requires PyCrypto, which must be installed "
+                "This script requires PyCrypto or OpenSSL which must be installed "
                 "separately.  Read the top-of-script comment for details.")
             return False
         root = GetSystemDirectory().split('\\')[0] + '\\'
@@ -274,7 +337,8 @@ if sys.platform.startswith('win'):
         if userkey is None:
             raise ADEPTError('Could not locate privateLicenseKey')
         userkey = userkey.decode('base64')
-        userkey = AES.new(keykey, AES.MODE_CBC).decrypt(userkey)
+        aes = AES(keykey)
+        userkey = aes.decrypt(userkey)
         userkey = userkey[26:-ord(userkey[-1])]
         with open(keypath, 'wb') as f:
             f.write(userkey)
index 38e23d1595b4d0d31766907ff185aa843d2f2be4..46cd4e800b045793712fb9ba6edd98be31601ff0 100644 (file)
@@ -11,6 +11,7 @@
 #   1 - Initial release
 #   2 - Added OS X support by using OpenSSL when available
 #   3 - screen out improper key lengths to prevent segfaults on Linux
+#   3.1 - Allow Windows versions of libcrypto to be found
 
 from __future__ import with_statement
 
@@ -36,7 +37,10 @@ def _load_crypto_libcrypto():
         Structure, c_ulong, create_string_buffer, cast
     from ctypes.util import find_library
 
-    libcrypto = find_library('crypto')
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
     if libcrypto is None:
         raise IGNOBLEError('libcrypto not found')
     libcrypto = CDLL(libcrypto)
index 70d0535a83b5166de5f5ddeae655073b0442e00d..479c11d7c58a5ba89fc346a4bda7aee000ad1b8a 100644 (file)
@@ -10,6 +10,7 @@
 # Revision history:
 #   1 - Initial release
 #   2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
+#   2.1 - Allow Windows versions of libcrypto to be found
 
 """
 Generate Barnes & Noble EPUB user key from name and credit card number.
@@ -40,7 +41,10 @@ def _load_crypto_libcrypto():
         Structure, c_ulong, create_string_buffer, cast
     from ctypes.util import find_library
 
-    libcrypto = find_library('crypto')
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
     if libcrypto is None:
         print 'libcrypto not found'
         raise IGNOBLEError('libcrypto not found')
diff --git a/Calibre_Plugins/Win_OpenSSL_0.9.8o.txt b/Calibre_Plugins/Win_OpenSSL_0.9.8o.txt
new file mode 100644 (file)
index 0000000..c3ee4fb
--- /dev/null
@@ -0,0 +1,26 @@
+Installing openssl on Windows 64-bit (Windows 2000 and higher)
+
+Win64 OpenSSL v0.9.8o (8Mb)
+http://www.slproweb.com/download/Win64OpenSSL-0_9_8o.exe
+(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
+
+Visual C++ 2008 Redistributables (x64) (1.7Mb)
+http://www.microsoft.com/downloads/details.aspx?familyid=bd2a6171-e2d6-4230-b809-9a8d7548c1b6
+
+
+
+Installing openssl on Windows 32-bit (Windows 2000 and higher)
+
+Win32 OpenSSL v0.9.8o (8Mb)
+http://www.slproweb.com/download/Win32OpenSSL-0_9_8o.exe
+(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
+
+Visual C++ 2008 Redistributables (1.7Mb)
+http://www.microsoft.com/downloads/details.aspx?familyid=9B2DA534-3E03-4391-8A4D-074B9F2BC1BF
+
+
+
+Other versions of OpenSSL (and versions for Windows older than Windows 2000) can be found on the following website.
+
+Shining Light Productions
+http://www.slproweb.com/products/Win32OpenSSL.html
index c1d3563694358a991fbc32ba5bc8020602c5ce36..5e0fc75d14ea23f15b872e0d44b5cb9597ad3c56 100644 (file)
Binary files a/Calibre_Plugins/eReaderPDB2PML_plugin.zip and b/Calibre_Plugins/eReaderPDB2PML_plugin.zip differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML-README.txt b/Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML-README.txt
deleted file mode 100644 (file)
index 75dfda5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip
-
-All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
-
-This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any  dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
-
-Installation:
-Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file  (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
-
-Configuration:
-Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234
-
-If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
-
-Troubleshooting:
-If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
-as well get used to it. ;)
-
-Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
-
-** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.
index 64b3019aa1e247526081af11c8538bcb9a23d628..fe4c9b3f353431bd0675d459a94330d8be8b1dac 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# eReaderPDB2PML_v01_plugin.py
+# eReaderPDB2PML_plugin.py
 # Released under the terms of the GNU General Public Licence, version 3 or
 # later.  <http://www.gnu.org/licenses/>
 #
@@ -30,7 +30,8 @@
 # NOTE: Do NOT put quotes around your name like you do with the original script!!
 #
 # Revision history:
-#   0.1 - Initial release
+#   0.0.1 - Initial release
+#   0.0.2 - updated to distinguish it from earlier non-openssl version
 
 import sys, os
 
@@ -42,7 +43,7 @@ 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, 1)   # The version number of this plugin
+    version             = (0, 0, 2)   # 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
 
@@ -52,7 +53,6 @@ class eRdrDeDRM(FileTypePlugin):
         pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
         ppath = os.path.join(self.sys_insertion_path, pdir)
         sys.path.insert(0, ppath)
-        #sys.path.append(ppath)
         
         global bookname, erdr2pml
         import erdr2pml
index 089d0009befc1e2dabe67b938a6896e7d7e85750..daa6b2171f79719e57ed7ff0993ee99929e4405e 100644 (file)
 #  0.13 - change to unbuffered stdout for use with gui front ends
 #  0.14 - contributed enhancement to support --make-pmlz switch
 #  0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
+#  0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
 
-__version__='0.15'
+Des = None
+
+import openssl_des
+Des = openssl_des.load_libcrypto()
+
+# 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
+    Des = python_des.Des
+    # Import Psyco if available
+    try:
+        # Dumb speed hack 1
+        # http://psyco.sourceforge.net
+        import psyco
+        psyco.full()
+        pass
+    except ImportError:
+        pass
 
-# Import Psyco if available
-try:
-    # Dumb speed hack 1
-    # http://psyco.sourceforge.net
-    import psyco
-    psyco.full()
-    pass
-except ImportError:
-    pass
-try:
-    # Dumb speed hack 2
-    # All map() calls converted to list comprehension (some use zip)
-    # override zip with izip - saves memory and in rough testing
-    # appears to be faster zip() is only used in the converted map() calls
-    from itertools import izip as zip
-except ImportError:
-    pass
+
+__version__='0.16'
 
 class Unbuffered:
     def __init__(self, stream):
@@ -101,223 +105,6 @@ import logging
 logging.basicConfig()
 #logging.basicConfig(level=logging.DEBUG)
 
-ECB =  0
-CBC =  1
-class Des(object):
-    __pc1 = [56, 48, 40, 32, 24, 16,  8,  0, 57, 49, 41, 33, 25, 17,
-          9,  1, 58, 50, 42, 34, 26, 18, 10,  2, 59, 51, 43, 35,
-         62, 54, 46, 38, 30, 22, 14,  6, 61, 53, 45, 37, 29, 21,
-         13,  5, 60, 52, 44, 36, 28, 20, 12,  4, 27, 19, 11,  3]
-    __left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
-    __pc2 = [13, 16, 10, 23,  0,  4,2, 27, 14,  5, 20,  9,
-        22, 18, 11,  3, 25,  7,        15,  6, 26, 19, 12,  1,
-        40, 51, 30, 36, 46, 54,        29, 39, 50, 44, 32, 47,
-        43, 48, 38, 55, 33, 52,        45, 41, 49, 35, 28, 31]
-    __ip = [57, 49, 41, 33, 25, 17, 9,  1,     59, 51, 43, 35, 27, 19, 11, 3,
-        61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
-        56, 48, 40, 32, 24, 16, 8,  0, 58, 50, 42, 34, 26, 18, 10, 2,
-        60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
-    __expansion_table = [31,  0,  1,  2,  3,  4, 3,  4,  5,  6,  7,  8,
-         7,  8,  9, 10, 11, 12,11, 12, 13, 14, 15, 16,
-        15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
-        23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31,  0]
-    __sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
-         0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
-         4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
-         15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
-        [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
-         3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
-         0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
-         13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
-        [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
-         13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
-         13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
-         1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
-        [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
-         13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
-         10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
-         3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
-        [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
-         14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
-         4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
-         11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
-        [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
-         10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
-         9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
-         4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
-        [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
-         13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
-         1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
-         6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
-        [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
-         1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
-         7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
-         2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
-    __p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
-        4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
-    __fp = [39,  7, 47, 15, 55, 23, 63, 31,38,  6, 46, 14, 54, 22, 62, 30,
-        37,  5, 45, 13, 53, 21, 61, 29,36,  4, 44, 12, 52, 20, 60, 28,
-        35,  3, 43, 11, 51, 19, 59, 27,34,  2, 42, 10, 50, 18, 58, 26,
-        33,  1, 41,  9, 49, 17, 57, 25,32,  0, 40,  8, 48, 16, 56, 24]
-    # Type of crypting being done
-    ENCRYPT =  0x00
-    DECRYPT =  0x01
-    def __init__(self, key, mode=ECB, IV=None):
-        if len(key) != 8:
-            raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
-        self.block_size = 8
-        self.key_size = 8
-        self.__padding = ''
-        self.setMode(mode)
-        if IV:
-            self.setIV(IV)
-        self.L = []
-        self.R = []
-        self.Kn = [ [0] * 48 ] * 16    # 16 48-bit keys (K1 - K16)
-        self.final = []
-        self.setKey(key)
-    def getKey(self):
-        return self.__key
-    def setKey(self, key):
-        self.__key = key
-        self.__create_sub_keys()
-    def getMode(self):
-        return self.__mode
-    def setMode(self, mode):
-        self.__mode = mode
-    def getIV(self):
-        return self.__iv
-    def setIV(self, IV):
-        if not IV or len(IV) != self.block_size:
-            raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
-        self.__iv = IV
-    def getPadding(self):
-        return self.__padding
-    def __String_to_BitList(self, data):
-        l = len(data) * 8
-        result = [0] * l
-        pos = 0
-        for c in data:
-            i = 7
-            ch = ord(c)
-            while i >= 0:
-                if ch & (1 << i) != 0:
-                    result[pos] = 1
-                else:
-                    result[pos] = 0
-                pos += 1
-                i -= 1
-        return result
-    def __BitList_to_String(self, data):
-        result = ''
-        pos = 0
-        c = 0
-        while pos < len(data):
-            c += data[pos] << (7 - (pos % 8))
-            if (pos % 8) == 7:
-                result += chr(c)
-                c = 0
-            pos += 1
-        return result
-    def __permutate(self, table, block):
-        return [block[x] for x in table]
-    def __create_sub_keys(self):
-        key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
-        i = 0
-        self.L = key[:28]
-        self.R = key[28:]
-        while i < 16:
-            j = 0
-            while j < Des.__left_rotations[i]:
-                self.L.append(self.L[0])
-                del self.L[0]
-                self.R.append(self.R[0])
-                del self.R[0]
-                j += 1
-            self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
-            i += 1
-    def __des_crypt(self, block, crypt_type):
-        block = self.__permutate(Des.__ip, block)
-        self.L = block[:32]
-        self.R = block[32:]
-        if crypt_type == Des.ENCRYPT:
-            iteration = 0
-            iteration_adjustment = 1
-        else:
-            iteration = 15
-            iteration_adjustment = -1
-        i = 0
-        while i < 16:
-            tempR = self.R[:]
-            self.R = self.__permutate(Des.__expansion_table, self.R)
-            self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
-            B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
-            j = 0
-            Bn = [0] * 32
-            pos = 0
-            while j < 8:
-                m = (B[j][0] << 1) + B[j][5]
-                n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
-                v = Des.__sbox[j][(m << 4) + n]
-                Bn[pos] = (v & 8) >> 3
-                Bn[pos + 1] = (v & 4) >> 2
-                Bn[pos + 2] = (v & 2) >> 1
-                Bn[pos + 3] = v & 1
-                pos += 4
-                j += 1
-            self.R = self.__permutate(Des.__p, Bn)
-            self.R = [x ^ y for x, y in zip(self.R, self.L)]
-            self.L = tempR
-            i += 1
-            iteration += iteration_adjustment
-        self.final = self.__permutate(Des.__fp, self.R + self.L)
-        return self.final
-    def crypt(self, data, crypt_type):
-        if not data:
-            return ''
-        if len(data) % self.block_size != 0:
-            if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
-                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
-            if not self.getPadding():
-                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
-            else:
-                data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
-        if self.getMode() == CBC:
-            if self.getIV():
-                iv = self.__String_to_BitList(self.getIV())
-            else:
-                raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
-        i = 0
-        dict = {}
-        result = []
-        while i < len(data):
-            block = self.__String_to_BitList(data[i:i+8])
-            if self.getMode() == CBC:
-                if crypt_type == Des.ENCRYPT:
-                    block = [x ^ y for x, y in zip(block, iv)]
-                processed_block = self.__des_crypt(block, crypt_type)
-                if crypt_type == Des.DECRYPT:
-                    processed_block = [x ^ y for x, y in zip(processed_block, iv)]
-                    iv = block
-                else:
-                    iv = processed_block
-            else:
-                processed_block = self.__des_crypt(block, crypt_type)
-            result.append(self.__BitList_to_String(processed_block))
-            i += 8
-        if crypt_type == Des.DECRYPT and self.getPadding():
-            s = result[-1]
-            while s[-1] == self.getPadding():
-                s = s[:-1]
-            result[-1] = s
-        return ''.join(result)
-    def encrypt(self, data, pad=''):
-        self.__padding = pad
-        return self.crypt(data, Des.ENCRYPT)
-    def decrypt(self, data, pad=''):
-        self.__padding = pad
-        return self.crypt(data, Des.DECRYPT)
-
 class Sectionizer(object):
     def __init__(self, filename, ident):
         self.contents = file(filename, 'rb').read()
@@ -685,8 +472,5 @@ def main(argv=None):
     return 0
 
 if __name__ == "__main__":
-    #import cProfile
-    #command = """sys.exit(main())"""
-    #cProfile.runctx( command, globals(), locals(), filename="cprofile.profile" )
-    
     sys.exit(main())
+
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/openssl_des.py b/Calibre_Plugins/eReaderPDB2PML_plugin/openssl_des.py
new file mode 100644 (file)
index 0000000..8a044fa
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+# implement just enough of des from openssl to make erdr2pml.py happy
+
+def load_libcrypto():
+    from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
+        Structure, c_ulong, create_string_buffer, cast
+    from ctypes.util import find_library
+    import sys
+
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
+
+    if libcrypto is None:
+        return None
+
+    libcrypto = CDLL(libcrypto)
+    
+    # typedef struct DES_ks
+    #     {
+    #     union
+    #         {
+    #         DES_cblock cblock;
+    #         /* make sure things are correct size on machines with
+    #          * 8 byte longs */
+    #         DES_LONG deslong[2];
+    #         } ks[16];
+    #     } DES_key_schedule;
+
+    # just create a big enough place to hold everything 
+    # it will have alignment of structure so we should be okay (16 byte aligned?)
+    class DES_KEY_SCHEDULE(Structure):
+        _fields_ = [('DES_cblock1', c_char * 16),
+                    ('DES_cblock2', c_char * 16),
+                    ('DES_cblock3', c_char * 16),
+                    ('DES_cblock4', c_char * 16),
+                    ('DES_cblock5', c_char * 16),
+                    ('DES_cblock6', c_char * 16),
+                    ('DES_cblock7', c_char * 16),
+                    ('DES_cblock8', c_char * 16),
+                    ('DES_cblock9', c_char * 16),
+                    ('DES_cblock10', c_char * 16),
+                    ('DES_cblock11', c_char * 16),
+                    ('DES_cblock12', c_char * 16),
+                    ('DES_cblock13', c_char * 16),
+                    ('DES_cblock14', c_char * 16),
+                    ('DES_cblock15', c_char * 16),
+                    ('DES_cblock16', c_char * 16)]
+
+    DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
+
+    def F(restype, name, argtypes):
+        func = getattr(libcrypto, name)
+        func.restype = restype
+        func.argtypes = argtypes
+        return func
+
+    DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
+    DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
+
+    
+    class DES(object):
+        def __init__(self, key):
+            if len(key) != 8 :
+                raise Error('DES improper key used')
+                return
+            self.key = key
+            self.keyschedule = DES_KEY_SCHEDULE()
+            DES_set_key(self.key, self.keyschedule)
+        def desdecrypt(self, data):
+            ob = create_string_buffer(len(data))
+            DES_ecb_encrypt(data, ob, self.keyschedule, 0)
+            return ob.raw
+        def decrypt(self, data):
+            if not data:
+                return ''
+            i = 0
+            result = []
+            while i < len(data):
+                block = data[i:i+8]
+                processed_block = self.desdecrypt(block)
+                result.append(processed_block)
+                i += 8
+            return ''.join(result)
+
+    return DES
+
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/all-wcprops b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/all-wcprops
deleted file mode 100644 (file)
index 881cf30..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-K 25
-svn:wc:ra_dav:version-url
-V 41
-/svn/!svn/ver/70200/psyco/dist/py-support
-END
-core.py
-K 25
-svn:wc:ra_dav:version-url
-V 49
-/svn/!svn/ver/70200/psyco/dist/py-support/core.py
-END
-support.py
-K 25
-svn:wc:ra_dav:version-url
-V 52
-/svn/!svn/ver/49315/psyco/dist/py-support/support.py
-END
-classes.py
-K 25
-svn:wc:ra_dav:version-url
-V 52
-/svn/!svn/ver/35003/psyco/dist/py-support/classes.py
-END
-__init__.py
-K 25
-svn:wc:ra_dav:version-url
-V 53
-/svn/!svn/ver/35003/psyco/dist/py-support/__init__.py
-END
-logger.py
-K 25
-svn:wc:ra_dav:version-url
-V 51
-/svn/!svn/ver/23284/psyco/dist/py-support/logger.py
-END
-kdictproxy.py
-K 25
-svn:wc:ra_dav:version-url
-V 55
-/svn/!svn/ver/35003/psyco/dist/py-support/kdictproxy.py
-END
-profiler.py
-K 25
-svn:wc:ra_dav:version-url
-V 53
-/svn/!svn/ver/70200/psyco/dist/py-support/profiler.py
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/dir-prop-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/dir-prop-base
deleted file mode 100644 (file)
index a87157b..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-K 10
-svn:ignore
-V 14
-*~
-*.pyc
-*.pyo
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/entries b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/entries
deleted file mode 100644 (file)
index 936b265..0000000
+++ /dev/null
@@ -1,266 +0,0 @@
-10
-
-dir
-78269
-http://codespeak.net/svn/psyco/dist/py-support
-http://codespeak.net/svn
-
-
-
-2009-12-18T16:35:35.119276Z
-70200
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
-\f
-core.py
-file
-
-
-
-
-2010-10-25T15:10:42.000000Z
-3b362177a839893c9e867880b3a7cef3
-2009-12-18T16:35:35.119276Z
-70200
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-8144
-\f
-support.py
-file
-
-
-
-
-2010-10-25T15:10:42.000000Z
-b0551e975d774f2f7f58a29ed4b6b90e
-2007-12-03T12:27:25.632574Z
-49315
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-6043
-\f
-classes.py
-file
-
-
-
-
-2010-10-25T15:10:42.000000Z
-5932ed955198d16ec17285dfb195d341
-2006-11-26T13:03:26.949973Z
-35003
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1440
-\f
-__init__.py
-file
-
-
-
-
-2010-10-25T15:10:42.000000Z
-219582b5182dfa38a9119d059a71965f
-2006-11-26T13:03:26.949973Z
-35003
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1895
-\f
-logger.py
-file
-
-
-
-
-2010-10-25T15:10:42.000000Z
-aa21f905df036af43082e1ea2a2561ee
-2006-02-13T15:02:51.744168Z
-23284
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2678
-\f
-kdictproxy.py
-file
-
-
-
-
-2010-10-25T15:10:42.000000Z
-1c8611748dcee5b29848bf25be3ec473
-2006-11-26T13:03:26.949973Z
-35003
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-4369
-\f
-profiler.py
-file
-
-
-
-
-2010-10-25T15:10:42.000000Z
-858162366cbc39cd9e249e35e6f510c4
-2009-12-18T16:35:35.119276Z
-70200
-arigo
-has-props
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-11238
-\f
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/__init__.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/__init__.py.svn-base
deleted file mode 100644 (file)
index 7b57b30..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-K 13
-svn:eol-style
-V 6
-native
-K 12
-svn:keywords
-V 23
-Author Date Id Revision
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/classes.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/classes.py.svn-base
deleted file mode 100644 (file)
index 7b57b30..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-K 13
-svn:eol-style
-V 6
-native
-K 12
-svn:keywords
-V 23
-Author Date Id Revision
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/core.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/core.py.svn-base
deleted file mode 100644 (file)
index 7b57b30..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-K 13
-svn:eol-style
-V 6
-native
-K 12
-svn:keywords
-V 23
-Author Date Id Revision
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/kdictproxy.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/kdictproxy.py.svn-base
deleted file mode 100644 (file)
index 7b57b30..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-K 13
-svn:eol-style
-V 6
-native
-K 12
-svn:keywords
-V 23
-Author Date Id Revision
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/logger.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/logger.py.svn-base
deleted file mode 100644 (file)
index 7b57b30..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-K 13
-svn:eol-style
-V 6
-native
-K 12
-svn:keywords
-V 23
-Author Date Id Revision
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/profiler.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/profiler.py.svn-base
deleted file mode 100644 (file)
index 7b57b30..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-K 13
-svn:eol-style
-V 6
-native
-K 12
-svn:keywords
-V 23
-Author Date Id Revision
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/support.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/prop-base/support.py.svn-base
deleted file mode 100644 (file)
index 7b57b30..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-K 13
-svn:eol-style
-V 6
-native
-K 12
-svn:keywords
-V 23
-Author Date Id Revision
-END
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/__init__.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/__init__.py.svn-base
deleted file mode 100644 (file)
index d25e197..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-###########################################################################
-# 
-#  Psyco top-level file of the Psyco package.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco -- the Python Specializing Compiler.
-
-Typical usage: add the following lines to your application's main module,
-preferably after the other imports:
-
-try:
-    import psyco
-    psyco.full()
-except ImportError:
-    print 'Psyco not installed, the program will just run slower'
-"""
-###########################################################################
-
-
-#
-# This module is present to make 'psyco' a package and to
-# publish the main functions and variables.
-#
-# More documentation can be found in core.py.
-#
-
-
-# Try to import the dynamic-loading _psyco and report errors
-try:
-    import _psyco
-except ImportError, e:
-    extramsg = ''
-    import sys, imp
-    try:
-        file, filename, (suffix, mode, type) = imp.find_module('_psyco', __path__)
-    except ImportError:
-        ext = [suffix for suffix, mode, type in imp.get_suffixes()
-               if type == imp.C_EXTENSION]
-        if ext:
-            extramsg = (" (cannot locate the compiled extension '_psyco%s' "
-                        "in the package path '%s')" % (ext[0], '; '.join(__path__)))
-    else:
-        extramsg = (" (check that the compiled extension '%s' is for "
-                    "the correct Python version; this is Python %s)" %
-                    (filename, sys.version.split()[0]))
-    raise ImportError, str(e) + extramsg
-
-# Publish important data by importing them in the package
-from support import __version__, error, warning, _getrealframe, _getemulframe
-from support import version_info, __version__ as hexversion
-from core import full, profile, background, runonly, stop, cannotcompile
-from core import log, bind, unbind, proxy, unproxy, dumpcodebuf
-from _psyco import setfilter
-from _psyco import compact, compacttype
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/classes.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/classes.py.svn-base
deleted file mode 100644 (file)
index 0563f84..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-###########################################################################
-# 
-#  Psyco class support module.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco class support module.
-
-'psyco.classes.psyobj' is an alternate Psyco-optimized root for classes.
-Any class inheriting from it or using the metaclass '__metaclass__' might
-get optimized specifically for Psyco. It is equivalent to call
-psyco.bind() on the class object after its creation.
-
-Importing everything from psyco.classes in a module will import the
-'__metaclass__' name, so all classes defined after a
-
-       from psyco.classes import *
-
-will automatically use the Psyco-optimized metaclass.
-"""
-###########################################################################
-
-__all__ = ['psyobj', 'psymetaclass', '__metaclass__']
-
-
-from _psyco import compacttype
-import core
-from types import FunctionType
-
-class psymetaclass(compacttype):
-    "Psyco-optimized meta-class. Turns all methods into Psyco proxies."
-
-    def __new__(cls, name, bases, dict):
-        bindlist = dict.get('__psyco__bind__')
-        if bindlist is None:
-            bindlist = [key for key, value in dict.items()
-                        if isinstance(value, FunctionType)]
-        for attr in bindlist:
-            dict[attr] = core.proxy(dict[attr])
-        return super(psymetaclass, cls).__new__(cls, name, bases, dict)
-
-psyobj = psymetaclass("psyobj", (), {})
-__metaclass__ = psymetaclass
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/core.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/core.py.svn-base
deleted file mode 100644 (file)
index 995c9ae..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-###########################################################################
-# 
-#  Psyco main functions.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco main functions.
-
-Here are the routines that you can use from your applications.
-These are mostly interfaces to the C core, but they depend on
-the Python version.
-
-You can use these functions from the 'psyco' module instead of
-'psyco.core', e.g.
-
-    import psyco
-    psyco.log('/tmp/psyco.log')
-    psyco.profile()
-"""
-###########################################################################
-
-import _psyco
-import types
-from support import *
-
-newfunction = types.FunctionType
-newinstancemethod = types.MethodType
-
-
-# Default charge profiler values
-default_watermark     = 0.09     # between 0.0 (0%) and 1.0 (100%)
-default_halflife      = 0.5      # seconds
-default_pollfreq_profile    = 20       # Hz
-default_pollfreq_background = 100      # Hz -- a maximum for sleep's resolution
-default_parentframe   = 0.25     # should not be more than 0.5 (50%)
-
-
-def full(memory=None, time=None, memorymax=None, timemax=None):
-    """Compile as much as possible.
-
-Typical use is for small scripts performing intensive computations
-or string handling."""
-    import profiler
-    p = profiler.FullCompiler()
-    p.run(memory, time, memorymax, timemax)
-
-
-def profile(watermark   = default_watermark,
-            halflife    = default_halflife,
-            pollfreq    = default_pollfreq_profile,
-            parentframe = default_parentframe,
-            memory=None, time=None, memorymax=None, timemax=None):
-    """Turn on profiling.
-
-The 'watermark' parameter controls how easily running functions will
-be compiled. The smaller the value, the more functions are compiled."""
-    import profiler
-    p = profiler.ActivePassiveProfiler(watermark, halflife,
-                                       pollfreq, parentframe)
-    p.run(memory, time, memorymax, timemax)
-
-
-def background(watermark   = default_watermark,
-               halflife    = default_halflife,
-               pollfreq    = default_pollfreq_background,
-               parentframe = default_parentframe,
-               memory=None, time=None, memorymax=None, timemax=None):
-    """Turn on passive profiling.
-
-This is a very lightweight mode in which only intensively computing
-functions can be detected. The smaller the 'watermark', the more functions
-are compiled."""
-    import profiler
-    p = profiler.PassiveProfiler(watermark, halflife, pollfreq, parentframe)
-    p.run(memory, time, memorymax, timemax)
-
-
-def runonly(memory=None, time=None, memorymax=None, timemax=None):
-    """Nonprofiler.
-
-XXX check if this is useful and document."""
-    import profiler
-    p = profiler.RunOnly()
-    p.run(memory, time, memorymax, timemax)
-
-
-def stop():
-    """Turn off all automatic compilation.  bind() calls remain in effect."""
-    import profiler
-    profiler.go([])
-
-
-def log(logfile='', mode='w', top=10):
-    """Enable logging to the given file.
-
-If the file name is unspecified, a default name is built by appending
-a 'log-psyco' extension to the main script name.
-
-Mode is 'a' to append to a possibly existing file or 'w' to overwrite
-an existing file. Note that the log file may grow quickly in 'a' mode."""
-    import profiler, logger
-    if not logfile:
-        import os
-        logfile, dummy = os.path.splitext(sys.argv[0])
-        if os.path.basename(logfile):
-            logfile += '.'
-        logfile += 'log-psyco'
-    if hasattr(_psyco, 'VERBOSE_LEVEL'):
-        print >> sys.stderr, 'psyco: logging to', logfile
-    # logger.current should be a real file object; subtle problems
-    # will show up if its write() and flush() methods are written
-    # in Python, as Psyco will invoke them while compiling.
-    logger.current = open(logfile, mode)
-    logger.print_charges = top
-    profiler.logger = logger
-    logger.writedate('Logging started')
-    cannotcompile(logger.psycowrite)
-    _psyco.statwrite(logger=logger.psycowrite)
-
-
-def bind(x, rec=None):
-    """Enable compilation of the given function, method, or class object.
-
-If C is a class (or anything with a '__dict__' attribute), bind(C) will
-rebind all functions and methods found in C.__dict__ (which means, for
-classes, all methods defined in the class but not in its parents).
-
-The optional second argument specifies the number of recursive
-compilation levels: all functions called by func are compiled
-up to the given depth of indirection."""
-    if isinstance(x, types.MethodType):
-        x = x.im_func
-    if isinstance(x, types.FunctionType):
-        if rec is None:
-            x.func_code = _psyco.proxycode(x)
-        else:
-            x.func_code = _psyco.proxycode(x, rec)
-        return
-    if hasattr(x, '__dict__'):
-        funcs = [o for o in x.__dict__.values()
-                 if isinstance(o, types.MethodType)
-                 or isinstance(o, types.FunctionType)]
-        if not funcs:
-            raise error, ("nothing bindable found in %s object" %
-                          type(x).__name__)
-        for o in funcs:
-            bind(o, rec)
-        return
-    raise TypeError, "cannot bind %s objects" % type(x).__name__
-
-
-def unbind(x):
-    """Reverse of bind()."""
-    if isinstance(x, types.MethodType):
-        x = x.im_func
-    if isinstance(x, types.FunctionType):
-        try:
-            f = _psyco.unproxycode(x.func_code)
-        except error:
-            pass
-        else:
-            x.func_code = f.func_code
-        return
-    if hasattr(x, '__dict__'):
-        for o in x.__dict__.values():
-            if (isinstance(o, types.MethodType)
-             or isinstance(o, types.FunctionType)):
-                unbind(o)
-        return
-    raise TypeError, "cannot unbind %s objects" % type(x).__name__
-
-
-def proxy(x, rec=None):
-    """Return a Psyco-enabled copy of the function.
-
-The original function is still available for non-compiled calls.
-The optional second argument specifies the number of recursive
-compilation levels: all functions called by func are compiled
-up to the given depth of indirection."""
-    if isinstance(x, types.FunctionType):
-        if rec is None:
-            code = _psyco.proxycode(x)
-        else:
-            code = _psyco.proxycode(x, rec)
-        return newfunction(code, x.func_globals, x.func_name)
-    if isinstance(x, types.MethodType):
-        p = proxy(x.im_func, rec)
-        return newinstancemethod(p, x.im_self, x.im_class)
-    raise TypeError, "cannot proxy %s objects" % type(x).__name__
-
-
-def unproxy(proxy):
-    """Return a new copy of the original function of method behind a proxy.
-The result behaves like the original function in that calling it
-does not trigger compilation nor execution of any compiled code."""
-    if isinstance(proxy, types.FunctionType):
-        return _psyco.unproxycode(proxy.func_code)
-    if isinstance(proxy, types.MethodType):
-        f = unproxy(proxy.im_func)
-        return newinstancemethod(f, proxy.im_self, proxy.im_class)
-    raise TypeError, "%s objects cannot be proxies" % type(proxy).__name__
-
-
-def cannotcompile(x):
-    """Instruct Psyco never to compile the given function, method
-or code object."""
-    if isinstance(x, types.MethodType):
-        x = x.im_func
-    if isinstance(x, types.FunctionType):
-        x = x.func_code
-    if isinstance(x, types.CodeType):
-        _psyco.cannotcompile(x)
-    else:
-        raise TypeError, "unexpected %s object" % type(x).__name__
-
-
-def dumpcodebuf():
-    """Write in file psyco.dump a copy of the emitted machine code,
-provided Psyco was compiled with a non-zero CODE_DUMP.
-See py-utils/httpxam.py to examine psyco.dump."""
-    if hasattr(_psyco, 'dumpcodebuf'):
-        _psyco.dumpcodebuf()
-
-
-###########################################################################
-# Psyco variables
-#   error         * the error raised by Psyco
-#   warning       * the warning raised by Psyco
-#   __in_psyco__  * a new built-in variable which is always zero, but which
-#                     Psyco special-cases by returning 1 instead. So
-#                     __in_psyco__ can be used in a function to know if
-#                     that function is being executed by Psyco or not.
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/kdictproxy.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/kdictproxy.py.svn-base
deleted file mode 100644 (file)
index c764e5e..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-###########################################################################
-#
-#  Support code for the 'psyco.compact' type.
-
-from __future__ import generators
-
-try:
-    from UserDict import DictMixin
-except ImportError:
-
-    # backported from Python 2.3 to Python 2.2
-    class DictMixin:
-        # Mixin defining all dictionary methods for classes that already have
-        # a minimum dictionary interface including getitem, setitem, delitem,
-        # and keys. Without knowledge of the subclass constructor, the mixin
-        # does not define __init__() or copy().  In addition to the four base
-        # methods, progressively more efficiency comes with defining
-        # __contains__(), __iter__(), and iteritems().
-
-        # second level definitions support higher levels
-        def __iter__(self):
-            for k in self.keys():
-                yield k
-        def has_key(self, key):
-            try:
-                value = self[key]
-            except KeyError:
-                return False
-            return True
-        def __contains__(self, key):
-            return self.has_key(key)
-
-        # third level takes advantage of second level definitions
-        def iteritems(self):
-            for k in self:
-                yield (k, self[k])
-        def iterkeys(self):
-            return self.__iter__()
-
-        # fourth level uses definitions from lower levels
-        def itervalues(self):
-            for _, v in self.iteritems():
-                yield v
-        def values(self):
-            return [v for _, v in self.iteritems()]
-        def items(self):
-            return list(self.iteritems())
-        def clear(self):
-            for key in self.keys():
-                del self[key]
-        def setdefault(self, key, default):
-            try:
-                return self[key]
-            except KeyError:
-                self[key] = default
-            return default
-        def pop(self, key, *args):
-            if len(args) > 1:
-                raise TypeError, "pop expected at most 2 arguments, got "\
-                                  + repr(1 + len(args))
-            try:
-                value = self[key]
-            except KeyError:
-                if args:
-                    return args[0]
-                raise
-            del self[key]
-            return value
-        def popitem(self):
-            try:
-                k, v = self.iteritems().next()
-            except StopIteration:
-                raise KeyError, 'container is empty'
-            del self[k]
-            return (k, v)
-        def update(self, other):
-            # Make progressively weaker assumptions about "other"
-            if hasattr(other, 'iteritems'):  # iteritems saves memory and lookups
-                for k, v in other.iteritems():
-                    self[k] = v
-            elif hasattr(other, '__iter__'): # iter saves memory
-                for k in other:
-                    self[k] = other[k]
-            else:
-                for k in other.keys():
-                    self[k] = other[k]
-        def get(self, key, default=None):
-            try:
-                return self[key]
-            except KeyError:
-                return default
-        def __repr__(self):
-            return repr(dict(self.iteritems()))
-        def __cmp__(self, other):
-            if other is None:
-                return 1
-            if isinstance(other, DictMixin):
-                other = dict(other.iteritems())
-            return cmp(dict(self.iteritems()), other)
-        def __len__(self):
-            return len(self.keys())
-
-###########################################################################
-
-from _psyco import compact
-
-
-class compactdictproxy(DictMixin):
-
-    def __init__(self, ko):
-        self._ko = ko    # compact object of which 'self' is the dict
-
-    def __getitem__(self, key):
-        return compact.__getslot__(self._ko, key)
-
-    def __setitem__(self, key, value):
-        compact.__setslot__(self._ko, key, value)
-
-    def __delitem__(self, key):
-        compact.__delslot__(self._ko, key)
-
-    def keys(self):
-        return compact.__members__.__get__(self._ko)
-
-    def clear(self):
-        keys = self.keys()
-        keys.reverse()
-        for key in keys:
-            del self[key]
-
-    def __repr__(self):
-        keys = ', '.join(self.keys())
-        return '<compactdictproxy object {%s}>' % (keys,)
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/logger.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/logger.py.svn-base
deleted file mode 100644 (file)
index a3c5219..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-###########################################################################
-# 
-#  Psyco logger.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco logger.
-
-See log() in core.py.
-"""
-###########################################################################
-
-
-import _psyco
-from time import time, localtime, strftime
-
-
-current = None
-print_charges = 10
-dump_delay = 0.2
-dump_last = 0.0
-
-def write(s, level):
-    t = time()
-    f = t-int(t)
-    try:
-        current.write("%s.%02d  %-*s%s\n" % (
-            strftime("%X", localtime(int(t))),
-            int(f*100.0), 63-level, s,
-            "%"*level))
-        current.flush()
-    except (OSError, IOError):
-        pass
-
-def psycowrite(s):
-    t = time()
-    f = t-int(t)
-    try:
-        current.write("%s.%02d  %-*s%s\n" % (
-            strftime("%X", localtime(int(t))),
-            int(f*100.0), 60, s.strip(),
-            "% %"))
-        current.flush()
-    except (OSError, IOError):
-        pass
-
-##def writelines(lines, level=0):
-##    if lines:
-##        t = time()
-##        f = t-int(t)
-##        timedesc = strftime("%x %X", localtime(int(t)))
-##        print >> current, "%s.%03d  %-*s %s" % (
-##            timedesc, int(f*1000),
-##            50-level, lines[0],
-##            "+"*level)
-##        timedesc = " " * (len(timedesc)+5)
-##        for line in lines[1:]:
-##            print >> current, timedesc, line
-
-def writememory():
-    write("memory usage: %d+ kb" % _psyco.memory(), 1)
-
-def dumpcharges():
-    global dump_last
-    if print_charges:
-        t = time()
-        if not (dump_last <= t < dump_last+dump_delay):
-            if t <= dump_last+1.5*dump_delay:
-                dump_last += dump_delay
-            else:
-                dump_last = t
-            #write("%s: charges:" % who, 0)
-            lst = _psyco.stattop(print_charges)
-            if lst:
-                f = t-int(t)
-                lines = ["%s.%02d   ______\n" % (
-                    strftime("%X", localtime(int(t))),
-                    int(f*100.0))]
-                i = 1
-                for co, charge in lst:
-                    detail = co.co_filename
-                    if len(detail) > 19:
-                        detail = '...' + detail[-17:]
-                    lines.append("        #%-3d |%4.1f %%|  %-26s%20s:%d\n" %
-                                 (i, charge*100.0, co.co_name, detail,
-                                  co.co_firstlineno))
-                    i += 1
-                current.writelines(lines)
-                current.flush()
-
-def writefinalstats():
-    dumpcharges()
-    writememory()
-    writedate("program exit")
-
-def writedate(msg):
-    write('%s, %s' % (msg, strftime("%x")), 20)
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/profiler.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/profiler.py.svn-base
deleted file mode 100644 (file)
index f1d06f1..0000000
+++ /dev/null
@@ -1,379 +0,0 @@
-###########################################################################
-# 
-#  Psyco profiler (Python part).
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco profiler (Python part).
-
-The implementation of the non-time-critical parts of the profiler.
-See profile() and full() in core.py for the easy interface.
-"""
-###########################################################################
-
-import _psyco
-from support import *
-import math, time, types, atexit
-now = time.time
-try:
-    import thread
-except ImportError:
-    import dummy_thread as thread
-
-
-# current profiler instance
-current = None
-
-# enabled profilers, in order of priority
-profilers = []
-
-# logger module (when enabled by core.log())
-logger = None
-
-# a lock for a thread-safe go()
-go_lock = thread.allocate_lock()
-
-def go(stop=0):
-    # run the highest-priority profiler in 'profilers'
-    global current
-    go_lock.acquire()
-    try:
-        prev = current
-        if stop:
-            del profilers[:]
-        if prev:
-            if profilers and profilers[0] is prev:
-                return    # best profiler already running
-            prev.stop()
-            current = None
-        for p in profilers[:]:
-            if p.start():
-                current = p
-                if logger: # and p is not prev:
-                    logger.write("%s: starting" % p.__class__.__name__, 5)
-                return
-    finally:
-        go_lock.release()
-    # no profiler is running now
-    if stop:
-        if logger:
-            logger.writefinalstats()
-    else:
-        tag2bind()
-
-atexit.register(go, 1)
-
-
-def buildfncache(globals, cache):
-    if hasattr(types.IntType, '__dict__'):
-        clstypes = (types.ClassType, types.TypeType)
-    else:
-        clstypes = types.ClassType
-    for x in globals.values():
-        if isinstance(x, types.MethodType):
-            x = x.im_func
-        if isinstance(x, types.FunctionType):
-            cache[x.func_code] = x, ''
-        elif isinstance(x, clstypes):
-            for y in x.__dict__.values():
-                if isinstance(y, types.MethodType):
-                    y = y.im_func
-                if isinstance(y, types.FunctionType):
-                    cache[y.func_code] = y, x.__name__
-
-# code-to-function mapping (cache)
-function_cache = {}
-
-def trytobind(co, globals, log=1):
-    try:
-        f, clsname = function_cache[co]
-    except KeyError:
-        buildfncache(globals, function_cache)
-        try:
-            f, clsname = function_cache[co]
-        except KeyError:
-            if logger:
-                logger.write('warning: cannot find function %s in %s' %
-                             (co.co_name, globals.get('__name__', '?')), 3)
-            return  # give up
-    if logger and log:
-        modulename = globals.get('__name__', '?')
-        if clsname:
-            modulename += '.' + clsname
-        logger.write('bind function: %s.%s' % (modulename, co.co_name), 1)
-    f.func_code = _psyco.proxycode(f)
-
-
-# the list of code objects that have been tagged
-tagged_codes = []
-
-def tag(co, globals):
-    if logger:
-        try:
-            f, clsname = function_cache[co]
-        except KeyError:
-            buildfncache(globals, function_cache)
-            try:
-                f, clsname = function_cache[co]
-            except KeyError:
-                clsname = ''  # give up
-        modulename = globals.get('__name__', '?')
-        if clsname:
-            modulename += '.' + clsname
-        logger.write('tag function: %s.%s' % (modulename, co.co_name), 1)
-    tagged_codes.append((co, globals))
-    _psyco.turbo_frame(co)
-    _psyco.turbo_code(co)
-
-def tag2bind():
-    if tagged_codes:
-        if logger:
-            logger.write('profiling stopped, binding %d functions' %
-                         len(tagged_codes), 2)
-        for co, globals in tagged_codes:
-            trytobind(co, globals, 0)
-        function_cache.clear()
-        del tagged_codes[:]
-
-
-class Profiler:
-    MemoryTimerResolution = 0.103
-
-    def run(self, memory, time, memorymax, timemax):
-        self.memory = memory
-        self.memorymax = memorymax
-        self.time = time
-        if timemax is None:
-            self.endtime = None
-        else:
-            self.endtime = now() + timemax
-        self.alarms = []
-        profilers.append(self)
-        go()
-    
-    def start(self):
-        curmem = _psyco.memory()
-        memlimits = []
-        if self.memorymax is not None:
-            if curmem >= self.memorymax:
-                if logger:
-                    logger.writememory()
-                return self.limitreached('memorymax')
-            memlimits.append(self.memorymax)
-        if self.memory is not None:
-            if self.memory <= 0:
-                if logger:
-                    logger.writememory()
-                return self.limitreached('memory')
-            memlimits.append(curmem + self.memory)
-            self.memory_at_start = curmem
-
-        curtime = now()
-        timelimits = []
-        if self.endtime is not None:
-            if curtime >= self.endtime:
-                return self.limitreached('timemax')
-            timelimits.append(self.endtime - curtime)
-        if self.time is not None:
-            if self.time <= 0.0:
-                return self.limitreached('time')
-            timelimits.append(self.time)
-            self.time_at_start = curtime
-        
-        try:
-            self.do_start()
-        except error, e:
-            if logger:
-                logger.write('%s: disabled by psyco.error:' % (
-                    self.__class__.__name__), 4)
-                logger.write('    %s' % str(e), 3)
-            return 0
-        
-        if memlimits:
-            self.memlimits_args = (time.sleep, (self.MemoryTimerResolution,),
-                                   self.check_memory, (min(memlimits),))
-            self.alarms.append(_psyco.alarm(*self.memlimits_args))
-        if timelimits:
-            self.alarms.append(_psyco.alarm(time.sleep, (min(timelimits),),
-                                            self.time_out))
-        return 1
-    
-    def stop(self):
-        for alarm in self.alarms:
-            alarm.stop(0)
-        for alarm in self.alarms:
-            alarm.stop(1)   # wait for parallel threads to stop
-        del self.alarms[:]
-        if self.time is not None:
-            self.time -= now() - self.time_at_start
-        if self.memory is not None:
-            self.memory -= _psyco.memory() - self.memory_at_start
-
-        try:
-            self.do_stop()
-        except error:
-            return 0
-        return 1
-
-    def check_memory(self, limit):
-        if _psyco.memory() < limit:
-            return self.memlimits_args
-        go()
-
-    def time_out(self):
-        self.time = 0.0
-        go()
-
-    def limitreached(self, limitname):
-        try:
-            profilers.remove(self)
-        except ValueError:
-            pass
-        if logger:
-            logger.write('%s: disabled (%s limit reached)' % (
-                self.__class__.__name__, limitname), 4)
-        return 0
-
-
-class FullCompiler(Profiler):
-
-    def do_start(self):
-        _psyco.profiling('f')
-
-    def do_stop(self):
-        _psyco.profiling('.')
-
-
-class RunOnly(Profiler):
-
-    def do_start(self):
-        _psyco.profiling('n')
-
-    def do_stop(self):
-        _psyco.profiling('.')
-
-
-class ChargeProfiler(Profiler):
-
-    def __init__(self, watermark, parentframe):
-        self.watermark = watermark
-        self.parent2 = parentframe * 2.0
-        self.lock = thread.allocate_lock()
-
-    def init_charges(self):
-        _psyco.statwrite(watermark = self.watermark,
-                         parent2   = self.parent2)
-
-    def do_stop(self):
-        _psyco.profiling('.')
-        _psyco.statwrite(callback = None)
-
-
-class ActiveProfiler(ChargeProfiler):
-
-    def active_start(self):
-        _psyco.profiling('p')
-
-    def do_start(self):
-        self.init_charges()
-        self.active_start()
-        _psyco.statwrite(callback = self.charge_callback)
-
-    def charge_callback(self, frame, charge):
-        tag(frame.f_code, frame.f_globals)
-
-
-class PassiveProfiler(ChargeProfiler):
-
-    initial_charge_unit   = _psyco.statread('unit')
-    reset_stats_after     = 120      # half-lives (maximum 200!)
-    reset_limit           = initial_charge_unit * (2.0 ** reset_stats_after)
-
-    def __init__(self, watermark, halflife, pollfreq, parentframe):
-        ChargeProfiler.__init__(self, watermark, parentframe)
-        self.pollfreq = pollfreq
-        # self.progress is slightly more than 1.0, and computed so that
-        # do_profile() will double the change_unit every 'halflife' seconds.
-        self.progress = 2.0 ** (1.0 / (halflife * pollfreq))
-
-    def reset(self):
-        _psyco.statwrite(unit = self.initial_charge_unit, callback = None)
-        _psyco.statreset()
-        if logger:
-            logger.write("%s: resetting stats" % self.__class__.__name__, 1)
-
-    def passive_start(self):
-        self.passivealarm_args = (time.sleep, (1.0 / self.pollfreq,),
-                                  self.do_profile)
-        self.alarms.append(_psyco.alarm(*self.passivealarm_args))
-
-    def do_start(self):
-        tag2bind()
-        self.init_charges()
-        self.passive_start()
-
-    def do_profile(self):
-        _psyco.statcollect()
-        if logger:
-            logger.dumpcharges()
-        nunit = _psyco.statread('unit') * self.progress
-        if nunit > self.reset_limit:
-            self.reset()
-        else:
-            _psyco.statwrite(unit = nunit, callback = self.charge_callback)
-        return self.passivealarm_args
-
-    def charge_callback(self, frame, charge):
-        trytobind(frame.f_code, frame.f_globals)
-
-
-class ActivePassiveProfiler(PassiveProfiler, ActiveProfiler):
-
-    def do_start(self):
-        self.init_charges()
-        self.active_start()
-        self.passive_start()
-
-    def charge_callback(self, frame, charge):
-        tag(frame.f_code, frame.f_globals)
-
-
-
-#
-# we register our own version of sys.settrace(), sys.setprofile()
-# and thread.start_new_thread().
-#
-
-def psyco_settrace(*args, **kw):
-    "This is the Psyco-aware version of sys.settrace()."
-    result = original_settrace(*args, **kw)
-    go()
-    return result
-
-def psyco_setprofile(*args, **kw):
-    "This is the Psyco-aware version of sys.setprofile()."
-    result = original_setprofile(*args, **kw)
-    go()
-    return result
-
-def psyco_thread_stub(callable, args, kw):
-    _psyco.statcollect()
-    if kw is None:
-        return callable(*args)
-    else:
-        return callable(*args, **kw)
-
-def psyco_start_new_thread(callable, args, kw=None):
-    "This is the Psyco-aware version of thread.start_new_thread()."
-    return original_start_new_thread(psyco_thread_stub, (callable, args, kw))
-
-original_settrace         = sys.settrace
-original_setprofile       = sys.setprofile
-original_start_new_thread = thread.start_new_thread
-sys.settrace            = psyco_settrace
-sys.setprofile          = psyco_setprofile
-thread.start_new_thread = psyco_start_new_thread
-# hack to patch threading._start_new_thread if the module is
-# already loaded
-if ('threading' in sys.modules and
-    hasattr(sys.modules['threading'], '_start_new_thread')):
-    sys.modules['threading']._start_new_thread = psyco_start_new_thread
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/support.py.svn-base b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/.svn/text-base/support.py.svn-base
deleted file mode 100644 (file)
index 387321a..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-###########################################################################
-# 
-#  Psyco general support module.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco general support module.
-
-For internal use.
-"""
-###########################################################################
-
-import sys, _psyco, __builtin__
-
-error = _psyco.error
-class warning(Warning):
-    pass
-
-_psyco.NoLocalsWarning = warning
-
-def warn(msg):
-    from warnings import warn
-    warn(msg, warning, stacklevel=2)
-
-#
-# Version checks
-#
-__version__ = 0x010600f0
-if _psyco.PSYVER != __version__:
-    raise error, "version mismatch between Psyco parts, reinstall it"
-
-version_info = (__version__ >> 24,
-                (__version__ >> 16) & 0xff,
-                (__version__ >> 8) & 0xff,
-                {0xa0: 'alpha',
-                 0xb0: 'beta',
-                 0xc0: 'candidate',
-                 0xf0: 'final'}[__version__ & 0xf0],
-                __version__ & 0xf)
-
-
-VERSION_LIMITS = [0x02020200,   # 2.2.2
-                  0x02030000,   # 2.3
-                  0x02040000]   # 2.4
-
-if ([v for v in VERSION_LIMITS if v <= sys.hexversion] !=
-    [v for v in VERSION_LIMITS if v <= _psyco.PYVER  ]):
-    if sys.hexversion < VERSION_LIMITS[0]:
-        warn("Psyco requires Python version 2.2.2 or later")
-    else:
-        warn("Psyco version does not match Python version. "
-             "Psyco must be updated or recompiled")
-
-
-if hasattr(_psyco, 'ALL_CHECKS') and hasattr(_psyco, 'VERBOSE_LEVEL'):
-    print >> sys.stderr, ('psyco: running in debugging mode on %s' %
-                          _psyco.PROCESSOR)
-
-
-###########################################################################
-# sys._getframe() gives strange results on a mixed Psyco- and Python-style
-# stack frame. Psyco provides a replacement that partially emulates Python
-# frames from Psyco frames. The new sys._getframe() may return objects of
-# a custom "Psyco frame" type, which is a subtype of the normal frame type.
-#
-# The same problems require some other built-in functions to be replaced
-# as well. Note that the local variables are not available in any
-# dictionary with Psyco.
-
-
-class Frame:
-    pass
-
-
-class PythonFrame(Frame):
-
-    def __init__(self, frame):
-        self.__dict__.update({
-            '_frame': frame,
-            })
-
-    def __getattr__(self, attr):
-        if attr == 'f_back':
-            try:
-                result = embedframe(_psyco.getframe(self._frame))
-            except ValueError:
-                result = None
-            except error:
-                warn("f_back is skipping dead Psyco frames")
-                result = self._frame.f_back
-            self.__dict__['f_back'] = result
-            return result
-        else:
-            return getattr(self._frame, attr)
-
-    def __setattr__(self, attr, value):
-        setattr(self._frame, attr, value)
-
-    def __delattr__(self, attr):
-        delattr(self._frame, attr)
-
-
-class PsycoFrame(Frame):
-
-    def __init__(self, tag):
-        self.__dict__.update({
-            '_tag'     : tag,
-            'f_code'   : tag[0],
-            'f_globals': tag[1],
-            })
-
-    def __getattr__(self, attr):
-        if attr == 'f_back':
-            try:
-                result = embedframe(_psyco.getframe(self._tag))
-            except ValueError:
-                result = None
-        elif attr == 'f_lineno':
-            result = self.f_code.co_firstlineno  # better than nothing
-        elif attr == 'f_builtins':
-            result = self.f_globals['__builtins__']
-        elif attr == 'f_restricted':
-            result = self.f_builtins is not __builtins__
-        elif attr == 'f_locals':
-            raise AttributeError, ("local variables of functions run by Psyco "
-                                   "cannot be accessed in any way, sorry")
-        else:
-            raise AttributeError, ("emulated Psyco frames have "
-                                   "no '%s' attribute" % attr)
-        self.__dict__[attr] = result
-        return result
-
-    def __setattr__(self, attr, value):
-        raise AttributeError, "Psyco frame objects are read-only"
-
-    def __delattr__(self, attr):
-        if attr == 'f_trace':
-            # for bdb which relies on CPython frames exhibiting a slightly
-            # buggy behavior: you can 'del f.f_trace' as often as you like
-            # even without having set it previously.
-            return
-        raise AttributeError, "Psyco frame objects are read-only"
-
-
-def embedframe(result):
-    if type(result) is type(()):
-        return PsycoFrame(result)
-    else:
-        return PythonFrame(result)
-
-def _getframe(depth=0):
-    """Return a frame object from the call stack. This is a replacement for
-sys._getframe() which is aware of Psyco frames.
-
-The returned objects are instances of either PythonFrame or PsycoFrame
-instead of being real Python-level frame object, so that they can emulate
-the common attributes of frame objects.
-
-The original sys._getframe() ignoring Psyco frames altogether is stored in
-psyco._getrealframe(). See also psyco._getemulframe()."""
-    # 'depth+1' to account for this _getframe() Python function
-    return embedframe(_psyco.getframe(depth+1))
-
-def _getemulframe(depth=0):
-    """As _getframe(), but the returned objects are real Python frame objects
-emulating Psyco frames. Some of their attributes can be wrong or missing,
-however."""
-    # 'depth+1' to account for this _getemulframe() Python function
-    return _psyco.getframe(depth+1, 1)
-
-def patch(name, module=__builtin__):
-    f = getattr(_psyco, name)
-    org = getattr(module, name)
-    if org is not f:
-        setattr(module, name, f)
-        setattr(_psyco, 'original_' + name, org)
-
-_getrealframe = sys._getframe
-sys._getframe = _getframe
-patch('globals')
-patch('eval')
-patch('execfile')
-patch('locals')
-patch('vars')
-patch('dir')
-patch('input')
-_psyco.original_raw_input = raw_input
-__builtin__.__in_psyco__ = 0==1   # False
-
-if hasattr(_psyco, 'compact'):
-    import kdictproxy
-    _psyco.compactdictproxy = kdictproxy.compactdictproxy
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/__init__.py b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/__init__.py
deleted file mode 100644 (file)
index d25e197..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-###########################################################################
-# 
-#  Psyco top-level file of the Psyco package.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco -- the Python Specializing Compiler.
-
-Typical usage: add the following lines to your application's main module,
-preferably after the other imports:
-
-try:
-    import psyco
-    psyco.full()
-except ImportError:
-    print 'Psyco not installed, the program will just run slower'
-"""
-###########################################################################
-
-
-#
-# This module is present to make 'psyco' a package and to
-# publish the main functions and variables.
-#
-# More documentation can be found in core.py.
-#
-
-
-# Try to import the dynamic-loading _psyco and report errors
-try:
-    import _psyco
-except ImportError, e:
-    extramsg = ''
-    import sys, imp
-    try:
-        file, filename, (suffix, mode, type) = imp.find_module('_psyco', __path__)
-    except ImportError:
-        ext = [suffix for suffix, mode, type in imp.get_suffixes()
-               if type == imp.C_EXTENSION]
-        if ext:
-            extramsg = (" (cannot locate the compiled extension '_psyco%s' "
-                        "in the package path '%s')" % (ext[0], '; '.join(__path__)))
-    else:
-        extramsg = (" (check that the compiled extension '%s' is for "
-                    "the correct Python version; this is Python %s)" %
-                    (filename, sys.version.split()[0]))
-    raise ImportError, str(e) + extramsg
-
-# Publish important data by importing them in the package
-from support import __version__, error, warning, _getrealframe, _getemulframe
-from support import version_info, __version__ as hexversion
-from core import full, profile, background, runonly, stop, cannotcompile
-from core import log, bind, unbind, proxy, unproxy, dumpcodebuf
-from _psyco import setfilter
-from _psyco import compact, compacttype
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/classes.py b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/classes.py
deleted file mode 100644 (file)
index 0563f84..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-###########################################################################
-# 
-#  Psyco class support module.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco class support module.
-
-'psyco.classes.psyobj' is an alternate Psyco-optimized root for classes.
-Any class inheriting from it or using the metaclass '__metaclass__' might
-get optimized specifically for Psyco. It is equivalent to call
-psyco.bind() on the class object after its creation.
-
-Importing everything from psyco.classes in a module will import the
-'__metaclass__' name, so all classes defined after a
-
-       from psyco.classes import *
-
-will automatically use the Psyco-optimized metaclass.
-"""
-###########################################################################
-
-__all__ = ['psyobj', 'psymetaclass', '__metaclass__']
-
-
-from _psyco import compacttype
-import core
-from types import FunctionType
-
-class psymetaclass(compacttype):
-    "Psyco-optimized meta-class. Turns all methods into Psyco proxies."
-
-    def __new__(cls, name, bases, dict):
-        bindlist = dict.get('__psyco__bind__')
-        if bindlist is None:
-            bindlist = [key for key, value in dict.items()
-                        if isinstance(value, FunctionType)]
-        for attr in bindlist:
-            dict[attr] = core.proxy(dict[attr])
-        return super(psymetaclass, cls).__new__(cls, name, bases, dict)
-
-psyobj = psymetaclass("psyobj", (), {})
-__metaclass__ = psymetaclass
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/core.py b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/core.py
deleted file mode 100644 (file)
index 995c9ae..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-###########################################################################
-# 
-#  Psyco main functions.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco main functions.
-
-Here are the routines that you can use from your applications.
-These are mostly interfaces to the C core, but they depend on
-the Python version.
-
-You can use these functions from the 'psyco' module instead of
-'psyco.core', e.g.
-
-    import psyco
-    psyco.log('/tmp/psyco.log')
-    psyco.profile()
-"""
-###########################################################################
-
-import _psyco
-import types
-from support import *
-
-newfunction = types.FunctionType
-newinstancemethod = types.MethodType
-
-
-# Default charge profiler values
-default_watermark     = 0.09     # between 0.0 (0%) and 1.0 (100%)
-default_halflife      = 0.5      # seconds
-default_pollfreq_profile    = 20       # Hz
-default_pollfreq_background = 100      # Hz -- a maximum for sleep's resolution
-default_parentframe   = 0.25     # should not be more than 0.5 (50%)
-
-
-def full(memory=None, time=None, memorymax=None, timemax=None):
-    """Compile as much as possible.
-
-Typical use is for small scripts performing intensive computations
-or string handling."""
-    import profiler
-    p = profiler.FullCompiler()
-    p.run(memory, time, memorymax, timemax)
-
-
-def profile(watermark   = default_watermark,
-            halflife    = default_halflife,
-            pollfreq    = default_pollfreq_profile,
-            parentframe = default_parentframe,
-            memory=None, time=None, memorymax=None, timemax=None):
-    """Turn on profiling.
-
-The 'watermark' parameter controls how easily running functions will
-be compiled. The smaller the value, the more functions are compiled."""
-    import profiler
-    p = profiler.ActivePassiveProfiler(watermark, halflife,
-                                       pollfreq, parentframe)
-    p.run(memory, time, memorymax, timemax)
-
-
-def background(watermark   = default_watermark,
-               halflife    = default_halflife,
-               pollfreq    = default_pollfreq_background,
-               parentframe = default_parentframe,
-               memory=None, time=None, memorymax=None, timemax=None):
-    """Turn on passive profiling.
-
-This is a very lightweight mode in which only intensively computing
-functions can be detected. The smaller the 'watermark', the more functions
-are compiled."""
-    import profiler
-    p = profiler.PassiveProfiler(watermark, halflife, pollfreq, parentframe)
-    p.run(memory, time, memorymax, timemax)
-
-
-def runonly(memory=None, time=None, memorymax=None, timemax=None):
-    """Nonprofiler.
-
-XXX check if this is useful and document."""
-    import profiler
-    p = profiler.RunOnly()
-    p.run(memory, time, memorymax, timemax)
-
-
-def stop():
-    """Turn off all automatic compilation.  bind() calls remain in effect."""
-    import profiler
-    profiler.go([])
-
-
-def log(logfile='', mode='w', top=10):
-    """Enable logging to the given file.
-
-If the file name is unspecified, a default name is built by appending
-a 'log-psyco' extension to the main script name.
-
-Mode is 'a' to append to a possibly existing file or 'w' to overwrite
-an existing file. Note that the log file may grow quickly in 'a' mode."""
-    import profiler, logger
-    if not logfile:
-        import os
-        logfile, dummy = os.path.splitext(sys.argv[0])
-        if os.path.basename(logfile):
-            logfile += '.'
-        logfile += 'log-psyco'
-    if hasattr(_psyco, 'VERBOSE_LEVEL'):
-        print >> sys.stderr, 'psyco: logging to', logfile
-    # logger.current should be a real file object; subtle problems
-    # will show up if its write() and flush() methods are written
-    # in Python, as Psyco will invoke them while compiling.
-    logger.current = open(logfile, mode)
-    logger.print_charges = top
-    profiler.logger = logger
-    logger.writedate('Logging started')
-    cannotcompile(logger.psycowrite)
-    _psyco.statwrite(logger=logger.psycowrite)
-
-
-def bind(x, rec=None):
-    """Enable compilation of the given function, method, or class object.
-
-If C is a class (or anything with a '__dict__' attribute), bind(C) will
-rebind all functions and methods found in C.__dict__ (which means, for
-classes, all methods defined in the class but not in its parents).
-
-The optional second argument specifies the number of recursive
-compilation levels: all functions called by func are compiled
-up to the given depth of indirection."""
-    if isinstance(x, types.MethodType):
-        x = x.im_func
-    if isinstance(x, types.FunctionType):
-        if rec is None:
-            x.func_code = _psyco.proxycode(x)
-        else:
-            x.func_code = _psyco.proxycode(x, rec)
-        return
-    if hasattr(x, '__dict__'):
-        funcs = [o for o in x.__dict__.values()
-                 if isinstance(o, types.MethodType)
-                 or isinstance(o, types.FunctionType)]
-        if not funcs:
-            raise error, ("nothing bindable found in %s object" %
-                          type(x).__name__)
-        for o in funcs:
-            bind(o, rec)
-        return
-    raise TypeError, "cannot bind %s objects" % type(x).__name__
-
-
-def unbind(x):
-    """Reverse of bind()."""
-    if isinstance(x, types.MethodType):
-        x = x.im_func
-    if isinstance(x, types.FunctionType):
-        try:
-            f = _psyco.unproxycode(x.func_code)
-        except error:
-            pass
-        else:
-            x.func_code = f.func_code
-        return
-    if hasattr(x, '__dict__'):
-        for o in x.__dict__.values():
-            if (isinstance(o, types.MethodType)
-             or isinstance(o, types.FunctionType)):
-                unbind(o)
-        return
-    raise TypeError, "cannot unbind %s objects" % type(x).__name__
-
-
-def proxy(x, rec=None):
-    """Return a Psyco-enabled copy of the function.
-
-The original function is still available for non-compiled calls.
-The optional second argument specifies the number of recursive
-compilation levels: all functions called by func are compiled
-up to the given depth of indirection."""
-    if isinstance(x, types.FunctionType):
-        if rec is None:
-            code = _psyco.proxycode(x)
-        else:
-            code = _psyco.proxycode(x, rec)
-        return newfunction(code, x.func_globals, x.func_name)
-    if isinstance(x, types.MethodType):
-        p = proxy(x.im_func, rec)
-        return newinstancemethod(p, x.im_self, x.im_class)
-    raise TypeError, "cannot proxy %s objects" % type(x).__name__
-
-
-def unproxy(proxy):
-    """Return a new copy of the original function of method behind a proxy.
-The result behaves like the original function in that calling it
-does not trigger compilation nor execution of any compiled code."""
-    if isinstance(proxy, types.FunctionType):
-        return _psyco.unproxycode(proxy.func_code)
-    if isinstance(proxy, types.MethodType):
-        f = unproxy(proxy.im_func)
-        return newinstancemethod(f, proxy.im_self, proxy.im_class)
-    raise TypeError, "%s objects cannot be proxies" % type(proxy).__name__
-
-
-def cannotcompile(x):
-    """Instruct Psyco never to compile the given function, method
-or code object."""
-    if isinstance(x, types.MethodType):
-        x = x.im_func
-    if isinstance(x, types.FunctionType):
-        x = x.func_code
-    if isinstance(x, types.CodeType):
-        _psyco.cannotcompile(x)
-    else:
-        raise TypeError, "unexpected %s object" % type(x).__name__
-
-
-def dumpcodebuf():
-    """Write in file psyco.dump a copy of the emitted machine code,
-provided Psyco was compiled with a non-zero CODE_DUMP.
-See py-utils/httpxam.py to examine psyco.dump."""
-    if hasattr(_psyco, 'dumpcodebuf'):
-        _psyco.dumpcodebuf()
-
-
-###########################################################################
-# Psyco variables
-#   error         * the error raised by Psyco
-#   warning       * the warning raised by Psyco
-#   __in_psyco__  * a new built-in variable which is always zero, but which
-#                     Psyco special-cases by returning 1 instead. So
-#                     __in_psyco__ can be used in a function to know if
-#                     that function is being executed by Psyco or not.
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/kdictproxy.py b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/kdictproxy.py
deleted file mode 100644 (file)
index c764e5e..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-###########################################################################
-#
-#  Support code for the 'psyco.compact' type.
-
-from __future__ import generators
-
-try:
-    from UserDict import DictMixin
-except ImportError:
-
-    # backported from Python 2.3 to Python 2.2
-    class DictMixin:
-        # Mixin defining all dictionary methods for classes that already have
-        # a minimum dictionary interface including getitem, setitem, delitem,
-        # and keys. Without knowledge of the subclass constructor, the mixin
-        # does not define __init__() or copy().  In addition to the four base
-        # methods, progressively more efficiency comes with defining
-        # __contains__(), __iter__(), and iteritems().
-
-        # second level definitions support higher levels
-        def __iter__(self):
-            for k in self.keys():
-                yield k
-        def has_key(self, key):
-            try:
-                value = self[key]
-            except KeyError:
-                return False
-            return True
-        def __contains__(self, key):
-            return self.has_key(key)
-
-        # third level takes advantage of second level definitions
-        def iteritems(self):
-            for k in self:
-                yield (k, self[k])
-        def iterkeys(self):
-            return self.__iter__()
-
-        # fourth level uses definitions from lower levels
-        def itervalues(self):
-            for _, v in self.iteritems():
-                yield v
-        def values(self):
-            return [v for _, v in self.iteritems()]
-        def items(self):
-            return list(self.iteritems())
-        def clear(self):
-            for key in self.keys():
-                del self[key]
-        def setdefault(self, key, default):
-            try:
-                return self[key]
-            except KeyError:
-                self[key] = default
-            return default
-        def pop(self, key, *args):
-            if len(args) > 1:
-                raise TypeError, "pop expected at most 2 arguments, got "\
-                                  + repr(1 + len(args))
-            try:
-                value = self[key]
-            except KeyError:
-                if args:
-                    return args[0]
-                raise
-            del self[key]
-            return value
-        def popitem(self):
-            try:
-                k, v = self.iteritems().next()
-            except StopIteration:
-                raise KeyError, 'container is empty'
-            del self[k]
-            return (k, v)
-        def update(self, other):
-            # Make progressively weaker assumptions about "other"
-            if hasattr(other, 'iteritems'):  # iteritems saves memory and lookups
-                for k, v in other.iteritems():
-                    self[k] = v
-            elif hasattr(other, '__iter__'): # iter saves memory
-                for k in other:
-                    self[k] = other[k]
-            else:
-                for k in other.keys():
-                    self[k] = other[k]
-        def get(self, key, default=None):
-            try:
-                return self[key]
-            except KeyError:
-                return default
-        def __repr__(self):
-            return repr(dict(self.iteritems()))
-        def __cmp__(self, other):
-            if other is None:
-                return 1
-            if isinstance(other, DictMixin):
-                other = dict(other.iteritems())
-            return cmp(dict(self.iteritems()), other)
-        def __len__(self):
-            return len(self.keys())
-
-###########################################################################
-
-from _psyco import compact
-
-
-class compactdictproxy(DictMixin):
-
-    def __init__(self, ko):
-        self._ko = ko    # compact object of which 'self' is the dict
-
-    def __getitem__(self, key):
-        return compact.__getslot__(self._ko, key)
-
-    def __setitem__(self, key, value):
-        compact.__setslot__(self._ko, key, value)
-
-    def __delitem__(self, key):
-        compact.__delslot__(self._ko, key)
-
-    def keys(self):
-        return compact.__members__.__get__(self._ko)
-
-    def clear(self):
-        keys = self.keys()
-        keys.reverse()
-        for key in keys:
-            del self[key]
-
-    def __repr__(self):
-        keys = ', '.join(self.keys())
-        return '<compactdictproxy object {%s}>' % (keys,)
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/logger.py b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/logger.py
deleted file mode 100644 (file)
index a3c5219..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-###########################################################################
-# 
-#  Psyco logger.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco logger.
-
-See log() in core.py.
-"""
-###########################################################################
-
-
-import _psyco
-from time import time, localtime, strftime
-
-
-current = None
-print_charges = 10
-dump_delay = 0.2
-dump_last = 0.0
-
-def write(s, level):
-    t = time()
-    f = t-int(t)
-    try:
-        current.write("%s.%02d  %-*s%s\n" % (
-            strftime("%X", localtime(int(t))),
-            int(f*100.0), 63-level, s,
-            "%"*level))
-        current.flush()
-    except (OSError, IOError):
-        pass
-
-def psycowrite(s):
-    t = time()
-    f = t-int(t)
-    try:
-        current.write("%s.%02d  %-*s%s\n" % (
-            strftime("%X", localtime(int(t))),
-            int(f*100.0), 60, s.strip(),
-            "% %"))
-        current.flush()
-    except (OSError, IOError):
-        pass
-
-##def writelines(lines, level=0):
-##    if lines:
-##        t = time()
-##        f = t-int(t)
-##        timedesc = strftime("%x %X", localtime(int(t)))
-##        print >> current, "%s.%03d  %-*s %s" % (
-##            timedesc, int(f*1000),
-##            50-level, lines[0],
-##            "+"*level)
-##        timedesc = " " * (len(timedesc)+5)
-##        for line in lines[1:]:
-##            print >> current, timedesc, line
-
-def writememory():
-    write("memory usage: %d+ kb" % _psyco.memory(), 1)
-
-def dumpcharges():
-    global dump_last
-    if print_charges:
-        t = time()
-        if not (dump_last <= t < dump_last+dump_delay):
-            if t <= dump_last+1.5*dump_delay:
-                dump_last += dump_delay
-            else:
-                dump_last = t
-            #write("%s: charges:" % who, 0)
-            lst = _psyco.stattop(print_charges)
-            if lst:
-                f = t-int(t)
-                lines = ["%s.%02d   ______\n" % (
-                    strftime("%X", localtime(int(t))),
-                    int(f*100.0))]
-                i = 1
-                for co, charge in lst:
-                    detail = co.co_filename
-                    if len(detail) > 19:
-                        detail = '...' + detail[-17:]
-                    lines.append("        #%-3d |%4.1f %%|  %-26s%20s:%d\n" %
-                                 (i, charge*100.0, co.co_name, detail,
-                                  co.co_firstlineno))
-                    i += 1
-                current.writelines(lines)
-                current.flush()
-
-def writefinalstats():
-    dumpcharges()
-    writememory()
-    writedate("program exit")
-
-def writedate(msg):
-    write('%s, %s' % (msg, strftime("%x")), 20)
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/profiler.py b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/profiler.py
deleted file mode 100644 (file)
index f1d06f1..0000000
+++ /dev/null
@@ -1,379 +0,0 @@
-###########################################################################
-# 
-#  Psyco profiler (Python part).
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco profiler (Python part).
-
-The implementation of the non-time-critical parts of the profiler.
-See profile() and full() in core.py for the easy interface.
-"""
-###########################################################################
-
-import _psyco
-from support import *
-import math, time, types, atexit
-now = time.time
-try:
-    import thread
-except ImportError:
-    import dummy_thread as thread
-
-
-# current profiler instance
-current = None
-
-# enabled profilers, in order of priority
-profilers = []
-
-# logger module (when enabled by core.log())
-logger = None
-
-# a lock for a thread-safe go()
-go_lock = thread.allocate_lock()
-
-def go(stop=0):
-    # run the highest-priority profiler in 'profilers'
-    global current
-    go_lock.acquire()
-    try:
-        prev = current
-        if stop:
-            del profilers[:]
-        if prev:
-            if profilers and profilers[0] is prev:
-                return    # best profiler already running
-            prev.stop()
-            current = None
-        for p in profilers[:]:
-            if p.start():
-                current = p
-                if logger: # and p is not prev:
-                    logger.write("%s: starting" % p.__class__.__name__, 5)
-                return
-    finally:
-        go_lock.release()
-    # no profiler is running now
-    if stop:
-        if logger:
-            logger.writefinalstats()
-    else:
-        tag2bind()
-
-atexit.register(go, 1)
-
-
-def buildfncache(globals, cache):
-    if hasattr(types.IntType, '__dict__'):
-        clstypes = (types.ClassType, types.TypeType)
-    else:
-        clstypes = types.ClassType
-    for x in globals.values():
-        if isinstance(x, types.MethodType):
-            x = x.im_func
-        if isinstance(x, types.FunctionType):
-            cache[x.func_code] = x, ''
-        elif isinstance(x, clstypes):
-            for y in x.__dict__.values():
-                if isinstance(y, types.MethodType):
-                    y = y.im_func
-                if isinstance(y, types.FunctionType):
-                    cache[y.func_code] = y, x.__name__
-
-# code-to-function mapping (cache)
-function_cache = {}
-
-def trytobind(co, globals, log=1):
-    try:
-        f, clsname = function_cache[co]
-    except KeyError:
-        buildfncache(globals, function_cache)
-        try:
-            f, clsname = function_cache[co]
-        except KeyError:
-            if logger:
-                logger.write('warning: cannot find function %s in %s' %
-                             (co.co_name, globals.get('__name__', '?')), 3)
-            return  # give up
-    if logger and log:
-        modulename = globals.get('__name__', '?')
-        if clsname:
-            modulename += '.' + clsname
-        logger.write('bind function: %s.%s' % (modulename, co.co_name), 1)
-    f.func_code = _psyco.proxycode(f)
-
-
-# the list of code objects that have been tagged
-tagged_codes = []
-
-def tag(co, globals):
-    if logger:
-        try:
-            f, clsname = function_cache[co]
-        except KeyError:
-            buildfncache(globals, function_cache)
-            try:
-                f, clsname = function_cache[co]
-            except KeyError:
-                clsname = ''  # give up
-        modulename = globals.get('__name__', '?')
-        if clsname:
-            modulename += '.' + clsname
-        logger.write('tag function: %s.%s' % (modulename, co.co_name), 1)
-    tagged_codes.append((co, globals))
-    _psyco.turbo_frame(co)
-    _psyco.turbo_code(co)
-
-def tag2bind():
-    if tagged_codes:
-        if logger:
-            logger.write('profiling stopped, binding %d functions' %
-                         len(tagged_codes), 2)
-        for co, globals in tagged_codes:
-            trytobind(co, globals, 0)
-        function_cache.clear()
-        del tagged_codes[:]
-
-
-class Profiler:
-    MemoryTimerResolution = 0.103
-
-    def run(self, memory, time, memorymax, timemax):
-        self.memory = memory
-        self.memorymax = memorymax
-        self.time = time
-        if timemax is None:
-            self.endtime = None
-        else:
-            self.endtime = now() + timemax
-        self.alarms = []
-        profilers.append(self)
-        go()
-    
-    def start(self):
-        curmem = _psyco.memory()
-        memlimits = []
-        if self.memorymax is not None:
-            if curmem >= self.memorymax:
-                if logger:
-                    logger.writememory()
-                return self.limitreached('memorymax')
-            memlimits.append(self.memorymax)
-        if self.memory is not None:
-            if self.memory <= 0:
-                if logger:
-                    logger.writememory()
-                return self.limitreached('memory')
-            memlimits.append(curmem + self.memory)
-            self.memory_at_start = curmem
-
-        curtime = now()
-        timelimits = []
-        if self.endtime is not None:
-            if curtime >= self.endtime:
-                return self.limitreached('timemax')
-            timelimits.append(self.endtime - curtime)
-        if self.time is not None:
-            if self.time <= 0.0:
-                return self.limitreached('time')
-            timelimits.append(self.time)
-            self.time_at_start = curtime
-        
-        try:
-            self.do_start()
-        except error, e:
-            if logger:
-                logger.write('%s: disabled by psyco.error:' % (
-                    self.__class__.__name__), 4)
-                logger.write('    %s' % str(e), 3)
-            return 0
-        
-        if memlimits:
-            self.memlimits_args = (time.sleep, (self.MemoryTimerResolution,),
-                                   self.check_memory, (min(memlimits),))
-            self.alarms.append(_psyco.alarm(*self.memlimits_args))
-        if timelimits:
-            self.alarms.append(_psyco.alarm(time.sleep, (min(timelimits),),
-                                            self.time_out))
-        return 1
-    
-    def stop(self):
-        for alarm in self.alarms:
-            alarm.stop(0)
-        for alarm in self.alarms:
-            alarm.stop(1)   # wait for parallel threads to stop
-        del self.alarms[:]
-        if self.time is not None:
-            self.time -= now() - self.time_at_start
-        if self.memory is not None:
-            self.memory -= _psyco.memory() - self.memory_at_start
-
-        try:
-            self.do_stop()
-        except error:
-            return 0
-        return 1
-
-    def check_memory(self, limit):
-        if _psyco.memory() < limit:
-            return self.memlimits_args
-        go()
-
-    def time_out(self):
-        self.time = 0.0
-        go()
-
-    def limitreached(self, limitname):
-        try:
-            profilers.remove(self)
-        except ValueError:
-            pass
-        if logger:
-            logger.write('%s: disabled (%s limit reached)' % (
-                self.__class__.__name__, limitname), 4)
-        return 0
-
-
-class FullCompiler(Profiler):
-
-    def do_start(self):
-        _psyco.profiling('f')
-
-    def do_stop(self):
-        _psyco.profiling('.')
-
-
-class RunOnly(Profiler):
-
-    def do_start(self):
-        _psyco.profiling('n')
-
-    def do_stop(self):
-        _psyco.profiling('.')
-
-
-class ChargeProfiler(Profiler):
-
-    def __init__(self, watermark, parentframe):
-        self.watermark = watermark
-        self.parent2 = parentframe * 2.0
-        self.lock = thread.allocate_lock()
-
-    def init_charges(self):
-        _psyco.statwrite(watermark = self.watermark,
-                         parent2   = self.parent2)
-
-    def do_stop(self):
-        _psyco.profiling('.')
-        _psyco.statwrite(callback = None)
-
-
-class ActiveProfiler(ChargeProfiler):
-
-    def active_start(self):
-        _psyco.profiling('p')
-
-    def do_start(self):
-        self.init_charges()
-        self.active_start()
-        _psyco.statwrite(callback = self.charge_callback)
-
-    def charge_callback(self, frame, charge):
-        tag(frame.f_code, frame.f_globals)
-
-
-class PassiveProfiler(ChargeProfiler):
-
-    initial_charge_unit   = _psyco.statread('unit')
-    reset_stats_after     = 120      # half-lives (maximum 200!)
-    reset_limit           = initial_charge_unit * (2.0 ** reset_stats_after)
-
-    def __init__(self, watermark, halflife, pollfreq, parentframe):
-        ChargeProfiler.__init__(self, watermark, parentframe)
-        self.pollfreq = pollfreq
-        # self.progress is slightly more than 1.0, and computed so that
-        # do_profile() will double the change_unit every 'halflife' seconds.
-        self.progress = 2.0 ** (1.0 / (halflife * pollfreq))
-
-    def reset(self):
-        _psyco.statwrite(unit = self.initial_charge_unit, callback = None)
-        _psyco.statreset()
-        if logger:
-            logger.write("%s: resetting stats" % self.__class__.__name__, 1)
-
-    def passive_start(self):
-        self.passivealarm_args = (time.sleep, (1.0 / self.pollfreq,),
-                                  self.do_profile)
-        self.alarms.append(_psyco.alarm(*self.passivealarm_args))
-
-    def do_start(self):
-        tag2bind()
-        self.init_charges()
-        self.passive_start()
-
-    def do_profile(self):
-        _psyco.statcollect()
-        if logger:
-            logger.dumpcharges()
-        nunit = _psyco.statread('unit') * self.progress
-        if nunit > self.reset_limit:
-            self.reset()
-        else:
-            _psyco.statwrite(unit = nunit, callback = self.charge_callback)
-        return self.passivealarm_args
-
-    def charge_callback(self, frame, charge):
-        trytobind(frame.f_code, frame.f_globals)
-
-
-class ActivePassiveProfiler(PassiveProfiler, ActiveProfiler):
-
-    def do_start(self):
-        self.init_charges()
-        self.active_start()
-        self.passive_start()
-
-    def charge_callback(self, frame, charge):
-        tag(frame.f_code, frame.f_globals)
-
-
-
-#
-# we register our own version of sys.settrace(), sys.setprofile()
-# and thread.start_new_thread().
-#
-
-def psyco_settrace(*args, **kw):
-    "This is the Psyco-aware version of sys.settrace()."
-    result = original_settrace(*args, **kw)
-    go()
-    return result
-
-def psyco_setprofile(*args, **kw):
-    "This is the Psyco-aware version of sys.setprofile()."
-    result = original_setprofile(*args, **kw)
-    go()
-    return result
-
-def psyco_thread_stub(callable, args, kw):
-    _psyco.statcollect()
-    if kw is None:
-        return callable(*args)
-    else:
-        return callable(*args, **kw)
-
-def psyco_start_new_thread(callable, args, kw=None):
-    "This is the Psyco-aware version of thread.start_new_thread()."
-    return original_start_new_thread(psyco_thread_stub, (callable, args, kw))
-
-original_settrace         = sys.settrace
-original_setprofile       = sys.setprofile
-original_start_new_thread = thread.start_new_thread
-sys.settrace            = psyco_settrace
-sys.setprofile          = psyco_setprofile
-thread.start_new_thread = psyco_start_new_thread
-# hack to patch threading._start_new_thread if the module is
-# already loaded
-if ('threading' in sys.modules and
-    hasattr(sys.modules['threading'], '_start_new_thread')):
-    sys.modules['threading']._start_new_thread = psyco_start_new_thread
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/support.py b/Calibre_Plugins/eReaderPDB2PML_plugin/osx/psyco/support.py
deleted file mode 100644 (file)
index 387321a..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-###########################################################################
-# 
-#  Psyco general support module.
-#   Copyright (C) 2001-2002  Armin Rigo et.al.
-
-"""Psyco general support module.
-
-For internal use.
-"""
-###########################################################################
-
-import sys, _psyco, __builtin__
-
-error = _psyco.error
-class warning(Warning):
-    pass
-
-_psyco.NoLocalsWarning = warning
-
-def warn(msg):
-    from warnings import warn
-    warn(msg, warning, stacklevel=2)
-
-#
-# Version checks
-#
-__version__ = 0x010600f0
-if _psyco.PSYVER != __version__:
-    raise error, "version mismatch between Psyco parts, reinstall it"
-
-version_info = (__version__ >> 24,
-                (__version__ >> 16) & 0xff,
-                (__version__ >> 8) & 0xff,
-                {0xa0: 'alpha',
-                 0xb0: 'beta',
-                 0xc0: 'candidate',
-                 0xf0: 'final'}[__version__ & 0xf0],
-                __version__ & 0xf)
-
-
-VERSION_LIMITS = [0x02020200,   # 2.2.2
-                  0x02030000,   # 2.3
-                  0x02040000]   # 2.4
-
-if ([v for v in VERSION_LIMITS if v <= sys.hexversion] !=
-    [v for v in VERSION_LIMITS if v <= _psyco.PYVER  ]):
-    if sys.hexversion < VERSION_LIMITS[0]:
-        warn("Psyco requires Python version 2.2.2 or later")
-    else:
-        warn("Psyco version does not match Python version. "
-             "Psyco must be updated or recompiled")
-
-
-if hasattr(_psyco, 'ALL_CHECKS') and hasattr(_psyco, 'VERBOSE_LEVEL'):
-    print >> sys.stderr, ('psyco: running in debugging mode on %s' %
-                          _psyco.PROCESSOR)
-
-
-###########################################################################
-# sys._getframe() gives strange results on a mixed Psyco- and Python-style
-# stack frame. Psyco provides a replacement that partially emulates Python
-# frames from Psyco frames. The new sys._getframe() may return objects of
-# a custom "Psyco frame" type, which is a subtype of the normal frame type.
-#
-# The same problems require some other built-in functions to be replaced
-# as well. Note that the local variables are not available in any
-# dictionary with Psyco.
-
-
-class Frame:
-    pass
-
-
-class PythonFrame(Frame):
-
-    def __init__(self, frame):
-        self.__dict__.update({
-            '_frame': frame,
-            })
-
-    def __getattr__(self, attr):
-        if attr == 'f_back':
-            try:
-                result = embedframe(_psyco.getframe(self._frame))
-            except ValueError:
-                result = None
-            except error:
-                warn("f_back is skipping dead Psyco frames")
-                result = self._frame.f_back
-            self.__dict__['f_back'] = result
-            return result
-        else:
-            return getattr(self._frame, attr)
-
-    def __setattr__(self, attr, value):
-        setattr(self._frame, attr, value)
-
-    def __delattr__(self, attr):
-        delattr(self._frame, attr)
-
-
-class PsycoFrame(Frame):
-
-    def __init__(self, tag):
-        self.__dict__.update({
-            '_tag'     : tag,
-            'f_code'   : tag[0],
-            'f_globals': tag[1],
-            })
-
-    def __getattr__(self, attr):
-        if attr == 'f_back':
-            try:
-                result = embedframe(_psyco.getframe(self._tag))
-            except ValueError:
-                result = None
-        elif attr == 'f_lineno':
-            result = self.f_code.co_firstlineno  # better than nothing
-        elif attr == 'f_builtins':
-            result = self.f_globals['__builtins__']
-        elif attr == 'f_restricted':
-            result = self.f_builtins is not __builtins__
-        elif attr == 'f_locals':
-            raise AttributeError, ("local variables of functions run by Psyco "
-                                   "cannot be accessed in any way, sorry")
-        else:
-            raise AttributeError, ("emulated Psyco frames have "
-                                   "no '%s' attribute" % attr)
-        self.__dict__[attr] = result
-        return result
-
-    def __setattr__(self, attr, value):
-        raise AttributeError, "Psyco frame objects are read-only"
-
-    def __delattr__(self, attr):
-        if attr == 'f_trace':
-            # for bdb which relies on CPython frames exhibiting a slightly
-            # buggy behavior: you can 'del f.f_trace' as often as you like
-            # even without having set it previously.
-            return
-        raise AttributeError, "Psyco frame objects are read-only"
-
-
-def embedframe(result):
-    if type(result) is type(()):
-        return PsycoFrame(result)
-    else:
-        return PythonFrame(result)
-
-def _getframe(depth=0):
-    """Return a frame object from the call stack. This is a replacement for
-sys._getframe() which is aware of Psyco frames.
-
-The returned objects are instances of either PythonFrame or PsycoFrame
-instead of being real Python-level frame object, so that they can emulate
-the common attributes of frame objects.
-
-The original sys._getframe() ignoring Psyco frames altogether is stored in
-psyco._getrealframe(). See also psyco._getemulframe()."""
-    # 'depth+1' to account for this _getframe() Python function
-    return embedframe(_psyco.getframe(depth+1))
-
-def _getemulframe(depth=0):
-    """As _getframe(), but the returned objects are real Python frame objects
-emulating Psyco frames. Some of their attributes can be wrong or missing,
-however."""
-    # 'depth+1' to account for this _getemulframe() Python function
-    return _psyco.getframe(depth+1, 1)
-
-def patch(name, module=__builtin__):
-    f = getattr(_psyco, name)
-    org = getattr(module, name)
-    if org is not f:
-        setattr(module, name, f)
-        setattr(_psyco, 'original_' + name, org)
-
-_getrealframe = sys._getframe
-sys._getframe = _getframe
-patch('globals')
-patch('eval')
-patch('execfile')
-patch('locals')
-patch('vars')
-patch('dir')
-patch('input')
-_psyco.original_raw_input = raw_input
-__builtin__.__in_psyco__ = 0==1   # False
-
-if hasattr(_psyco, 'compact'):
-    import kdictproxy
-    _psyco.compactdictproxy = kdictproxy.compactdictproxy
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/python_des.py b/Calibre_Plugins/eReaderPDB2PML_plugin/python_des.py
new file mode 100644 (file)
index 0000000..c5bb204
--- /dev/null
@@ -0,0 +1,218 @@
+import sys
+
+ECB =  0
+CBC =  1
+class Des(object):
+    __pc1 = [56, 48, 40, 32, 24, 16,  8,  0, 57, 49, 41, 33, 25, 17,
+          9,  1, 58, 50, 42, 34, 26, 18, 10,  2, 59, 51, 43, 35,
+         62, 54, 46, 38, 30, 22, 14,  6, 61, 53, 45, 37, 29, 21,
+         13,  5, 60, 52, 44, 36, 28, 20, 12,  4, 27, 19, 11,  3]
+    __left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
+    __pc2 = [13, 16, 10, 23,  0,  4,2, 27, 14,  5, 20,  9,
+        22, 18, 11,  3, 25,  7,        15,  6, 26, 19, 12,  1,
+        40, 51, 30, 36, 46, 54,        29, 39, 50, 44, 32, 47,
+        43, 48, 38, 55, 33, 52,        45, 41, 49, 35, 28, 31]
+    __ip = [57, 49, 41, 33, 25, 17, 9,  1,     59, 51, 43, 35, 27, 19, 11, 3,
+        61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
+        56, 48, 40, 32, 24, 16, 8,  0, 58, 50, 42, 34, 26, 18, 10, 2,
+        60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
+    __expansion_table = [31,  0,  1,  2,  3,  4, 3,  4,  5,  6,  7,  8,
+         7,  8,  9, 10, 11, 12,11, 12, 13, 14, 15, 16,
+        15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
+        23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31,  0]
+    __sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
+         0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
+         4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
+         15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
+        [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
+         3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
+         0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
+         13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
+        [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
+         13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
+         13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
+         1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
+        [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
+         13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
+         10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
+         3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
+        [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
+         14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
+         4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
+         11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
+        [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
+         10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
+         9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
+         4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
+        [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
+         13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
+         1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
+         6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
+        [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
+         1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
+         7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
+         2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
+    __p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
+        4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
+    __fp = [39,  7, 47, 15, 55, 23, 63, 31,38,  6, 46, 14, 54, 22, 62, 30,
+        37,  5, 45, 13, 53, 21, 61, 29,36,  4, 44, 12, 52, 20, 60, 28,
+        35,  3, 43, 11, 51, 19, 59, 27,34,  2, 42, 10, 50, 18, 58, 26,
+        33,  1, 41,  9, 49, 17, 57, 25,32,  0, 40,  8, 48, 16, 56, 24]
+    # Type of crypting being done
+    ENCRYPT =  0x00
+    DECRYPT =  0x01
+    def __init__(self, key, mode=ECB, IV=None):
+        if len(key) != 8:
+            raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
+        self.block_size = 8
+        self.key_size = 8
+        self.__padding = ''
+        self.setMode(mode)
+        if IV:
+            self.setIV(IV)
+        self.L = []
+        self.R = []
+        self.Kn = [ [0] * 48 ] * 16    # 16 48-bit keys (K1 - K16)
+        self.final = []
+        self.setKey(key)
+    def getKey(self):
+        return self.__key
+    def setKey(self, key):
+        self.__key = key
+        self.__create_sub_keys()
+    def getMode(self):
+        return self.__mode
+    def setMode(self, mode):
+        self.__mode = mode
+    def getIV(self):
+        return self.__iv
+    def setIV(self, IV):
+        if not IV or len(IV) != self.block_size:
+            raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
+        self.__iv = IV
+    def getPadding(self):
+        return self.__padding
+    def __String_to_BitList(self, data):
+        l = len(data) * 8
+        result = [0] * l
+        pos = 0
+        for c in data:
+            i = 7
+            ch = ord(c)
+            while i >= 0:
+                if ch & (1 << i) != 0:
+                    result[pos] = 1
+                else:
+                    result[pos] = 0
+                pos += 1
+                i -= 1
+        return result
+    def __BitList_to_String(self, data):
+        result = ''
+        pos = 0
+        c = 0
+        while pos < len(data):
+            c += data[pos] << (7 - (pos % 8))
+            if (pos % 8) == 7:
+                result += chr(c)
+                c = 0
+            pos += 1
+        return result
+    def __permutate(self, table, block):
+        return [block[x] for x in table]
+    def __create_sub_keys(self):
+        key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
+        i = 0
+        self.L = key[:28]
+        self.R = key[28:]
+        while i < 16:
+            j = 0
+            while j < Des.__left_rotations[i]:
+                self.L.append(self.L[0])
+                del self.L[0]
+                self.R.append(self.R[0])
+                del self.R[0]
+                j += 1
+            self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
+            i += 1
+    def __des_crypt(self, block, crypt_type):
+        block = self.__permutate(Des.__ip, block)
+        self.L = block[:32]
+        self.R = block[32:]
+        if crypt_type == Des.ENCRYPT:
+            iteration = 0
+            iteration_adjustment = 1
+        else:
+            iteration = 15
+            iteration_adjustment = -1
+        i = 0
+        while i < 16:
+            tempR = self.R[:]
+            self.R = self.__permutate(Des.__expansion_table, self.R)
+            self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
+            B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
+            j = 0
+            Bn = [0] * 32
+            pos = 0
+            while j < 8:
+                m = (B[j][0] << 1) + B[j][5]
+                n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
+                v = Des.__sbox[j][(m << 4) + n]
+                Bn[pos] = (v & 8) >> 3
+                Bn[pos + 1] = (v & 4) >> 2
+                Bn[pos + 2] = (v & 2) >> 1
+                Bn[pos + 3] = v & 1
+                pos += 4
+                j += 1
+            self.R = self.__permutate(Des.__p, Bn)
+            self.R = [x ^ y for x, y in zip(self.R, self.L)]
+            self.L = tempR
+            i += 1
+            iteration += iteration_adjustment
+        self.final = self.__permutate(Des.__fp, self.R + self.L)
+        return self.final
+    def crypt(self, data, crypt_type):
+        if not data:
+            return ''
+        if len(data) % self.block_size != 0:
+            if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
+                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
+            if not self.getPadding():
+                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
+            else:
+                data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
+        if self.getMode() == CBC:
+            if self.getIV():
+                iv = self.__String_to_BitList(self.getIV())
+            else:
+                raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
+        i = 0
+        dict = {}
+        result = []
+        while i < len(data):
+            block = self.__String_to_BitList(data[i:i+8])
+            if self.getMode() == CBC:
+                if crypt_type == Des.ENCRYPT:
+                    block = [x ^ y for x, y in zip(block, iv)]
+                processed_block = self.__des_crypt(block, crypt_type)
+                if crypt_type == Des.DECRYPT:
+                    processed_block = [x ^ y for x, y in zip(processed_block, iv)]
+                    iv = block
+                else:
+                    iv = processed_block
+            else:
+                processed_block = self.__des_crypt(block, crypt_type)
+            result.append(self.__BitList_to_String(processed_block))
+            i += 8
+        if crypt_type == Des.DECRYPT and self.getPadding():
+            s = result[-1]
+            while s[-1] == self.getPadding():
+                s = s[:-1]
+            result[-1] = s
+        return ''.join(result)
+    def encrypt(self, data, pad=''):
+        self.__padding = pad
+        return self.crypt(data, Des.ENCRYPT)
+    def decrypt(self, data, pad=''):
+        self.__padding = pad
+        return self.crypt(data, Des.DECRYPT)
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo
new file mode 100644 (file)
index 0000000..170fe36
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd
new file mode 100644 (file)
index 0000000..6acad6c
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo
new file mode 100644 (file)
index 0000000..dc65ec6
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo
new file mode 100644 (file)
index 0000000..24ae574
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo
new file mode 100644 (file)
index 0000000..593d23c
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo
new file mode 100644 (file)
index 0000000..23a84f8
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/kdictproxy.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/kdictproxy.pyo
new file mode 100644 (file)
index 0000000..d313a3f
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/kdictproxy.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo
new file mode 100644 (file)
index 0000000..869f005
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo
new file mode 100644 (file)
index 0000000..bea5e11
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo differ
diff --git a/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo
new file mode 100644 (file)
index 0000000..d276bd5
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo differ
index a043ceb914738700473d4fa90e760f939a4f093a..fc1bd10791e80f870ab2a88e45c411341fe00922 100644 (file)
Binary files a/Calibre_Plugins/ignobleepub_plugin.zip and b/Calibre_Plugins/ignobleepub_plugin.zip differ
diff --git a/Calibre_Plugins/ignobleepub_plugin/Ignobleepub-README.txt b/Calibre_Plugins/ignobleepub_plugin/Ignobleepub-README.txt
deleted file mode 100644 (file)
index 8ec9d1e..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-Ignoble Epub DeDRM - ignobleepub_vXX_plugin.zip
-Requires Calibre version 0.6.44 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.
-
-This plugin is meant to decrypt Barnes & Noble Epubs that are protected
-with Adobe's Adept encryption. It is meant to function without having to install
-any dependencies... other than having Calibre installed, of course. It will still
-work if you have Python and PyCrypto already installed, but they aren't necessary.
-
-Installation:
-
-Go to Calibre's Preferences page... click on the Plugins button. Use the file
-dialog button to select the plugin's zip file (ignobleepub_vXX_plugin.zip) and
-click the 'Add' button. you're done.
-
-Configuration:
-
-1) The easiest way to configure the plugin is to enter your name (Barnes & Noble account
-name) and credit card number (the one used to purchase the books) into the plugin's
-customization window. It's the same info you would enter into the ignoblekeygen script.
-Highlight the plugin (Ignoble Epub DeDRM) and click the "Customize Plugin" button on
-Calibre's Preferences->Plugins page. Enter the name and credit card number separated
-by a comma: Your Name,1234123412341234
-
-If you've purchased books with more than one credit card, separate that other info with
-a colon: Your Name,1234123412341234:Other Name,2345234523452345
-
-** NOTE ** The above method is your only option if you don't have/can't run the original
-I <3 Cabbages scripts on your particular machine.
-
-** NOTE ** Your credit card number will be on display in Calibre's Plugin configuration
-page when using the above method. If other people have access to your computer,
-you may want to use the second configuration method below.
-
-2) If you already have keyfiles generated with I <3 Cabbages' ignoblekeygen.pyw
-script, you can put those keyfiles into Calibre's configuration directory. The easiest
-way to find the correct directory is to go to Calibre's Preferences page... click
-on the 'Miscellaneous' button (looks like a gear),  and then click the 'Open Calibre
-configuration directory' button. Paste your keyfiles in there. Just make sure that
-they have different names and are saved with the '.b64' extension (like the ignoblekeygen
-script produces). This directory isn't touched when upgrading Calibre, so it's quite safe
-to leave then there.
-
-All keyfiles from method 2 and all data entered from method 1 will be used to attempt
-to decrypt a book. You can use method 1 or method 2, or a combination of both.
-
-Troubleshooting:
-
-If you find that it's not working for you (imported epubs still have DRM), you can
-save a lot of time and trouble by trying to add the epub to Calibre with the command
-line tools. This will print out a lot of helpful debugging info that can be copied into
-any online help requests. I'm going to ask you to do it first, anyway, so you might
-as well get used to it. ;)
-
-Open a command prompt (terminal) and change to the directory where the ebook you're
-trying to import resides. Then type the command "calibredb add your_ebook.epub".
-Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the
-filename of your book is. Copy the resulting output and paste it into any online
-help request you make.
-
-** Note: the Mac version of Calibre doesn't install the command line tools by default.
-If you go to the 'Preferences' page and click on the miscellaneous button, you'll
-see the option to install the command line tools.
index 5147f06d10bd20cdd536f409f36de64dd9e01366..91d472f1e00775712c63b508ea9e92ea28db5287 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# ignobleepub_v01_plugin.py
+# ignobleepub_plugin.py
 # Released under the terms of the GNU General Public Licence, version 3 or
 # later.  <http://www.gnu.org/licenses/>
 #
@@ -41,7 +41,9 @@
 #
 #
 # Revision history:
-#   0.1 - Initial release
+#   0.1.0 - Initial release
+#   0.1.1 - Allow Windows users to make use of openssl if they have it installed.
+#          - Incorporated SomeUpdates zipfix routine.
 
 
 """
@@ -77,7 +79,10 @@ def _load_crypto_libcrypto():
         Structure, c_ulong, create_string_buffer, cast
     from ctypes.util import find_library
 
-    libcrypto = find_library('crypto')
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
     if libcrypto is None:
         raise IGNOBLEError('libcrypto not found')
     libcrypto = CDLL(libcrypto)
@@ -261,7 +266,7 @@ 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, 0)
+    version                 = (0, 1, 1)
     minimum_calibre_version = (0, 6, 44)  # Compiled python libraries cannot be imported in earlier versions.
     file_types              = set(['epub'])
     on_import               = True
@@ -277,13 +282,12 @@ class IgnobleDeDRM(FileTypePlugin):
         # Add the included pycrypto import directory for Windows users.
         pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
         ppath = os.path.join(self.sys_insertion_path, pdir)
-        #sys.path.insert(0, ppath)
         sys.path.append(ppath)
         
         AES, AES2 = _load_crypto()
         
         if AES == None or AES2 == None:
-            # Failed to load libcrypto or PyCrypto... Adobe Epubs can\'t be decrypted.'
+            # Failed to load libcrypto or PyCrypto... Adobe Epubs can't be decrypted.'
             sys.path.remove(ppath)
             raise IGNOBLEError('IgnobleEpub - Failed to load crypto libs.')
             return
@@ -337,10 +341,19 @@ class IgnobleDeDRM(FileTypePlugin):
         # Attempt to decrypt epub with each encryption key (generated or provided).
         for userkey in userkeys:
             # Create a TemporaryPersistent file to work with.
+            # Check original epub archive for zip errors.
+            import zipfix
+            inf = self.temporary_file('.epub')
+            try:
+                fr = zipfix.fixZip(path_to_ebook, inf.name)
+                fr.fix()
+            except Exception, e:
+                raise Exception(e)
+                return
             of = self.temporary_file('.epub')
         
             # Give the user key, ebook and TemporaryPersistent file to the Stripper function.
-            result = plugin_main(userkey, path_to_ebook, of.name)
+            result = plugin_main(userkey, inf.name, of.name)
         
             # Ebook is not a B&N Adept epub... do nothing and pass it on.
             # This allows a non-encrypted epub to be imported without error messages.
@@ -372,4 +385,4 @@ class IgnobleDeDRM(FileTypePlugin):
         
         
     def customization_help(self, gui=False):
-        return 'Enter B&N Account name and CC# (separate name and CC# with a comma)'
\ No newline at end of file
+        return 'Enter B&N Account name and CC# (separate name and CC# with a comma)'
diff --git a/Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd b/Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd
new file mode 100644 (file)
index 0000000..46a60a5
Binary files /dev/null and b/Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd differ
diff --git a/Calibre_Plugins/ignobleepub_plugin/zipfix.py b/Calibre_Plugins/ignobleepub_plugin/zipfix.py
new file mode 100644 (file)
index 0000000..40c41d2
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+
+import sys
+import zlib
+import zipfile
+import os
+import os.path
+import getopt
+from struct import unpack
+
+
+_FILENAME_LEN_OFFSET = 26
+_EXTRA_LEN_OFFSET = 28
+_FILENAME_OFFSET = 30
+_MAX_SIZE = 64 * 1024
+
+class fixZip:
+    def __init__(self, zinput, zoutput):
+        self.inzip = zipfile.ZipFile(zinput,'r')
+        self.outzip = zipfile.ZipFile(zoutput,'w')
+        # open the input zip for reading only as a raw file
+       self.bzf = file(zinput,'rb')
+        
+    def getlocalname(self, zi):
+        local_header_offset = zi.header_offset
+        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
+        leninfo = self.bzf.read(2)
+        local_name_length, = unpack('<H', leninfo)
+        self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
+        local_name = self.bzf.read(local_name_length)
+        return local_name
+
+    def uncompress(self, cmpdata):
+        dc = zlib.decompressobj(-15)
+        data = ''
+        while len(cmpdata) > 0:
+            if len(cmpdata) > _MAX_SIZE :
+                newdata = cmpdata[0:_MAX_SIZE]
+                cmpdata = cmpdata[_MAX_SIZE:]
+            else:
+                newdata = cmpdata
+                cmpdata = ''
+            newdata = dc.decompress(newdata)
+            unprocessed = dc.unconsumed_tail
+            if len(unprocessed) == 0:
+                newdata += dc.flush()
+            data += newdata
+            cmpdata += unprocessed
+            unprocessed = ''
+        return data
+
+    def getfiledata(self, zi):
+        # get file name length and exta data length to find start of file data
+        local_header_offset = zi.header_offset
+
+        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
+        leninfo = self.bzf.read(2)
+        local_name_length, = unpack('<H', leninfo)
+
+        self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
+        exinfo = self.bzf.read(2)
+        extra_field_length, = unpack('<H', exinfo)
+
+        self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
+        data = None
+
+        # if not compressed we are good to go
+        if zi.compress_type == zipfile.ZIP_STORED:
+            data = self.bzf.read(zi.file_size)
+
+        # if compressed we must decompress it using zlib
+        if zi.compress_type == zipfile.ZIP_DEFLATED:
+            cmpdata = self.bzf.read(zi.compress_size)
+            data = self.uncompress(cmpdata)
+
+        return data
+
+        
+
+    def fix(self):
+        # get the zipinfo for each member of the input archive
+        # and copy member over to output archive
+        # if problems exist with local vs central filename, fix them
+
+        for i, zinfo in enumerate(self.inzip.infolist()):
+            data = None
+            nzinfo = zinfo
+
+            try: 
+                data = self.inzip.read(zinfo)
+            except zipfile.BadZipfile or zipfile.error:
+                local_name = self.getlocalname(zinfo)
+                data = self.getfiledata(zinfo)
+                nzinfo.filename = local_name
+
+            nzinfo.date_time = zinfo.date_time
+            nzinfo.compress_type = zinfo.compress_type
+            nzinfo.flag_bits = 0
+            nzinfo.internal_attr = 0
+            self.outzip.writestr(nzinfo,data)
+
+        self.bzf.close()
+        self.inzip.close()
+        self.outzip.close()
+
+
+def usage():
+    print """usage: zipfix.py inputzip outputzip
+     inputzip is the source zipfile to fix
+     outputzip is the fixed zip archive
+    """
+    
+
+def main(argv=sys.argv):
+    if len(argv)!=3:
+        usage()
+        return 1
+    infile = None
+    outfile = None
+    infile = argv[1]
+    outfile = argv[2]
+    if not os.path.exists(infile):
+        print "Error: Input Zip File does not exist"
+        return 1
+    try:
+        fr = fixZip(infile, outfile)
+        fr.fix()
+        return 0
+    except Exception, e:
+        print "Error Occurred ", e
+        return 2
+
+if __name__ == '__main__' :
+    sys.exit(main())
+
+
index 4b188546baffa152b29771e1327ccdbe005ab722..31ffcde98994648d1e0bfb34e3b3d722c84cc592 100644 (file)
Binary files a/Calibre_Plugins/ineptepub_plugin.zip and b/Calibre_Plugins/ineptepub_plugin.zip differ
diff --git a/Calibre_Plugins/ineptepub_plugin/Ineptepub-README.txt b/Calibre_Plugins/ineptepub_plugin/Ineptepub-README.txt
deleted file mode 100644 (file)
index df7e31b..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-Inept Epub DeDRM - ineptepub_vXX_plugin.zip
-Requires Calibre version 0.6.44 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.
-
-This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected
-with Adobe's Adept encryption. It is meant to function without having to install
-any dependencies... other than having Calibre installed, of course. It will still
-work if you have Python and PyCrypto already installed, but they aren't necessary.
-
-Installation:
-
-Go to Calibre's Preferences page... click on the Plugins button. Use the file
-dialog button to select the plugin's zip file (ineptepub_vXX_plugin.zip) and
-click the 'Add' button. you're done.
-
-Configuration:
-
-When first run, the plugin will attempt to find your Adobe Digital Editions installation
-(on Windows and Mac OS's). If successful, it will create an 'adeptkey.der' file and
-save it in Calibre's configuration directory. It will use that file on subsequent runs.
-If there are already '*.der' files in the directory, the plugin won't attempt to
-find the Adobe Digital Editions installation installation.
-
-So if you have Adobe Digital Editions installation installed on the same machine as Calibre...
-you are ready to go. If not... keep reading.
-
-If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script,
-you can put those keyfiles in Calibre's configuration directory. The easiest
-way to find the correct directory is to go to Calibre's Preferences page... click
-on the 'Miscellaneous' button (looks like a gear),  and then click the 'Open Calibre
-configuration directory' button. Paste your keyfiles in there. Just make sure that
-they have different names and are saved with the '.der' extension (like the ineptkey
-script produces). This directory isn't touched when upgrading Calibre, so it's quite
-safe to leave them there.
-
-Since there is no Linux version of Adobe Digital Editions, Linux users will have to
-obtain a keyfile through other methods and put the file in Calibre's configuration directory.
-
-All keyfiles with a '.der' extension found in Calibre's configuration directory will
-be used to attempt to decrypt a book.
-
-** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
-
-Troubleshooting:
-
-If you find that it's not working for you (imported epubs still have DRM), you can
-save a lot of time and trouble by trying to add the epub to Calibre with the command
-line tools. This will print out a lot of helpful debugging info that can be copied into
-any online help requests. I'm going to ask you to do it first, anyway, so you might
-as well get used to it. ;)
-
-Open a command prompt (terminal) and change to the directory where the ebook you're
-trying to import resides. Then type the command "calibredb add your_ebook.epub".
-Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the
-filename of your book is. Copy the resulting output and paste it into any online
-help request you make.
-
-** Note: the Mac version of Calibre doesn't install the command line tools by default.
-If you go to the 'Preferences' page and click on the miscellaneous button, you'll
-see the option to install the command line tools.
\ No newline at end of file
index fa63c3753cc00fd1cdcbd5886c3c1e24123f7b77..9c7f6cfd560afac8537c8a1a2b644506bdfa31ca 100644 (file)
@@ -19,24 +19,86 @@ class ADEPTError(Exception):
 if iswindows:
     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, c_size_t, memmove
+        string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
+        c_long, c_ulong
+
     from ctypes.wintypes import LPVOID, DWORD, BOOL
     import _winreg as winreg
+
+    def _load_crypto_libcrypto():
+        from ctypes.util import find_library
+        libcrypto = find_library('libeay32')
+        if libcrypto is None:
+            raise ADEPTError('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)
     
-    try:
-        from Crypto.Cipher import AES as _aes
-    except ImportError:
-        _aes = None
+        def F(restype, name, argtypes):
+            func = getattr(libcrypto, name)
+            func.restype = restype
+            func.argtypes = argtypes
+            return func
     
+        AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
+                                [c_char_p, c_int, AES_KEY_p])
+        AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
+                            [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
+                             c_int])
+        class AES(object):
+            def __init__(self, userkey):
+                self._blocksize = len(userkey)
+                if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+                    raise ADEPTError('AES improper key used')
+                key = self._key = AES_KEY()
+                rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
+                if rv < 0:
+                    raise ADEPTError('Failed to initialize AES key')
+            def decrypt(self, data):
+                out = create_string_buffer(len(data))
+                iv = ("\x00" * self._blocksize)
+                rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
+                if rv == 0:
+                    raise ADEPTError('AES decryption failed')
+                return out.raw
+        return AES
+
+    def _load_crypto_pycrypto():
+        from Crypto.Cipher import AES as _AES
+        class AES(object):
+            def __init__(self, key):
+                self._aes = _AES.new(key, _AES.MODE_CBC)
+            def decrypt(self, data):
+                return self._aes.decrypt(data)
+        return AES
+
+    def _load_crypto():
+        AES = None
+        for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
+            try:
+                AES = loader()
+                break
+            except (ImportError, ADEPTError):
+                pass
+        return AES
+
+    AES = _load_crypto()
+
+
     DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
     PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
-    
+
     MAX_PATH = 255
-    
+
     kernel32 = windll.kernel32
     advapi32 = windll.advapi32
     crypt32 = windll.crypt32
-    
+
     def GetSystemDirectory():
         GetSystemDirectoryW = kernel32.GetSystemDirectoryW
         GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
@@ -47,7 +109,7 @@ if iswindows:
             return buffer.value
         return GetSystemDirectory
     GetSystemDirectory = GetSystemDirectory()
-    
+
     def GetVolumeSerialNumber():
         GetVolumeInformationW = kernel32.GetVolumeInformationW
         GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
@@ -61,7 +123,7 @@ if iswindows:
             return vsn.value
         return GetVolumeSerialNumber
     GetVolumeSerialNumber = GetVolumeSerialNumber()
-    
+
     def GetUserName():
         GetUserNameW = advapi32.GetUserNameW
         GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
@@ -75,11 +137,11 @@ if iswindows:
             return buffer.value.encode('utf-16-le')[::2]
         return GetUserName
     GetUserName = GetUserName()
-    
+
     PAGE_EXECUTE_READWRITE = 0x40
     MEM_COMMIT  = 0x1000
     MEM_RESERVE = 0x2000
-    
+
     def VirtualAlloc():
         _VirtualAlloc = kernel32.VirtualAlloc
         _VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD]
@@ -89,9 +151,9 @@ if iswindows:
             return _VirtualAlloc(addr, size, alloctype, protect)
         return VirtualAlloc
     VirtualAlloc = VirtualAlloc()
-    
+
     MEM_RELEASE = 0x8000
-    
+
     def VirtualFree():
         _VirtualFree = kernel32.VirtualFree
         _VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
@@ -100,22 +162,22 @@ if iswindows:
             return _VirtualFree(addr, size, freetype)
         return VirtualFree
     VirtualFree = VirtualFree()
-    
+
     class NativeFunction(object):
         def __init__(self, restype, argtypes, insns):
             self._buf = buf = VirtualAlloc(None, len(insns))
             memmove(buf, insns, len(insns))
             ftype = CFUNCTYPE(restype, *argtypes)
             self._native = ftype(buf)
-    
+
         def __call__(self, *args):
             return self._native(*args)
-    
+
         def __del__(self):
             if self._buf is not None:
                 VirtualFree(self._buf)
                 self._buf = None
-    
+
     if struct.calcsize("P") == 4:
         CPUID0_INSNS = (
             "\x53"             # push   %ebx
@@ -157,7 +219,7 @@ if iswindows:
             "\x5b"             # pop    %rbx
             "\xc3"             # retq
         )
-    
+
     def cpuid0():
         _cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
         buf = create_string_buffer(12)
@@ -166,14 +228,14 @@ if iswindows:
             return buf.raw
         return cpuid0
     cpuid0 = cpuid0()
-    
+
     cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
-    
+
     class DataBlob(Structure):
         _fields_ = [('cbData', c_uint),
                     ('pbData', c_void_p)]
     DataBlob_p = POINTER(DataBlob)
-    
+
     def CryptUnprotectData():
         _CryptUnprotectData = crypt32.CryptUnprotectData
         _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
@@ -191,10 +253,14 @@ if iswindows:
             return string_at(outdata.pbData, outdata.cbData)
         return CryptUnprotectData
     CryptUnprotectData = CryptUnprotectData()
-    
+
     def retrieve_key():
-        if _aes is None:
-            raise ADEPTError("Couldn\'t load PyCrypto")
+        if AES is None:
+            tkMessageBox.showerror(
+                "ADEPT Key",
+                "This script requires PyCrypto or OpenSSL which must be installed "
+                "separately.  Read the top-of-script comment for details.")
+            return False
         root = GetSystemDirectory().split('\\')[0] + '\\'
         serial = GetVolumeSerialNumber(root)
         vendor = cpuid0()
@@ -236,7 +302,8 @@ if iswindows:
         if userkey is None:
             raise ADEPTError('Could not locate privateLicenseKey')
         userkey = userkey.decode('base64')
-        userkey = _aes.new(keykey, _aes.MODE_CBC).decrypt(userkey)
+        aes = AES(keykey)
+        userkey = aes.decrypt(userkey)
         userkey = userkey[26:-ord(userkey[-1])]
         return userkey
 
index c9cc4bda034e58e385aa3012f021521b3d4451d0..ea52f0a8670d49984f7a57e316a539ed57af9121 100644 (file)
@@ -1,6 +1,6 @@
 #! /usr/bin/python
 
-# ineptepub_v01_plugin.py
+# ineptepub_plugin.py
 # Released under the terms of the GNU General Public Licence, version 3 or
 # later.  <http://www.gnu.org/licenses/>
 #
@@ -41,6 +41,8 @@
 #
 # Revision history:
 #   0.1 - Initial release
+#   0.1.1 - Allow Windows users to make use of openssl if they have it installed.
+#          - Incorporated SomeUpdates zipfix routine.
 
 
 """
@@ -76,7 +78,10 @@ def _load_crypto_libcrypto():
         Structure, c_ulong, create_string_buffer, cast
     from ctypes.util import find_library
 
-    libcrypto = find_library('crypto')
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
     if libcrypto is None:
         raise ADEPTError('libcrypto not found')
     libcrypto = CDLL(libcrypto)
@@ -358,7 +363,7 @@ 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, 0)
+    version                 = (0, 1, 1)
     minimum_calibre_version = (0, 6, 44)  # Compiled python libraries cannot be imported in earlier versions.
     file_types              = set(['epub'])
     on_import               = True
@@ -376,7 +381,6 @@ class IneptDeDRM(FileTypePlugin):
         # Add the included Carbon import directory for Mac users.
         pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
         ppath = os.path.join(self.sys_insertion_path, pdir)
-        #sys.path.insert(0, ppath)
         sys.path.append(ppath)
             
         AES, RSA = _load_crypto()
@@ -433,10 +437,19 @@ class IneptDeDRM(FileTypePlugin):
         # Attempt to decrypt epub with each encryption key found.
         for userkey in userkeys:
             # Create a TemporaryPersistent file to work with.
+            # Check original epub archive for zip errors.
+            import zipfix
+            inf = self.temporary_file('.epub')
+            try:
+                fr = zipfix.fixZip(path_to_ebook, inf.name)
+                fr.fix()
+            except Exception, e:
+                raise Exception(e)
+                return
             of = self.temporary_file('.epub')
         
             # Give the user key, ebook and TemporaryPersistent file to the plugin_main function.
-            result = plugin_main(userkey, path_to_ebook, of.name)
+            result = plugin_main(userkey, inf.name, of.name)
         
             # Ebook is not an Adobe Adept epub... do nothing and pass it on.
             # This allows a non-encrypted epub to be imported without error messages.
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo
new file mode 100644 (file)
index 0000000..be3ab04
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo differ
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo
new file mode 100644 (file)
index 0000000..5a82134
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo differ
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo
new file mode 100644 (file)
index 0000000..c5a2ee0
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo differ
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo
new file mode 100644 (file)
index 0000000..0f5325d
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo differ
diff --git a/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd
new file mode 100644 (file)
index 0000000..46a60a5
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd differ
diff --git a/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd
new file mode 100644 (file)
index 0000000..10d91f8
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd differ
diff --git a/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Random/OSRNG/winrandom.pyd b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Random/OSRNG/winrandom.pyd
new file mode 100644 (file)
index 0000000..d877a0c
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Random/OSRNG/winrandom.pyd differ
diff --git a/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Util/_counter.pyd b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Util/_counter.pyd
new file mode 100644 (file)
index 0000000..bb7de6c
Binary files /dev/null and b/Calibre_Plugins/ineptepub_plugin/windows/Crypto/Util/_counter.pyd differ
diff --git a/Calibre_Plugins/ineptepub_plugin/zipfix.py b/Calibre_Plugins/ineptepub_plugin/zipfix.py
new file mode 100644 (file)
index 0000000..40c41d2
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+
+import sys
+import zlib
+import zipfile
+import os
+import os.path
+import getopt
+from struct import unpack
+
+
+_FILENAME_LEN_OFFSET = 26
+_EXTRA_LEN_OFFSET = 28
+_FILENAME_OFFSET = 30
+_MAX_SIZE = 64 * 1024
+
+class fixZip:
+    def __init__(self, zinput, zoutput):
+        self.inzip = zipfile.ZipFile(zinput,'r')
+        self.outzip = zipfile.ZipFile(zoutput,'w')
+        # open the input zip for reading only as a raw file
+       self.bzf = file(zinput,'rb')
+        
+    def getlocalname(self, zi):
+        local_header_offset = zi.header_offset
+        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
+        leninfo = self.bzf.read(2)
+        local_name_length, = unpack('<H', leninfo)
+        self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
+        local_name = self.bzf.read(local_name_length)
+        return local_name
+
+    def uncompress(self, cmpdata):
+        dc = zlib.decompressobj(-15)
+        data = ''
+        while len(cmpdata) > 0:
+            if len(cmpdata) > _MAX_SIZE :
+                newdata = cmpdata[0:_MAX_SIZE]
+                cmpdata = cmpdata[_MAX_SIZE:]
+            else:
+                newdata = cmpdata
+                cmpdata = ''
+            newdata = dc.decompress(newdata)
+            unprocessed = dc.unconsumed_tail
+            if len(unprocessed) == 0:
+                newdata += dc.flush()
+            data += newdata
+            cmpdata += unprocessed
+            unprocessed = ''
+        return data
+
+    def getfiledata(self, zi):
+        # get file name length and exta data length to find start of file data
+        local_header_offset = zi.header_offset
+
+        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
+        leninfo = self.bzf.read(2)
+        local_name_length, = unpack('<H', leninfo)
+
+        self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
+        exinfo = self.bzf.read(2)
+        extra_field_length, = unpack('<H', exinfo)
+
+        self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
+        data = None
+
+        # if not compressed we are good to go
+        if zi.compress_type == zipfile.ZIP_STORED:
+            data = self.bzf.read(zi.file_size)
+
+        # if compressed we must decompress it using zlib
+        if zi.compress_type == zipfile.ZIP_DEFLATED:
+            cmpdata = self.bzf.read(zi.compress_size)
+            data = self.uncompress(cmpdata)
+
+        return data
+
+        
+
+    def fix(self):
+        # get the zipinfo for each member of the input archive
+        # and copy member over to output archive
+        # if problems exist with local vs central filename, fix them
+
+        for i, zinfo in enumerate(self.inzip.infolist()):
+            data = None
+            nzinfo = zinfo
+
+            try: 
+                data = self.inzip.read(zinfo)
+            except zipfile.BadZipfile or zipfile.error:
+                local_name = self.getlocalname(zinfo)
+                data = self.getfiledata(zinfo)
+                nzinfo.filename = local_name
+
+            nzinfo.date_time = zinfo.date_time
+            nzinfo.compress_type = zinfo.compress_type
+            nzinfo.flag_bits = 0
+            nzinfo.internal_attr = 0
+            self.outzip.writestr(nzinfo,data)
+
+        self.bzf.close()
+        self.inzip.close()
+        self.outzip.close()
+
+
+def usage():
+    print """usage: zipfix.py inputzip outputzip
+     inputzip is the source zipfile to fix
+     outputzip is the fixed zip archive
+    """
+    
+
+def main(argv=sys.argv):
+    if len(argv)!=3:
+        usage()
+        return 1
+    infile = None
+    outfile = None
+    infile = argv[1]
+    outfile = argv[2]
+    if not os.path.exists(infile):
+        print "Error: Input Zip File does not exist"
+        return 1
+    try:
+        fr = fixZip(infile, outfile)
+        fr.fix()
+        return 0
+    except Exception, e:
+        print "Error Occurred ", e
+        return 2
+
+if __name__ == '__main__' :
+    sys.exit(main())
+
+
index c2f4a717b4a6650492cf83374582f21897a54e58..a8dbe1c4f8cf560acfe3ef84fb1db307272b401b 100644 (file)
Binary files a/Calibre_Plugins/k4mobidedrm_plugin.zip and b/Calibre_Plugins/k4mobidedrm_plugin.zip differ
index 6a5c07173cbd8f9ee6515632c34d3548a513fb67..4fc0337e6f381fc316a9fd8d0a20c8b5d6d694c2 100644 (file)
@@ -43,6 +43,7 @@ import sys
 import os, csv, getopt
 import binascii
 import zlib
+import re
 from struct import pack, unpack, unpack_from
 
 
@@ -115,9 +116,9 @@ def decode(data,map):
 
 
 # Parse the Kindle.info file and return the records as a list of key-values
-def parseKindleInfo():
+def parseKindleInfo(kInfoFile):
     DB = {}
-    infoReader = openKindleInfo()
+    infoReader = openKindleInfo(kInfoFile)
     infoReader.read(1)
     data = infoReader.read()
     if sys.platform.startswith('win'):
@@ -279,10 +280,10 @@ class MobiPeek:
 
 # DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
 # file to calculate the book pid.
-def getK4Pids(exth, title):
+def getK4Pids(exth, title, kInfoFile=None):
     global kindleDatabase
     try:
-        kindleDatabase = parseKindleInfo()
+        kindleDatabase = parseKindleInfo(kInfoFile)
     except Exception as message:
         print(message)
     
@@ -353,30 +354,49 @@ def getK4Pids(exth, title):
     raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
     return null
 
+def usage(progname):
+    print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
+    print "Usage:"
+    print "    %s [-k <kindle.info>] [-p <pidnums>] <infile> <outfile>  " % progname
+
 #
 # Main
 #   
 def main(argv=sys.argv):
     global kindleDatabase
     import mobidedrm
+    
+    progname = os.path.basename(argv[0])
+    kInfoFiles = []
+    pidnums = ""
+    
     print ('K4MobiDeDrm v%(__version__)s '
           'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
 
-    if len(argv)<3:
-        print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
-        print "Usage:"
-        print "    %s <infile> <outfile> [<pidnums>]" % argv[0]
-        return 1
-
-    if len(argv) == 4:
-        pidnums = argv[3]
-
-    if len(argv) == 3:
-        pidnums = "" 
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "k:p:")
+    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")
+            pidnums = a
 
     kindleDatabase = None
-    infile = argv[1]
-    outfile = argv[2]
+    infile = args[0]
+    outfile = args[1]
     try:
         # first try with K4PC/K4M
         ex = MobiPeek(infile)
@@ -394,8 +414,25 @@ def main(argv=sys.argv):
     else:
         file(outfile, 'wb').write(unlocked_file)
         return 0
-
-    # now try from the pid list
+    
+    # now try alternate kindle.info files
+    if kInfoFiles:
+        for infoFile in kInfoFiles:
+            kindleDatabase = None
+            try:
+                title = ex.getBookTitle()
+                exth = ex.getexthData()
+                pid = getK4Pids(exth, title, infoFile)
+                unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
+            except DrmException:
+                pass
+            except mobidedrm.DrmException:
+                pass
+            else:
+                file(outfile, 'wb').write(unlocked_file)
+                return 0            
+    
+    # Lastly, try from the pid list
     pids = pidnums.split(',')
     for pid in pids:
         try:
@@ -426,7 +463,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, 0, 1)   # The version number of this plugin
+        version             = (0, 1, 1)   # The version number of this plugin
         file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
         on_import           = True # Run this plugin during the import
         priority            = 200  # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -442,7 +479,27 @@ if not __name__ == "__main__" and inCalibre:
                 from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
             import mobidedrm
 
+            # Get supplied list of PIDs to try from plugin customization.
             pidnums = self.site_customization
+            
+            # Load any kindle info files (*.info) included Calibre's config directory.
+            kInfoFiles = []
+            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
 
             # first try with book specifc pid from K4PC or K4M
             try:
@@ -463,6 +520,25 @@ if not __name__ == "__main__" and inCalibre:
                 of.write(unlocked_file)
                 of.close()
                 return of.name
+            
+            # Now try alternate kindle info files
+            if kInfoFiles:
+                for infoFile in kInfoFiles:
+                    kindleDatabase = None 
+                    try:
+                        title = ex.getBookTitle()
+                        exth = ex.getexthData()
+                        pid = getK4Pids(exth, title, infoFile)
+                        unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
+                    except DrmException:
+                        pass
+                    except mobidedrm.DrmException:
+                        pass
+                    else:
+                        of = self.temporary_file('.mobi')
+                        of.write(unlocked_file)
+                        of.close()
+                        return of.name            
 
             # now try from the pid list
             pids = pidnums.split(',')
index cb13e5f09928fed64a272ba145dc8edab16824e2..977d81c50d8c4ca929a9345053ced019555c964b 100644 (file)
@@ -298,22 +298,25 @@ def CryptUnprotectData(encryptedData):
     return cleartext
 
 # Locate and open the .kindle-info file
-def openKindleInfo():
-    home = os.getenv('HOME')
-    cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
-    poll = p1.wait('wait')
-    results = p1.read()
-    reslst = results.split('\n')
-    kinfopath = 'NONE'
-    cnt = len(reslst)
-    for j in xrange(cnt):
-        resline = reslst[j]
-        pp = resline.find('.kindle-info')
-        if pp >= 0:
-            kinfopath = resline
-            break
-    if not os.path.exists(kinfopath):
-        raise K4MDrmException('Error: .kindle-info file can not be found')
-    return open(kinfopath,'r')
+def openKindleInfo(kInfoFile=None):
+    if kInfoFile == None:
+       home = os.getenv('HOME')
+       cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
+       cmdline = cmdline.encode(sys.getfilesystemencoding())
+       p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
+       poll = p1.wait('wait')
+       results = p1.read()
+       reslst = results.split('\n')
+       kinfopath = 'NONE'
+       cnt = len(reslst)
+       for j in xrange(cnt):
+           resline = reslst[j]
+           pp = resline.find('.kindle-info')
+           if pp >= 0:
+               kinfopath = resline
+               break
+       if not os.path.exists(kinfopath):
+           raise K4MDrmException('Error: .kindle-info file can not be found')
+       return open(kinfopath,'r')
+    else:
+        return open(kInfoFile, 'r')
\ No newline at end of file
index 777376ddcb2fd5aceca6e94cce6bbba574fb3471..337b992e4bd56369c0bd2ed8fce4217a0460176e 100644 (file)
@@ -101,7 +101,10 @@ CryptUnprotectData = CryptUnprotectData()
 #
 # Locate and open the Kindle.info file.
 #
-def openKindleInfo():
-    regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
-    path = winreg.QueryValueEx(regkey, 'Local AppData')[0] 
-    return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
+def openKindleInfo(kInfoFile=None):
+    if kInfoFile == None:
+        regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+        path = winreg.QueryValueEx(regkey, 'Local AppData')[0] 
+        return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
+    else:
+        return open(kInfoFile, 'r')
index 5ed58a52aeafc886838f1bdce2506b8657f3ac4c..eed1cceed1550b6a08b939925948e28eccce7cd9 100644 (file)
 #         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 'hearbeat', and is also quicker for long files.
+#  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
+#  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
 
-__version__ = '0.17'
+__version__ = '0.18'
 
 import sys
 import struct
@@ -127,10 +130,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
         if testflags & 1:
             num += getSizeOfTrailingDataEntry(ptr, size - num)
         testflags >>= 1
-    # Multibyte data, if present, is included in the encryption, so
-    # we do not need to check the low bit.
-    # if flags & 1:
-    #    num += (ord(ptr[size - num - 1]) & 0x3) + 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 DrmStripper:
@@ -181,9 +185,14 @@ class DrmStripper:
         return found_key
 
     def __init__(self, data_file, pid):
-        if checksumPid(pid[0:-2]) != pid:
-            raise DrmException("invalid PID checksum")
-        pid = pid[0:-2]
+        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]
@@ -206,6 +215,10 @@ class DrmStripper:
         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 5 (& 6?)
+            # 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:
@@ -282,44 +295,3 @@ def main(argv=sys.argv):
 
 if __name__ == "__main__":
     sys.exit(main())
-
-#if not __name__ == "__main__":
-if False:
-
-    # note a calibre plugin can not import code with another calibre plugin
-    # in it as it ends up registering two different plugins 
-    from calibre.customize import FileTypePlugin
-
-    class MobiDeDRM(FileTypePlugin):
-        name                = 'MobiDeDRM' # Name of the plugin
-        description         = 'Removes DRM from secure Mobi files'
-        supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
-        author              = 'The Dark Reverser' # The author of this plugin
-        version             = (0, 1, 7)   # The version number of this plugin
-        file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
-        on_import           = True # Run this plugin during the import
-
-        def run(self, path_to_ebook):
-            from calibre.gui2 import is_ok_to_use_qt
-            from PyQt4.Qt import QMessageBox
-            PID = self.site_customization
-            data_file = file(path_to_ebook, 'rb').read()
-            ar = PID.split(',')
-            for i in ar:
-                try:
-                    unlocked_file = DrmStripper(data_file, i).getResult()
-                except DrmException:
-                    if is_ok_to_use_qt():
-                        d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
-                        d.show()
-                        d.raise_()
-                        d.exec_()
-                    raise Exception("MobiDeDRM Plugin: Error decoding ebook")
-                else:
-                    of = self.temporary_file('.mobi')
-                    of.write(unlocked_file)
-                    of.close()
-                    return of.name
-
-        def customization_help(self, gui=False):
-            return 'Enter PID (separate multiple PIDs with comma)'
index 467d87ee1d31ca32940a2cbc02d48cc3191ab34a..0d995e790891beb365ed8affd103bb817cec65bb 100644 (file)
@@ -45,11 +45,20 @@ class MainDialog(Tkinter.Frame):
         self.outpath.insert(0, outname)
         button = Tkinter.Button(body, text="...", command=self.get_outpath)
         button.grid(row=1, column=2)
-
-        Tkinter.Label(body, text='Comma Separated List of 10 Character PIDs (no spaces)').grid(row=2, sticky=Tkconstants.E)
+        
+        Tkinter.Label(body, text='Kindle.info file (optional)').grid(row=2, sticky=Tkconstants.E)
+        self.altinfopath = Tkinter.Entry(body, width=50)
+        self.altinfopath.grid(row=2, column=1, sticky=sticky)
+        #cwd = os.getcwdu()
+        #cwd = cwd.encode('utf-8')
+        #self.altinfopath.insert(0, cwd)
+        button = Tkinter.Button(body, text="...", command=self.get_altinfopath)
+        button.grid(row=2, column=2)
+
+        Tkinter.Label(body, text='Comma Separated List of 10 Character PIDs (no spaces)').grid(row=3, sticky=Tkconstants.E)
         self.pidnums = Tkinter.StringVar()
         self.pidinfo = Tkinter.Entry(body, width=50, textvariable=self.pidnums)
-        self.pidinfo.grid(row=2, column=1, sticky=sticky)
+        self.pidinfo.grid(row=3, column=1, sticky=sticky)
 
         msg1 = 'Conversion Log \n\n'
         self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
@@ -100,16 +109,23 @@ class MainDialog(Tkinter.Frame):
         return
 
     # run as a subprocess via pipes and collect stdout
-    def mobirdr(self, infile, outfile, pidnums):
+    def mobirdr(self, infile, outfile, altinfopath, pidnums):
         # os.putenv('PYTHONUNBUFFERED', '1')
-        cmdline = 'python ./lib/k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
+        pidoption = ''
+        if pidnums and pidnums != '':
+            pidoption = ' -p "' + pidnums + '" '
+        infooption = ''
+        if altinfopath and altinfopath != '':
+            infooption = ' -k "' + altinfopath + '" '
+        cmdline = 'python ./lib/k4mobidedrm.py ' + pidoption + infooption + '"' + infile + '" "' + outfile + '"'
+        print cmdline
         if sys.platform.startswith('win'):
             search_path = os.environ['PATH']
             search_path = search_path.lower()
             if search_path.find('python') >= 0: 
-                cmdline = 'python lib\k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
+                cmdline = 'python lib\k4mobidedrm.py ' + pidoption + infooption + '"' + infile + '" "' + outfile + '"'
             else :
-                cmdline = 'lib\k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
+                cmdline = 'lib\k4mobidedrm.py ' + pidoption + infooption + '"' + infile + '" "' + outfile + '"'
 
         cmdline = cmdline.encode(sys.getfilesystemencoding())
         p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
@@ -141,6 +157,20 @@ class MainDialog(Tkinter.Frame):
             self.outpath.insert(0, outpath)
         return
 
+    def get_altinfopath(self):
+        cwd = os.getcwdu()
+        cwd = cwd.encode('utf-8')
+        altinfopath = tkFileDialog.askopenfilename(
+            parent=None, title='Select kindle.info File',
+            defaultextension='.prc', filetypes=[('Kindle Info', '.info'),
+                                                ('All Files', '.*')],
+            initialdir=cwd)
+        if altinfopath:
+            altinfopath = os.path.normpath(altinfopath)
+            self.altinfopath.delete(0, Tkconstants.END)
+            self.altinfopath.insert(0, altinfopath)
+        return
+
     def quitting(self):
         # kill any still running subprocess
         if self.p2 != None:
@@ -154,6 +184,7 @@ class MainDialog(Tkinter.Frame):
         self.sbotton.configure(state='disabled')
         mobipath = self.mobipath.get()
         outpath = self.outpath.get()
+        altinfopath = self.altinfopath.get()
         pidnums = self.pidinfo.get()
 
         if not mobipath or not os.path.exists(mobipath):
@@ -168,6 +199,10 @@ class MainDialog(Tkinter.Frame):
             self.status['text'] = 'Error specified output directory does not exist'
             self.sbotton.configure(state='normal')
             return
+        if altinfopath and not os.path.exists(altinfopath):
+            self.status['text'] = 'Specified kindle.info file does not exist'
+            self.sbotton.configure(state='normal')
+            return
         # default output file name to be input file name + '_nodrm.mobi'
         initname = os.path.splitext(os.path.basename(mobipath))[0]
         initname += '_nodrm.mobi' 
@@ -176,12 +211,13 @@ class MainDialog(Tkinter.Frame):
         log = 'Command = "python k4mobidedrm.py"\n'
         log += 'K4PC, K4M or Mobi Path = "'+ mobipath + '"\n'
         log += 'Output File = "' + outpath + '"\n'
+        log += 'Kindle.info file = "' + altinfopath + '"\n'
         log += 'PID list = "' + pidnums + '"\n'
         log += '\n\n'
         log += 'Please Wait ...\n\n'
         log = log.encode('utf-8')
         self.stext.insert(Tkconstants.END,log)
-        self.p2 = self.mobirdr(mobipath, outpath, pidnums)
+        self.p2 = self.mobirdr(mobipath, outpath, altinfopath, pidnums)
 
         # python does not seem to allow you to create
         # your own eventloop which every other gui does - strange 
index ce97ee37259d1bef759c96f1d38cd123b4067c65..71bf28ebe8907bc9abaf500d99879b84bb5a03e6 100644 (file)
@@ -13,11 +13,13 @@ hit the first '...' button to locate your DRM Kindle-style ebook
 
 3. Then hit the second '...' button to select an output directory for the unlocked file
 
-4. Then add in any PIDs you need from KindleV1, Kindle for iPhone/iPad/iPodTouch, or other single PID devices to the provided box as a comma separated list of 10 digit PID numbers.
+4. If you have multiple Kindle.Info files and would like to use one specific one, please hit the third "...' button to select it.  Note, if you only have one Kindle.Info file (like most users) this can and should be left blank.
+
+5. . Then add in any PIDs you need from KindleV1, Kindle for iPhone/iPad/iPodTouch, or other single PID devices to the provided box as a comma separated list of 10 digit PID numbers.
 
 If this is a Kindle for Mac or a Kindle for PC book then you can leave this box blank
 
-5.  hit the 'Start' button
+6.  hit the 'Start' button
 
 After a short delay, you should see progress in the Conversion Log window indicating is the unlocking was a success or failure.
 
index 6a5c07173cbd8f9ee6515632c34d3548a513fb67..4fc0337e6f381fc316a9fd8d0a20c8b5d6d694c2 100644 (file)
@@ -43,6 +43,7 @@ import sys
 import os, csv, getopt
 import binascii
 import zlib
+import re
 from struct import pack, unpack, unpack_from
 
 
@@ -115,9 +116,9 @@ def decode(data,map):
 
 
 # Parse the Kindle.info file and return the records as a list of key-values
-def parseKindleInfo():
+def parseKindleInfo(kInfoFile):
     DB = {}
-    infoReader = openKindleInfo()
+    infoReader = openKindleInfo(kInfoFile)
     infoReader.read(1)
     data = infoReader.read()
     if sys.platform.startswith('win'):
@@ -279,10 +280,10 @@ class MobiPeek:
 
 # DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
 # file to calculate the book pid.
-def getK4Pids(exth, title):
+def getK4Pids(exth, title, kInfoFile=None):
     global kindleDatabase
     try:
-        kindleDatabase = parseKindleInfo()
+        kindleDatabase = parseKindleInfo(kInfoFile)
     except Exception as message:
         print(message)
     
@@ -353,30 +354,49 @@ def getK4Pids(exth, title):
     raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
     return null
 
+def usage(progname):
+    print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
+    print "Usage:"
+    print "    %s [-k <kindle.info>] [-p <pidnums>] <infile> <outfile>  " % progname
+
 #
 # Main
 #   
 def main(argv=sys.argv):
     global kindleDatabase
     import mobidedrm
+    
+    progname = os.path.basename(argv[0])
+    kInfoFiles = []
+    pidnums = ""
+    
     print ('K4MobiDeDrm v%(__version__)s '
           'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
 
-    if len(argv)<3:
-        print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
-        print "Usage:"
-        print "    %s <infile> <outfile> [<pidnums>]" % argv[0]
-        return 1
-
-    if len(argv) == 4:
-        pidnums = argv[3]
-
-    if len(argv) == 3:
-        pidnums = "" 
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "k:p:")
+    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")
+            pidnums = a
 
     kindleDatabase = None
-    infile = argv[1]
-    outfile = argv[2]
+    infile = args[0]
+    outfile = args[1]
     try:
         # first try with K4PC/K4M
         ex = MobiPeek(infile)
@@ -394,8 +414,25 @@ def main(argv=sys.argv):
     else:
         file(outfile, 'wb').write(unlocked_file)
         return 0
-
-    # now try from the pid list
+    
+    # now try alternate kindle.info files
+    if kInfoFiles:
+        for infoFile in kInfoFiles:
+            kindleDatabase = None
+            try:
+                title = ex.getBookTitle()
+                exth = ex.getexthData()
+                pid = getK4Pids(exth, title, infoFile)
+                unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
+            except DrmException:
+                pass
+            except mobidedrm.DrmException:
+                pass
+            else:
+                file(outfile, 'wb').write(unlocked_file)
+                return 0            
+    
+    # Lastly, try from the pid list
     pids = pidnums.split(',')
     for pid in pids:
         try:
@@ -426,7 +463,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, 0, 1)   # The version number of this plugin
+        version             = (0, 1, 1)   # The version number of this plugin
         file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
         on_import           = True # Run this plugin during the import
         priority            = 200  # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -442,7 +479,27 @@ if not __name__ == "__main__" and inCalibre:
                 from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
             import mobidedrm
 
+            # Get supplied list of PIDs to try from plugin customization.
             pidnums = self.site_customization
+            
+            # Load any kindle info files (*.info) included Calibre's config directory.
+            kInfoFiles = []
+            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
 
             # first try with book specifc pid from K4PC or K4M
             try:
@@ -463,6 +520,25 @@ if not __name__ == "__main__" and inCalibre:
                 of.write(unlocked_file)
                 of.close()
                 return of.name
+            
+            # Now try alternate kindle info files
+            if kInfoFiles:
+                for infoFile in kInfoFiles:
+                    kindleDatabase = None 
+                    try:
+                        title = ex.getBookTitle()
+                        exth = ex.getexthData()
+                        pid = getK4Pids(exth, title, infoFile)
+                        unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
+                    except DrmException:
+                        pass
+                    except mobidedrm.DrmException:
+                        pass
+                    else:
+                        of = self.temporary_file('.mobi')
+                        of.write(unlocked_file)
+                        of.close()
+                        return of.name            
 
             # now try from the pid list
             pids = pidnums.split(',')
index cb13e5f09928fed64a272ba145dc8edab16824e2..977d81c50d8c4ca929a9345053ced019555c964b 100644 (file)
@@ -298,22 +298,25 @@ def CryptUnprotectData(encryptedData):
     return cleartext
 
 # Locate and open the .kindle-info file
-def openKindleInfo():
-    home = os.getenv('HOME')
-    cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
-    poll = p1.wait('wait')
-    results = p1.read()
-    reslst = results.split('\n')
-    kinfopath = 'NONE'
-    cnt = len(reslst)
-    for j in xrange(cnt):
-        resline = reslst[j]
-        pp = resline.find('.kindle-info')
-        if pp >= 0:
-            kinfopath = resline
-            break
-    if not os.path.exists(kinfopath):
-        raise K4MDrmException('Error: .kindle-info file can not be found')
-    return open(kinfopath,'r')
+def openKindleInfo(kInfoFile=None):
+    if kInfoFile == None:
+       home = os.getenv('HOME')
+       cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
+       cmdline = cmdline.encode(sys.getfilesystemencoding())
+       p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
+       poll = p1.wait('wait')
+       results = p1.read()
+       reslst = results.split('\n')
+       kinfopath = 'NONE'
+       cnt = len(reslst)
+       for j in xrange(cnt):
+           resline = reslst[j]
+           pp = resline.find('.kindle-info')
+           if pp >= 0:
+               kinfopath = resline
+               break
+       if not os.path.exists(kinfopath):
+           raise K4MDrmException('Error: .kindle-info file can not be found')
+       return open(kinfopath,'r')
+    else:
+        return open(kInfoFile, 'r')
\ No newline at end of file
index 777376ddcb2fd5aceca6e94cce6bbba574fb3471..337b992e4bd56369c0bd2ed8fce4217a0460176e 100644 (file)
@@ -101,7 +101,10 @@ CryptUnprotectData = CryptUnprotectData()
 #
 # Locate and open the Kindle.info file.
 #
-def openKindleInfo():
-    regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
-    path = winreg.QueryValueEx(regkey, 'Local AppData')[0] 
-    return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
+def openKindleInfo(kInfoFile=None):
+    if kInfoFile == None:
+        regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+        path = winreg.QueryValueEx(regkey, 'Local AppData')[0] 
+        return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
+    else:
+        return open(kInfoFile, 'r')
index 5ed58a52aeafc886838f1bdce2506b8657f3ac4c..eed1cceed1550b6a08b939925948e28eccce7cd9 100644 (file)
 #         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 'hearbeat', and is also quicker for long files.
+#  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
+#  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
 
-__version__ = '0.17'
+__version__ = '0.18'
 
 import sys
 import struct
@@ -127,10 +130,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
         if testflags & 1:
             num += getSizeOfTrailingDataEntry(ptr, size - num)
         testflags >>= 1
-    # Multibyte data, if present, is included in the encryption, so
-    # we do not need to check the low bit.
-    # if flags & 1:
-    #    num += (ord(ptr[size - num - 1]) & 0x3) + 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 DrmStripper:
@@ -181,9 +185,14 @@ class DrmStripper:
         return found_key
 
     def __init__(self, data_file, pid):
-        if checksumPid(pid[0:-2]) != pid:
-            raise DrmException("invalid PID checksum")
-        pid = pid[0:-2]
+        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]
@@ -206,6 +215,10 @@ class DrmStripper:
         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 5 (& 6?)
+            # 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:
@@ -282,44 +295,3 @@ def main(argv=sys.argv):
 
 if __name__ == "__main__":
     sys.exit(main())
-
-#if not __name__ == "__main__":
-if False:
-
-    # note a calibre plugin can not import code with another calibre plugin
-    # in it as it ends up registering two different plugins 
-    from calibre.customize import FileTypePlugin
-
-    class MobiDeDRM(FileTypePlugin):
-        name                = 'MobiDeDRM' # Name of the plugin
-        description         = 'Removes DRM from secure Mobi files'
-        supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
-        author              = 'The Dark Reverser' # The author of this plugin
-        version             = (0, 1, 7)   # The version number of this plugin
-        file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
-        on_import           = True # Run this plugin during the import
-
-        def run(self, path_to_ebook):
-            from calibre.gui2 import is_ok_to_use_qt
-            from PyQt4.Qt import QMessageBox
-            PID = self.site_customization
-            data_file = file(path_to_ebook, 'rb').read()
-            ar = PID.split(',')
-            for i in ar:
-                try:
-                    unlocked_file = DrmStripper(data_file, i).getResult()
-                except DrmException:
-                    if is_ok_to_use_qt():
-                        d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
-                        d.show()
-                        d.raise_()
-                        d.exec_()
-                    raise Exception("MobiDeDRM Plugin: Error decoding ebook")
-                else:
-                    of = self.temporary_file('.mobi')
-                    of.write(unlocked_file)
-                    of.close()
-                    return of.name
-
-        def customization_help(self, gui=False):
-            return 'Enter PID (separate multiple PIDs with comma)'
index 234f38fff9bca30e687c5aa49ff70e4044a0ffed..58a8e96129ff1a127688bcf4212cddfb26eec76b 100644 (file)
@@ -145,7 +145,7 @@ class MainDialog(Tkinter.Frame):
 
     # run as a gdb subprocess via pipes and collect stdout
     def gdbrdr(self, k4mappfile, gdbcmds):
-        cmdline = 'gdb -q -silent -readnow -batch -x ' +  gdbcmds + ' "' + k4mappfile + '"'
+        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')
@@ -169,8 +169,12 @@ class MainDialog(Tkinter.Frame):
             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
 
@@ -194,6 +198,9 @@ class MainDialog(Tkinter.Frame):
         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
@@ -220,12 +227,16 @@ class MainDialog(Tkinter.Frame):
             self.sbotton.configure(state='normal')
             return
 
-        # now check if the K4M app bianry is known and if so which gdbcmds to use
+
+        # 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:
@@ -246,6 +257,7 @@ class MainDialog(Tkinter.Frame):
             log += '\n\n'
             log = log.encode('utf-8')
             self.stext.insert(Tkconstants.END,log)
+            self.sbotton.configure(state='normal')
             return
 
         pidnum = self.checksumPid(pidnum)
index 46f6a38d6c8621e82d27067d79d206648ba4476f..1f1907bdf09e108929047bf47b70b300272dd82f 100644 (file)
@@ -1,18 +1,21 @@
-K4MUnswindle
+K4Munswindle
 
 Prerequisites:
 
    - Kindle for Mac.app Version 1.0.0 Beta 1 (27214)
-     (this is the original version)
-
        or
-
      Kindle.app Version 1.2.0 (30689)
-     (this is the current version at Amazon)
+        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)
 
-   - XCode Developer Tools **must** be Installed 
-    (see your latest Mac OS X Install Disk for the installer)
+***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:
index 07d5f6f5eca968c5492909043cc4cab7df3f1e15..eed1cceed1550b6a08b939925948e28eccce7cd9 100644 (file)
 #         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 'hearbeat', and is also quicker for long files.
+#  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
 
-__version__ = '0.16'
+__version__ = '0.18'
 
 import sys
 import struct
@@ -123,10 +130,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
         if testflags & 1:
             num += getSizeOfTrailingDataEntry(ptr, size - num)
         testflags >>= 1
-    # Multibyte data, if present, is included in the encryption, so
-    # we do not need to check the low bit.
-    # if flags & 1:
-    #    num += (ord(ptr[size - num - 1]) & 0x3) + 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 DrmStripper:
@@ -177,9 +185,14 @@ class DrmStripper:
         return found_key
 
     def __init__(self, data_file, pid):
-        if checksumPid(pid[0:-2]) != pid:
-            raise DrmException("invalid PID checksum")
-        pid = pid[0:-2]
+        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]
@@ -202,6 +215,10 @@ class DrmStripper:
         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 5 (& 6?)
+            # 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:
@@ -248,63 +265,33 @@ class DrmStripper:
     def getResult(self):
         return self.data_file
 
-if not __name__ == "__main__":
-    from calibre.customize import FileTypePlugin
-
-    class MobiDeDRM(FileTypePlugin):
-        name                = 'MobiDeDRM' # Name of the plugin
-        description         = 'Removes DRM from secure Mobi files'
-        supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
-        author              = 'The Dark Reverser' # The author of this plugin
-        version             = (0, 1, 6)   # The version number of this plugin
-        file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
-        on_import           = True # Run this plugin during the import
-
-        def run(self, path_to_ebook):
-            from calibre.gui2 import is_ok_to_use_qt
-            from PyQt4.Qt import QMessageBox
-            PID = self.site_customization
-            data_file = file(path_to_ebook, 'rb').read()
-            ar = PID.split(',')
-            for i in ar:
-                try:
-                    unlocked_file = DrmStripper(data_file, i).getResult()
-                except DrmException:
-                    # ignore the error
-                    pass
-                else:
-                    of = self.temporary_file('.mobi')
-                    of.write(unlocked_file)
-                    of.close()
-                    return of.name
-            if is_ok_to_use_qt():
-                d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
-                d.show()
-                d.raise_()
-                d.exec_()
-            return path_to_ebook
-
-        def customization_help(self, gui=False):
-            return 'Enter PID (separate multiple PIDs with comma)'
+def getUnencryptedBook(infile,pid):
+    sys.stdout=Unbuffered(sys.stdout)
+    data_file = file(infile, 'rb').read()
+    strippedFile = DrmStripper(data_file, pid)
+    return strippedFile.getResult()
 
-if __name__ == "__main__":
+def main(argv=sys.argv):
     sys.stdout=Unbuffered(sys.stdout)
     print ('MobiDeDrm v%(__version__)s. '
           'Copyright 2008-2010 The Dark Reverser.' % globals())
-    if len(sys.argv)<4:
+    if len(argv)<4:
         print "Removes protection from Mobipocket books"
         print "Usage:"
         print "    %s <infile> <outfile> <PID>" % sys.argv[0]
-        sys.exit(1)
+        return 1
     else:
-        infile = sys.argv[1]
-        outfile = sys.argv[2]
-        pid = sys.argv[3]
-        data_file = file(infile, 'rb').read()
+        infile = argv[1]
+        outfile = argv[2]
+        pid = argv[3]
         try:
-            strippedFile = DrmStripper(data_file, pid)
-            file(outfile, 'wb').write(strippedFile.getResult())
+            stripped_file = getUnencryptedBook(infile, pid)
+            file(outfile, 'wb').write(stripped_file)
         except DrmException, e:
             print "Error: %s" % e
-            sys.exit(1)
-    sys.exit(0)
\ No newline at end of file
+            return 1
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
index 07d5f6f5eca968c5492909043cc4cab7df3f1e15..eed1cceed1550b6a08b939925948e28eccce7cd9 100644 (file)
 #         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 'hearbeat', and is also quicker for long files.
+#  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
 
-__version__ = '0.16'
+__version__ = '0.18'
 
 import sys
 import struct
@@ -123,10 +130,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
         if testflags & 1:
             num += getSizeOfTrailingDataEntry(ptr, size - num)
         testflags >>= 1
-    # Multibyte data, if present, is included in the encryption, so
-    # we do not need to check the low bit.
-    # if flags & 1:
-    #    num += (ord(ptr[size - num - 1]) & 0x3) + 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 DrmStripper:
@@ -177,9 +185,14 @@ class DrmStripper:
         return found_key
 
     def __init__(self, data_file, pid):
-        if checksumPid(pid[0:-2]) != pid:
-            raise DrmException("invalid PID checksum")
-        pid = pid[0:-2]
+        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]
@@ -202,6 +215,10 @@ class DrmStripper:
         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 5 (& 6?)
+            # 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:
@@ -248,63 +265,33 @@ class DrmStripper:
     def getResult(self):
         return self.data_file
 
-if not __name__ == "__main__":
-    from calibre.customize import FileTypePlugin
-
-    class MobiDeDRM(FileTypePlugin):
-        name                = 'MobiDeDRM' # Name of the plugin
-        description         = 'Removes DRM from secure Mobi files'
-        supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
-        author              = 'The Dark Reverser' # The author of this plugin
-        version             = (0, 1, 6)   # The version number of this plugin
-        file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
-        on_import           = True # Run this plugin during the import
-
-        def run(self, path_to_ebook):
-            from calibre.gui2 import is_ok_to_use_qt
-            from PyQt4.Qt import QMessageBox
-            PID = self.site_customization
-            data_file = file(path_to_ebook, 'rb').read()
-            ar = PID.split(',')
-            for i in ar:
-                try:
-                    unlocked_file = DrmStripper(data_file, i).getResult()
-                except DrmException:
-                    # ignore the error
-                    pass
-                else:
-                    of = self.temporary_file('.mobi')
-                    of.write(unlocked_file)
-                    of.close()
-                    return of.name
-            if is_ok_to_use_qt():
-                d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
-                d.show()
-                d.raise_()
-                d.exec_()
-            return path_to_ebook
-
-        def customization_help(self, gui=False):
-            return 'Enter PID (separate multiple PIDs with comma)'
+def getUnencryptedBook(infile,pid):
+    sys.stdout=Unbuffered(sys.stdout)
+    data_file = file(infile, 'rb').read()
+    strippedFile = DrmStripper(data_file, pid)
+    return strippedFile.getResult()
 
-if __name__ == "__main__":
+def main(argv=sys.argv):
     sys.stdout=Unbuffered(sys.stdout)
     print ('MobiDeDrm v%(__version__)s. '
           'Copyright 2008-2010 The Dark Reverser.' % globals())
-    if len(sys.argv)<4:
+    if len(argv)<4:
         print "Removes protection from Mobipocket books"
         print "Usage:"
         print "    %s <infile> <outfile> <PID>" % sys.argv[0]
-        sys.exit(1)
+        return 1
     else:
-        infile = sys.argv[1]
-        outfile = sys.argv[2]
-        pid = sys.argv[3]
-        data_file = file(infile, 'rb').read()
+        infile = argv[1]
+        outfile = argv[2]
+        pid = argv[3]
         try:
-            strippedFile = DrmStripper(data_file, pid)
-            file(outfile, 'wb').write(strippedFile.getResult())
+            stripped_file = getUnencryptedBook(infile, pid)
+            file(outfile, 'wb').write(stripped_file)
         except DrmException, e:
             print "Error: %s" % e
-            sys.exit(1)
-    sys.exit(0)
\ No newline at end of file
+            return 1
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/Kindle_Mobi_Tools/MobiDeDRM.py b/Kindle_Mobi_Tools/MobiDeDRM.py
new file mode 100644 (file)
index 0000000..eed1cce
--- /dev/null
@@ -0,0 +1,297 @@
+#!/usr/bin/python
+#
+# This is a python script. You need a Python interpreter to run it.
+# For example, ActiveState Python, which exists for windows.
+#
+# It can run standalone to convert files, or it can be installed as a
+# plugin for Calibre (http://calibre-ebook.com/about) so that
+# importing files with DRM 'Just Works'.
+#
+# To create a Calibre plugin, rename this file so that the filename
+# ends in '_plugin.py', put it into a ZIP file and import that Calibre
+# using its plugin configuration GUI.
+#
+# 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 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 
+#         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
+
+__version__ = '0.18'
+
+import sys
+import struct
+import binascii
+
+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)
+
+class DrmException(Exception):
+    pass
+
+# 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 DrmStripper:
+    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 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, pid):
+        pid = pid.ljust(16,'\0')
+        keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
+        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
+            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])
+                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 __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 5 (& 6?)
+            # 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."
+        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)
+
+            # calculate the keys
+            drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
+            if drm_count == 0:
+                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. 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)
+            # 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"
+
+    def getResult(self):
+        return self.data_file
+
+def getUnencryptedBook(infile,pid):
+    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)<4:
+        print "Removes protection from Mobipocket books"
+        print "Usage:"
+        print "    %s <infile> <outfile> <PID>" % sys.argv[0]
+        return 1
+    else:
+        infile = argv[1]
+        outfile = argv[2]
+        pid = argv[3]
+        try:
+            stripped_file = getUnencryptedBook(infile, pid)
+            file(outfile, 'wb').write(stripped_file)
+        except DrmException, e:
+            print "Error: %s" % e
+            return 1
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
index 5ed58a52aeafc886838f1bdce2506b8657f3ac4c..18514a01634aec37b9aa5560328f6a0794c55242 100644 (file)
@@ -40,9 +40,9 @@
 #  0.15 - Now outputs 'hearbeat', 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
+#         both inside calibre and also in other places (ie K4MobiDeDRM tools)
+#         and modified the plugin code so that it will not interfere with other
+#         mobi/azw plugins if installed at the same time
 
 __version__ = '0.17'
 
@@ -284,8 +284,6 @@ if __name__ == "__main__":
     sys.exit(main())
 
 #if not __name__ == "__main__":
-if False:
-
     # note a calibre plugin can not import code with another calibre plugin
     # in it as it ends up registering two different plugins 
     from calibre.customize import FileTypePlugin
index 02bd298b2e900c201397b0c19c4e0a0b2fef01b6..49e6ae90ce5c2905b0cb69f53cb8b196838234c0 100644 (file)
@@ -1,4 +1,5 @@
-The Topaz Tools only work for "Kindle for PC" books, and original standalone Kindles that have never been updated to firmware 2.5 or later, Kindle for iPhone/iPad/iPodTouch (where the PID is known) and Kindle for Mac (with the PID provided by the Kindle_4_Mac_Tools).
+The Topaz Tools work for "Kindle for PC" books, "Kindle for Mac" books, original standalone Kindles that have never been updated to firmware 2.5 or later, and Kindle for iPhone/iPad/iPodTouch (where the PID is known).
+
 
 For Topaz:
 
@@ -8,19 +9,13 @@ For Topaz:
 
 3. move to tools\Topaz_Tools\
 
-4. If you have an old Kindle (never updated to 2.5 or later) or an iPod, iPhone, or iPad or Kindle for Mac and you know your PID then double-click on the following:
-
-TopazExtract_iPhone_iPad_K4M.pyw
-
-If you have Kindle for PC (and no Kindle for Mac will NOT work here) then instead double-click on the following:
-
-TopazExtract_Kindle4PC.pyw
+4. double-click on TopazExtract.pyw
 
 Hit the first “…” button to select the Topaz book with DRM that you want to convert
 
 Hit the second “…” to select an entirely new directory to extract the many book pieces into
 
-And add info for your PID (or extra PIDs) if needed (should not be needed for Kindle For PC).
+And add info for your PID (or extra PIDs) if needed (should not be needed for Kindle For PC or Kindle for Mac).  This field is useful if you have Kindle for iPad/iPhone/iPodTouch or an old Kindle V1 and know your device PID.
 
 Hit the Start button
 
similarity index 81%
rename from Topaz_Tools/TopazExtract_Kindle4PC.pyw
rename to Topaz_Tools/TopazExtract.pyw
index 924d4c90a0dc0492b0caff9d263ff6b43e1ea71b..9d3b7faf310ab5bc8f0baedeb873e1ade6ddf4c2 100644 (file)
@@ -45,6 +45,15 @@ class MainDialog(Tkinter.Frame):
         self.outpath.insert(0, cwd)
         button = Tkinter.Button(body, text="...", command=self.get_outpath)
         button.grid(row=1, column=2)
+        
+        Tkinter.Label(body, text='Kindle.info file (optional)').grid(row=2, sticky=Tkconstants.E)
+        self.altinfopath = Tkinter.Entry(body, width=50)
+        self.altinfopath.grid(row=2, column=1, sticky=sticky)
+        #cwd = os.getcwdu()
+        #cwd = cwd.encode('utf-8')
+        #self.altinfopath.insert(0, cwd)
+        button = Tkinter.Button(body, text="...", command=self.get_altinfopath)
+        button.grid(row=2, column=2)
 
         Tkinter.Label(body, text='First 8 char of PID (optional)').grid(row=3, sticky=Tkconstants.E)
         self.pidnum = Tkinter.StringVar()
@@ -98,20 +107,23 @@ class MainDialog(Tkinter.Frame):
         return
 
     # run as a subprocess via pipes and collect stdout
-    def topazrdr(self, infile, outdir, pidnum):
+    def topazrdr(self, infile, outdir, altinfopath, pidnum):
         # os.putenv('PYTHONUNBUFFERED', '1')
         pidoption = ''
         if pidnum and pidnum != '':
             pidoption = ' -p "' + pidnum + '" '
+        infooption = ''
+        if altinfopath and altinfopath != '':
+            infooption = ' -k "' + altinfopath + '" '
         outoption = ' -o "' + outdir + '" '
-        cmdline = 'python ./lib/cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"'
+        cmdline = 'python ./lib/cmbtc_dump.py -v -d ' + pidoption + infooption + outoption + '"' + infile + '"'
         if sys.platform[0:3] == 'win':
             search_path = os.environ['PATH']
             search_path = search_path.lower()
             if search_path.find('python') >= 0: 
-                cmdline = 'python lib\cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"'
+                cmdline = 'python lib\cmbtc_dump.py -v -d ' + pidoption + infooption + outoption + '"' + infile + '"'
             else :
-                cmdline = 'lib\cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"'
+                cmdline = 'lib\cmbtc_dump.py -v -d ' + pidoption + infooption + outoption + '"' + infile + '"'
 
         cmdline = cmdline.encode(sys.getfilesystemencoding())
         p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
@@ -140,6 +152,20 @@ class MainDialog(Tkinter.Frame):
             self.outpath.delete(0, Tkconstants.END)
             self.outpath.insert(0, outpath)
         return
+    
+    def get_altinfopath(self):
+        cwd = os.getcwdu()
+        cwd = cwd.encode('utf-8')
+        altinfopath = tkFileDialog.askopenfilename(
+            parent=None, title='Select kindle.info File',
+            defaultextension='.prc', filetypes=[('Kindle Info', '.info'),
+                                                ('All Files', '.*')],
+            initialdir=cwd)
+        if altinfopath:
+            altinfopath = os.path.normpath(altinfopath)
+            self.altinfopath.delete(0, Tkconstants.END)
+            self.altinfopath.insert(0, altinfopath)
+        return
 
     def quitting(self):
         # kill any still running subprocess
@@ -154,6 +180,7 @@ class MainDialog(Tkinter.Frame):
         self.sbotton.configure(state='disabled')
         tpzpath = self.tpzpath.get()
         outpath = self.outpath.get()
+        altinfopath = self.altinfopath.get()
         if not tpzpath or not os.path.exists(tpzpath):
             self.status['text'] = 'Specified Topaz eBook file does not exist'
             self.sbotton.configure(state='normal')
@@ -164,6 +191,10 @@ class MainDialog(Tkinter.Frame):
             return
         if not os.path.exists(outpath):
             os.makedirs(outpath)
+        if altinfopath and not os.path.exists(altinfopath):
+            self.status['text'] = 'Specified kindle.info file does not exist'
+            self.sbotton.configure(state='normal')
+            return
         pidnum = self.pidnum.get()
         # if not pidnum or pidnum == '':
         #     self.status['text'] = 'You have not entered a PID '
@@ -173,12 +204,13 @@ class MainDialog(Tkinter.Frame):
         log = 'Command = "python cmbtc_dump.py"\n'
         log += 'Topaz Path Path = "'+ tpzpath + '"\n'
         log += 'Output Directory = "' + outpath + '"\n'
+        log += 'Kindle.info file = "' + altinfopath + '"\n'
         log += 'First 8 chars of PID = "' + pidnum + '"\n'
         log += '\n\n'
         log += 'Please Wait ...\n'
         log = log.encode('utf-8')
         self.stext.insert(Tkconstants.END,log)
-        self.p2 = self.topazrdr(tpzpath, outpath, pidnum)
+        self.p2 = self.topazrdr(tpzpath, outpath, altinfopath, pidnum)
 
         # python does not seem to allow you to create
         # your own eventloop which every other gui does - strange 
diff --git a/Topaz_Tools/lib/changes.txt b/Topaz_Tools/lib/changes.txt
deleted file mode 100644 (file)
index 125a869..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-Changes in version 2.0
-
-       - gensvg.py now accepts two options
-             -x : output browseable XHTML+SVG pages (default)
-            -r : output raw SVG images (useful for later conversion to pdf)
-         
-       - flatxml2html.py now understands page.groups of type graphic
-            and handles vertical regions as svg images
-
-       - genhtml.py now accepts an option
-            --fixed-image : which will force the conversion
-                            of all fixed regions to svg images
-
-       - minor bug fixes and html conversion improvements
-
-
-Changes in version 1.8
-       - gensvg.py now builds wonderful xhtml pages with embedded svg 
-           that can be easily paged through as if reading a book!
-           (tested in Safari for Mac and Win and Firefox)
-           (requires javascript to be enabled)
-       - genhtml.py now REQUIRES that gensvg.py be run FIRST
-            this allows create of images on the fly from glyphs
-       - genhtml.py now automatically makes tables of words into svg
-            based images and will handle glyph based ornate first 
-            letters of words
-       - cmbtc_dump_mac_linux.py has been renamed to be
-            cmbtc_dump_nonK4PC.py to make it clearer
-            when it needs to be used
-       
-
-Changes in version 1.7
-       - gensvg.py has been improved so that the glyphs render exactly (ClarkNova)
-       - gensvg.py has fixed a render order "bug" that allowed some images to cover or hide text. (ClarkNova)
-       - change generated html to use external stylesheet via a link to "style.css"
-       - add missing <title> tag
-       - make xhtml compliant doctype and minor changes to write correct xhtml
-       - make divs that act as anchors be hidden visually and to take up 0 height and 0 width to prevent any impact on layout
-
-Changes in version 1.6
-       - support for books whose paragraphs have no styles
-       - support to run cmbtc_dump on Linux and Mac OSX provided you know your PID of your ipod or standalone Kindle
-        (contributed by DiapDealer)
-
-Changes in version 1.5
-       - completely reworked generation of styles to use actual page heights and widths
-       - added new script getpagedim.py to support the above
-       - style names with underscores in them are now properly paired with their base class
-       - fixed hanging indents that did not ever set a left margin
-       - added support for a number of not previously known region types
-       - added support for a previously unknown snippet - <empty></empty>
-       - corrected a bug that caused unknown regions to abort the program
-       - added code to make the handling of unknown regions better in general
-       - corrected a bug that caused the last link on a page to be missing (if it was the last thing on the page)
-
-Changes in version 1.3
-       - font generation by gensvg.py is now greatly improved with support for contour points added
-       - support for more region types
-       - support for inline images in paragraphs or text fields (ie. initial graphics for the first letter of a word)
-       - greatly improved dtd information used for the xml to prevent parsing mistakes
-
-Version 1.0
-       - initial release
-
index 96a75b4ecdc0acce39b84deb990fce050142f62d..9dca393b2cb6160ea125f77120c32a5af380b95d 100644 (file)
@@ -1,19 +1,5 @@
-#! /usr/bin/python
-# For use in Topaz Scripts version 2.6
-
-"""
-
-Comprehensive Mazama Book DRM with Topaz Cryptography V2.0
-
------BEGIN PUBLIC KEY-----
-MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
-M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
-B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
-y2/pHuYme7U1TsgSjwIDAQAB
------END PUBLIC KEY-----
-
-"""
-from __future__ import with_statement
+#!/usr/bin/env python
+# For use with Topaz Scripts Version 2.6
 
 class Unbuffered:
     def __init__(self, stream):
@@ -27,155 +13,64 @@ class Unbuffered:
 import sys
 sys.stdout=Unbuffered(sys.stdout)
 
-
 import csv
 import os
 import getopt
 import zlib
 from struct import pack
 from struct import unpack
-from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
-    create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
-    string_at, Structure, c_void_p, cast
-import _winreg as winreg
-import Tkinter
-import Tkconstants
-import tkMessageBox
 import traceback
 import hashlib
 
 MAX_PATH = 255
 
-kernel32 = windll.kernel32
-advapi32 = windll.advapi32
-crypt32 = windll.crypt32
-
-global kindleDatabase
 global bookFile
 global bookPayloadOffset
 global bookHeaderRecords
 global bookMetadata
 global bookKey
 global command
+global kindleDatabase
+global verbose
+global PIDs
 
-#
-# Various character maps used to decrypt books. Probably supposed to act as obfuscation
-#
+if sys.platform.startswith('win'):
+    from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
+if sys.platform.startswith('darwin'):
+    from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
 
-charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
-charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
-charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
 
-#
-# Exceptions for all the problems that might happen during the script
-#
 
+# Exceptions for all the problems that might happen during the script
 class CMBDTCError(Exception):
     pass
     
 class CMBDTCFatal(Exception):
     pass
     
-#
-# Stolen stuff
-#
-
-class DataBlob(Structure):
-    _fields_ = [('cbData', c_uint),
-                ('pbData', c_void_p)]
-DataBlob_p = POINTER(DataBlob)
-
-def GetSystemDirectory():
-    GetSystemDirectoryW = kernel32.GetSystemDirectoryW
-    GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
-    GetSystemDirectoryW.restype = c_uint
-    def GetSystemDirectory():
-        buffer = create_unicode_buffer(MAX_PATH + 1)
-        GetSystemDirectoryW(buffer, len(buffer))
-        return buffer.value
-    return GetSystemDirectory
-GetSystemDirectory = GetSystemDirectory()
-
-
-def GetVolumeSerialNumber():
-    GetVolumeInformationW = kernel32.GetVolumeInformationW
-    GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
-                                      POINTER(c_uint), POINTER(c_uint),
-                                      POINTER(c_uint), c_wchar_p, c_uint]
-    GetVolumeInformationW.restype = c_uint
-    def GetVolumeSerialNumber(path):
-        vsn = c_uint(0)
-        GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
-        return vsn.value
-    return GetVolumeSerialNumber
-GetVolumeSerialNumber = GetVolumeSerialNumber()
-
-
-def GetUserName():
-    GetUserNameW = advapi32.GetUserNameW
-    GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
-    GetUserNameW.restype = c_uint
-    def GetUserName():
-        buffer = create_unicode_buffer(32)
-        size = c_uint(len(buffer))
-        while not GetUserNameW(buffer, byref(size)):
-            buffer = create_unicode_buffer(len(buffer) * 2)
-            size.value = len(buffer)
-        return buffer.value.encode('utf-16-le')[::2]
-    return GetUserName
-GetUserName = GetUserName()
-
-
-def CryptUnprotectData():
-    _CryptUnprotectData = crypt32.CryptUnprotectData
-    _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
-                                   c_void_p, c_void_p, c_uint, DataBlob_p]
-    _CryptUnprotectData.restype = c_uint
-    def CryptUnprotectData(indata, entropy):
-        indatab = create_string_buffer(indata)
-        indata = DataBlob(len(indata), cast(indatab, c_void_p))
-        entropyb = create_string_buffer(entropy)
-        entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
-        outdata = DataBlob()
-        if not _CryptUnprotectData(byref(indata), None, byref(entropy),
-                                   None, None, 0, byref(outdata)):
-            raise CMBDTCFatal("Failed to Unprotect Data")
-        return string_at(outdata.pbData, outdata.cbData)
-    return CryptUnprotectData
-CryptUnprotectData = CryptUnprotectData()
 
-#
 # Returns the MD5 digest of "message"
-#
-
 def MD5(message):
     ctx = hashlib.md5()
     ctx.update(message)
     return ctx.digest()
 
-#
-# Returns the MD5 digest of "message"
-#
 
+# Returns the MD5 digest of "message"
 def SHA1(message):
     ctx = hashlib.sha1()
     ctx.update(message)
     return ctx.digest()
 
-#
-# Open the book file at path
-#
 
+# Open the book file at path
 def openBook(path):
     try:
         return open(path,'rb')
     except:
         raise CMBDTCFatal("Could not open book file: " + path)
-#
-# Encode the bytes in data with the characters in map
-#
 
+# Encode the bytes in data with the characters in map        
 def encode(data, map):
     result = ""
     for char in data:
@@ -186,55 +81,52 @@ def encode(data, map):
         result += map[R]
     return result
   
-#
 # Hash the bytes in data and then encode the digest with the characters in map
-#
-  
 def encodeHash(data,map):
     return encode(MD5(data),map)
 
-#
 # Decode the string in data with the characters in map. Returns the decoded bytes
-#
-   
 def decode(data,map):
     result = ""
-    for i in range (0,len(data),2):
+    for i in range (0,len(data)-1,2):
         high = map.find(data[i])
         low = map.find(data[i+1])
-        value = (((high * 0x40) ^ 0x80) & 0xFF) + low
+        if (high == -1) or (low == -1) :
+            break
+        value = (((high * len(map)) ^ 0x80) & 0xFF) + low
         result += pack("B",value)
     return result
-  
-#
-# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
-#
-  
-def openKindleInfo():
-    regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
-    path = winreg.QueryValueEx(regkey, 'Local AppData')[0] 
-    return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
 
-#
 # Parse the Kindle.info file and return the records as a list of key-values
-#
-
-def parseKindleInfo():
+def parseKindleInfo(kInfoFile):
     DB = {}
-    infoReader = openKindleInfo()
+    infoReader = openKindleInfo(kInfoFile)
     infoReader.read(1)
     data = infoReader.read()
-    items = data.split('{')
-    
+    if sys.platform.startswith('win'):
+        items = data.split('{')
+    else :
+        items = data.split('[')
     for item in items:
         splito = item.split(':')
         DB[splito[0]] =splito[1]
     return DB
 
-#
-# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
-#
+# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
+def getKindleInfoValueForHash(hashedKey):
+    global kindleDatabase
+    encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
+    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):
+    return getKindleInfoValueForHash(encodeHash(key,charMap2))
+
+# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
 def findNameForHash(hash):
     names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
     result = ""
@@ -242,42 +134,81 @@ def findNameForHash(hash):
         if hash == encodeHash(name, charMap2):
            result = name
            break
-    return name
+    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 ("--------------------------\n")
+            print ("--------------------------")
         else :
             print ("Unknown Record")
         print getKindleInfoValueForHash(record)
         print "\n"
-#
-# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
-#
 
-def getKindleInfoValueForHash(hashedKey):
-    global kindleDatabase
-    encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
-    return CryptUnprotectData(encryptedValue,"")
 #
-#  Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
+# PID generation routines
 #
-   
-def getKindleInfoValueForKey(key):
-    return getKindleInfoValueForHash(encodeHash(key,charMap2))
   
-#
-# Get a 7 bit encoded number from the book file
-#
+# Returns two bit at offset from a bit field
+def getTwoBitsFromBitField(bitField,offset):
+    byteNumber = offset // 4
+    bitPosition = 6 - 2*(offset % 4)
+    return ord(bitField[byteNumber]) >> bitPosition & 3
+
+# Returns the six bits at offset from a bit field
+def getSixBitsFromBitField(bitField,offset):
+     offset *= 3
+     value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
+     return value
+     
+# 8 bits to six bits encoding from hash to generate PID string
+def encodePID(hash):
+    global charMap3
+    PID = ""
+    for position in range (0,8):
+        PID += charMap3[getSixBitsFromBitField(hash,position)]
+    return PID
 
+# Encryption table used to generate the device PID
+def generatePidEncryptionTable() :
+    table = []
+    for counter1 in range (0,0x100):
+        value = counter1
+        for counter2 in range (0,8):
+            if (value & 1 == 0) :
+                value = value >> 1
+            else :
+                value = value >> 1
+                value = value ^ 0xEDB88320
+        table.append(value)
+    return table
+
+# Seed value used to generate the device PID
+def generatePidSeed(table,dsn) :
+    value = 0
+    for counter in range (0,4) :
+       index = (ord(dsn[counter]) ^ value) &0xFF
+       value = (value >> 8) ^ table[index]
+    return value
+
+# Generate the device PID
+def generateDevicePID(table,dsn,nbRoll):
+    seed = generatePidSeed(table,dsn)
+    pidAscii = ""
+    pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
+    index = 0
+    for counter in range (0,nbRoll):
+        pid[index] = pid[index] ^ ord(dsn[counter])
+        index = (index+1) %8
+    for counter in range (0,8):
+        index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
+        pidAscii += charMap4[index]
+    return pidAscii
+
+# Get a 7 bit encoded number from the book file
 def bookReadEncodedNumber():
     flag = False
     data = ord(bookFile.read(1))
@@ -297,14 +228,12 @@ def bookReadEncodedNumber():
        data = -data
     return data
     
-#
 # Encode a number in 7 bit format
-#
-
 def encodeNumber(number):
    result = ""
    negative = False
    flag = 0
+   print("Using encodeNumber routine")
    
    if number < 0 :
        number = -number + 1
@@ -326,26 +255,17 @@ def encodeNumber(number):
    
    return result[::-1]
   
-#
-# Get a length prefixed string from the file 
-#
 
+# Get a length prefixed string from the file 
 def bookReadString():
     stringLength = bookReadEncodedNumber()
     return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]  
     
-#
 # Returns a length prefixed string
-#
-
 def lengthPrefixString(data):
     return encodeNumber(len(data))+data
     
-
-#
 # Read and return the data of one header record at the current book file position [[offset,decompressedLength,compressedLength],...]
-#
-    
 def bookReadHeaderRecordData():
     nbValues = bookReadEncodedNumber()
     values = []
@@ -353,10 +273,7 @@ def bookReadHeaderRecordData():
         values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
     return values
    
-#
 # Read and parse one header record at the current book file position and return the associated data [[offset,decompressedLength,compressedLength],...]
-#
-
 def parseTopazHeaderRecord():
     if ord(bookFile.read(1)) != 0x63:
         raise CMBDTCFatal("Parse Error : Invalid Header")
@@ -365,10 +282,7 @@ def parseTopazHeaderRecord():
     record = bookReadHeaderRecordData()
     return [tag,record]
 
-#
 # Parse the header of a Topaz file, get all the header records and the offset for the payload
-#
 def parseTopazHeader():
     global bookHeaderRecords
     global bookPayloadOffset
@@ -382,7 +296,7 @@ def parseTopazHeader():
    
     for i in range (0,nbRecords):
         result = parseTopazHeaderRecord()
-        print result[0], result[1]
+        #print result[0], result[1]
         bookHeaderRecords[result[0]] = result[1]
     
     if ord(bookFile.read(1))  != 0x64 :
@@ -390,11 +304,8 @@ def parseTopazHeader():
     
     bookPayloadOffset = bookFile.tell()
    
-#
 # Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
 # Correction, the record is correctly decompressed too
-#
-
 def getBookPayloadRecord(name, index):   
     encrypted = False
     compressed = False
@@ -434,10 +345,7 @@ def getBookPayloadRecord(name, index):
     
     return record
 
-#
 # Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
-#
-
 def extractBookPayloadRecord(name, index, filename):
     compressed = False
 
@@ -463,17 +371,11 @@ def extractBookPayloadRecord(name, index, filename):
     else:
         print(record)
     
-#
 # return next record [key,value] from the book metadata from the current book position
-#  
-
 def readMetadataRecord():
     return [bookReadString(),bookReadString()]
     
-#
 # Parse the metadata record from the book payload and return a list of [key,values]
-#
-
 def parseMetadata():
     global bookHeaderRecords
     global bookPayloadAddress
@@ -491,40 +393,7 @@ def parseMetadata():
         record =readMetadataRecord()
         bookMetadata[record[0]] = record[1]
 
-#
-# Returns two bit at offset from a bit field
-#
-   
-def getTwoBitsFromBitField(bitField,offset):
-    byteNumber = offset // 4
-    bitPosition = 6 - 2*(offset % 4)
-    
-    return ord(bitField[byteNumber]) >> bitPosition & 3
-
-#
-# Returns the six bits at offset from a bit field
-#    
-
-def getSixBitsFromBitField(bitField,offset):
-     offset *= 3
-     value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
-     return value
-     
-#
-# 8 bits to six bits encoding from hash to generate PID string
-#
-
-def encodePID(hash):
-    global charMap3
-    PID = ""
-    for position in range (0,8):
-        PID += charMap3[getSixBitsFromBitField(hash,position)]
-    return PID
-    
-#
 # Context initialisation for the Topaz Crypto
-#
-
 def topazCryptoInit(key):
     ctx1 = 0x0CAFFE19E
     
@@ -534,10 +403,7 @@ def topazCryptoInit(key):
         ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
     return [ctx1,ctx2]
     
-#
 # decrypt data with the context prepared by topazCryptoInit()
-#
-    
 def topazCryptoDecrypt(data, ctx):
     ctx1 = ctx[0]
     ctx2 = ctx[1]
@@ -553,18 +419,12 @@ def topazCryptoDecrypt(data, ctx):
         
     return plainText
 
-#
 # Decrypt a payload record with the PID
-#
-
 def decryptRecord(data,PID):
     ctx = topazCryptoInit(PID)
     return topazCryptoDecrypt(data, ctx)
 
-#
 # Try to decrypt a dkey record (contains the book PID)
-#
-
 def decryptDkeyRecord(data,PID):
     record = decryptRecord(data,PID)
     fields = unpack("3sB8sB8s3s",record)
@@ -577,11 +437,8 @@ def decryptDkeyRecord(data,PID):
         raise CMBDTCError("Record didn't contain PID")
     
     return fields[4]
-    
-#
+
 # Decrypt all the book's dkey records (contain the book PID)
-#
-  
 def decryptDkeyRecords(data,PID):
     nbKeyRecords = ord(data[0])
     records = []
@@ -597,57 +454,7 @@ def decryptDkeyRecords(data,PID):
         
     return records
     
-#
-# Encryption table used to generate the device PID
-#
-    
-def generatePidEncryptionTable() :
-    table = []
-    for counter1 in range (0,0x100):
-        value = counter1
-        for counter2 in range (0,8):
-            if (value & 1 == 0) :
-                value = value >> 1
-            else :
-                value = value >> 1
-                value = value ^ 0xEDB88320
-        table.append(value)
-    return table
-#
-# Seed value used to generate the device PID
-#
-   
-def generatePidSeed(table,dsn) :
-    value = 0
-    for counter in range (0,4) :
-       index = (ord(dsn[counter]) ^ value) &0xFF
-       value = (value >> 8) ^ table[index]
-    return value
-   
-#
-# Generate the device PID
-#
-
-def generateDevicePID(table,dsn,nbRoll):
-    seed = generatePidSeed(table,dsn)
-    pidAscii = ""
-    pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
-    index = 0
-    
-    for counter in range (0,nbRoll):
-        pid[index] = pid[index] ^ ord(dsn[counter])
-        index = (index+1) %8
-    for counter in range (0,8):
-        index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
-        pidAscii += charMap4[index]
-    return pidAscii
-    
-#
 # Create decrypted book payload
-#
-
 def createDecryptedPayload(payload):
     for headerRecord in bookHeaderRecords:
        name = headerRecord
@@ -670,10 +477,7 @@ def createDecryptedPayload(payload):
                outputFile = os.path.join(destdir,fname)
                file(outputFile, 'wb').write(getBookPayloadRecord(name, index))
                    
-
 # Create decrypted book
-#
-
 def createDecryptedBook(outdir):
     if not os.path.exists(outdir):
         os.makedirs(outdir)
@@ -696,11 +500,7 @@ def createDecryptedBook(outdir):
 
     createDecryptedPayload(outdir)
 
-
-#
 # Set the command to execute by the programm according to cmdLine parameters
-#
-
 def setCommand(name) :
     global command
     if command != "" :
@@ -708,28 +508,85 @@ def setCommand(name) :
     else :
         command = name
 
-# 
 # Program usage
-#
-   
 def usage():
     print("\nUsage:")
-    print("\ncmbtc_dump.py [options] bookFileName\n")
+    print("\ncmbtc_dump_linux.py [options] bookFileName\n")
     print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
     print("-d Dumps the unencrypted book as files to outdir")
     print("-o Output directory to save book files to")
     print("-v Verbose (can be used several times)")
     print("-i Prints kindle.info database")
-#
-# Main
-#   
+    print("-k Adds the path to an alternate kindle.info file")
+    
+def prepTopazBook(bookPath):
+    global bookFile
+    bookFile = openBook(bookPath)
+    parseTopazHeader()
+    parseMetadata() 
 
-def main(argv=sys.argv):
+# Get Pids
+def getK4Pids(kInfoFile=None):
     global kindleDatabase
-    global bookMetadata
+    global PIDs
+    
+    # Read the encrypted database
+    kindleDatabase = None
+    try:
+        kindleDatabase = parseKindleInfo(kInfoFile)
+    except Exception as message:
+        #if verbose > 0:
+        #    print(message)
+        pass
+    
+    if kindleDatabase != None :
+        # Compute the DSN
+        # Get the Mazama Random number
+        MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
+    
+        # Get the HDD serial
+        encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
+    
+        # Get the current user name
+        encodedUsername = encodeHash(GetUserName(),charMap1)
+    
+        # concat, hash and encode
+        DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
+       
+        if verbose > 0:
+            print("DSN: " + DSN)
+        
+        # Compute the device PID    
+        table =  generatePidEncryptionTable()
+        devicePID = generateDevicePID(table,DSN,4)
+        PIDs.append(devicePID)
+    
+        if verbose > 0:
+            print("Device PID: " + devicePID)
+        
+        # Compute book PID
+        # Get the account token
+        kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
+    
+        if verbose > 0:
+            print("Account Token: " + kindleAccountToken)
+
+            keysRecord = bookMetadata["keys"]
+            keysRecordRecord = bookMetadata[keysRecord]
+    
+            pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
+   
+            bookPID = encodePID(pidHash)
+            PIDs.append(bookPID)
+    
+            if verbose > 0:
+                print ("Book PID: " + bookPID )
+
+# Main
+def main(argv=sys.argv):
+    global verbose
+    global PIDs
     global bookKey
-    global bookFile
     global command
     
     progname = os.path.basename(argv[0])
@@ -739,12 +596,12 @@ def main(argv=sys.argv):
     recordIndex = 0
     outdir = ""
     PIDs = []
-    kindleDatabase = None
     command = ""
+    kInfoFiles = []
     
-    
+  
     try:
-        opts, args = getopt.getopt(sys.argv[1:], "vi:o:p:d")
+        opts, args = getopt.getopt(sys.argv[1:], "vi:k:o:p:d")
     except getopt.GetoptError, err:
         # print help information and exit:
         print str(err) # will print something like "option -a not recognized"
@@ -760,103 +617,50 @@ def main(argv=sys.argv):
             verbose+=1
         if o == "-i":
             setCommand("printInfo")
+        if o == "-k":
+            if a == None :
+                raise CMBDTCFatal("Invalid parameter for -k")
+            kInfoFiles.append(a)
         if o =="-o":
             if a == None :
                 raise CMBDTCFatal("Invalid parameter for -o")
             outdir = a
         if o =="-p":
+            if a == None :
+                raise CMBDTCFatal("Invalid parameter for -p")
             PIDs.append(a)
         if o =="-d":
             setCommand("doit")
             
     if command == "" :
-        raise CMBDTCFatal("No action supplied on command line")
-   
-    #
-    # Read the encrypted database
-    #
-    
-    try:
-        kindleDatabase = parseKindleInfo()
-    except Exception as message:
-        if verbose>0:
-            print(message)
-    
-    if kindleDatabase != None :
-        if command == "printInfo" :
-            printKindleInfo()
-     
-    #
-    # Compute the DSN
-    #
-    
-    # Get the Mazama Random number
-        MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
-    
-    # Get the HDD serial
-        encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
-    
-    # Get the current user name
-        encodedUsername = encodeHash(GetUserName(),charMap1)
-    
-    # concat, hash and encode
-        DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
-       
-        if verbose >1:
-            print("DSN: " + DSN)
-    
-    #
-    # Compute the device PID
-    #
-     
-        table =  generatePidEncryptionTable()
-        devicePID = generateDevicePID(table,DSN,4)
-        PIDs.append(devicePID)
-    
-        if verbose > 0:
-            print("Device PID: " + devicePID)
-    
-    #
-    # Open book and parse metadata
-    #
+        raise Exception("No action supplied on command line")
         
+    # Open book and parse metadata
     if len(args) == 1:
-    
-        bookFile = openBook(args[0])
-        parseTopazHeader()
-        parseMetadata()
-    
-    #
-    # Compute book PID
-    # 
-    
-    # Get the account token
-    
-        if kindleDatabase != None:
-            kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
-    
-            if verbose >1:
-                print("Account Token: " + kindleAccountToken)
-
-            keysRecord = bookMetadata["keys"]
-            keysRecordRecord = bookMetadata[keysRecord]
-    
-            pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
-   
-            bookPID = encodePID(pidHash)
-            PIDs.append(bookPID)
-    
-            if verbose > 0:
-                print ("Book PID: " + bookPID )
-    
-    #
-    #  Decrypt book key
-    #
-    
-        dkey = getBookPayloadRecord('dkey', 0) 
+        # Open the ebook
+        prepTopazBook(args[0])
+        # Always try to get the default Kindle installation info.
+        getK4Pids()
+        
+        # If Alternate kindle.info files were supplied, parse them too.
+        if kInfoFiles:
+            for infoFile in kInfoFiles:
+                getK4Pids(infoFile)
+        
+        # Print the kindle info if requested.        
+        if kindleDatabase != None :        
+            if command == "printInfo" :
+                printKindleInfo()
+                
+        # Remove any duplicates that may occur from the PIDs List
+        PIDs = list(set(PIDs))
+        
+        #  Decrypt book key
+        dkey = getBookPayloadRecord('dkey', 0)
         
         bookKeys = []
         for PID in PIDs :
+            print PID
             bookKeys+=decryptDkeyRecords(dkey,PID)
             
         if len(bookKeys) == 0 :
@@ -867,7 +671,7 @@ def main(argv=sys.argv):
             bookKey = bookKeys[0]
             if verbose > 0:
                 print("Book key: " + bookKey.encode('hex'))
-                
+                  
             if command == "printRecord" :
                 extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
                 if outputFile != "" and verbose>0 :
@@ -875,7 +679,7 @@ def main(argv=sys.argv):
             elif command == "doit" :
                 if outdir != "" :
                     createDecryptedBook(outdir)
-                    if verbose >0 :
+                    if verbose > 0 :
                         print ("Decrypted book saved. Don't pirate!")
                 elif verbose > 0:
                     print("Output directory name was not supplied.")
diff --git a/Topaz_Tools/lib/cmbtc_dump_nonK4PC.py b/Topaz_Tools/lib/cmbtc_dump_nonK4PC.py
deleted file mode 100644 (file)
index 949fe51..0000000
+++ /dev/null
@@ -1,524 +0,0 @@
-#!/usr/bin/python
-# For use with Topaz Scripts Version 2.6
-
-class Unbuffered:
-    def __init__(self, stream):
-        self.stream = stream
-    def write(self, data):
-        self.stream.write(data)
-        self.stream.flush()
-    def __getattr__(self, attr):
-        return getattr(self.stream, attr)
-
-import sys
-sys.stdout=Unbuffered(sys.stdout)
-
-import csv
-import os
-import getopt
-import zlib
-from struct import pack
-from struct import unpack
-
-MAX_PATH = 255
-
-# Put the first 8 characters of your Kindle PID here
-# or supply it with the -p option in the command line
-####################################################
-kindlePID = "12345678"
-####################################################
-
-global bookFile
-global bookPayloadOffset
-global bookHeaderRecords
-global bookMetadata
-global bookKey
-global command
-
-#
-# Exceptions for all the problems that might happen during the script
-#
-
-class CMBDTCError(Exception):
-    pass
-    
-class CMBDTCFatal(Exception):
-    pass
-    
-
-#
-# Open the book file at path
-#
-
-def openBook(path):
-    try:
-        return open(path,'rb')
-    except:
-        raise CMBDTCFatal("Could not open book file: " + path)
-
-#
-# Get a 7 bit encoded number from the book file
-#
-
-def bookReadEncodedNumber():
-    flag = False
-    data = ord(bookFile.read(1))
-    
-    if data == 0xFF:
-       flag = True
-       data = ord(bookFile.read(1))
-       
-    if data >= 0x80:
-        datax = (data & 0x7F)
-        while data >= 0x80 :
-            data = ord(bookFile.read(1))
-            datax = (datax <<7) + (data & 0x7F)
-        data = datax 
-    
-    if flag:
-       data = -data
-    return data
-    
-#
-# Encode a number in 7 bit format
-#
-
-def encodeNumber(number):
-   result = ""
-   negative = False
-   flag = 0
-   print("Using encodeNumber routine")
-   
-   if number < 0 :
-       number = -number + 1
-       negative = True
-   
-   while True:
-       byte = number & 0x7F
-       number = number >> 7
-       byte += flag
-       result += chr(byte)
-       flag = 0x80
-       if number == 0 :
-           if (byte == 0xFF and negative == False) :
-               result += chr(0x80)
-           break
-   
-   if negative:
-       result += chr(0xFF)
-   
-   return result[::-1]
-  
-#
-# Get a length prefixed string from the file 
-#
-
-def bookReadString():
-    stringLength = bookReadEncodedNumber()
-    return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]  
-    
-#
-# Returns a length prefixed string
-#
-
-def lengthPrefixString(data):
-    return encodeNumber(len(data))+data
-    
-
-#
-# Read and return the data of one header record at the current book file position [[offset,decompressedLength,compressedLength],...]
-#
-    
-def bookReadHeaderRecordData():
-    nbValues = bookReadEncodedNumber()
-    values = []
-    for i in range (0,nbValues):
-        values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
-    return values
-   
-#
-# Read and parse one header record at the current book file position and return the associated data [[offset,decompressedLength,compressedLength],...]
-#
-
-def parseTopazHeaderRecord():
-    if ord(bookFile.read(1)) != 0x63:
-        raise CMBDTCFatal("Parse Error : Invalid Header")
-    
-    tag = bookReadString()
-    record = bookReadHeaderRecordData()
-    return [tag,record]
-
-#
-# Parse the header of a Topaz file, get all the header records and the offset for the payload
-#
-def parseTopazHeader():
-    global bookHeaderRecords
-    global bookPayloadOffset
-    magic = unpack("4s",bookFile.read(4))[0]
-    
-    if magic != 'TPZ0':
-        raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file")
-        
-    nbRecords = bookReadEncodedNumber()
-    bookHeaderRecords = {}
-   
-    for i in range (0,nbRecords):
-        result = parseTopazHeaderRecord()
-        print result[0], result[1]
-        bookHeaderRecords[result[0]] = result[1]
-    
-    if ord(bookFile.read(1))  != 0x64 :
-        raise CMBDTCFatal("Parse Error : Invalid Header")
-    
-    bookPayloadOffset = bookFile.tell()
-   
-#
-# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
-# Correction, the record is correctly decompressed too
-#
-
-def getBookPayloadRecord(name, index):   
-    encrypted = False
-    compressed = False
-
-    try: 
-        recordOffset = bookHeaderRecords[name][index][0]
-    except:
-        raise CMBDTCFatal("Parse Error : Invalid Record, record not found")
-    
-    bookFile.seek(bookPayloadOffset + recordOffset)
-    
-    tag = bookReadString()
-    if tag != name :
-        raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match")
-    
-    recordIndex = bookReadEncodedNumber()
-    
-    if recordIndex < 0 :
-        encrypted = True
-        recordIndex = -recordIndex -1
-    
-    if recordIndex != index :
-      raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match")
-            
-    if (bookHeaderRecords[name][index][2] > 0):
-        compressed = True
-        record = bookFile.read(bookHeaderRecords[name][index][2])
-    else:
-        record = bookFile.read(bookHeaderRecords[name][index][1])
-    if encrypted:
-       ctx = topazCryptoInit(bookKey)
-       record = topazCryptoDecrypt(record,ctx)
-
-    if compressed:
-        record = zlib.decompress(record)
-    
-    return record
-
-#
-# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
-#
-
-def extractBookPayloadRecord(name, index, filename):
-    compressed = False
-
-    try:
-        compressed = bookHeaderRecords[name][index][2] != 0
-        record = getBookPayloadRecord(name,index)
-    except:
-        print("Could not find record")
-    
-    # if compressed:
-    #    try:
-    #        record = zlib.decompress(record)
-    #    except:
-    #        raise CMBDTCFatal("Could not decompress record")
-            
-    if filename != "":
-        try:
-            file = open(filename,"wb")
-            file.write(record)
-            file.close()
-        except:
-            raise CMBDTCFatal("Could not write to destination file")
-    else:
-        print(record)
-    
-#
-# return next record [key,value] from the book metadata from the current book position
-#  
-
-def readMetadataRecord():
-    return [bookReadString(),bookReadString()]
-    
-#
-# Parse the metadata record from the book payload and return a list of [key,values]
-#
-
-def parseMetadata():
-    global bookHeaderRecords
-    global bookPayloadAddress
-    global bookMetadata
-    bookMetadata = {}
-    bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0])
-    tag = bookReadString()
-    if tag != "metadata" :
-        raise CMBDTCFatal("Parse Error : Record Names Don't Match")
-    
-    flags = ord(bookFile.read(1))
-    nbRecords = ord(bookFile.read(1))
-    
-    for i in range (0,nbRecords) :
-        record =readMetadataRecord()
-        bookMetadata[record[0]] = record[1]
-
-#
-# Context initialisation for the Topaz Crypto
-#
-
-def topazCryptoInit(key):
-    ctx1 = 0x0CAFFE19E
-    
-    for keyChar in key:
-        keyByte = ord(keyChar)
-        ctx2 = ctx1 
-        ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
-    return [ctx1,ctx2]
-    
-#
-# decrypt data with the context prepared by topazCryptoInit()
-#
-    
-def topazCryptoDecrypt(data, ctx):
-    ctx1 = ctx[0]
-    ctx2 = ctx[1]
-    
-    plainText = ""
-    
-    for dataChar in data:
-        dataByte = ord(dataChar)
-        m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
-        ctx2 = ctx1
-        ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
-        plainText += chr(m)
-        
-    return plainText
-
-#
-# Decrypt a payload record with the PID
-#
-
-def decryptRecord(data,PID):
-    ctx = topazCryptoInit(PID)
-    return topazCryptoDecrypt(data, ctx)
-
-#
-# Try to decrypt a dkey record (contains the book PID)
-#
-
-def decryptDkeyRecord(data,PID):
-    record = decryptRecord(data,PID)
-    fields = unpack("3sB8sB8s3s",record)
-    
-    if fields[0] != "PID" or fields[5] != "pid" :
-        raise CMBDTCError("Didn't find PID magic numbers in record")
-    elif fields[1] != 8 or fields[3] != 8 :
-        raise CMBDTCError("Record didn't contain correct length fields")
-    elif fields[2] != PID :
-        raise CMBDTCError("Record didn't contain PID")
-    
-    return fields[4]
-    
-#
-# Decrypt all the book's dkey records (contain the book PID)
-#
-  
-def decryptDkeyRecords(data,PID):
-    nbKeyRecords = ord(data[0])
-    records = []
-    data = data[1:]
-    for i in range (0,nbKeyRecords):
-        length = ord(data[0])
-        try:
-            key = decryptDkeyRecord(data[1:length+1],PID)
-            records.append(key)
-        except CMBDTCError:
-            pass
-        data = data[1+length:]
-        
-    return records
-    
-#
-# Create decrypted book payload
-#
-
-def createDecryptedPayload(payload):
-    for headerRecord in bookHeaderRecords:
-       name = headerRecord
-       if name != "dkey" :
-           ext = '.dat'
-           if name == 'img' : ext = '.jpg'
-           if name == 'color' : ext = '.jpg'
-           for index in range (0,len(bookHeaderRecords[name])) :
-               fnum = "%04d" % index
-               fname = name + fnum + ext
-               destdir = payload
-               if name == 'img':
-                   destdir =  os.path.join(payload,'img')
-               if name == 'color':
-                   destdir =  os.path.join(payload,'color_img')
-               if name == 'page':
-                   destdir =  os.path.join(payload,'page')
-               if name == 'glyphs':
-                   destdir =  os.path.join(payload,'glyphs')
-               outputFile = os.path.join(destdir,fname)
-               file(outputFile, 'wb').write(getBookPayloadRecord(name, index))
-                   
-
-# Create decrypted book
-#
-
-def createDecryptedBook(outdir):
-    if not os.path.exists(outdir):
-        os.makedirs(outdir)
-
-    destdir =  os.path.join(outdir,'img')
-    if not os.path.exists(destdir):
-        os.makedirs(destdir)
-
-    destdir =  os.path.join(outdir,'color_img')
-    if not os.path.exists(destdir):
-        os.makedirs(destdir)
-
-    destdir =  os.path.join(outdir,'page')
-    if not os.path.exists(destdir):
-        os.makedirs(destdir)
-
-    destdir =  os.path.join(outdir,'glyphs')
-    if not os.path.exists(destdir):
-        os.makedirs(destdir)
-
-    createDecryptedPayload(outdir)
-
-
-#
-# Set the command to execute by the programm according to cmdLine parameters
-#
-
-def setCommand(name) :
-    global command
-    if command != "" :
-         raise CMBDTCFatal("Invalid command line parameters")
-    else :
-        command = name
-
-# 
-# Program usage
-#
-   
-def usage():
-    print("\nUsage:")
-    print("\ncmbtc_dump_linux.py [options] bookFileName\n")
-    print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
-    print("-d Dumps the unencrypted book as files to outdir")
-    print("-o Output directory to save book files to")
-    print("-v Verbose (can be used several times)")
-
-#
-# Main
-#   
-
-def main(argv=sys.argv):
-    global bookMetadata
-    global bookKey
-    global bookFile
-    global command
-    
-    progname = os.path.basename(argv[0])
-    
-    verbose = 0
-    recordName = ""
-    recordIndex = 0
-    outdir = ""
-    PIDs = []
-    command = ""
-    
-    # Preloads your Kindle pid from the top of the program.
-    PIDs.append(kindlePID)
-    
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], "vo:p:d")
-    except getopt.GetoptError, err:
-        # print help information and exit:
-        print str(err) # will print something like "option -a not recognized"
-        usage()
-        sys.exit(2)
-    
-    if len(opts) == 0 and len(args) == 0 :
-        usage()
-        sys.exit(2) 
-       
-    for o, a in opts:
-        if o == "-v":
-            verbose+=1
-        if o =="-o":
-            if a == None :
-                raise CMBDTCFatal("Invalid parameter for -o")
-            outdir = a
-        if o =="-p":
-            PIDs.append(a)
-        if o =="-d":
-            setCommand("doit")
-            
-    if command == "" :
-        raise CMBDTCFatal("No action supplied on command line")
-   
-    #
-    # Open book and parse metadata
-    #
-        
-    if len(args) == 1:
-    
-        bookFile = openBook(args[0])
-        parseTopazHeader()
-        parseMetadata()
-    
-    #
-    #  Decrypt book key
-    #
-    
-        dkey = getBookPayloadRecord('dkey', 0) 
-        
-        bookKeys = []
-        for PID in PIDs :
-            bookKeys+=decryptDkeyRecords(dkey,PID)
-            
-        if len(bookKeys) == 0 :
-            if verbose > 0 :
-                print ("Book key could not be found. Maybe this book is not registered with this device.")
-                return 1
-        else :
-            bookKey = bookKeys[0]
-            if verbose > 0:
-                print("Book key: " + bookKey.encode('hex'))
-                  
-            if command == "printRecord" :
-                extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
-                if outputFile != "" and verbose>0 :
-                    print("Wrote record to file: "+outputFile) 
-            elif command == "doit" :
-                if outdir != "" :
-                    createDecryptedBook(outdir)
-                    if verbose >0 :
-                        print ("Decrypted book saved. Don't pirate!")
-                elif verbose > 0:
-                    print("Output directory name was not supplied.")
-                    return 1
-    
-    return 0
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/Topaz_Tools/lib/k4mutils.py b/Topaz_Tools/lib/k4mutils.py
new file mode 100644 (file)
index 0000000..977d81c
--- /dev/null
@@ -0,0 +1,322 @@
+# standlone set of Mac OSX specific routines needed for K4DeDRM
+
+from __future__ import with_statement
+
+import sys
+import os
+
+#Exception Handling
+class K4MDrmException(Exception):
+    pass
+
+import signal
+import threading
+import subprocess
+from subprocess import Popen, PIPE, STDOUT
+
+# **heavily** chopped up and modfied version of asyncproc.py
+# to make it actually work on Windows as well as Mac/Linux
+# For the original see:
+# "http://www.lysator.liu.se/~bellman/download/"
+# author is  "Thomas Bellman <bellman@lysator.liu.se>"
+# available under GPL version 3 or Later
+
+# create an asynchronous subprocess whose output can be collected in
+# a non-blocking manner
+
+# What a mess!  Have to use threads just to get non-blocking io
+# in a cross-platform manner
+
+# luckily all thread use is hidden within this class
+
+class Process(object):
+    def __init__(self, *params, **kwparams):
+        if len(params) <= 3:
+            kwparams.setdefault('stdin', subprocess.PIPE)
+        if len(params) <= 4:
+            kwparams.setdefault('stdout', subprocess.PIPE)
+        if len(params) <= 5:
+            kwparams.setdefault('stderr', subprocess.PIPE)
+        self.__pending_input = []
+        self.__collected_outdata = []
+        self.__collected_errdata = []
+        self.__exitstatus = None
+        self.__lock = threading.Lock()
+        self.__inputsem = threading.Semaphore(0)
+        self.__quit = False
+
+        self.__process = subprocess.Popen(*params, **kwparams)
+
+        if self.__process.stdin:
+            self.__stdin_thread = threading.Thread(
+                name="stdin-thread",
+                target=self.__feeder, args=(self.__pending_input,
+                                            self.__process.stdin))
+            self.__stdin_thread.setDaemon(True)
+            self.__stdin_thread.start()
+
+        if self.__process.stdout:
+            self.__stdout_thread = threading.Thread(
+                name="stdout-thread",
+                target=self.__reader, args=(self.__collected_outdata,
+                                           self.__process.stdout))
+            self.__stdout_thread.setDaemon(True)
+            self.__stdout_thread.start()
+
+        if self.__process.stderr:
+            self.__stderr_thread = threading.Thread(
+                name="stderr-thread",
+                target=self.__reader, args=(self.__collected_errdata,
+                                           self.__process.stderr))
+            self.__stderr_thread.setDaemon(True)
+            self.__stderr_thread.start()
+
+    def pid(self):
+        return self.__process.pid
+
+    def kill(self, signal):
+        self.__process.send_signal(signal)
+
+    # check on subprocess (pass in 'nowait') to act like poll
+    def wait(self, flag):
+        if flag.lower() == 'nowait':
+            rc = self.__process.poll()
+        else:
+            rc = self.__process.wait()
+        if rc != None:
+            if self.__process.stdin:
+                self.closeinput()
+            if self.__process.stdout:
+                self.__stdout_thread.join()
+            if self.__process.stderr:
+                self.__stderr_thread.join()
+        return self.__process.returncode
+
+    def terminate(self):
+        if self.__process.stdin:
+            self.closeinput()
+        self.__process.terminate()
+
+    # thread gets data from subprocess stdout
+    def __reader(self, collector, source):
+        while True:
+            data = os.read(source.fileno(), 65536)
+            self.__lock.acquire()
+            collector.append(data)
+            self.__lock.release()
+            if data == "":
+                source.close()
+                break
+        return
+
+    # thread feeds data to subprocess stdin
+    def __feeder(self, pending, drain):
+        while True:
+            self.__inputsem.acquire()
+            self.__lock.acquire()
+            if not pending  and self.__quit:
+                drain.close()
+                self.__lock.release()
+                break
+            data = pending.pop(0)
+            self.__lock.release()
+            drain.write(data)
+
+    # non-blocking read of data from subprocess stdout
+    def read(self):
+        self.__lock.acquire()
+        outdata = "".join(self.__collected_outdata)
+        del self.__collected_outdata[:]
+        self.__lock.release()
+        return outdata
+
+    # non-blocking read of data from subprocess stderr
+    def readerr(self):
+        self.__lock.acquire()
+        errdata = "".join(self.__collected_errdata)
+        del self.__collected_errdata[:]
+        self.__lock.release()
+        return errdata
+
+    # non-blocking write to stdin of subprocess
+    def write(self, data):
+        if self.__process.stdin is None:
+            raise ValueError("Writing to process with stdin not a pipe")
+        self.__lock.acquire()
+        self.__pending_input.append(data)
+        self.__inputsem.release()
+        self.__lock.release()
+
+    # close stdinput of subprocess
+    def closeinput(self):
+        self.__lock.acquire()
+        self.__quit = True
+        self.__inputsem.release()
+        self.__lock.release()
+
+
+# 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 K4MDrmException('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 K4MDrmException('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 K4MDrmException('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 K4MDrmException('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, K4MDrmException):
+        pass
+    return LibCrypto
+
+LibCrypto = _load_crypto()
+
+#
+# Utility Routines
+#
+
+# uses a sub process to get the Hard Drive Serial Number using ioreg
+# returns with the first found serial number in that class
+def GetVolumeSerialNumber():
+    cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
+    poll = p.wait('wait')
+    results = p.read()
+    reslst = results.split('\n')
+    sernum = '9999999999'
+    cnt = len(reslst)
+    for j in xrange(cnt):
+        resline = reslst[j]
+        pp = resline.find('"Serial Number" = "')
+        if pp >= 0:
+            sernum = resline[pp+19:]
+            sernum = sernum[:-1]
+            sernum = sernum.lstrip()
+            break
+    return sernum
+
+# uses unix env to get username instead of using sysctlbyname 
+def GetUserName():
+    username = os.getenv('USER')
+    return username
+
+# Various character maps used to decrypt books. Probably supposed to act as obfuscation
+charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" 
+charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+
+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 and open the .kindle-info file
+def openKindleInfo(kInfoFile=None):
+    if kInfoFile == None:
+       home = os.getenv('HOME')
+       cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
+       cmdline = cmdline.encode(sys.getfilesystemencoding())
+       p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
+       poll = p1.wait('wait')
+       results = p1.read()
+       reslst = results.split('\n')
+       kinfopath = 'NONE'
+       cnt = len(reslst)
+       for j in xrange(cnt):
+           resline = reslst[j]
+           pp = resline.find('.kindle-info')
+           if pp >= 0:
+               kinfopath = resline
+               break
+       if not os.path.exists(kinfopath):
+           raise K4MDrmException('Error: .kindle-info file can not be found')
+       return open(kinfopath,'r')
+    else:
+        return open(kInfoFile, 'r')
\ No newline at end of file
diff --git a/Topaz_Tools/lib/k4pcutils.py b/Topaz_Tools/lib/k4pcutils.py
new file mode 100644 (file)
index 0000000..337b992
--- /dev/null
@@ -0,0 +1,110 @@
+# 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"
+
+#
+# Exceptions for all the problems that might happen during the script
+#
+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 and open the Kindle.info file.
+#
+def openKindleInfo(kInfoFile=None):
+    if kInfoFile == None:
+        regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+        path = winreg.QueryValueEx(regkey, 'Local AppData')[0] 
+        return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
+    else:
+        return open(kInfoFile, 'r')
diff --git a/Topaz_Tools/lib/topaz-changes.txt b/Topaz_Tools/lib/topaz-changes.txt
deleted file mode 100644 (file)
index 2f5bbbe..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-Changes in this Version
-       - bug fix to prevent problems with sample books
-       modified version of patch submitted by that-guy
-
-Changes in 2.6
-       - fix for many additional version tags
-       - fixes to generate better links
-       - fixes to handle external links
-       - now handles new "marker" page .dat files
-       - improved special region handling
-       - properly handle class names with spaces
-       - handle default alignment for synthetic regions
-
-
-Changes in 2.3
-       - fix for use with non-latin1 based systems (thank you Tedd)
-       - fixes for out of order tokens in xml
-
-Changes in 2.2
-       - fix for minor bug in encode_Number from clark nova
-       - more fixes to handle paths with spaces in them
-       - updates to work better with the gui front end
-
-
-Changes in 2.1
-       - extremely minor changes to support a gui frontend
-       - no changes to functionality
-
-
-Changes in version 2.0
-
-       - gensvg.py now accepts two options
-             -x : output browseable XHTML+SVG pages (default)
-            -r : output raw SVG images (useful for later conversion to pdf)
-         
-       - flatxml2html.py now understands page.groups of type graphic
-            and handles vertical regions as svg images
-
-       - genhtml.py now accepts an option
-            --fixed-image : which will force the conversion
-                            of all fixed regions to svg images
-
-       - minor bug fixes and html conversion improvements
-
-
-Changes in version 1.8
-       - gensvg.py now builds wonderful xhtml pages with embedded svg 
-           that can be easily paged through as if reading a book!
-           (tested in Safari for Mac and Win and Firefox)
-           (requires javascript to be enabled)
-       - genhtml.py now REQUIRES that gensvg.py be run FIRST
-            this allows create of images on the fly from glyphs
-       - genhtml.py now automatically makes tables of words into svg
-            based images and will handle glyph based ornate first 
-            letters of words
-       - cmbtc_dump_mac_linux.py has been renamed to be
-            cmbtc_dump_nonK4PC.py to make it clearer
-            when it needs to be used
-       
-
-Changes in version 1.7
-       - gensvg.py has been improved so that the glyphs render exactly (ClarkNova)
-       - gensvg.py has fixed a render order "bug" that allowed some images to cover or hide text. (ClarkNova)
-       - change generated html to use external stylesheet via a link to "style.css"
-       - add missing <title> tag
-       - make xhtml compliant doctype and minor changes to write correct xhtml
-       - make divs that act as anchors be hidden visually and to take up 0 height and 0 width to prevent any impact on layout
-
-Changes in version 1.6
-       - support for books whose paragraphs have no styles
-       - support to run cmbtc_dump on Linux and Mac OSX provided you know your PID of your ipod or standalone Kindle
-        (contributed by DiapDealer)
-
-Changes in version 1.5
-       - completely reworked generation of styles to use actual page heights and widths
-       - added new script getpagedim.py to support the above
-       - style names with underscores in them are now properly paired with their base class
-       - fixed hanging indents that did not ever set a left margin
-       - added support for a number of not previously known region types
-       - added support for a previously unknown snippet - <empty></empty>
-       - corrected a bug that caused unknown regions to abort the program
-       - added code to make the handling of unknown regions better in general
-       - corrected a bug that caused the last link on a page to be missing (if it was the last thing on the page)
-
-Changes in version 1.3
-       - font generation by gensvg.py is now greatly improved with support for contour points added
-       - support for more region types
-       - support for inline images in paragraphs or text fields (ie. initial graphics for the first letter of a word)
-       - greatly improved dtd information used for the xml to prevent parsing mistakes
-
-Version 1.0
-       - initial release
-
index 6bc17e411d431cb9b80bc311d4cdc4b39a77560c..a1bcdab5d50a231ff37dffc67738881541febffd 100644 (file)
@@ -19,8 +19,7 @@ Here are the steps:
 1. Unzip the topazscripts.zip file to get the full set of python scripts.
 The files you should have after unzipping are:
 
-cmbtc_dump.py - (author: cmbtc) unencrypts and dumps sections into separate files for Kindle for PC
-cmbtc_dump_nonK4PC.py - (author - DiapDealer) for use with standalone Kindle and ipod/iphone topaz books
+cmbtc_dump.py - (author: cmbtc) unencrypts and dumps sections into separate files for Kindle for PC and Mac
 decode_meta.py - converts metadata0000.dat to make it available
 convert2xml.py - converts page*.dat, other*.dat, and glyphs*.dat files to pseudo xml descriptions
 flatxml2html.py - converts a "flattened" xml description to html using the ocrtext
@@ -29,6 +28,9 @@ getpagedim.py - reads page0000.dat to get the book height and width parameters
 genxml.py - main program to convert everything to xml
 genhtml.py - main program to generate "book.html"
 gensvg.py - (author: clarknova) main program to create an xhmtl page with embedded svg graphics
+k4mutils.py - Mac OSX support routines for cmbtc_dump.py
+k4pcutils.py - Windows support routines for cmbtc_dump.py
+
 
 
 Please note, these scripts all import code from each other so please
@@ -42,18 +44,15 @@ of its contents as files
 All Thanks go to CMBTC who broke the DRM for Topaz - without it nothing else 
 would be possible
 
-If you purchased the book for Kindle For PC, you must do the following:
+If you purchased the book for Kindle for PC or Kindle for Mac, you must do the following:
 
    cmbtc_dump.py -d -o TARGETDIR [-p pid] YOURTOPAZBOOKNAMEHERE
 
 
-However, if you purchased the book for a standalone Kindle or ipod/iphone 
+If you purchased the book for a standalone Kindle 1 or ipod/iphone/ipad 
 and you know your pid (at least the first 8 characters) then you should 
-instead do the following
-
-   cmbtc_dump_nonK4PC.py -d -o TARGETDIR -p 12345678 YOURTOPAZBOOKNAMEHERE
-
-where 12345678 should be replaced by the first 8 characters of your PID
+add that using -p 12345678 switch as indicated above, replacing the 
+12345678 with the 8 characters of your pid
 
 
 This should create a directory called "TARGETDIR" in your current directory.  
@@ -64,7 +63,8 @@ other0000.dat - information used to create a style sheet
 dict0000.dat - dictionary of words used to build page descriptions
 page - directory filled with page*.dat files
 glyphs - directory filled with glyphs*.dat files
-
+img - directory filled with images
+color_img - directory used for color images
 
 3. REQUIRED: Create xhtml page descriptions with embedded svg
 that show the exact representation of each page as an image
diff --git a/ePub_Fixer/README_ePub_Fixer.txt b/ePub_Fixer/README_ePub_Fixer.txt
new file mode 100644 (file)
index 0000000..f7316f8
--- /dev/null
@@ -0,0 +1,17 @@
+ePub_Fixer
+
+ePubs are specially crafted zip archives.  Unfortunately, many of te DRM encoded Adobe Adept and Barnes & Noble ePubs are not "proper" zip archives in that the names of some files in the zip central directory do NOT match the local name given in archive itself.  This type of zip archive is technically incorrect/corrupted and can not be read by many other programs.
+
+ePub_Fixer was designed to fix improperly created zip archives of this type.
+
+1. Simply double-click to launch ePub_Fixer.pyw.
+
+2. use the first "..." button to select the ePub (with DRM) that needs to be fixed
+
+3. use the second "..." button to select where you want the fixed ePub (still with DRM!) to be placed.
+
+4. Hit the start button.
+
+Once the program has successfully completed, you can now feed the fixed ePubs into other programs such as DRM removal programs like ignobleepub.pyw and ineptepub.pyw.
+
+
similarity index 62%
rename from Topaz_Tools/TopazExtract_iPhone_iPad_K4M.pyw
rename to ePub_Fixer/ePub_Fixer.pyw
index 83cb79cda510c2149eb4dbe0377c582f53c7dda3..6e3a53ebabdfbc6040cc82c749205980842624aa 100644 (file)
@@ -3,16 +3,15 @@
 
 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
-import subasyncio
-from subasyncio import Process
 from scrolltextwidget import ScrolledText
 
 class MainDialog(Tkinter.Frame):
@@ -21,45 +20,41 @@ class MainDialog(Tkinter.Frame):
         self.root = root
         self.interval = 2000
         self.p2 = None
-        self.status = Tkinter.Label(self, text='Extract Contents of Topaz eBook to a Directory')
+        self.status = Tkinter.Label(self, text='Fix Improper ePubs')
         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='Topaz eBook input file').grid(row=0, sticky=Tkconstants.E)
-        self.tpzpath = Tkinter.Entry(body, width=50)
-        self.tpzpath.grid(row=0, column=1, sticky=sticky)
+        Tkinter.Label(body, text='ePub input file').grid(row=0, sticky=Tkconstants.E)
+        self.epubpath = Tkinter.Entry(body, width=50)
+        self.epubpath.grid(row=0, column=1, sticky=sticky)
         cwd = os.getcwdu()
         cwd = cwd.encode('utf-8')
-        self.tpzpath.insert(0, cwd)
-        button = Tkinter.Button(body, text="...", command=self.get_tpzpath)
+        self.epubpath.insert(0, cwd)
+        button = Tkinter.Button(body, text="...", command=self.get_epubpath)
         button.grid(row=0, column=2)
 
-        Tkinter.Label(body, text='Output Directory').grid(row=1, sticky=Tkconstants.E)
+        Tkinter.Label(body, text='Directory to store fixed ePub in').grid(row=1, sticky=Tkconstants.E)
         self.outpath = Tkinter.Entry(body, width=50)
         self.outpath.grid(row=1, column=1, sticky=sticky)
         cwd = os.getcwdu()
         cwd = cwd.encode('utf-8')
-        self.outpath.insert(0, cwd)
+        outname = cwd
+        self.outpath.insert(0, outname)
         button = Tkinter.Button(body, text="...", command=self.get_outpath)
         button.grid(row=1, column=2)
 
-        Tkinter.Label(body, text='First 8 characters of PID').grid(row=3, sticky=Tkconstants.E)
-        self.pidnum = Tkinter.StringVar()
-        self.ccinfo = Tkinter.Entry(body, width=10, textvariable=self.pidnum)
-        self.ccinfo.grid(row=3, column=1, sticky=sticky)
-
-        msg1 = 'Conversion Log \n\n'
+        msg1 = 'Log \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.grid(row=2, 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)
+            buttons, text="Start", width=10, command=self.fixit)
         self.sbotton.pack(side=Tkconstants.LEFT)
 
         Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
@@ -75,9 +70,9 @@ class MainDialog(Tkinter.Frame):
         if poll != None: 
             text = self.p2.readerr()
             text += self.p2.read()
-            msg = text + '\n\n' + 'Files successfully extracted\n'
+            msg = text + '\n\n' + 'ePub successfully fixed\n'
             if poll != 0:
-                msg = text + '\n\n' + 'Error: File Extraction Failed\n'
+                msg = text + '\n\n' + 'Error: ePub Fixing Failed\n'
             self.showCmdOutput(msg)
             self.p2 = None
             self.sbotton.configure(state='normal')
@@ -98,40 +93,37 @@ class MainDialog(Tkinter.Frame):
         return
 
     # run as a subprocess via pipes and collect stdout
-    def topazrdr(self, infile, outdir, pidnum):
+    def zipfixrdr(self, infile, outfile):
         # os.putenv('PYTHONUNBUFFERED', '1')
-        pidoption = ' -p "' + pidnum + '" '
-        outoption = ' -o "' + outdir + '" '
-        cmdline = 'python ./lib/cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"'
+        cmdline = 'python ./lib/zipfix.py "' + infile + '" "' + outfile + '"'
         if sys.platform[0:3] == 'win':
             search_path = os.environ['PATH']
             search_path = search_path.lower()
             if search_path.find('python') >= 0: 
-                cmdline = 'python lib\cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"'
+                cmdline = 'python lib\zipfix.py "' + infile + '" "' + outfile + '"'
             else :
-                cmdline = 'lib\cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"'
+                cmdline = 'lib\zipfix.py "' + infile + '" "' + outfile + '"'
 
         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_tpzpath(self):
-        tpzpath = tkFileDialog.askopenfilename(
-            parent=None, title='Select Topaz File',
-            defaultextension='.prc', filetypes=[('Topaz azw1', '.azw1'), ('Topaz prc', '.prc'),('Topaz azw', '.azw'),
-                                                ('All Files', '.*')])
-        if tpzpath:
-            tpzpath = os.path.normpath(tpzpath)
-            self.tpzpath.delete(0, Tkconstants.END)
-            self.tpzpath.insert(0, tpzpath)
+    def get_epubpath(self):
+        epubpath = tkFileDialog.askopenfilename(
+            parent=None, title='Select ePub to be Fixed',
+            defaultextension='.epub', filetypes=[('ePub eBook File', '.epub'), ('Zip File', '.zip'),('All Files', '.*')])
+        if epubpath:
+            epubpath = os.path.normpath(epubpath)
+            self.epubpath.delete(0, Tkconstants.END)
+            self.epubpath.insert(0, epubpath)
         return
 
     def get_outpath(self):
         cwd = os.getcwdu()
         cwd = cwd.encode('utf-8')
         outpath = tkFileDialog.askdirectory(
-            parent=None, title='Directory to Extract Files into',
+            parent=None, title='Directory to Store Fixed ePub into',
             initialdir=cwd, initialfile=None)
         if outpath:
             outpath = os.path.normpath(outpath)
@@ -147,36 +139,33 @@ class MainDialog(Tkinter.Frame):
         self.root.destroy()
 
     # actually ready to run the subprocess and get its output
-    def convertit(self):
+    def fixit(self):
         # now disable the button to prevent multiple launches
         self.sbotton.configure(state='disabled')
-        tpzpath = self.tpzpath.get()
+        epubpath = self.epubpath.get()
         outpath = self.outpath.get()
-        if not tpzpath or not os.path.exists(tpzpath):
-            self.status['text'] = 'Specified Topaz eBook file does not exist'
+        if not epubpath or not os.path.exists(epubpath):
+            self.status['text'] = 'Specified ePub eBook file does not exist'
             self.sbotton.configure(state='normal')
             return
         if not outpath:
-            self.status['text'] = 'No output directory specified'
-            self.sbotton.configure(state='normal')
-            return
-        if not os.path.exists(outpath):
-            os.makedirs(outpath)
-        pidnum = self.pidnum.get()
-        if not pidnum or pidnum == '':
-            self.status['text'] = 'You have not entered a PID '
+            self.status['text'] = 'Error specified output directory does not exist'
             self.sbotton.configure(state='normal')
             return
 
-        log = 'Command = "python cmbtc_dump_nonK4PC.py"\n'
-        log += 'Topaz Path Path = "'+ tpzpath + '"\n'
-        log += 'Output Directory = "' + outpath + '"\n'
-        log += 'First 8 chars of PID = "' + pidnum + '"\n'
+        # default output file name to be input file name + '_fixed.epub'
+        initname = os.path.splitext(os.path.basename(epubpath))[0]
+        initname += '_fixed.epub' 
+        outpath += os.sep + initname
+
+        log = 'Command = "python zipfix.py"\n'
+        log += 'ePub Path = "'+ epubpath + '"\n'
+        log += 'Output File = "' + outpath + '"\n'
         log += '\n\n'
-        log += 'Please Wait ...\n'
+        log += 'Please Wait ...\n\n'
         log = log.encode('utf-8')
         self.stext.insert(Tkconstants.END,log)
-        self.p2 = self.topazrdr(tpzpath, outpath, pidnum)
+        self.p2 = self.zipfixrdr(epubpath, outpath)
 
         # python does not seem to allow you to create
         # your own eventloop which every other gui does - strange 
@@ -188,7 +177,7 @@ class MainDialog(Tkinter.Frame):
 
 def main(argv=None):
     root = Tkinter.Tk()
-    root.title('Topaz eBook File Extraction')
+    root.title('Fix Incorrect ePubs')
     root.resizable(True, False)
     root.minsize(300, 0)
     MainDialog(root).pack(fill=Tkconstants.X, expand=1)
diff --git a/ePub_Fixer/lib/scrolltextwidget.py b/ePub_Fixer/lib/scrolltextwidget.py
new file mode 100644 (file)
index 0000000..98b4147
--- /dev/null
@@ -0,0 +1,27 @@
+#!/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/ePub_Fixer/lib/subasyncio.py b/ePub_Fixer/lib/subasyncio.py
new file mode 100644 (file)
index 0000000..ed13aa1
--- /dev/null
@@ -0,0 +1,149 @@
+#!/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 <bellman@lysator.liu.se>"
+# available under GPL version 3 or Later
+
+# create an asynchronous subprocess whose output can be collected in
+# a non-blocking manner
+
+# What a mess!  Have to use threads just to get non-blocking io
+# in a cross-platform manner
+
+# luckily all thread use is hidden within this class
+
+class Process(object):
+    def __init__(self, *params, **kwparams):
+        if len(params) <= 3:
+            kwparams.setdefault('stdin', subprocess.PIPE)
+        if len(params) <= 4:
+            kwparams.setdefault('stdout', subprocess.PIPE)
+        if len(params) <= 5:
+            kwparams.setdefault('stderr', subprocess.PIPE)
+        self.__pending_input = []
+        self.__collected_outdata = []
+        self.__collected_errdata = []
+        self.__exitstatus = None
+        self.__lock = threading.Lock()
+        self.__inputsem = threading.Semaphore(0)
+        self.__quit = False
+
+        self.__process = subprocess.Popen(*params, **kwparams)
+
+        if self.__process.stdin:
+            self.__stdin_thread = threading.Thread(
+                name="stdin-thread",
+                target=self.__feeder, args=(self.__pending_input,
+                                            self.__process.stdin))
+            self.__stdin_thread.setDaemon(True)
+            self.__stdin_thread.start()
+
+        if self.__process.stdout:
+            self.__stdout_thread = threading.Thread(
+                name="stdout-thread",
+                target=self.__reader, args=(self.__collected_outdata,
+                                           self.__process.stdout))
+            self.__stdout_thread.setDaemon(True)
+            self.__stdout_thread.start()
+
+        if self.__process.stderr:
+            self.__stderr_thread = threading.Thread(
+                name="stderr-thread",
+                target=self.__reader, args=(self.__collected_errdata,
+                                           self.__process.stderr))
+            self.__stderr_thread.setDaemon(True)
+            self.__stderr_thread.start()
+
+    def pid(self):
+        return self.__process.pid
+
+    def kill(self, signal):
+        self.__process.send_signal(signal)
+
+    # check on subprocess (pass in 'nowait') to act like poll
+    def wait(self, flag):
+        if flag.lower() == 'nowait':
+            rc = self.__process.poll()
+        else:
+            rc = self.__process.wait()
+        if rc != None:
+            if self.__process.stdin:
+                self.closeinput()
+            if self.__process.stdout:
+                self.__stdout_thread.join()
+            if self.__process.stderr:
+                self.__stderr_thread.join()
+        return self.__process.returncode
+
+    def terminate(self):
+        if self.__process.stdin:
+            self.closeinput()
+        self.__process.terminate()
+
+    # thread gets data from subprocess stdout
+    def __reader(self, collector, source):
+        while True:
+            data = os.read(source.fileno(), 65536)
+            self.__lock.acquire()
+            collector.append(data)
+            self.__lock.release()
+            if data == "":
+                source.close()
+                break
+        return
+
+    # thread feeds data to subprocess stdin
+    def __feeder(self, pending, drain):
+        while True:
+            self.__inputsem.acquire()
+            self.__lock.acquire()
+            if not pending  and self.__quit:
+                drain.close()
+                self.__lock.release()
+                break
+            data = pending.pop(0)
+            self.__lock.release()
+            drain.write(data)
+
+    # non-blocking read of data from subprocess stdout
+    def read(self):
+        self.__lock.acquire()
+        outdata = "".join(self.__collected_outdata)
+        del self.__collected_outdata[:]
+        self.__lock.release()
+        return outdata
+
+    # non-blocking read of data from subprocess stderr
+    def readerr(self):
+        self.__lock.acquire()
+        errdata = "".join(self.__collected_errdata)
+        del self.__collected_errdata[:]
+        self.__lock.release()
+        return errdata
+
+    # non-blocking write to stdin of subprocess
+    def write(self, data):
+        if self.__process.stdin is None:
+            raise ValueError("Writing to process with stdin not a pipe")
+        self.__lock.acquire()
+        self.__pending_input.append(data)
+        self.__inputsem.release()
+        self.__lock.release()
+
+    # close stdinput of subprocess
+    def closeinput(self):
+        self.__lock.acquire()
+        self.__quit = True
+        self.__inputsem.release()
+        self.__lock.release()
+
diff --git a/ePub_Fixer/lib/zipfix.py b/ePub_Fixer/lib/zipfix.py
new file mode 100644 (file)
index 0000000..40c41d2
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+
+import sys
+import zlib
+import zipfile
+import os
+import os.path
+import getopt
+from struct import unpack
+
+
+_FILENAME_LEN_OFFSET = 26
+_EXTRA_LEN_OFFSET = 28
+_FILENAME_OFFSET = 30
+_MAX_SIZE = 64 * 1024
+
+class fixZip:
+    def __init__(self, zinput, zoutput):
+        self.inzip = zipfile.ZipFile(zinput,'r')
+        self.outzip = zipfile.ZipFile(zoutput,'w')
+        # open the input zip for reading only as a raw file
+       self.bzf = file(zinput,'rb')
+        
+    def getlocalname(self, zi):
+        local_header_offset = zi.header_offset
+        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
+        leninfo = self.bzf.read(2)
+        local_name_length, = unpack('<H', leninfo)
+        self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
+        local_name = self.bzf.read(local_name_length)
+        return local_name
+
+    def uncompress(self, cmpdata):
+        dc = zlib.decompressobj(-15)
+        data = ''
+        while len(cmpdata) > 0:
+            if len(cmpdata) > _MAX_SIZE :
+                newdata = cmpdata[0:_MAX_SIZE]
+                cmpdata = cmpdata[_MAX_SIZE:]
+            else:
+                newdata = cmpdata
+                cmpdata = ''
+            newdata = dc.decompress(newdata)
+            unprocessed = dc.unconsumed_tail
+            if len(unprocessed) == 0:
+                newdata += dc.flush()
+            data += newdata
+            cmpdata += unprocessed
+            unprocessed = ''
+        return data
+
+    def getfiledata(self, zi):
+        # get file name length and exta data length to find start of file data
+        local_header_offset = zi.header_offset
+
+        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
+        leninfo = self.bzf.read(2)
+        local_name_length, = unpack('<H', leninfo)
+
+        self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
+        exinfo = self.bzf.read(2)
+        extra_field_length, = unpack('<H', exinfo)
+
+        self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
+        data = None
+
+        # if not compressed we are good to go
+        if zi.compress_type == zipfile.ZIP_STORED:
+            data = self.bzf.read(zi.file_size)
+
+        # if compressed we must decompress it using zlib
+        if zi.compress_type == zipfile.ZIP_DEFLATED:
+            cmpdata = self.bzf.read(zi.compress_size)
+            data = self.uncompress(cmpdata)
+
+        return data
+
+        
+
+    def fix(self):
+        # get the zipinfo for each member of the input archive
+        # and copy member over to output archive
+        # if problems exist with local vs central filename, fix them
+
+        for i, zinfo in enumerate(self.inzip.infolist()):
+            data = None
+            nzinfo = zinfo
+
+            try: 
+                data = self.inzip.read(zinfo)
+            except zipfile.BadZipfile or zipfile.error:
+                local_name = self.getlocalname(zinfo)
+                data = self.getfiledata(zinfo)
+                nzinfo.filename = local_name
+
+            nzinfo.date_time = zinfo.date_time
+            nzinfo.compress_type = zinfo.compress_type
+            nzinfo.flag_bits = 0
+            nzinfo.internal_attr = 0
+            self.outzip.writestr(nzinfo,data)
+
+        self.bzf.close()
+        self.inzip.close()
+        self.outzip.close()
+
+
+def usage():
+    print """usage: zipfix.py inputzip outputzip
+     inputzip is the source zipfile to fix
+     outputzip is the fixed zip archive
+    """
+    
+
+def main(argv=sys.argv):
+    if len(argv)!=3:
+        usage()
+        return 1
+    infile = None
+    outfile = None
+    infile = argv[1]
+    outfile = argv[2]
+    if not os.path.exists(infile):
+        print "Error: Input Zip File does not exist"
+        return 1
+    try:
+        fr = fixZip(infile, outfile)
+        fr.fix()
+        return 0
+    except Exception, e:
+        print "Error Occurred ", e
+        return 2
+
+if __name__ == '__main__' :
+    sys.exit(main())
+
+
diff --git a/eReader_PDB_Tools/lib/eReaderPDB2PML_plugin.py b/eReader_PDB_Tools/lib/eReaderPDB2PML_plugin.py
new file mode 100644 (file)
index 0000000..fe4c9b3
--- /dev/null
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+
+# eReaderPDB2PML_plugin.py
+# Released under the terms of the GNU General Public Licence, version 3 or
+# later.  <http://www.gnu.org/licenses/>
+#
+# All credit given to The Dark Reverser for the original standalone script.
+# I had the much easier job of converting it to Calibre a plugin.
+#
+# This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files.
+# Calibre can then convert it to whatever format you desire.
+# It is meant to function without having to install any dependencies...
+# other than having Calibre installed, of course. I've included the psyco libraries
+# (compiled for each platform) for speed. If your system can use them, great!
+# Otherwise, they won't be used and things will just work slower.
+#
+# Installation:
+# Go to Calibre's Preferences page... click on the Plugins button. Use the file
+# dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and
+# click the 'Add' button. You're done.
+#
+# Configuration:
+# Highlight the plugin (eReader PDB 2 PML) and click the
+# "Customize Plugin" button on Calibre's Preferences->Plugins page.
+# Enter your name and the last 8 digits of the credit card number separated by
+# a comma: Your Name,12341234
+#
+# If you've purchased books with more than one credit card, separate the info with
+# a colon: Your Name,12341234:Other Name,23452345
+# NOTE: Do NOT put quotes around your name like you do with the original script!!
+#
+# Revision history:
+#   0.0.1 - Initial release
+#   0.0.2 - updated to distinguish it from earlier non-openssl version
+
+import sys, os
+
+from calibre.customize import FileTypePlugin
+
+class eRdrDeDRM(FileTypePlugin):
+    name                = 'eReader PDB 2 PML' # Name of the plugin
+    description         = 'Removes DRM from secure pdb files. \
+                            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, 2)   # 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
+
+    def run(self, path_to_ebook):
+        from calibre.ptempfile import PersistentTemporaryDirectory
+        from calibre.constants import iswindows, isosx
+        pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
+        ppath = os.path.join(self.sys_insertion_path, pdir)
+        sys.path.insert(0, ppath)
+        
+        global bookname, erdr2pml
+        import erdr2pml
+        
+        if 'psyco' in sys.modules:
+            print 'Using psyco acceleration for %s.' % pdir
+        else:
+            print 'NOT using psyco acceleration for %s. Conversion may be slow.' % pdir
+        
+        infile = path_to_ebook
+        bookname = os.path.splitext(os.path.basename(infile))[0]
+        outdir = PersistentTemporaryDirectory()
+        pmlzfile = self.temporary_file(bookname + '.pmlz')
+        
+        if self.site_customization:
+            keydata = self.site_customization
+            ar = keydata.split(':')
+            for i in ar:
+                try:
+                    name, cc = i.split(',')
+                except ValueError:
+                    sys.path.remove(ppath)
+                    print '   Error parsing user supplied data.'
+                    return path_to_ebook
+
+                try:
+                    print "Processing..."
+                    import time
+                    start_time = time.time()
+                    pmlfilepath = self.convertEreaderToPml(infile, name, cc, outdir)
+                    
+                    if pmlfilepath and pmlfilepath != 1:
+                        import zipfile
+                        import shutil
+                        print "   Creating PMLZ file"
+                        myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False)
+                        list = os.listdir(outdir)
+                        for file in list:
+                            localname = file
+                            filePath = os.path.join(outdir,file)
+                            if os.path.isfile(filePath):
+                                myZipFile.write(filePath, localname)
+                            elif os.path.isdir(filePath):
+                                imageList = os.listdir(filePath)
+                                localimgdir = os.path.basename(filePath)
+                                for image in imageList:
+                                    localname = os.path.join(localimgdir,image)
+                                    imagePath = os.path.join(filePath,image)
+                                    if os.path.isfile(imagePath):
+                                        myZipFile.write(imagePath, localname)
+                        myZipFile.close()
+                        end_time = time.time()
+                        search_time = end_time - start_time
+                        print 'elapsed time: %.2f seconds' % (search_time, ) 
+                        print "done"
+                        return pmlzfile.name
+                    else:
+                        raise ValueError('Error Creating PML file.')
+                except ValueError, e:
+                        print "Error: %s" % e
+                        pass
+            raise Exception('Couldn\'t decrypt pdb file.')
+        else:
+            raise Exception('No name and CC# provided.')
+        
+    def convertEreaderToPml(self, infile, name, cc, outdir):
+
+        print "   Decoding File"
+        sect = erdr2pml.Sectionizer(infile, 'PNRdPPrs')
+        er = erdr2pml.EreaderProcessor(sect.loadSection, name, cc)
+
+        if er.getNumImages() > 0:
+            print "   Extracting images"
+            #imagedir = bookname + '_img/'
+            imagedir = 'images/'
+            imagedirpath = os.path.join(outdir,imagedir)
+            if not os.path.exists(imagedirpath):
+                os.makedirs(imagedirpath)
+            for i in xrange(er.getNumImages()):
+                name, contents = er.getImage(i)
+                file(os.path.join(imagedirpath, name), 'wb').write(contents)
+
+        print "   Extracting pml"
+        pml_string = er.getText()
+        pmlfilename = bookname + ".pml"
+        try:
+            file(os.path.join(outdir, pmlfilename),'wb').write(erdr2pml.cleanPML(pml_string))
+            return os.path.join(outdir, pmlfilename)
+        except:
+            return 1
+    def customization_help(self, gui=False):
+        return 'Enter Account Name & Last 8 digits of Credit Card number (separate with a comma)'
index 089d0009befc1e2dabe67b938a6896e7d7e85750..daa6b2171f79719e57ed7ff0993ee99929e4405e 100644 (file)
 #  0.13 - change to unbuffered stdout for use with gui front ends
 #  0.14 - contributed enhancement to support --make-pmlz switch
 #  0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
+#  0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
 
-__version__='0.15'
+Des = None
+
+import openssl_des
+Des = openssl_des.load_libcrypto()
+
+# 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
+    Des = python_des.Des
+    # Import Psyco if available
+    try:
+        # Dumb speed hack 1
+        # http://psyco.sourceforge.net
+        import psyco
+        psyco.full()
+        pass
+    except ImportError:
+        pass
 
-# Import Psyco if available
-try:
-    # Dumb speed hack 1
-    # http://psyco.sourceforge.net
-    import psyco
-    psyco.full()
-    pass
-except ImportError:
-    pass
-try:
-    # Dumb speed hack 2
-    # All map() calls converted to list comprehension (some use zip)
-    # override zip with izip - saves memory and in rough testing
-    # appears to be faster zip() is only used in the converted map() calls
-    from itertools import izip as zip
-except ImportError:
-    pass
+
+__version__='0.16'
 
 class Unbuffered:
     def __init__(self, stream):
@@ -101,223 +105,6 @@ import logging
 logging.basicConfig()
 #logging.basicConfig(level=logging.DEBUG)
 
-ECB =  0
-CBC =  1
-class Des(object):
-    __pc1 = [56, 48, 40, 32, 24, 16,  8,  0, 57, 49, 41, 33, 25, 17,
-          9,  1, 58, 50, 42, 34, 26, 18, 10,  2, 59, 51, 43, 35,
-         62, 54, 46, 38, 30, 22, 14,  6, 61, 53, 45, 37, 29, 21,
-         13,  5, 60, 52, 44, 36, 28, 20, 12,  4, 27, 19, 11,  3]
-    __left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
-    __pc2 = [13, 16, 10, 23,  0,  4,2, 27, 14,  5, 20,  9,
-        22, 18, 11,  3, 25,  7,        15,  6, 26, 19, 12,  1,
-        40, 51, 30, 36, 46, 54,        29, 39, 50, 44, 32, 47,
-        43, 48, 38, 55, 33, 52,        45, 41, 49, 35, 28, 31]
-    __ip = [57, 49, 41, 33, 25, 17, 9,  1,     59, 51, 43, 35, 27, 19, 11, 3,
-        61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
-        56, 48, 40, 32, 24, 16, 8,  0, 58, 50, 42, 34, 26, 18, 10, 2,
-        60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
-    __expansion_table = [31,  0,  1,  2,  3,  4, 3,  4,  5,  6,  7,  8,
-         7,  8,  9, 10, 11, 12,11, 12, 13, 14, 15, 16,
-        15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
-        23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31,  0]
-    __sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
-         0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
-         4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
-         15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
-        [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
-         3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
-         0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
-         13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
-        [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
-         13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
-         13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
-         1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
-        [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
-         13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
-         10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
-         3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
-        [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
-         14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
-         4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
-         11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
-        [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
-         10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
-         9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
-         4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
-        [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
-         13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
-         1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
-         6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
-        [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
-         1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
-         7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
-         2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
-    __p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
-        4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
-    __fp = [39,  7, 47, 15, 55, 23, 63, 31,38,  6, 46, 14, 54, 22, 62, 30,
-        37,  5, 45, 13, 53, 21, 61, 29,36,  4, 44, 12, 52, 20, 60, 28,
-        35,  3, 43, 11, 51, 19, 59, 27,34,  2, 42, 10, 50, 18, 58, 26,
-        33,  1, 41,  9, 49, 17, 57, 25,32,  0, 40,  8, 48, 16, 56, 24]
-    # Type of crypting being done
-    ENCRYPT =  0x00
-    DECRYPT =  0x01
-    def __init__(self, key, mode=ECB, IV=None):
-        if len(key) != 8:
-            raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
-        self.block_size = 8
-        self.key_size = 8
-        self.__padding = ''
-        self.setMode(mode)
-        if IV:
-            self.setIV(IV)
-        self.L = []
-        self.R = []
-        self.Kn = [ [0] * 48 ] * 16    # 16 48-bit keys (K1 - K16)
-        self.final = []
-        self.setKey(key)
-    def getKey(self):
-        return self.__key
-    def setKey(self, key):
-        self.__key = key
-        self.__create_sub_keys()
-    def getMode(self):
-        return self.__mode
-    def setMode(self, mode):
-        self.__mode = mode
-    def getIV(self):
-        return self.__iv
-    def setIV(self, IV):
-        if not IV or len(IV) != self.block_size:
-            raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
-        self.__iv = IV
-    def getPadding(self):
-        return self.__padding
-    def __String_to_BitList(self, data):
-        l = len(data) * 8
-        result = [0] * l
-        pos = 0
-        for c in data:
-            i = 7
-            ch = ord(c)
-            while i >= 0:
-                if ch & (1 << i) != 0:
-                    result[pos] = 1
-                else:
-                    result[pos] = 0
-                pos += 1
-                i -= 1
-        return result
-    def __BitList_to_String(self, data):
-        result = ''
-        pos = 0
-        c = 0
-        while pos < len(data):
-            c += data[pos] << (7 - (pos % 8))
-            if (pos % 8) == 7:
-                result += chr(c)
-                c = 0
-            pos += 1
-        return result
-    def __permutate(self, table, block):
-        return [block[x] for x in table]
-    def __create_sub_keys(self):
-        key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
-        i = 0
-        self.L = key[:28]
-        self.R = key[28:]
-        while i < 16:
-            j = 0
-            while j < Des.__left_rotations[i]:
-                self.L.append(self.L[0])
-                del self.L[0]
-                self.R.append(self.R[0])
-                del self.R[0]
-                j += 1
-            self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
-            i += 1
-    def __des_crypt(self, block, crypt_type):
-        block = self.__permutate(Des.__ip, block)
-        self.L = block[:32]
-        self.R = block[32:]
-        if crypt_type == Des.ENCRYPT:
-            iteration = 0
-            iteration_adjustment = 1
-        else:
-            iteration = 15
-            iteration_adjustment = -1
-        i = 0
-        while i < 16:
-            tempR = self.R[:]
-            self.R = self.__permutate(Des.__expansion_table, self.R)
-            self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
-            B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
-            j = 0
-            Bn = [0] * 32
-            pos = 0
-            while j < 8:
-                m = (B[j][0] << 1) + B[j][5]
-                n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
-                v = Des.__sbox[j][(m << 4) + n]
-                Bn[pos] = (v & 8) >> 3
-                Bn[pos + 1] = (v & 4) >> 2
-                Bn[pos + 2] = (v & 2) >> 1
-                Bn[pos + 3] = v & 1
-                pos += 4
-                j += 1
-            self.R = self.__permutate(Des.__p, Bn)
-            self.R = [x ^ y for x, y in zip(self.R, self.L)]
-            self.L = tempR
-            i += 1
-            iteration += iteration_adjustment
-        self.final = self.__permutate(Des.__fp, self.R + self.L)
-        return self.final
-    def crypt(self, data, crypt_type):
-        if not data:
-            return ''
-        if len(data) % self.block_size != 0:
-            if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
-                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
-            if not self.getPadding():
-                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
-            else:
-                data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
-        if self.getMode() == CBC:
-            if self.getIV():
-                iv = self.__String_to_BitList(self.getIV())
-            else:
-                raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
-        i = 0
-        dict = {}
-        result = []
-        while i < len(data):
-            block = self.__String_to_BitList(data[i:i+8])
-            if self.getMode() == CBC:
-                if crypt_type == Des.ENCRYPT:
-                    block = [x ^ y for x, y in zip(block, iv)]
-                processed_block = self.__des_crypt(block, crypt_type)
-                if crypt_type == Des.DECRYPT:
-                    processed_block = [x ^ y for x, y in zip(processed_block, iv)]
-                    iv = block
-                else:
-                    iv = processed_block
-            else:
-                processed_block = self.__des_crypt(block, crypt_type)
-            result.append(self.__BitList_to_String(processed_block))
-            i += 8
-        if crypt_type == Des.DECRYPT and self.getPadding():
-            s = result[-1]
-            while s[-1] == self.getPadding():
-                s = s[:-1]
-            result[-1] = s
-        return ''.join(result)
-    def encrypt(self, data, pad=''):
-        self.__padding = pad
-        return self.crypt(data, Des.ENCRYPT)
-    def decrypt(self, data, pad=''):
-        self.__padding = pad
-        return self.crypt(data, Des.DECRYPT)
-
 class Sectionizer(object):
     def __init__(self, filename, ident):
         self.contents = file(filename, 'rb').read()
@@ -685,8 +472,5 @@ def main(argv=None):
     return 0
 
 if __name__ == "__main__":
-    #import cProfile
-    #command = """sys.exit(main())"""
-    #cProfile.runctx( command, globals(), locals(), filename="cprofile.profile" )
-    
     sys.exit(main())
+
diff --git a/eReader_PDB_Tools/lib/openssl_des.py b/eReader_PDB_Tools/lib/openssl_des.py
new file mode 100644 (file)
index 0000000..8a044fa
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+# implement just enough of des from openssl to make erdr2pml.py happy
+
+def load_libcrypto():
+    from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
+        Structure, c_ulong, create_string_buffer, cast
+    from ctypes.util import find_library
+    import sys
+
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
+
+    if libcrypto is None:
+        return None
+
+    libcrypto = CDLL(libcrypto)
+    
+    # typedef struct DES_ks
+    #     {
+    #     union
+    #         {
+    #         DES_cblock cblock;
+    #         /* make sure things are correct size on machines with
+    #          * 8 byte longs */
+    #         DES_LONG deslong[2];
+    #         } ks[16];
+    #     } DES_key_schedule;
+
+    # just create a big enough place to hold everything 
+    # it will have alignment of structure so we should be okay (16 byte aligned?)
+    class DES_KEY_SCHEDULE(Structure):
+        _fields_ = [('DES_cblock1', c_char * 16),
+                    ('DES_cblock2', c_char * 16),
+                    ('DES_cblock3', c_char * 16),
+                    ('DES_cblock4', c_char * 16),
+                    ('DES_cblock5', c_char * 16),
+                    ('DES_cblock6', c_char * 16),
+                    ('DES_cblock7', c_char * 16),
+                    ('DES_cblock8', c_char * 16),
+                    ('DES_cblock9', c_char * 16),
+                    ('DES_cblock10', c_char * 16),
+                    ('DES_cblock11', c_char * 16),
+                    ('DES_cblock12', c_char * 16),
+                    ('DES_cblock13', c_char * 16),
+                    ('DES_cblock14', c_char * 16),
+                    ('DES_cblock15', c_char * 16),
+                    ('DES_cblock16', c_char * 16)]
+
+    DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
+
+    def F(restype, name, argtypes):
+        func = getattr(libcrypto, name)
+        func.restype = restype
+        func.argtypes = argtypes
+        return func
+
+    DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
+    DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
+
+    
+    class DES(object):
+        def __init__(self, key):
+            if len(key) != 8 :
+                raise Error('DES improper key used')
+                return
+            self.key = key
+            self.keyschedule = DES_KEY_SCHEDULE()
+            DES_set_key(self.key, self.keyschedule)
+        def desdecrypt(self, data):
+            ob = create_string_buffer(len(data))
+            DES_ecb_encrypt(data, ob, self.keyschedule, 0)
+            return ob.raw
+        def decrypt(self, data):
+            if not data:
+                return ''
+            i = 0
+            result = []
+            while i < len(data):
+                block = data[i:i+8]
+                processed_block = self.desdecrypt(block)
+                result.append(processed_block)
+                i += 8
+            return ''.join(result)
+
+    return DES
+
diff --git a/eReader_PDB_Tools/lib/python_des.py b/eReader_PDB_Tools/lib/python_des.py
new file mode 100644 (file)
index 0000000..c5bb204
--- /dev/null
@@ -0,0 +1,218 @@
+import sys
+
+ECB =  0
+CBC =  1
+class Des(object):
+    __pc1 = [56, 48, 40, 32, 24, 16,  8,  0, 57, 49, 41, 33, 25, 17,
+          9,  1, 58, 50, 42, 34, 26, 18, 10,  2, 59, 51, 43, 35,
+         62, 54, 46, 38, 30, 22, 14,  6, 61, 53, 45, 37, 29, 21,
+         13,  5, 60, 52, 44, 36, 28, 20, 12,  4, 27, 19, 11,  3]
+    __left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
+    __pc2 = [13, 16, 10, 23,  0,  4,2, 27, 14,  5, 20,  9,
+        22, 18, 11,  3, 25,  7,        15,  6, 26, 19, 12,  1,
+        40, 51, 30, 36, 46, 54,        29, 39, 50, 44, 32, 47,
+        43, 48, 38, 55, 33, 52,        45, 41, 49, 35, 28, 31]
+    __ip = [57, 49, 41, 33, 25, 17, 9,  1,     59, 51, 43, 35, 27, 19, 11, 3,
+        61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
+        56, 48, 40, 32, 24, 16, 8,  0, 58, 50, 42, 34, 26, 18, 10, 2,
+        60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
+    __expansion_table = [31,  0,  1,  2,  3,  4, 3,  4,  5,  6,  7,  8,
+         7,  8,  9, 10, 11, 12,11, 12, 13, 14, 15, 16,
+        15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
+        23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31,  0]
+    __sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
+         0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
+         4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
+         15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
+        [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
+         3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
+         0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
+         13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
+        [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
+         13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
+         13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
+         1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
+        [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
+         13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
+         10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
+         3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
+        [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
+         14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
+         4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
+         11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
+        [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
+         10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
+         9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
+         4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
+        [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
+         13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
+         1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
+         6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
+        [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
+         1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
+         7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
+         2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
+    __p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
+        4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
+    __fp = [39,  7, 47, 15, 55, 23, 63, 31,38,  6, 46, 14, 54, 22, 62, 30,
+        37,  5, 45, 13, 53, 21, 61, 29,36,  4, 44, 12, 52, 20, 60, 28,
+        35,  3, 43, 11, 51, 19, 59, 27,34,  2, 42, 10, 50, 18, 58, 26,
+        33,  1, 41,  9, 49, 17, 57, 25,32,  0, 40,  8, 48, 16, 56, 24]
+    # Type of crypting being done
+    ENCRYPT =  0x00
+    DECRYPT =  0x01
+    def __init__(self, key, mode=ECB, IV=None):
+        if len(key) != 8:
+            raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
+        self.block_size = 8
+        self.key_size = 8
+        self.__padding = ''
+        self.setMode(mode)
+        if IV:
+            self.setIV(IV)
+        self.L = []
+        self.R = []
+        self.Kn = [ [0] * 48 ] * 16    # 16 48-bit keys (K1 - K16)
+        self.final = []
+        self.setKey(key)
+    def getKey(self):
+        return self.__key
+    def setKey(self, key):
+        self.__key = key
+        self.__create_sub_keys()
+    def getMode(self):
+        return self.__mode
+    def setMode(self, mode):
+        self.__mode = mode
+    def getIV(self):
+        return self.__iv
+    def setIV(self, IV):
+        if not IV or len(IV) != self.block_size:
+            raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
+        self.__iv = IV
+    def getPadding(self):
+        return self.__padding
+    def __String_to_BitList(self, data):
+        l = len(data) * 8
+        result = [0] * l
+        pos = 0
+        for c in data:
+            i = 7
+            ch = ord(c)
+            while i >= 0:
+                if ch & (1 << i) != 0:
+                    result[pos] = 1
+                else:
+                    result[pos] = 0
+                pos += 1
+                i -= 1
+        return result
+    def __BitList_to_String(self, data):
+        result = ''
+        pos = 0
+        c = 0
+        while pos < len(data):
+            c += data[pos] << (7 - (pos % 8))
+            if (pos % 8) == 7:
+                result += chr(c)
+                c = 0
+            pos += 1
+        return result
+    def __permutate(self, table, block):
+        return [block[x] for x in table]
+    def __create_sub_keys(self):
+        key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
+        i = 0
+        self.L = key[:28]
+        self.R = key[28:]
+        while i < 16:
+            j = 0
+            while j < Des.__left_rotations[i]:
+                self.L.append(self.L[0])
+                del self.L[0]
+                self.R.append(self.R[0])
+                del self.R[0]
+                j += 1
+            self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
+            i += 1
+    def __des_crypt(self, block, crypt_type):
+        block = self.__permutate(Des.__ip, block)
+        self.L = block[:32]
+        self.R = block[32:]
+        if crypt_type == Des.ENCRYPT:
+            iteration = 0
+            iteration_adjustment = 1
+        else:
+            iteration = 15
+            iteration_adjustment = -1
+        i = 0
+        while i < 16:
+            tempR = self.R[:]
+            self.R = self.__permutate(Des.__expansion_table, self.R)
+            self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
+            B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
+            j = 0
+            Bn = [0] * 32
+            pos = 0
+            while j < 8:
+                m = (B[j][0] << 1) + B[j][5]
+                n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
+                v = Des.__sbox[j][(m << 4) + n]
+                Bn[pos] = (v & 8) >> 3
+                Bn[pos + 1] = (v & 4) >> 2
+                Bn[pos + 2] = (v & 2) >> 1
+                Bn[pos + 3] = v & 1
+                pos += 4
+                j += 1
+            self.R = self.__permutate(Des.__p, Bn)
+            self.R = [x ^ y for x, y in zip(self.R, self.L)]
+            self.L = tempR
+            i += 1
+            iteration += iteration_adjustment
+        self.final = self.__permutate(Des.__fp, self.R + self.L)
+        return self.final
+    def crypt(self, data, crypt_type):
+        if not data:
+            return ''
+        if len(data) % self.block_size != 0:
+            if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
+                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
+            if not self.getPadding():
+                raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
+            else:
+                data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
+        if self.getMode() == CBC:
+            if self.getIV():
+                iv = self.__String_to_BitList(self.getIV())
+            else:
+                raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
+        i = 0
+        dict = {}
+        result = []
+        while i < len(data):
+            block = self.__String_to_BitList(data[i:i+8])
+            if self.getMode() == CBC:
+                if crypt_type == Des.ENCRYPT:
+                    block = [x ^ y for x, y in zip(block, iv)]
+                processed_block = self.__des_crypt(block, crypt_type)
+                if crypt_type == Des.DECRYPT:
+                    processed_block = [x ^ y for x, y in zip(processed_block, iv)]
+                    iv = block
+                else:
+                    iv = processed_block
+            else:
+                processed_block = self.__des_crypt(block, crypt_type)
+            result.append(self.__BitList_to_String(processed_block))
+            i += 8
+        if crypt_type == Des.DECRYPT and self.getPadding():
+            s = result[-1]
+            while s[-1] == self.getPadding():
+                s = s[:-1]
+            result[-1] = s
+        return ''.join(result)
+    def encrypt(self, data, pad=''):
+        self.__padding = pad
+        return self.crypt(data, Des.ENCRYPT)
+    def decrypt(self, data, pad=''):
+        self.__padding = pad
+        return self.crypt(data, Des.DECRYPT)