diff --git a/Calibre_Plugins/Ignobleepub ReadMe.txt b/Calibre_Plugins/Ignobleepub ReadMe.txt deleted file mode 100644 index 3eb916b..0000000 --- a/Calibre_Plugins/Ignobleepub ReadMe.txt +++ /dev/null @@ -1,67 +0,0 @@ -Ignoble Epub DeDRM - ignobleepub_v02.4_plugin.zip - -All credit given to I♥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. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_v02.4_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. - - -Configuration: - -Upon first installing the plugin (or upgrading from a version earlier than 0.2.0), the plugin will be unconfigured. Until you create at least one B&N key—or migrate your existing key(s)/data from an earlier version of the plugin—the plugin will not function. When unconfigured (no saved keys)... an error message will occur whenever ePubs are imported to calibre. To eliminate the error message, open the plugin's customization dialog and create/import/migrate a key (or disable/uninstall the plugin). You can get to the plugin's customization dialog by opening calibre's Preferences dialog, and clicking Plugins (under the Advanced section). Once in the Plugin Preferences, expand the "File type plugins" section and look for the "Ignoble Epub DeDRM" plugin. Highlight that plugin and click the "Customize plugin" button. - -Upgrading from old keys - -If you are upgrading from an earlier version of this plugin and have provided your name(s) and credit card number(s) as part of the old plugin's customization string, you will be prompted to migrate this data to the plugin's new, more secure, key storage method when you open the customization dialog for the first time. If you choose NOT to migrate that data, you will be prompted to save that data as a text file in a location of your choosing. Either way, this plugin will no longer be storing names and credit card numbers in plain sight (or anywhere for that matter) on your computer or in calibre. If you don't choose to migrate OR save the data, that data will be lost. You have been warned!! - -Upon configuring for the first time, you may also be asked if you wish to import your existing *.b64 keyfiles (if you use them) to the plugin's new key storage method. The new plugin no longer looks for keyfiles in calibre's configuration directory, so it's highly recommended that you import any existing keyfiles when prompted ... but you always have the ability to import existing keyfiles anytime you might need/want to. - -If you have upgraded from an earlier version of the plugin, the above instructions may be all you need to do to get the new plugin up and running. Continue reading for new-key generation and existing-key management instructions. - -Creating New Keys: - -On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key. - -* Unique Key Name: this is a unique name you choose to help you identify the key after it's created. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with. -* Your Name: Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences. -* Credit Card number: this is the credit card number that was set as default with Barnes & Noble at the time of download. Nothing fancy here; no dashes or spaces ... just the 16 (15?) digits. Again... this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences. -Click the 'OK" button to create and store the generated key. Or Cancel if you didn't want to create a key. - -Deleting Keys: - -On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that's what you truly mean to do. Once gone, it's permanently gone. - -Exporting Keys: - -On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a computer's hard-drive. Use this button to export the highlighted key to a file (*.b64). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file. - -Importing Existing Keyfiles: - -At the bottom-left of the plugin's customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing *.b64 keyfiles. Used for migrating keyfiles from older versions of the plugin (or keys generated with the original I <3 Cabbages script), or moving keyfiles from computer to computer, or restoring a backup. Some very basic validation is done to try to avoid overwriting already configured keys with incoming, imported keyfiles with the same base file name, but I'm sure that could be broken if someone tried hard. Just take care when importing. - -Once done creating/importing/exporting/deleting decryption keys; click "OK" to exit the customization dialogue (the cancel button will actually work the same way here ... at this point all data/changes are committed already, so take your pick). - -Troubleshooting: - -If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook 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. ;) - -On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.) - -On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run). -On Macintosh, open the Terminal application (in your Utilities folder). -On Linux open a command window. Hopefully all Linux users know how to do this, as I do not. - -You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running. - -Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information. - -Now copy the output from the terminal window. -On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. -On Macintosh and Linux, just use the normal text select and copy commands. - -Paste the information into a comment at my blog, describing your problem. diff --git a/Calibre_Plugins/Ineptepub ReadMe.txt b/Calibre_Plugins/Ineptepub ReadMe.txt deleted file mode 100644 index 9dfdf57..0000000 --- a/Calibre_Plugins/Ineptepub ReadMe.txt +++ /dev/null @@ -1,50 +0,0 @@ -Inept Epub DeDRM - ineptepub_v01.9_plugin.zip - -All credit given to I♥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. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Cahnge calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_v01.9_plugin.zip) and click the 'Add' button. you're done. - -Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added. - - -Configuration: - -When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS). If successful, it will create 'calibre-adeptkey[number].der' file(s) and save them in Calibre's configuration directory. It will use those files and any other '*.der' files in any decryption attempts. If there is already at least one 'calibre-adept*.der' file in the directory, the plugin won't attempt to find the Adobe Digital Editions installation keys again. - -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♥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 ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook 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. ;) - -On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.) - -On Windows, open a terminal/command window. (Start/Run… and then type ‘cmd’ (without the ‘s) as the program to run). -On Macintosh, open the Terminal application (in your Utilities folder). -On Linux open a command window. Hopefully all Linux users know how to do this, as I do not. - -You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running. - -Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information. - -Now copy the output from the terminal window. -On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. -On Macintosh and Linux, just use the normal text select and copy commands. - -Paste the information into a comment at my blog, describing your problem. \ No newline at end of file diff --git a/Calibre_Plugins/Ineptpdf ReadMe.txt b/Calibre_Plugins/Ineptpdf ReadMe.txt deleted file mode 100644 index ab5a510..0000000 --- a/Calibre_Plugins/Ineptpdf ReadMe.txt +++ /dev/null @@ -1,48 +0,0 @@ -Inept PDF Plugin - ineptpdf_v01.8_plugin.zip - -All credit given to I♥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 PDFs 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, PyCrypto and/or OpenSSL already installed, but they aren't necessary. - - -Installation: - -Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_v01.8_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. - - -Configuration: - -When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS). If successful, it will create 'calibre-adeptkey[number].der' file(s) and save them in Calibre's configuration directory. It will use those files and any other '*.der' files in any decryption attempts. If there is already at least one 'calibre-adept*.der' file in the directory, the plugin won't attempt to find the Adobe Digital Editions installation keys again. - -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♥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 PDF plugin. - - -Troubleshooting: - -If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook 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. ;) - -On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.) - -On Windows, open a terminal/command window. (Start/Run… and then type ‘cmd’ (without the ‘s) as the program to run). -On Macintosh, open the Terminal application (in your Utilities folder). -On Linux open a command window. Hopefully all Linux users know how to do this, as I do not. - -You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running. - -Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information. - -Now copy the output from the terminal window. -On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. -On Macintosh and Linux, just use the normal text select and copy commands. - -Paste the information into a comment at my blog, describing your problem. diff --git a/Calibre_Plugins/K4MobiDeDRM ReadMe.txt b/Calibre_Plugins/K4MobiDeDRM ReadMe.txt deleted file mode 100644 index d080d49..0000000 --- a/Calibre_Plugins/K4MobiDeDRM ReadMe.txt +++ /dev/null @@ -1,55 +0,0 @@ -K4MobiDeDRM_v04.7_plugin.zip - -Credit given to The Dark Reverser for the original standalone script. Credit also to the many people who have updated and expanded that script since then. - -Plugin for K4PC, K4Mac, eInk Kindles and Mobipocket. - -This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins should be removed. - -This plugin is meant to remove the DRM from .prc, .mobi, .azw, .azw1, .azw3, .azw4 and .tpz ebooks. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books. - - -Installation: - -Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (K4MobiDeDRM_v04.7_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. - -Make sure that you delete any old versions of the plugin. They might interfere with the operation of the new one. - - -Configuration: - -Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. - -If you have an eInk Kindle enter the 16 character serial number (these all begin a "B") in the serial numbers field. The easiest way to make sure that you have the serial number right is to copy it from your Amazon account pages (the "Manage Your Devices" page). If you have more than one eInk Kindle, you can enter multiple serial numbers separated by commas. - -If you have Mobipocket books, enter your 8 or 10 digit PID in the Mobipocket PIDs field. If you have more than one PID, separate them with commas. - -These configuration steps are not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books. - - -Linux Systems Only: - -If you install Kindle for PC in Wine, the plugin should be able to decode files from that Kindle for PC installation under Wine. You might need to enter a Wine Prefix if it's not already set in your Environment variables. - - -Troubleshooting: - -If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook 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. ;) - -On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.) - -On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run). -On Macintosh, open the Terminal application (in your Utilities folder). -On Linux open a command window. Hopefully all Linux users know how to do this, as I do not. - -You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running. - -Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information. - -Now copy the output from the terminal window. -On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. -On Macintosh and Linux, just use the normal text select and copy commands. - -Paste the information into a comment at my blog, describing your problem. - - diff --git a/Calibre_Plugins/K4MobiDeDRM_v04.7_plugin.zip b/Calibre_Plugins/K4MobiDeDRM_v04.7_plugin.zip deleted file mode 100644 index 75c17f0..0000000 Binary files a/Calibre_Plugins/K4MobiDeDRM_v04.7_plugin.zip and /dev/null differ diff --git a/Calibre_Plugins/eReaderPDB2PML ReadMe.txt b/Calibre_Plugins/eReaderPDB2PML ReadMe.txt deleted file mode 100644 index a4d3e81..0000000 --- a/Calibre_Plugins/eReaderPDB2PML ReadMe.txt +++ /dev/null @@ -1,41 +0,0 @@ -eReader PDB2PML - eReaderPDB2PML_v07_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. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (eReaderPDB2PML_v07_plugin.zip) and click the 'Add' button. You're done. - - -Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added. - - -Configuration: - -Highlight the plugin (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 - - -Troubleshooting: - -If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook 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. ;) - -On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.) - -On Windows, open a terminal/command window. (Start/Run… and then type ‘cmd’ (without the ‘s) as the program to run). -On Macintosh, open the Terminal application (in your Utilities folder). -On Linux open a command window. Hopefully all Linux users know how to do this, as I do not. - -You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running. - -Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information. - -Now copy the output from the terminal window. -On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. -On Macintosh and Linux, just use the normal text select and copy commands. - -Paste the information into a comment at my blog, describing your problem. \ No newline at end of file diff --git a/Calibre_Plugins/eReaderPDB2PML_v07_plugin.zip b/Calibre_Plugins/eReaderPDB2PML_v07_plugin.zip deleted file mode 100644 index 0282220..0000000 Binary files a/Calibre_Plugins/eReaderPDB2PML_v07_plugin.zip and /dev/null differ diff --git a/Calibre_Plugins/ignobleepub_v02.4_plugin.zip b/Calibre_Plugins/ignobleepub_v02.4_plugin.zip deleted file mode 100644 index 10e26e4..0000000 Binary files a/Calibre_Plugins/ignobleepub_v02.4_plugin.zip and /dev/null differ diff --git a/Calibre_Plugins/ineptepub_v01.9_plugin.zip b/Calibre_Plugins/ineptepub_v01.9_plugin.zip deleted file mode 100644 index 216505b..0000000 Binary files a/Calibre_Plugins/ineptepub_v01.9_plugin.zip and /dev/null differ diff --git a/Calibre_Plugins/ineptpdf_v01.8_plugin.zip b/Calibre_Plugins/ineptpdf_v01.8_plugin.zip deleted file mode 100644 index e63dcec..0000000 Binary files a/Calibre_Plugins/ineptpdf_v01.8_plugin.zip and /dev/null differ diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/MacOS/droplet b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/MacOS/droplet deleted file mode 100755 index 99d8475..0000000 Binary files a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/MacOS/droplet and /dev/null differ diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress deleted file mode 100755 index 8b3ea05..0000000 Binary files a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress and /dev/null differ diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/Scripts/main.scpt b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/Scripts/main.scpt deleted file mode 100644 index 14dc8b6..0000000 Binary files a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/Scripts/main.scpt and /dev/null differ diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.py deleted file mode 100755 index e25a0c8..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.py +++ /dev/null @@ -1,290 +0,0 @@ -#! /usr/bin/env python - -import sys, os -import hmac -from struct import pack -import hashlib - - -# interface to needed routines libalfcrypto -def _load_libalfcrypto(): - import ctypes - 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, sizeof - - pointer_size = ctypes.sizeof(ctypes.c_voidp) - name_of_lib = None - if sys.platform.startswith('darwin'): - name_of_lib = 'libalfcrypto.dylib' - elif sys.platform.startswith('win'): - if pointer_size == 4: - name_of_lib = 'alfcrypto.dll' - else: - name_of_lib = 'alfcrypto64.dll' - else: - if pointer_size == 4: - name_of_lib = 'libalfcrypto32.so' - else: - name_of_lib = 'libalfcrypto64.so' - - libalfcrypto = sys.path[0] + os.sep + name_of_lib - - if not os.path.isfile(libalfcrypto): - raise Exception('libalfcrypto not found') - - libalfcrypto = CDLL(libalfcrypto) - - c_char_pp = POINTER(c_char_p) - c_int_p = POINTER(c_int) - - - def F(restype, name, argtypes): - func = getattr(libalfcrypto, name) - func.restype = restype - func.argtypes = argtypes - return func - - # aes cbc decryption - # - # struct aes_key_st { - # unsigned long rd_key[4 *(AES_MAXNR + 1)]; - # int rounds; - # }; - # - # typedef struct aes_key_st AES_KEY; - # - # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); - # - # - # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, - # const unsigned long length, const AES_KEY *key, - # unsigned char *ivec, const int enc); - - AES_MAXNR = 14 - - class AES_KEY(Structure): - _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)] - - AES_KEY_p = POINTER(AES_KEY) - 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]) - - - - # Pukall 1 Cipher - # unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src, - # unsigned char *dest, unsigned int len, int decryption); - - PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong]) - - # Topaz Encryption - # typedef struct _TpzCtx { - # unsigned int v[2]; - # } TpzCtx; - # - # void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen); - # void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len); - - class TPZ_CTX(Structure): - _fields_ = [('v', c_long * 2)] - - TPZ_CTX_p = POINTER(TPZ_CTX) - topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong]) - topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong]) - - - class AES_CBC(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 Exception('AES CBC 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 Exception('Failed to initialize AES CBC key') - - def decrypt(self, data): - out = create_string_buffer(len(data)) - mutable_iv = create_string_buffer(self._iv, len(self._iv)) - rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0) - if rv == 0: - raise Exception('AES CBC decryption failed') - return out.raw - - class Pukall_Cipher(object): - def __init__(self): - self.key = None - - def PC1(self, key, src, decryption=True): - self.key = key - out = create_string_buffer(len(src)) - de = 0 - if decryption: - de = 1 - rv = PC1(key, len(key), src, out, len(src), de) - return out.raw - - class Topaz_Cipher(object): - def __init__(self): - self._ctx = None - - def ctx_init(self, key): - tpz_ctx = self._ctx = TPZ_CTX() - topazCryptoInit(tpz_ctx, key, len(key)) - return tpz_ctx - - def decrypt(self, data, ctx=None): - if ctx == None: - ctx = self._ctx - out = create_string_buffer(len(data)) - topazCryptoDecrypt(ctx, data, out, len(data)) - return out.raw - - print "Using Library AlfCrypto DLL/DYLIB/SO" - return (AES_CBC, Pukall_Cipher, Topaz_Cipher) - - -def _load_python_alfcrypto(): - - import aescbc - - class Pukall_Cipher(object): - def __init__(self): - self.key = None - - def PC1(self, 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 - - class Topaz_Cipher(object): - def __init__(self): - self._ctx = None - - def ctx_init(self, key): - ctx1 = 0x0CAFFE19E - for keyChar in key: - keyByte = ord(keyChar) - ctx2 = ctx1 - ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF ) - self._ctx = [ctx1, ctx2] - return [ctx1,ctx2] - - def decrypt(self, data, ctx=None): - if ctx == None: - ctx = self._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 - - class AES_CBC(object): - def __init__(self): - self._key = None - self._iv = None - self.aes = None - - def set_decrypt_key(self, userkey, iv): - self._key = userkey - self._iv = iv - self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey)) - - def decrypt(self, data): - iv = self._iv - cleartext = self.aes.decrypt(iv + data) - return cleartext - - return (AES_CBC, Pukall_Cipher, Topaz_Cipher) - - -def _load_crypto(): - AES_CBC = Pukall_Cipher = Topaz_Cipher = None - cryptolist = (_load_libalfcrypto, _load_python_alfcrypto) - for loader in cryptolist: - try: - AES_CBC, Pukall_Cipher, Topaz_Cipher = loader() - break - except (ImportError, Exception): - pass - return AES_CBC, Pukall_Cipher, Topaz_Cipher - -AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto() - - -class KeyIVGen(object): - # this only exists in openssl so we will use pure python implementation instead - # 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]) - def pbkdf2(self, passwd, salt, iter, keylen): - - def xorstr( a, b ): - if len(a) != len(b): - raise Exception("xorstr(): lengths differ") - return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) - - def prf( h, data ): - hm = h.copy() - hm.update( data ) - return hm.digest() - - def pbkdf2_F( h, salt, itercount, blocknum ): - U = prf( h, salt + pack('>i',blocknum ) ) - T = U - for i in range(2, itercount+1): - U = prf( h, U ) - T = xorstr( T, U ) - return T - - sha = hashlib.sha1 - digest_size = sha().digest_size - # l - number of output blocks to produce - l = keylen / digest_size - if keylen % digest_size != 0: - l += 1 - h = hmac.new( passwd, None, sha ) - T = "" - for i in range(1, l+1): - T += pbkdf2_F( h, salt, iter, i ) - return T[0: keylen] - - diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/config.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/config.py deleted file mode 100644 index 9825878..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/config.py +++ /dev/null @@ -1,59 +0,0 @@ -from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit - -from calibre.utils.config import JSONConfig - -# This is where all preferences for this plugin will be stored -# You should always prefix your config file name with plugins/, -# so as to ensure you dont accidentally clobber a calibre config file -prefs = JSONConfig('plugins/K4MobiDeDRM') - -# Set defaults -prefs.defaults['pids'] = "" -prefs.defaults['serials'] = "" -prefs.defaults['WINEPREFIX'] = None - - -class ConfigWidget(QWidget): - - def __init__(self): - QWidget.__init__(self) - self.l = QVBoxLayout() - self.setLayout(self.l) - - self.serialLabel = QLabel('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)') - self.l.addWidget(self.serialLabel) - - self.serials = QLineEdit(self) - self.serials.setText(prefs['serials']) - self.l.addWidget(self.serials) - self.serialLabel.setBuddy(self.serials) - - self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)') - self.l.addWidget(self.pidLabel) - - self.pids = QLineEdit(self) - self.pids.setText(prefs['pids']) - self.l.addWidget(self.pids) - self.pidLabel.setBuddy(self.serials) - - self.wpLabel = QLabel('For Linux only: WINEPREFIX (enter absolute path)') - self.l.addWidget(self.wpLabel) - - self.wineprefix = QLineEdit(self) - wineprefix = prefs['WINEPREFIX'] - if wineprefix is not None: - self.wineprefix.setText(wineprefix) - else: - self.wineprefix.setText('') - - self.l.addWidget(self.wineprefix) - self.wpLabel.setBuddy(self.wineprefix) - - def save_settings(self): - prefs['pids'] = str(self.pids.text()).replace(" ","") - prefs['serials'] = str(self.serials.text()).replace(" ","") - winepref=str(self.wineprefix.text()) - if winepref.strip() != '': - prefs['WINEPREFIX'] = winepref - else: - prefs['WINEPREFIX'] = None diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/description.rtfd/TXT.rtf b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/description.rtfd/TXT.rtf deleted file mode 100644 index 4ea1054..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/description.rtfd/TXT.rtf +++ /dev/null @@ -1,4 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360 -{\fonttbl} -{\colortbl;\red255\green255\blue255;} -} \ No newline at end of file diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2html.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2html.py deleted file mode 100755 index e5647f4..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2html.py +++ /dev/null @@ -1,793 +0,0 @@ -#! /usr/bin/python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.6 - -import sys -import csv -import os -import math -import getopt -from struct import pack -from struct import unpack - - -class DocParser(object): - def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage): - self.id = os.path.basename(fileid).replace('.dat','') - self.svgcount = 0 - self.docList = flatxml.split('\n') - self.docSize = len(self.docList) - self.classList = {} - self.bookDir = bookDir - self.gdict = gdict - tmpList = classlst.split('\n') - for pclass in tmpList: - if pclass != '': - # remove the leading period from the css name - cname = pclass[1:] - self.classList[cname] = True - self.fixedimage = fixedimage - self.ocrtext = [] - self.link_id = [] - self.link_title = [] - self.link_page = [] - self.link_href = [] - self.link_type = [] - self.dehyphen_rootid = [] - self.paracont_stemid = [] - self.parastems_stemid = [] - - - def getGlyph(self, gid): - result = '' - id='id="gl%d"' % gid - return self.gdict.lookup(id) - - def glyphs_to_image(self, glyphList): - - def extract(path, key): - b = path.find(key) + len(key) - e = path.find(' ',b) - return int(path[b:e]) - - svgDir = os.path.join(self.bookDir,'svg') - - imgDir = os.path.join(self.bookDir,'img') - imgname = self.id + '_%04d.svg' % self.svgcount - imgfile = os.path.join(imgDir,imgname) - - # get glyph information - gxList = self.getData('info.glyph.x',0,-1) - gyList = self.getData('info.glyph.y',0,-1) - gidList = self.getData('info.glyph.glyphID',0,-1) - - gids = [] - maxws = [] - maxhs = [] - xs = [] - ys = [] - gdefs = [] - - # get path defintions, positions, dimensions for each glyph - # that makes up the image, and find min x and min y to reposition origin - minx = -1 - miny = -1 - for j in glyphList: - gid = gidList[j] - gids.append(gid) - - xs.append(gxList[j]) - if minx == -1: minx = gxList[j] - else : minx = min(minx, gxList[j]) - - ys.append(gyList[j]) - if miny == -1: miny = gyList[j] - else : miny = min(miny, gyList[j]) - - path = self.getGlyph(gid) - gdefs.append(path) - - maxws.append(extract(path,'width=')) - maxhs.append(extract(path,'height=')) - - - # change the origin to minx, miny and calc max height and width - maxw = maxws[0] + xs[0] - minx - maxh = maxhs[0] + ys[0] - miny - for j in xrange(0, len(xs)): - xs[j] = xs[j] - minx - ys[j] = ys[j] - miny - maxw = max( maxw, (maxws[j] + xs[j]) ) - maxh = max( maxh, (maxhs[j] + ys[j]) ) - - # open the image file for output - ifile = open(imgfile,'w') - ifile.write('\n') - ifile.write('\n') - ifile.write('\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh)) - ifile.write('\n') - for j in xrange(0,len(gdefs)): - ifile.write(gdefs[j]) - ifile.write('\n') - for j in xrange(0,len(gids)): - ifile.write('\n' % (gids[j], xs[j], ys[j])) - ifile.write('') - ifile.close() - - return 0 - - - - # return tag at line pos in document - def lineinDoc(self, pos) : - if (pos >= 0) and (pos < self.docSize) : - item = self.docList[pos] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - return name, argres - - - # find tag in doc if within pos to end inclusive - def findinDoc(self, tagpath, pos, end) : - result = None - if end == -1 : - end = self.docSize - else: - end = min(self.docSize, end) - foundat = -1 - for j in xrange(pos, end): - item = self.docList[j] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - if name.endswith(tagpath) : - result = argres - foundat = j - break - return foundat, result - - - # return list of start positions for the tagpath - def posinDoc(self, tagpath): - startpos = [] - pos = 0 - res = "" - while res != None : - (foundpos, res) = self.findinDoc(tagpath, pos, -1) - if res != None : - startpos.append(foundpos) - pos = foundpos + 1 - return startpos - - - # returns a vector of integers for the tagpath - def getData(self, tagpath, pos, end): - argres=[] - (foundat, argt) = self.findinDoc(tagpath, pos, end) - if (argt != None) and (len(argt) > 0) : - argList = argt.split('|') - argres = [ int(strval) for strval in argList] - return argres - - - # get the class - def getClass(self, pclass): - nclass = pclass - - # class names are an issue given topaz may start them with numerals (not allowed), - # use a mix of cases (which cause some browsers problems), and actually - # attach numbers after "_reclustered*" to the end to deal classeses that inherit - # from a base class (but then not actually provide all of these _reclustereed - # classes in the stylesheet! - - # so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass - # that exists in the stylesheet first, and then adding this specific class - # after - - # also some class names have spaces in them so need to convert to dashes - if nclass != None : - nclass = nclass.replace(' ','-') - classres = '' - nclass = nclass.lower() - nclass = 'cl-' + nclass - baseclass = '' - # graphic is the base class for captions - if nclass.find('cl-cap-') >=0 : - classres = 'graphic' + ' ' - else : - # strip to find baseclass - p = nclass.find('_') - if p > 0 : - baseclass = nclass[0:p] - if baseclass in self.classList: - classres += baseclass + ' ' - classres += nclass - nclass = classres - return nclass - - - # develop a sorted description of the starting positions of - # groups and regions on the page, as well as the page type - def PageDescription(self): - - def compare(x, y): - (xtype, xval) = x - (ytype, yval) = y - if xval > yval: - return 1 - if xval == yval: - return 0 - return -1 - - result = [] - (pos, pagetype) = self.findinDoc('page.type',0,-1) - - groupList = self.posinDoc('page.group') - groupregionList = self.posinDoc('page.group.region') - pageregionList = self.posinDoc('page.region') - # integrate into one list - for j in groupList: - result.append(('grpbeg',j)) - for j in groupregionList: - result.append(('gregion',j)) - for j in pageregionList: - result.append(('pregion',j)) - result.sort(compare) - - # insert group end and page end indicators - inGroup = False - j = 0 - while True: - if j == len(result): break - rtype = result[j][0] - rval = result[j][1] - if not inGroup and (rtype == 'grpbeg') : - inGroup = True - j = j + 1 - elif inGroup and (rtype in ('grpbeg', 'pregion')): - result.insert(j,('grpend',rval)) - inGroup = False - else: - j = j + 1 - if inGroup: - result.append(('grpend',-1)) - result.append(('pageend', -1)) - return pagetype, result - - - - # build a description of the paragraph - def getParaDescription(self, start, end, regtype): - - result = [] - - # paragraph - (pos, pclass) = self.findinDoc('paragraph.class',start,end) - - pclass = self.getClass(pclass) - - # if paragraph uses extratokens (extra glyphs) then make it fixed - (pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end) - - # build up a description of the paragraph in result and return it - # first check for the basic - all words paragraph - (pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end) - (pos, slast) = self.findinDoc('paragraph.lastWord',start,end) - if (sfirst != None) and (slast != None) : - first = int(sfirst) - last = int(slast) - - makeImage = (regtype == 'vertical') or (regtype == 'table') - makeImage = makeImage or (extraglyphs != None) - if self.fixedimage: - makeImage = makeImage or (regtype == 'fixed') - - if (pclass != None): - makeImage = makeImage or (pclass.find('.inverted') >= 0) - if self.fixedimage : - makeImage = makeImage or (pclass.find('cl-f-') >= 0) - - # before creating an image make sure glyph info exists - gidList = self.getData('info.glyph.glyphID',0,-1) - - makeImage = makeImage & (len(gidList) > 0) - - if not makeImage : - # standard all word paragraph - for wordnum in xrange(first, last): - result.append(('ocr', wordnum)) - return pclass, result - - # convert paragraph to svg image - # translate first and last word into first and last glyphs - # and generate inline image and include it - glyphList = [] - firstglyphList = self.getData('word.firstGlyph',0,-1) - gidList = self.getData('info.glyph.glyphID',0,-1) - firstGlyph = firstglyphList[first] - if last < len(firstglyphList): - lastGlyph = firstglyphList[last] - else : - lastGlyph = len(gidList) - - # handle case of white sapce paragraphs with no actual glyphs in them - # by reverting to text based paragraph - if firstGlyph >= lastGlyph: - # revert to standard text based paragraph - for wordnum in xrange(first, last): - result.append(('ocr', wordnum)) - return pclass, result - - for glyphnum in xrange(firstGlyph, lastGlyph): - glyphList.append(glyphnum) - # include any extratokens if they exist - (pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end) - (pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end) - if (sfg != None) and (slg != None): - for glyphnum in xrange(int(sfg), int(slg)): - glyphList.append(glyphnum) - num = self.svgcount - self.glyphs_to_image(glyphList) - self.svgcount += 1 - result.append(('svg', num)) - return pclass, result - - # this type of paragraph may be made up of multiple spans, inline - # word monograms (images), and words with semantic meaning, - # plus glyphs used to form starting letter of first word - - # need to parse this type line by line - line = start + 1 - word_class = '' - - # if end is -1 then we must search to end of document - if end == -1 : - end = self.docSize - - # seems some xml has last* coming before first* so we have to - # handle any order - sp_first = -1 - sp_last = -1 - - gl_first = -1 - gl_last = -1 - - ws_first = -1 - ws_last = -1 - - word_class = '' - - word_semantic_type = '' - - while (line < end) : - - (name, argres) = self.lineinDoc(line) - - if name.endswith('span.firstWord') : - sp_first = int(argres) - - elif name.endswith('span.lastWord') : - sp_last = int(argres) - - elif name.endswith('word.firstGlyph') : - gl_first = int(argres) - - elif name.endswith('word.lastGlyph') : - gl_last = int(argres) - - elif name.endswith('word_semantic.firstWord'): - ws_first = int(argres) - - elif name.endswith('word_semantic.lastWord'): - ws_last = int(argres) - - elif name.endswith('word.class'): - (cname, space) = argres.split('-',1) - if space == '' : space = '0' - if (cname == 'spaceafter') and (int(space) > 0) : - word_class = 'sa' - - elif name.endswith('word.img.src'): - result.append(('img' + word_class, int(argres))) - word_class = '' - - elif name.endswith('region.img.src'): - result.append(('img' + word_class, int(argres))) - - if (sp_first != -1) and (sp_last != -1): - for wordnum in xrange(sp_first, sp_last): - result.append(('ocr', wordnum)) - sp_first = -1 - sp_last = -1 - - if (gl_first != -1) and (gl_last != -1): - glyphList = [] - for glyphnum in xrange(gl_first, gl_last): - glyphList.append(glyphnum) - num = self.svgcount - self.glyphs_to_image(glyphList) - self.svgcount += 1 - result.append(('svg', num)) - gl_first = -1 - gl_last = -1 - - if (ws_first != -1) and (ws_last != -1): - for wordnum in xrange(ws_first, ws_last): - result.append(('ocr', wordnum)) - ws_first = -1 - ws_last = -1 - - line += 1 - - return pclass, result - - - def buildParagraph(self, pclass, pdesc, type, regtype) : - parares = '' - sep ='' - - classres = '' - if pclass : - classres = ' class="' + pclass + '"' - - br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical') - - handle_links = len(self.link_id) > 0 - - if (type == 'full') or (type == 'begin') : - parares += '' - - if (type == 'end'): - parares += ' ' - - lstart = len(parares) - - cnt = len(pdesc) - - for j in xrange( 0, cnt) : - - (wtype, num) = pdesc[j] - - if wtype == 'ocr' : - word = self.ocrtext[num] - sep = ' ' - - if handle_links: - link = self.link_id[num] - if (link > 0): - linktype = self.link_type[link-1] - title = self.link_title[link-1] - if (title == "") or (parares.rfind(title) < 0): - title=parares[lstart:] - if linktype == 'external' : - linkhref = self.link_href[link-1] - linkhtml = '' % linkhref - else : - if len(self.link_page) >= link : - ptarget = self.link_page[link-1] - 1 - linkhtml = '' % ptarget - else : - # just link to the current page - linkhtml = '' - linkhtml += title + '' - pos = parares.rfind(title) - if pos >= 0: - parares = parares[0:pos] + linkhtml + parares[pos+len(title):] - else : - parares += linkhtml - lstart = len(parares) - if word == '_link_' : word = '' - elif (link < 0) : - if word == '_link_' : word = '' - - if word == '_lb_': - if ((num-1) in self.dehyphen_rootid ) or handle_links: - word = '' - sep = '' - elif br_lb : - word = '
\n' - sep = '' - else : - word = '\n' - sep = '' - - if num in self.dehyphen_rootid : - word = word[0:-1] - sep = '' - - parares += word + sep - - elif wtype == 'img' : - sep = '' - parares += '' % num - parares += sep - - elif wtype == 'imgsa' : - sep = ' ' - parares += '' % num - parares += sep - - elif wtype == 'svg' : - sep = '' - parares += '' % num - parares += sep - - if len(sep) > 0 : parares = parares[0:-1] - if (type == 'full') or (type == 'end') : - parares += '

' - return parares - - - def buildTOCEntry(self, pdesc) : - parares = '' - sep ='' - tocentry = '' - handle_links = len(self.link_id) > 0 - - lstart = 0 - - cnt = len(pdesc) - for j in xrange( 0, cnt) : - - (wtype, num) = pdesc[j] - - if wtype == 'ocr' : - word = self.ocrtext[num] - sep = ' ' - - if handle_links: - link = self.link_id[num] - if (link > 0): - linktype = self.link_type[link-1] - title = self.link_title[link-1] - title = title.rstrip('. ') - alt_title = parares[lstart:] - alt_title = alt_title.strip() - # now strip off the actual printed page number - alt_title = alt_title.rstrip('01234567890ivxldIVXLD-.') - alt_title = alt_title.rstrip('. ') - # skip over any external links - can't have them in a books toc - if linktype == 'external' : - title = '' - alt_title = '' - linkpage = '' - else : - if len(self.link_page) >= link : - ptarget = self.link_page[link-1] - 1 - linkpage = '%04d' % ptarget - else : - # just link to the current page - linkpage = self.id[4:] - if len(alt_title) >= len(title): - title = alt_title - if title != '' and linkpage != '': - tocentry += title + '|' + linkpage + '\n' - lstart = len(parares) - if word == '_link_' : word = '' - elif (link < 0) : - if word == '_link_' : word = '' - - if word == '_lb_': - word = '' - sep = '' - - if num in self.dehyphen_rootid : - word = word[0:-1] - sep = '' - - parares += word + sep - - else : - continue - - return tocentry - - - - - # walk the document tree collecting the information needed - # to build an html page using the ocrText - - def process(self): - - tocinfo = '' - hlst = [] - - # get the ocr text - (pos, argres) = self.findinDoc('info.word.ocrText',0,-1) - if argres : self.ocrtext = argres.split('|') - - # get information to dehyphenate the text - self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1) - - # determine if first paragraph is continued from previous page - (pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1) - first_para_continued = (self.parastems_stemid != None) - - # determine if last paragraph is continued onto the next page - (pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1) - last_para_continued = (self.paracont_stemid != None) - - # collect link ids - self.link_id = self.getData('info.word.link_id',0,-1) - - # collect link destination page numbers - self.link_page = self.getData('info.links.page',0,-1) - - # collect link types (container versus external) - (pos, argres) = self.findinDoc('info.links.type',0,-1) - if argres : self.link_type = argres.split('|') - - # collect link destinations - (pos, argres) = self.findinDoc('info.links.href',0,-1) - if argres : self.link_href = argres.split('|') - - # collect link titles - (pos, argres) = self.findinDoc('info.links.title',0,-1) - if argres : - self.link_title = argres.split('|') - else: - self.link_title.append('') - - # get a descriptions of the starting points of the regions - # and groups on the page - (pagetype, pageDesc) = self.PageDescription() - regcnt = len(pageDesc) - 1 - - anchorSet = False - breakSet = False - inGroup = False - - # process each region on the page and convert what you can to html - - for j in xrange(regcnt): - - (etype, start) = pageDesc[j] - (ntype, end) = pageDesc[j+1] - - - # set anchor for link target on this page - if not anchorSet and not first_para_continued: - hlst.append('\n') - anchorSet = True - - # handle groups of graphics with text captions - if (etype == 'grpbeg'): - (pos, grptype) = self.findinDoc('group.type', start, end) - if grptype != None: - if grptype == 'graphic': - gcstr = ' class="' + grptype + '"' - hlst.append('') - inGroup = True - - elif (etype == 'grpend'): - if inGroup: - hlst.append('\n') - inGroup = False - - else: - (pos, regtype) = self.findinDoc('region.type',start,end) - - if regtype == 'graphic' : - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - if inGroup: - hlst.append('' % int(simgsrc)) - else: - hlst.append('
' % int(simgsrc)) - - elif regtype == 'chapterheading' : - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if not breakSet: - hlst.append('
 
\n') - breakSet = True - tag = 'h1' - if pclass and (len(pclass) >= 7): - if pclass[3:7] == 'ch1-' : tag = 'h1' - if pclass[3:7] == 'ch2-' : tag = 'h2' - if pclass[3:7] == 'ch3-' : tag = 'h3' - hlst.append('<' + tag + ' class="' + pclass + '">') - else: - hlst.append('<' + tag + '>') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - - elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'): - ptype = 'full' - # check to see if this is a continution from the previous page - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if pclass and (len(pclass) >= 6) and (ptype == 'full'): - tag = 'p' - if pclass[3:6] == 'h1-' : tag = 'h4' - if pclass[3:6] == 'h2-' : tag = 'h5' - if pclass[3:6] == 'h3-' : tag = 'h6' - hlst.append('<' + tag + ' class="' + pclass + '">') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - else : - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - elif (regtype == 'tocentry') : - ptype = 'full' - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - tocinfo += self.buildTOCEntry(pdesc) - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - elif (regtype == 'vertical') or (regtype == 'table') : - ptype = 'full' - if inGroup: - ptype = 'middle' - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start, end, regtype) - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - - elif (regtype == 'synth_fcvr.center'): - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - hlst.append('
' % int(simgsrc)) - - else : - print ' Making region type', regtype, - (pos, temp) = self.findinDoc('paragraph',start,end) - (pos2, temp) = self.findinDoc('span',start,end) - if pos != -1 or pos2 != -1: - print ' a "text" region' - orig_regtype = regtype - regtype = 'fixed' - ptype = 'full' - # check to see if this is a continution from the previous page - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if not pclass: - if orig_regtype.endswith('.right') : pclass = 'cl-right' - elif orig_regtype.endswith('.center') : pclass = 'cl-center' - elif orig_regtype.endswith('.left') : pclass = 'cl-left' - elif orig_regtype.endswith('.justify') : pclass = 'cl-justify' - if pclass and (ptype == 'full') and (len(pclass) >= 6): - tag = 'p' - if pclass[3:6] == 'h1-' : tag = 'h4' - if pclass[3:6] == 'h2-' : tag = 'h5' - if pclass[3:6] == 'h3-' : tag = 'h6' - hlst.append('<' + tag + ' class="' + pclass + '">') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - else : - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - else : - print ' a "graphic" region' - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - hlst.append('
' % int(simgsrc)) - - - htmlpage = "".join(hlst) - if last_para_continued : - if htmlpage[-4:] == '

': - htmlpage = htmlpage[0:-4] - last_para_continued = False - - return htmlpage, tocinfo - - -def convert2HTML(flatxml, classlst, fileid, bookDir, gdict, fixedimage): - # create a document parser - dp = DocParser(flatxml, classlst, fileid, bookDir, gdict, fixedimage) - htmlpage, tocinfo = dp.process() - return htmlpage, tocinfo diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/genbook.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/genbook.py deleted file mode 100755 index 9733887..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/genbook.py +++ /dev/null @@ -1,721 +0,0 @@ -#! /usr/bin/python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -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 -from struct import pack -from struct import unpack - -class TpzDRMError(Exception): - pass - -# local support routines -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -if inCalibre : - from calibre_plugins.k4mobidedrm import convert2xml - from calibre_plugins.k4mobidedrm import flatxml2html - from calibre_plugins.k4mobidedrm import flatxml2svg - from calibre_plugins.k4mobidedrm import stylexml2css -else : - import convert2xml - import flatxml2html - import flatxml2svg - import stylexml2css - -# global switch -buildXML = False - -# Get a 7 bit encoded number from a file -def readEncodedNumber(file): - flag = False - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - if data == 0xFF: - flag = True - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - if data >= 0x80: - datax = (data & 0x7F) - while data >= 0x80 : - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - datax = (datax <<7) + (data & 0x7F) - data = datax - if flag: - data = -data - return data - -# Get a length prefixed string from the file -def lengthPrefixString(data): - return encodeNumber(len(data))+data - -def readString(file): - stringLength = readEncodedNumber(file) - if (stringLength == None): - return None - sv = file.read(stringLength) - if (len(sv) != stringLength): - return "" - return unpack(str(stringLength)+"s",sv)[0] - -def getMetaArray(metaFile): - # parse the meta file - result = {} - fo = file(metaFile,'rb') - size = readEncodedNumber(fo) - for i in xrange(size): - tag = readString(fo) - value = readString(fo) - result[tag] = value - # print tag, value - fo.close() - return result - - -# dictionary of all text strings by index value -class Dictionary(object): - def __init__(self, dictFile): - self.filename = dictFile - self.size = 0 - self.fo = file(dictFile,'rb') - self.stable = [] - self.size = readEncodedNumber(self.fo) - for i in xrange(self.size): - self.stable.append(self.escapestr(readString(self.fo))) - self.pos = 0 - def escapestr(self, str): - str = str.replace('&','&') - str = str.replace('<','<') - str = str.replace('>','>') - str = str.replace('=','=') - return str - def lookup(self,val): - if ((val >= 0) and (val < self.size)) : - self.pos = val - return self.stable[self.pos] - else: - print "Error - %d outside of string table limits" % val - raise TpzDRMError('outside or string table limits') - # sys.exit(-1) - def getSize(self): - return self.size - def getPos(self): - return self.pos - - -class PageDimParser(object): - def __init__(self, flatxml): - self.flatdoc = flatxml.split('\n') - # find tag if within pos to end inclusive - def findinDoc(self, tagpath, pos, end) : - result = None - docList = self.flatdoc - cnt = len(docList) - if end == -1 : - end = cnt - else: - end = min(cnt,end) - foundat = -1 - for j in xrange(pos, end): - item = docList[j] - if item.find('=') >= 0: - (name, argres) = item.split('=') - else : - name = item - argres = '' - if name.endswith(tagpath) : - result = argres - foundat = j - break - return foundat, result - def process(self): - (pos, sph) = self.findinDoc('page.h',0,-1) - (pos, spw) = self.findinDoc('page.w',0,-1) - if (sph == None): sph = '-1' - if (spw == None): spw = '-1' - return sph, spw - -def getPageDim(flatxml): - # create a document parser - dp = PageDimParser(flatxml) - (ph, pw) = dp.process() - return ph, pw - -class GParser(object): - def __init__(self, flatxml): - self.flatdoc = flatxml.split('\n') - self.dpi = 1440 - self.gh = self.getData('info.glyph.h') - self.gw = self.getData('info.glyph.w') - self.guse = self.getData('info.glyph.use') - if self.guse : - self.count = len(self.guse) - else : - self.count = 0 - self.gvtx = self.getData('info.glyph.vtx') - self.glen = self.getData('info.glyph.len') - self.gdpi = self.getData('info.glyph.dpi') - self.vx = self.getData('info.vtx.x') - self.vy = self.getData('info.vtx.y') - self.vlen = self.getData('info.len.n') - if self.vlen : - self.glen.append(len(self.vlen)) - elif self.glen: - self.glen.append(0) - if self.vx : - self.gvtx.append(len(self.vx)) - elif self.gvtx : - self.gvtx.append(0) - def getData(self, path): - result = None - cnt = len(self.flatdoc) - for j in xrange(cnt): - item = self.flatdoc[j] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (name == path): - result = argres - break - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - return result - def getGlyphDim(self, gly): - if self.gdpi[gly] == 0: - return 0, 0 - maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly] - maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly] - return maxh, maxw - def getPath(self, gly): - path = '' - if (gly < 0) or (gly >= self.count): - return path - tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]] - ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]] - p = 0 - for k in xrange(self.glen[gly], self.glen[gly+1]): - if (p == 0): - zx = tx[0:self.vlen[k]+1] - zy = ty[0:self.vlen[k]+1] - else: - zx = tx[self.vlen[k-1]+1:self.vlen[k]+1] - zy = ty[self.vlen[k-1]+1:self.vlen[k]+1] - p += 1 - j = 0 - while ( j < len(zx) ): - if (j == 0): - # Start Position. - path += 'M %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly]) - elif (j <= len(zx)-3): - # Cubic Bezier Curve - path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[j+2] * self.dpi / self.gdpi[gly], zy[j+2] * self.dpi / self.gdpi[gly]) - j += 2 - elif (j == len(zx)-2): - # Cubic Bezier Curve to Start Position - path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly]) - j += 1 - elif (j == len(zx)-1): - # Quadratic Bezier Curve to Start Position - path += 'Q %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly]) - - j += 1 - path += 'z' - return path - - - -# dictionary of all text strings by index value -class GlyphDict(object): - def __init__(self): - self.gdict = {} - def lookup(self, id): - # id='id="gl%d"' % val - if id in self.gdict: - return self.gdict[id] - return None - def addGlyph(self, val, path): - id='id="gl%d"' % val - self.gdict[id] = path - - -def generateBook(bookDir, raw, fixedimage): - # sanity check Topaz file extraction - if not os.path.exists(bookDir) : - print "Can not find directory with unencrypted book" - return 1 - - dictFile = os.path.join(bookDir,'dict0000.dat') - if not os.path.exists(dictFile) : - print "Can not find dict0000.dat file" - return 1 - - pageDir = os.path.join(bookDir,'page') - if not os.path.exists(pageDir) : - print "Can not find page directory in unencrypted book" - return 1 - - imgDir = os.path.join(bookDir,'img') - if not os.path.exists(imgDir) : - print "Can not find image directory in unencrypted book" - return 1 - - glyphsDir = os.path.join(bookDir,'glyphs') - if not os.path.exists(glyphsDir) : - print "Can not find glyphs directory in unencrypted book" - return 1 - - metaFile = os.path.join(bookDir,'metadata0000.dat') - if not os.path.exists(metaFile) : - print "Can not find metadata0000.dat in unencrypted book" - return 1 - - svgDir = os.path.join(bookDir,'svg') - if not os.path.exists(svgDir) : - os.makedirs(svgDir) - - if buildXML: - xmlDir = os.path.join(bookDir,'xml') - if not os.path.exists(xmlDir) : - os.makedirs(xmlDir) - - otherFile = os.path.join(bookDir,'other0000.dat') - if not os.path.exists(otherFile) : - print "Can not find other0000.dat in unencrypted book" - return 1 - - print "Updating to color images if available" - spath = os.path.join(bookDir,'color_img') - dpath = os.path.join(bookDir,'img') - filenames = os.listdir(spath) - filenames = sorted(filenames) - for filename in filenames: - imgname = filename.replace('color','img') - sfile = os.path.join(spath,filename) - dfile = os.path.join(dpath,imgname) - imgdata = file(sfile,'rb').read() - file(dfile,'wb').write(imgdata) - - print "Creating cover.jpg" - isCover = False - cpath = os.path.join(bookDir,'img') - cpath = os.path.join(cpath,'img0000.jpg') - if os.path.isfile(cpath): - cover = file(cpath, 'rb').read() - cpath = os.path.join(bookDir,'cover.jpg') - file(cpath, 'wb').write(cover) - isCover = True - - - print 'Processing Dictionary' - dict = Dictionary(dictFile) - - print 'Processing Meta Data and creating OPF' - meta_array = getMetaArray(metaFile) - - # replace special chars in title and authors like & < > - title = meta_array.get('Title','No Title Provided') - title = title.replace('&','&') - title = title.replace('<','<') - title = title.replace('>','>') - meta_array['Title'] = title - authors = meta_array.get('Authors','No Authors Provided') - authors = authors.replace('&','&') - authors = authors.replace('<','<') - authors = authors.replace('>','>') - meta_array['Authors'] = authors - - if buildXML: - xname = os.path.join(xmlDir, 'metadata.xml') - mlst = [] - for key in meta_array: - mlst.append('\n') - metastr = "".join(mlst) - mlst = None - file(xname, 'wb').write(metastr) - - print 'Processing StyleSheet' - - # get some scaling info from metadata to use while processing styles - # and first page info - - fontsize = '135' - if 'fontSize' in meta_array: - fontsize = meta_array['fontSize'] - - # also get the size of a normal text page - # get the total number of pages unpacked as a safety check - filenames = os.listdir(pageDir) - numfiles = len(filenames) - - spage = '1' - if 'firstTextPage' in meta_array: - spage = meta_array['firstTextPage'] - pnum = int(spage) - if pnum >= numfiles or pnum < 0: - # metadata is wrong so just select a page near the front - # 10% of the book to get a normal text page - pnum = int(0.10 * numfiles) - # print "first normal text page is", spage - - # get page height and width from first text page for use in stylesheet scaling - pname = 'page%04d.dat' % (pnum + 1) - fname = os.path.join(pageDir,pname) - flat_xml = convert2xml.fromData(dict, fname) - - (ph, pw) = getPageDim(flat_xml) - if (ph == '-1') or (ph == '0') : ph = '11000' - if (pw == '-1') or (pw == '0') : pw = '8500' - meta_array['pageHeight'] = ph - meta_array['pageWidth'] = pw - if 'fontSize' not in meta_array.keys(): - meta_array['fontSize'] = fontsize - - # process other.dat for css info and for map of page files to svg images - # this map is needed because some pages actually are made up of multiple - # pageXXXX.xml files - xname = os.path.join(bookDir, 'style.css') - flat_xml = convert2xml.fromData(dict, otherFile) - - # extract info.original.pid to get original page information - pageIDMap = {} - pageidnums = stylexml2css.getpageIDMap(flat_xml) - if len(pageidnums) == 0: - filenames = os.listdir(pageDir) - numfiles = len(filenames) - for k in range(numfiles): - pageidnums.append(k) - # create a map from page ids to list of page file nums to process for that page - for i in range(len(pageidnums)): - id = pageidnums[i] - if id in pageIDMap.keys(): - pageIDMap[id].append(i) - else: - pageIDMap[id] = [i] - - # now get the css info - cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw) - file(xname, 'wb').write(cssstr) - if buildXML: - xname = os.path.join(xmlDir, 'other0000.xml') - file(xname, 'wb').write(convert2xml.getXML(dict, otherFile)) - - print 'Processing Glyphs' - gd = GlyphDict() - filenames = os.listdir(glyphsDir) - filenames = sorted(filenames) - glyfname = os.path.join(svgDir,'glyphs.svg') - glyfile = open(glyfname, 'w') - glyfile.write('\n') - glyfile.write('\n') - glyfile.write('\n') - glyfile.write('Glyphs for %s\n' % meta_array['Title']) - glyfile.write('\n') - counter = 0 - for filename in filenames: - # print ' ', filename - print '.', - fname = os.path.join(glyphsDir,filename) - flat_xml = convert2xml.fromData(dict, fname) - - if buildXML: - xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) - file(xname, 'wb').write(convert2xml.getXML(dict, fname)) - - gp = GParser(flat_xml) - for i in xrange(0, gp.count): - path = gp.getPath(i) - maxh, maxw = gp.getGlyphDim(i) - fullpath = '\n' % (counter * 256 + i, path, maxw, maxh) - glyfile.write(fullpath) - gd.addGlyph(counter * 256 + i, fullpath) - counter += 1 - glyfile.write('\n') - glyfile.write('\n') - glyfile.close() - print " " - - - # start up the html - # also build up tocentries while processing html - htmlFileName = "book.html" - hlst = [] - hlst.append('\n') - hlst.append('\n') - hlst.append('\n') - hlst.append('\n') - hlst.append('\n') - hlst.append('' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '\n') - hlst.append('\n') - hlst.append('\n') - if 'ASIN' in meta_array: - hlst.append('\n') - if 'GUID' in meta_array: - hlst.append('\n') - hlst.append('\n') - hlst.append('\n\n') - - print 'Processing Pages' - # Books are at 1440 DPI. This is rendering at twice that size for - # readability when rendering to the screen. - scaledpi = 1440.0 - - filenames = os.listdir(pageDir) - filenames = sorted(filenames) - numfiles = len(filenames) - - xmllst = [] - elst = [] - - for filename in filenames: - # print ' ', filename - print ".", - fname = os.path.join(pageDir,filename) - flat_xml = convert2xml.fromData(dict, fname) - - # keep flat_xml for later svg processing - xmllst.append(flat_xml) - - if buildXML: - xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) - file(xname, 'wb').write(convert2xml.getXML(dict, fname)) - - # first get the html - pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage) - elst.append(tocinfo) - hlst.append(pagehtml) - - # finish up the html string and output it - hlst.append('\n\n') - htmlstr = "".join(hlst) - hlst = None - file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) - - print " " - print 'Extracting Table of Contents from Amazon OCR' - - # first create a table of contents file for the svg images - tlst = [] - tlst.append('\n') - tlst.append('\n') - tlst.append('') - tlst.append('\n') - tlst.append('' + meta_array['Title'] + '\n') - tlst.append('\n') - tlst.append('\n') - if 'ASIN' in meta_array: - tlst.append('\n') - if 'GUID' in meta_array: - tlst.append('\n') - tlst.append('\n') - tlst.append('\n') - - tlst.append('

Table of Contents

\n') - start = pageidnums[0] - if (raw): - startname = 'page%04d.svg' % start - else: - startname = 'page%04d.xhtml' % start - - tlst.append('

Start of Book

\n') - # build up a table of contents for the svg xhtml output - tocentries = "".join(elst) - elst = None - toclst = tocentries.split('\n') - toclst.pop() - for entry in toclst: - print entry - title, pagenum = entry.split('|') - id = pageidnums[int(pagenum)] - if (raw): - fname = 'page%04d.svg' % id - else: - fname = 'page%04d.xhtml' % id - tlst.append('

' + title + '

\n') - tlst.append('\n') - tlst.append('\n') - tochtml = "".join(tlst) - file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml) - - - # now create index_svg.xhtml that points to all required files - slst = [] - slst.append('\n') - slst.append('\n') - slst.append('') - slst.append('\n') - slst.append('' + meta_array['Title'] + '\n') - slst.append('\n') - slst.append('\n') - if 'ASIN' in meta_array: - slst.append('\n') - if 'GUID' in meta_array: - slst.append('\n') - slst.append('\n') - slst.append('\n') - - print "Building svg images of each book page" - slst.append('

List of Pages

\n') - slst.append('
\n') - idlst = sorted(pageIDMap.keys()) - numids = len(idlst) - cnt = len(idlst) - previd = None - for j in range(cnt): - pageid = idlst[j] - if j < cnt - 1: - nextid = idlst[j+1] - else: - nextid = None - print '.', - pagelst = pageIDMap[pageid] - flst = [] - for page in pagelst: - flst.append(xmllst[page]) - flat_svg = "".join(flst) - flst=None - svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi) - if (raw) : - pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w') - slst.append('Page %d\n' % (pageid, pageid)) - else : - pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w') - slst.append('Page %d\n' % (pageid, pageid)) - previd = pageid - pfile.write(svgxml) - pfile.close() - counter += 1 - slst.append('
\n') - slst.append('

Table of Contents

\n') - slst.append('\n\n') - svgindex = "".join(slst) - slst = None - file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex) - - print " " - - # build the opf file - opfname = os.path.join(bookDir, 'book.opf') - olst = [] - olst.append('\n') - olst.append('\n') - # adding metadata - olst.append(' \n') - if 'GUID' in meta_array: - olst.append(' ' + meta_array['GUID'] + '\n') - if 'ASIN' in meta_array: - olst.append(' ' + meta_array['ASIN'] + '\n') - if 'oASIN' in meta_array: - olst.append(' ' + meta_array['oASIN'] + '\n') - olst.append(' ' + meta_array['Title'] + '\n') - olst.append(' ' + meta_array['Authors'] + '\n') - olst.append(' en\n') - olst.append(' ' + meta_array['UpdateTime'] + '\n') - if isCover: - olst.append(' \n') - olst.append(' \n') - olst.append('\n') - olst.append(' \n') - olst.append(' \n') - # adding image files to manifest - filenames = os.listdir(imgDir) - filenames = sorted(filenames) - for filename in filenames: - imgname, imgext = os.path.splitext(filename) - if imgext == '.jpg': - imgext = 'jpeg' - if imgext == '.svg': - imgext = 'svg+xml' - olst.append(' \n') - if isCover: - olst.append(' \n') - olst.append('\n') - # adding spine - olst.append('\n \n\n') - if isCover: - olst.append(' \n') - olst.append(' \n') - olst.append(' \n') - olst.append('\n') - opfstr = "".join(olst) - olst = None - file(opfname, 'wb').write(opfstr) - - print 'Processing Complete' - - return 0 - -def usage(): - print "genbook.py generates a book from the extract Topaz Files" - print "Usage:" - print " genbook.py [-r] [-h [--fixed-image] " - print " " - print "Options:" - print " -h : help - print this usage message" - print " -r : generate raw svg files (not wrapped in xhtml)" - print " --fixed-image : genearate any Fixed Area as an svg image in the html" - print " " - - -def main(argv): - bookDir = '' - if len(argv) == 0: - argv = sys.argv - - try: - opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"]) - - except getopt.GetoptError, err: - print str(err) - usage() - return 1 - - if len(opts) == 0 and len(args) == 0 : - usage() - return 1 - - raw = 0 - fixedimage = True - for o, a in opts: - if o =="-h": - usage() - return 0 - if o =="-r": - raw = 1 - if o =="--fixed-image": - fixedimage = True - - bookDir = args[0] - - rv = generateBook(bookDir, raw, fixedimage) - return rv - - -if __name__ == '__main__': - sys.exit(main('')) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/getk4pcpids.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/getk4pcpids.py deleted file mode 100644 index cc8bcd4..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/getk4pcpids.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 1.00 - Initial version -# 1.01 - getPidList interface change - -__version__ = '1.01' - -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii -import kgenpids -import topazextract -import mobidedrm -from alfcrypto import Pukall_Cipher - -class DrmException(Exception): - pass - -def getK4PCpids(path_to_ebook): - # Return Kindle4PC PIDs. Assumes that the caller checked that we are not on Linux, which will raise an exception - - mobi = True - magic3 = file(path_to_ebook,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - if mobi: - mb = mobidedrm.MobiBook(path_to_ebook,False) - else: - mb = topazextract.TopazBook(path_to_ebook) - - md1, md2 = mb.getPIDMetaInfo() - - return kgenpids.getPidList(md1, md2) - - -def main(argv=sys.argv): - print ('getk4pcpids.py v%(__version__)s. ' - 'Copyright 2012 Apprentice Alf' % globals()) - - if len(argv)<2 or len(argv)>3: - print "Gets the possible book-specific PIDs from K4PC for a particular book" - print "Usage:" - print " %s []" % sys.argv[0] - return 1 - else: - infile = argv[1] - try: - pidlist = getK4PCpids(infile) - except DrmException, e: - print "Error: %s" % e - return 1 - pidstring = ','.join(pidlist) - print "Possible PIDs are: ", pidstring - if len(argv) is 3: - outfile = argv[2] - file(outfile, 'w').write(pidstring) - - return 0 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignobleepub.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignobleepub.py deleted file mode 100644 index 03aa91f..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignobleepub.py +++ /dev/null @@ -1,337 +0,0 @@ -#! /usr/bin/python - -from __future__ import with_statement - -# ignobleepub.pyw, version 3.5 - -# To run this program install Python 2.6 from -# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignobleepub.pyw and double-click on it to run it. - -# Revision history: -# 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 -# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml -# 3.3 - On Windows try PyCrypto first and OpenSSL next -# 3.4 - Modify interace to allow use with import -# 3.5 - Fix for potential problem with PyCrypto - - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class IGNOBLEError(Exception): - pass - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - 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) - - 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]) - 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 IGNOBLEError('AES improper key used') - return - key = self._key = AES_KEY() - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) - if rv < 0: - raise IGNOBLEError('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 IGNOBLEError('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, '\x00'*16) - - def decrypt(self, data): - return self._aes.decrypt(data) - - return AES - -def _load_crypto(): - AES = None - cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) - if sys.platform.startswith('win'): - cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) - for loader in cryptolist: - try: - AES = loader() - break - except (ImportError, IGNOBLEError): - pass - return AES - -AES = _load_crypto() - - - -""" -Decrypt Barnes & Noble ADEPT encrypted EPUB books. -""" - - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - # self._aes = AES.new(bookkey, AES.MODE_CBC, '\x00'*16) - self._aes = AES(bookkey) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - path = path.encode('utf-8') - if path is not None: - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('bnepubkey.b64'): - self.keypath.insert(0, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N EPUB key file', - defaultextension='.b64', - filetypes=[('base64-encoded files', '.b64'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - - -def decryptBook(keypath, inpath, outpath): - with open(keypath, 'rb') as f: - keyb64 = f.read() - key = keyb64.decode('base64')[:16] - # aes = AES.new(key, AES.MODE_CBC, '\x00'*16) - aes = AES(key) - - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = aes.decrypt(bookkey.decode('base64')) - bookkey = bookkey[:-ord(bookkey[-1])] - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) - - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "Ignoble EPUB Decrypter", - "This script requires OpenSSL or PyCrypto, which must be installed " - "separately. Read the top-of-script comment for details.") - return 1 - root.title('Ignoble EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptepub.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptepub.py deleted file mode 100644 index 2bb32b1..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptepub.py +++ /dev/null @@ -1,478 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import with_statement - -# ineptepub.pyw, version 5.6 -# Copyright © 2009-2010 i♥cabbages - -# Released under the terms of the GNU General Public Licence, version 3 or -# later. - -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make sure to -# install the version for Python 2.6). Save this script file as -# ineptepub.pyw and double-click on it to run it. -# -# Mac OS X users: Save this script file as ineptepub.pyw. You can run this -# program from the command line (pythonw ineptepub.pyw) or by double-clicking -# it when it has been associated with PythonLauncher. - -# Revision history: -# 1 - Initial release -# 2 - Rename to INEPT, fix exit code -# 5 - Version bump to avoid (?) confusion; -# 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 -# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml -# 5.5 - On Windows try PyCrypto first, OpenSSL next -# 5.6 - Modify interface to allow use with import -# 5.7 - Fix for potential problem with PyCrypto - -""" -Decrypt Adobe ADEPT-encrypted EPUB books. -""" - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class ADEPTError(Exception): - pass - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - 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) - - RSA_NO_PADDING = 3 - AES_MAXNR = 14 - - c_char_pp = POINTER(c_char_p) - c_int_p = POINTER(c_int) - - class RSA(Structure): - pass - RSA_p = POINTER(RSA) - - 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 - - d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', - [RSA_p, c_char_pp, c_long]) - RSA_size = F(c_int, 'RSA_size', [RSA_p]) - RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', - [c_int, c_char_p, c_char_p, RSA_p, c_int]) - RSA_free = F(None, 'RSA_free', [RSA_p]) - 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 RSA(object): - def __init__(self, der): - buf = create_string_buffer(der) - pp = c_char_pp(cast(buf, c_char_p)) - rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) - if rsa is None: - raise ADEPTError('Error parsing ADEPT user key DER') - - def decrypt(self, from_): - rsa = self._rsa - to = create_string_buffer(RSA_size(rsa)) - dlen = RSA_private_decrypt(len(from_), from_, to, rsa, - RSA_NO_PADDING) - if dlen < 0: - raise ADEPTError('RSA decryption failed') - return to[:dlen] - - def __del__(self): - if self._rsa is not None: - RSA_free(self._rsa) - self._rsa = None - - 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: - 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, RSA) - -def _load_crypto_pycrypto(): - from Crypto.Cipher import AES as _AES - from Crypto.PublicKey import RSA as _RSA - - # ASN.1 parsing code from tlslite - class ASN1Error(Exception): - pass - - class ASN1Parser(object): - class Parser(object): - def __init__(self, bytes): - self.bytes = bytes - self.index = 0 - - def get(self, length): - if self.index + length > len(self.bytes): - raise ASN1Error("Error decoding ASN.1") - x = 0 - for count in range(length): - x <<= 8 - x |= self.bytes[self.index] - self.index += 1 - return x - - def getFixBytes(self, lengthBytes): - bytes = self.bytes[self.index : self.index+lengthBytes] - self.index += lengthBytes - return bytes - - def getVarBytes(self, lengthLength): - lengthBytes = self.get(lengthLength) - return self.getFixBytes(lengthBytes) - - def getFixList(self, length, lengthList): - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def getVarList(self, length, lengthLength): - lengthList = self.get(lengthLength) - if lengthList % length != 0: - raise ASN1Error("Error decoding ASN.1") - lengthList = int(lengthList/length) - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def startLengthCheck(self, lengthLength): - self.lengthCheck = self.get(lengthLength) - self.indexCheck = self.index - - def setLengthCheck(self, length): - self.lengthCheck = length - self.indexCheck = self.index - - def stopLengthCheck(self): - if (self.index - self.indexCheck) != self.lengthCheck: - raise ASN1Error("Error decoding ASN.1") - - def atLengthCheck(self): - if (self.index - self.indexCheck) < self.lengthCheck: - return False - elif (self.index - self.indexCheck) == self.lengthCheck: - return True - else: - raise ASN1Error("Error decoding ASN.1") - - def __init__(self, bytes): - p = self.Parser(bytes) - p.get(1) - self.length = self._getASN1Length(p) - self.value = p.getFixBytes(self.length) - - def getChild(self, which): - p = self.Parser(self.value) - for x in range(which+1): - markIndex = p.index - p.get(1) - length = self._getASN1Length(p) - p.getFixBytes(length) - return ASN1Parser(p.bytes[markIndex:p.index]) - - def _getASN1Length(self, p): - firstLength = p.get(1) - if firstLength<=127: - return firstLength - else: - lengthLength = firstLength & 0x7F - return p.get(lengthLength) - - class AES(object): - def __init__(self, key): - self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16) - - def decrypt(self, data): - return self._aes.decrypt(data) - - class RSA(object): - def __init__(self, der): - key = ASN1Parser([ord(x) for x in der]) - key = [key.getChild(x).value for x in xrange(1, 4)] - key = [self.bytesToNumber(v) for v in key] - self._rsa = _RSA.construct(key) - - def bytesToNumber(self, bytes): - total = 0L - for byte in bytes: - total = (total << 8) + byte - return total - - def decrypt(self, data): - return self._rsa.decrypt(data) - - return (AES, RSA) - -def _load_crypto(): - AES = RSA = None - cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) - if sys.platform.startswith('win'): - cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) - for loader in cryptolist: - try: - AES, RSA = loader() - break - except (ImportError, ADEPTError): - pass - return (AES, RSA) -AES, RSA = _load_crypto() - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - self._aes = AES(bookkey) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - if path is not None: - path = path.encode('utf-8') - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('adeptkey.der'): - self.keypath.insert(0, 'adeptkey.der') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT key file', - defaultextension='.der', filetypes=[('DER-encoded files', '.der'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - - -def decryptBook(keypath, inpath, outpath): - with open(keypath, 'rb') as f: - keyder = f.read() - rsa = RSA(keyder) - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = rsa.decrypt(bookkey.decode('base64')) - # Padded as per RSAES-PKCS1-v1_5 - if bookkey[-17] != '\x00': - raise ADEPTError('problem decrypting session key') - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be" \ - " installed separately. Read the top-of-script comment for" \ - " details." % (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) - - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "INEPT EPUB Decrypter", - "This script requires OpenSSL or PyCrypto, which must be" - " installed separately. Read the top-of-script comment for" - " details.") - return 1 - root.title('INEPT EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mobidedrm.py deleted file mode 100755 index 717b0d0..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mobidedrm.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python - -from __future__ import with_statement - -# engine to remove drm from Kindle for Mac and Kindle for PC books -# for personal use for archiving and converting your ebooks - -# PLEASE DO NOT PIRATE EBOOKS! - -# We want all authors and publishers, and eBook stores to live -# long and prosperous lives but at the same time we just want to -# be able to read OUR books on whatever device we want and to keep -# readable for a long, long time - -# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, -# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates -# and many many others - - -__version__ = '4.4' - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -import os, csv, getopt -import string -import re -import traceback -import time - -buildXML = False - -class DrmException(Exception): - pass - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -if inCalibre: - from calibre_plugins.k4mobidedrm import mobidedrm - from calibre_plugins.k4mobidedrm import topazextract - from calibre_plugins.k4mobidedrm import kgenpids -else: - import mobidedrm - import topazextract - import kgenpids - - -# cleanup bytestring filenames -# borrowed from calibre from calibre/src/calibre/__init__.py -# added in removal of non-printing chars -# and removal of . at start -# convert underscores to spaces (we're OK with spaces in file names) -def cleanup_name(name): - _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') - substitute='_' - one = ''.join(char for char in name if char in string.printable) - one = _filename_sanitize.sub(substitute, one) - one = re.sub(r'\s', ' ', one).strip() - one = re.sub(r'^\.+$', '_', one) - one = one.replace('..', substitute) - # Windows doesn't like path components that end with a period - if one.endswith('.'): - one = one[:-1]+substitute - # Mac and Unix don't like file names that begin with a full stop - if len(one) > 0 and one[0] == '.': - one = substitute+one[1:] - one = one.replace('_',' ') - return one - -def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): - global buildXML - - - # handle the obvious cases at the beginning - if not os.path.isfile(infile): - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist" - return 1 - - starttime = time.time() - print "Starting decryptBook routine." - - - mobi = True - magic3 = file(infile,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(infile))[0] - - if mobi: - mb = mobidedrm.MobiBook(infile) - else: - mb = topazextract.TopazBook(infile) - - title = mb.getBookTitle() - print "Processing Book: ", title - filenametitle = cleanup_name(title) - outfilename = cleanup_name(bookname) - - # generate 'sensible' filename, that will sort with the original name, - # but is close to the name from the file. - outlength = len(outfilename) - comparelength = min(8,min(outlength,len(filenametitle))) - copylength = min(max(outfilename.find(' '),8),len(outfilename)) - if outlength==0: - outfilename = filenametitle - elif comparelength > 0: - if outfilename[:comparelength] == filenametitle[:comparelength]: - outfilename = filenametitle - else: - outfilename = outfilename[:copylength] + " " + filenametitle - - # avoid excessively long file names - if len(outfilename)>150: - outfilename = outfilename[:150] - - # build pid list - md1, md2 = mb.getPIDMetaInfo() - pids.extend(kgenpids.getPidList(md1, md2, k4, serials, kInfoFiles)) - - print "Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids)) - - - try: - mb.processBook(pids) - - except mobidedrm.DrmException, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - except topazextract.TpzDRMError, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - except Exception, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - - print "Successfully decrypted book after {0:.1f} seconds".format(time.time()-starttime) - - if mobi: - if mb.getPrintReplica(): - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4') - elif mb.getMobiVersion() >= 8: - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3') - else: - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') - mb.getMobiFile(outfile) - print "Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename + '_nodrm') - return 0 - - # topaz: - print " Creating NoDRM HTMLZ Archive" - zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz') - mb.getHTMLZip(zipname) - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') - mb.getSVGZip(zipname) - - if buildXML: - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') - mb.getXMLZip(zipname) - - # remove internal temporary directory of Topaz pieces - mb.cleanup() - print "Saved decrypted Topaz book parts after {0:.1f} seconds".format(time.time()-starttime) - return 0 - - -def usage(progname): - print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - -# -# Main -# -def main(argv=sys.argv): - progname = os.path.basename(argv[0]) - - k4 = False - kInfoFiles = [] - serials = [] - pids = [] - - print ('K4MobiDeDrm v%(__version__)s ' - 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) - - try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") - except getopt.GetoptError, err: - print str(err) - usage(progname) - sys.exit(2) - if len(args)<2: - usage(progname) - sys.exit(2) - - for o, a in opts: - if o == "-k": - if a == None : - raise DrmException("Invalid parameter for -k") - kInfoFiles.append(a) - if o == "-p": - if a == None : - raise DrmException("Invalid parameter for -p") - pids = a.split(',') - if o == "-s": - if a == None : - raise DrmException("Invalid parameter for -s") - serials = a.split(',') - - # try with built in Kindle Info files - k4 = True - if sys.platform.startswith('linux'): - k4 = False - kInfoFiles = None - infile = args[0] - outdir = args[1] - return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mutils.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mutils.py deleted file mode 100644 index 1fc08cb..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mutils.py +++ /dev/null @@ -1,730 +0,0 @@ -# standlone set of Mac OSX specific routines needed for KindleBooks - -from __future__ import with_statement - -import sys -import os -import os.path -import re -import copy -import subprocess -from struct import pack, unpack, unpack_from - -class DrmException(Exception): - pass - - -# interface to needed routines in openssl's libcrypto -def _load_crypto_libcrypto(): - from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, addressof, string_at, cast - from ctypes.util import find_library - - libcrypto = find_library('crypto') - if libcrypto is None: - raise DrmException('libcrypto not found') - libcrypto = CDLL(libcrypto) - - # From OpenSSL's crypto aes header - # - # AES_ENCRYPT 1 - # AES_DECRYPT 0 - # AES_MAXNR 14 (in bytes) - # AES_BLOCK_SIZE 16 (in bytes) - # - # struct aes_key_st { - # unsigned long rd_key[4 *(AES_MAXNR + 1)]; - # int rounds; - # }; - # typedef struct aes_key_st AES_KEY; - # - # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); - # - # note: the ivec string, and output buffer are both mutable - # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, - # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc); - - 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]) - - # From OpenSSL's Crypto evp/p5_crpt2.c - # - # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, - # const unsigned char *salt, int saltlen, int iter, - # int keylen, unsigned char *out); - - PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', - [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) - - class LibCrypto(object): - def __init__(self): - self._blocksize = 0 - self._keyctx = None - self._iv = 0 - - def set_decrypt_key(self, userkey, iv): - self._blocksize = len(userkey) - if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : - raise DrmException('AES improper key used') - return - keyctx = self._keyctx = AES_KEY() - self._iv = iv - self._userkey = userkey - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) - if rv < 0: - raise DrmException('Failed to initialize AES key') - - def decrypt(self, data): - out = create_string_buffer(len(data)) - mutable_iv = create_string_buffer(self._iv, len(self._iv)) - keyctx = self._keyctx - rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) - if rv == 0: - raise DrmException('AES decryption failed') - return out.raw - - def keyivgen(self, passwd, salt, iter, keylen): - saltlen = len(salt) - passlen = len(passwd) - out = create_string_buffer(keylen) - rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) - return out.raw - return LibCrypto - -def _load_crypto(): - LibCrypto = None - try: - LibCrypto = _load_crypto_libcrypto() - except (ImportError, DrmException): - pass - return LibCrypto - -LibCrypto = _load_crypto() - -# -# Utility Routines -# - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" - -# For kinf approach of K4Mac 1.6.X or later -# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" -# For Mac they seem to re-use charMap2 here -charMap5 = charMap2 - -# new in K4M 1.9.X -testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD" - - -def encode(data, map): - result = "" - for char in data: - value = ord(char) - Q = (value ^ 0x80) // len(map) - R = value % len(map) - result += map[Q] - result += map[R] - return result - -# Hash the bytes in data and then encode the digest with the characters in map -def encodeHash(data,map): - return encode(MD5(data),map) - -# Decode the string in data with the characters in map. Returns the decoded bytes -def decode(data,map): - result = "" - for i in range (0,len(data)-1,2): - high = map.find(data[i]) - low = map.find(data[i+1]) - if (high == -1) or (low == -1) : - break - value = (((high * len(map)) ^ 0x80) & 0xFF) + low - result += pack("B",value) - return result - -# For K4M 1.6.X and later -# generate table of prime number less than or equal to int n -def primes(n): - if n==2: return [2] - elif n<2: return [] - s=range(3,n+1,2) - mroot = n ** 0.5 - half=(n+1)/2-1 - i=0 - m=3 - while m <= mroot: - if s[i]: - j=(m*m-3)/2 - s[j]=0 - while j 7: - print('Using Munged MAC Address for ID: '+mungedmac) - return mungedmac - sernum = GetVolumeSerialNumber() - if len(sernum) > 7: - print('Using Volume Serial Number for ID: '+sernum) - return sernum - diskpart = GetUserHomeAppSupKindleDirParitionName() - uuidnum = GetDiskPartitionUUID(diskpart) - if len(uuidnum) > 7: - print('Using Disk Partition UUID for ID: '+uuidnum) - return uuidnum - mungedmac = GetMACAddressMunged() - if len(mungedmac) > 7: - print('Using Munged MAC Address for ID: '+mungedmac) - return mungedmac - print('Using Fixed constant 9999999999 for ID.') - return '9999999999' - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used by Kindle for Mac versions < 1.6.0 -class CryptUnprotectData(object): - def __init__(self): - sernum = GetVolumeSerialNumber() - if sernum == '': - sernum = '9999999999' - sp = sernum + '!@#' + GetUserName() - passwdData = encode(SHA256(sp),charMap1) - salt = '16743' - self.crp = LibCrypto() - iter = 0x3e8 - keylen = 0x80 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext,charMap1) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.6.0 -class CryptUnprotectDataV2(object): - def __init__(self): - sp = GetUserName() + ':&%:' + GetIDString() - passwdData = encode(SHA256(sp),charMap5) - # salt generation as per the code - salt = 0x0512981d * 2 * 1 * 1 - salt = str(salt) + GetUserName() - salt = encode(salt,charMap5) - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap5) - return cleartext - - -# unprotect the new header blob in .kinf2011 -# used in Kindle for Mac Version >= 1.9.0 -def UnprotectHeaderData(encryptedData): - passwdData = 'header_key_data' - salt = 'HEADER.2011' - iter = 0x80 - keylen = 0x100 - crp = LibCrypto() - key_iv = crp.keyivgen(passwdData, salt, iter, keylen) - key = key_iv[0:32] - iv = key_iv[32:48] - crp.set_decrypt_key(key,iv) - cleartext = crp.decrypt(encryptedData) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.9.0 -class CryptUnprotectDataV3(object): - def __init__(self, entropy): - sp = GetUserName() + '+@#$%+' + GetIDString() - passwdData = encode(SHA256(sp),charMap2) - salt = entropy - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap2) - return cleartext - - -# Locate the .kindle-info files -def getKindleInfoFiles(): - # file searches can take a long time on some systems, so just look in known specific places. - kInfoFiles=[] - found = False - home = os.getenv('HOME') - # check for .kinf2011 file in new location (App Store Kindle for Mac) - testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kinf2011 file: ' + testpath) - found = True - # check for .kinf2011 files - testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kinf2011 file: ' + testpath) - found = True - # check for .rainier-2.1.1-kinf files - testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac rainier file: ' + testpath) - found = True - # check for .rainier-2.1.1-kinf files - testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kindle-info file: ' + testpath) - found = True - if not found: - print('No k4Mac kindle-info/rainier/kinf2011 files have been found.') - return kInfoFiles - -# determine type of kindle info provided and return a -# database of keynames and values -def getDBfromFile(kInfoFile): - names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"] - DB = {} - cnt = 0 - infoReader = open(kInfoFile, 'r') - hdr = infoReader.read(1) - data = infoReader.read() - - if data.find('[') != -1 : - - # older style kindle-info file - cud = CryptUnprotectData() - items = data.split('[') - for item in items: - if item != '': - keyhash, rawdata = item.split(':') - keyname = "unknown" - for name in names: - if encodeHash(name,charMap2) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - encryptedValue = decode(rawdata,charMap2) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - if cnt == 0: - DB = None - return DB - - if hdr == '/': - - # else newer style .kinf file used by K4Mac >= 1.6.0 - # the .kinf file uses "/" to separate it into records - # so remove the trailing "/" to make it easy to use split - data = data[:-1] - items = data.split('/') - cud = CryptUnprotectDataV2() - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # the raw keyhash string is also used to create entropy for the actual - # CryptProtectData Blob that represents that keys contents - # "entropy" not used for K4Mac only K4PC - # entropy = SHA1(keyhash) - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,charMap5) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the charMap5 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using charMap5 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the charMap5 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by charMap5 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using charMap5 to get the CryptProtect Data - encryptedValue = decode(encdata,charMap5) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB - - # the latest .kinf2011 version for K4M 1.9.1 - # put back the hdr char, it is needed - data = hdr + data - data = data[:-1] - items = data.split('/') - - # the headerblob is the encrypted information needed to build the entropy string - headerblob = items.pop(0) - encryptedValue = decode(headerblob, charMap1) - cleartext = UnprotectHeaderData(encryptedValue) - - # now extract the pieces in the same way - # this version is different from K4PC it scales the build number by multipying by 735 - pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) - for m in re.finditer(pattern, cleartext): - entropy = str(int(m.group(2)) * 0x2df) + m.group(4) - - cud = CryptUnprotectDataV3(entropy) - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # unlike K4PC the keyhash is not used in generating entropy - # entropy = SHA1(keyhash) + added_entropy - # entropy = added_entropy - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,testMap8) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the testMap8 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using testMap8 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the testMap8 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by testMap8 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using testMap8 to get the CryptProtect Data - encryptedValue = decode(encdata,testMap8) - cleartext = cud.decrypt(encryptedValue) - # print keyname - # print cleartext - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4pcutils.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4pcutils.py deleted file mode 100755 index 9f9ca07..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4pcutils.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/env python -# K4PC Windows specific routines - -from __future__ import with_statement - -import sys, os, re -from struct import pack, unpack, unpack_from - -from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ - create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ - string_at, Structure, c_void_p, cast - -import _winreg as winreg -MAX_PATH = 255 -kernel32 = windll.kernel32 -advapi32 = windll.advapi32 -crypt32 = windll.crypt32 - -import traceback - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# For K4PC 1.9.X -# use routines in alfcrypto: -# AES_cbc_encrypt -# AES_set_decrypt_key -# PKCS5_PBKDF2_HMAC_SHA1 - -from alfcrypto import AES_CBC, KeyIVGen - -def UnprotectHeaderData(encryptedData): - passwdData = 'header_key_data' - salt = 'HEADER.2011' - iter = 0x80 - keylen = 0x100 - key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen) - key = key_iv[0:32] - iv = key_iv[32:48] - aes=AES_CBC() - aes.set_decrypt_key(key, iv) - cleartext = aes.decrypt(encryptedData) - return cleartext - - -# simple primes table (<= n) calculator -def primes(n): - if n==2: return [2] - elif n<2: return [] - s=range(3,n+1,2) - mroot = n ** 0.5 - half=(n+1)/2-1 - i=0 - m=3 - while m <= mroot: - if s[i]: - j=(m*m-3)/2 - s[j]=0 - while j 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - - # the raw keyhash string is used to create entropy for the actual - # CryptProtectData Blob that represents that keys contents - entropy = SHA1(keyhash) - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,charMap5) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - # the charMap5 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using charMap5 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the charMap5 encoded contents seems to be: - # len(contents)-largest prime number <= int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by charMap5 - encdata = "".join(edlst) - contlen = len(encdata) - noffset = contlen - primes(int(contlen/3))[-1] - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using Map5 to get the CryptProtect Data - encryptedValue = decode(encdata,charMap5) - DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB - - # else newest .kinf2011 style .kinf file - # the .kinf file uses "/" to separate it into records - # so remove the trailing "/" to make it easy to use split - # need to put back the first char read because it it part - # of the added entropy blob - data = hdr + data[:-1] - items = data.split('/') - - # starts with and encoded and encrypted header blob - headerblob = items.pop(0) - encryptedValue = decode(headerblob, testMap1) - cleartext = UnprotectHeaderData(encryptedValue) - # now extract the pieces that form the added entropy - pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) - for m in re.finditer(pattern, cleartext): - added_entropy = m.group(2) + m.group(4) - - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - - # the sha1 of raw keyhash string is used to create entropy along - # with the added entropy provided above from the headerblob - entropy = SHA1(keyhash) + added_entropy - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - # key names now use the new testMap8 encoding - keyname = "unknown" - for name in names: - if encodeHash(name,testMap8) == keyhash: - keyname = name - break - - # the testMap8 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using testMap8 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the testMap8 encoded contents seems to be: - # len(contents)-largest prime number <= int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by testMap8 - # by moving noffset chars from the start of the - # string to the end of the string - encdata = "".join(edlst) - contlen = len(encdata) - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using new testMap8 to get the original CryptProtect Data - encryptedValue = decode(encdata,testMap8) - cleartext = CryptUnprotectData(encryptedValue, entropy, 1) - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kindlepid.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kindlepid.py deleted file mode 100755 index 90a59ad..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kindlepid.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/python -# Mobipocket PID calculator v0.2 for Amazon Kindle. -# Copyright (c) 2007, 2009 Igor Skochinsky -# History: -# 0.1 Initial release -# 0.2 Added support for generating PID for iPhone (thanks to mbp) -# 0.3 changed to autoflush stdout, fixed return code usage -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 binascii - -if sys.hexversion >= 0x3000000: - print "This script is incompatible with Python 3.x. Please install Python 2.6.x from python.org" - sys.exit(2) - -letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - -def crc32(s): - return (~binascii.crc32(s,-1))&0xFFFFFFFF - -def checksumPid(s): - crc = crc32(s) - 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 pidFromSerial(s, l): - crc = crc32(s) - - arr1 = [0]*l - for i in xrange(len(s)): - arr1[i%l] ^= ord(s[i]) - - crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] - for i in xrange(l): - arr1[i] ^= crc_bytes[i&3] - - pid = "" - for i in xrange(l): - b = arr1[i] & 0xff - pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] - - return pid - -def main(argv=sys.argv): - print "Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky" - if len(sys.argv)==2: - serial = sys.argv[1] - else: - print "Usage: kindlepid.py /" - return 1 - if len(serial)==16: - if serial.startswith("B"): - print "Kindle serial number detected" - else: - print "Warning: unrecognized serial number. Please recheck input." - return 1 - pid = pidFromSerial(serial,7)+"*" - print "Mobipocket PID for Kindle serial# "+serial+" is "+checksumPid(pid) - return 0 - elif len(serial)==40: - print "iPhone serial number (UDID) detected" - pid = pidFromSerial(serial,8) - print "Mobipocket PID for iPhone serial# "+serial+" is "+checksumPid(pid) - return 0 - else: - print "Warning: unrecognized serial number. Please recheck input." - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/subasyncio.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/subasyncio.py deleted file mode 100755 index de084d3..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/subasyncio.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import os, sys -import signal -import threading -import subprocess -from subprocess import Popen, PIPE, STDOUT - -# **heavily** chopped up and modfied version of asyncproc.py -# to make it actually work on Windows as well as Mac/Linux -# For the original see: -# "http://www.lysator.liu.se/~bellman/download/" -# author is "Thomas Bellman " -# available under GPL version 3 or Later - -# create an asynchronous subprocess whose output can be collected in -# a non-blocking manner - -# What a mess! Have to use threads just to get non-blocking io -# in a cross-platform manner - -# luckily all thread use is hidden within this class - -class Process(object): - def __init__(self, *params, **kwparams): - if len(params) <= 3: - kwparams.setdefault('stdin', subprocess.PIPE) - if len(params) <= 4: - kwparams.setdefault('stdout', subprocess.PIPE) - if len(params) <= 5: - kwparams.setdefault('stderr', subprocess.PIPE) - self.__pending_input = [] - self.__collected_outdata = [] - self.__collected_errdata = [] - self.__exitstatus = None - self.__lock = threading.Lock() - self.__inputsem = threading.Semaphore(0) - self.__quit = False - - self.__process = subprocess.Popen(*params, **kwparams) - - if self.__process.stdin: - self.__stdin_thread = threading.Thread( - name="stdin-thread", - target=self.__feeder, args=(self.__pending_input, - self.__process.stdin)) - self.__stdin_thread.setDaemon(True) - self.__stdin_thread.start() - - if self.__process.stdout: - self.__stdout_thread = threading.Thread( - name="stdout-thread", - target=self.__reader, args=(self.__collected_outdata, - self.__process.stdout)) - self.__stdout_thread.setDaemon(True) - self.__stdout_thread.start() - - if self.__process.stderr: - self.__stderr_thread = threading.Thread( - name="stderr-thread", - target=self.__reader, args=(self.__collected_errdata, - self.__process.stderr)) - self.__stderr_thread.setDaemon(True) - self.__stderr_thread.start() - - def pid(self): - return self.__process.pid - - def kill(self, signal): - self.__process.send_signal(signal) - - # check on subprocess (pass in 'nowait') to act like poll - def wait(self, flag): - if flag.lower() == 'nowait': - rc = self.__process.poll() - else: - rc = self.__process.wait() - if rc != None: - if self.__process.stdin: - self.closeinput() - if self.__process.stdout: - self.__stdout_thread.join() - if self.__process.stderr: - self.__stderr_thread.join() - return self.__process.returncode - - def terminate(self): - if self.__process.stdin: - self.closeinput() - self.__process.terminate() - - # thread gets data from subprocess stdout - def __reader(self, collector, source): - while True: - data = os.read(source.fileno(), 65536) - self.__lock.acquire() - collector.append(data) - self.__lock.release() - if data == "": - source.close() - break - return - - # thread feeds data to subprocess stdin - def __feeder(self, pending, drain): - while True: - self.__inputsem.acquire() - self.__lock.acquire() - if not pending and self.__quit: - drain.close() - self.__lock.release() - break - data = pending.pop(0) - self.__lock.release() - drain.write(data) - - # non-blocking read of data from subprocess stdout - def read(self): - self.__lock.acquire() - outdata = "".join(self.__collected_outdata) - del self.__collected_outdata[:] - self.__lock.release() - return outdata - - # non-blocking read of data from subprocess stderr - def readerr(self): - self.__lock.acquire() - errdata = "".join(self.__collected_errdata) - del self.__collected_errdata[:] - self.__lock.release() - return errdata - - # non-blocking write to stdin of subprocess - def write(self, data): - if self.__process.stdin is None: - raise ValueError("Writing to process with stdin not a pipe") - self.__lock.acquire() - self.__pending_input.append(data) - self.__inputsem.release() - self.__lock.release() - - # close stdinput of subprocess - def closeinput(self): - self.__lock.acquire() - self.__quit = True - self.__inputsem.release() - self.__lock.release() diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/zipfix.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/zipfix.py deleted file mode 100755 index c7921f2..0000000 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/zipfix.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python - -import sys -import zlib -import zipfilerugged -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 -_MIMETYPE = 'application/epub+zip' - -class ZipInfo(zipfilerugged.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class fixZip: - def __init__(self, zinput, zoutput): - self.ztype = 'zip' - if zinput.lower().find('.epub') >= 0 : - self.ztype = 'epub' - print "opening input" - self.inzip = zipfilerugged.ZipFile(zinput,'r') - print "opening outout" - self.outzip = zipfilerugged.ZipFile(zoutput,'w') - print "opening input as raw file" - # open the input zip for reading only as a raw file - self.bzf = file(zinput,'rb') - print "finished initialising" - - 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(' 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('= 0x80: - datax = (data & 0x7F) - while data >= 0x80 : - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - datax = (datax <<7) + (data & 0x7F) - data = datax - - if flag: - data = -data - return data - - -# returns a binary string that encodes a number into 7 bits -# most significant byte first which has the high bit set - -def encodeNumber(number): - result = "" - negative = False - flag = 0 - - if number < 0 : - number = -number + 1 - negative = True - - while True: - byte = number & 0x7F - number = number >> 7 - byte += flag - result += chr(byte) - flag = 0x80 - if number == 0 : - if (byte == 0xFF and negative == False) : - result += chr(0x80) - break - - if negative: - result += chr(0xFF) - - return result[::-1] - - - -# create / read a length prefixed string from the file - -def lengthPrefixString(data): - return encodeNumber(len(data))+data - -def readString(file): - stringLength = readEncodedNumber(file) - if (stringLength == None): - return "" - sv = file.read(stringLength) - if (len(sv) != stringLength): - return "" - return unpack(str(stringLength)+"s",sv)[0] - - -# convert a binary string generated by encodeNumber (7 bit encoded number) -# to the value you would find inside the page*.dat files to be processed - -def convert(i): - result = '' - val = encodeNumber(i) - for j in xrange(len(val)): - c = ord(val[j:j+1]) - result += '%02x' % c - return result - - - -# the complete string table used to store all book text content -# as well as the xml tokens and values that make sense out of it - -class Dictionary(object): - def __init__(self, dictFile): - self.filename = dictFile - self.size = 0 - self.fo = file(dictFile,'rb') - self.stable = [] - self.size = readEncodedNumber(self.fo) - for i in xrange(self.size): - self.stable.append(self.escapestr(readString(self.fo))) - self.pos = 0 - - def escapestr(self, str): - str = str.replace('&','&') - str = str.replace('<','<') - str = str.replace('>','>') - str = str.replace('=','=') - return str - - def lookup(self,val): - if ((val >= 0) and (val < self.size)) : - self.pos = val - return self.stable[self.pos] - else: - print "Error - %d outside of string table limits" % val - raise TpzDRMError('outside of string table limits') - # sys.exit(-1) - - def getSize(self): - return self.size - - def getPos(self): - return self.pos - - def dumpDict(self): - for i in xrange(self.size): - print "%d %s %s" % (i, convert(i), self.stable[i]) - return - -# parses the xml snippets that are represented by each page*.dat file. -# also parses the other0.dat file - the main stylesheet -# and information used to inject the xml snippets into page*.dat files - -class PageParser(object): - def __init__(self, filename, dict, debug, flat_xml): - self.fo = file(filename,'rb') - self.id = os.path.basename(filename).replace('.dat','') - self.dict = dict - self.debug = debug - self.flat_xml = flat_xml - self.tagpath = [] - self.doc = [] - self.snippetList = [] - - - # hash table used to enable the decoding process - # This has all been developed by trial and error so it may still have omissions or - # contain errors - # Format: - # tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped) - - token_tags = { - 'x' : (1, 'scalar_number', 0, 0), - 'y' : (1, 'scalar_number', 0, 0), - 'h' : (1, 'scalar_number', 0, 0), - 'w' : (1, 'scalar_number', 0, 0), - 'firstWord' : (1, 'scalar_number', 0, 0), - 'lastWord' : (1, 'scalar_number', 0, 0), - 'rootID' : (1, 'scalar_number', 0, 0), - 'stemID' : (1, 'scalar_number', 0, 0), - 'type' : (1, 'scalar_text', 0, 0), - - 'info' : (0, 'number', 1, 0), - - 'info.word' : (0, 'number', 1, 1), - 'info.word.ocrText' : (1, 'text', 0, 0), - 'info.word.firstGlyph' : (1, 'raw', 0, 0), - 'info.word.lastGlyph' : (1, 'raw', 0, 0), - 'info.word.bl' : (1, 'raw', 0, 0), - 'info.word.link_id' : (1, 'number', 0, 0), - - 'glyph' : (0, 'number', 1, 1), - 'glyph.x' : (1, 'number', 0, 0), - 'glyph.y' : (1, 'number', 0, 0), - 'glyph.glyphID' : (1, 'number', 0, 0), - - 'dehyphen' : (0, 'number', 1, 1), - 'dehyphen.rootID' : (1, 'number', 0, 0), - 'dehyphen.stemID' : (1, 'number', 0, 0), - 'dehyphen.stemPage' : (1, 'number', 0, 0), - 'dehyphen.sh' : (1, 'number', 0, 0), - - 'links' : (0, 'number', 1, 1), - 'links.page' : (1, 'number', 0, 0), - 'links.rel' : (1, 'number', 0, 0), - 'links.row' : (1, 'number', 0, 0), - 'links.title' : (1, 'text', 0, 0), - 'links.href' : (1, 'text', 0, 0), - 'links.type' : (1, 'text', 0, 0), - 'links.id' : (1, 'number', 0, 0), - - 'paraCont' : (0, 'number', 1, 1), - 'paraCont.rootID' : (1, 'number', 0, 0), - 'paraCont.stemID' : (1, 'number', 0, 0), - 'paraCont.stemPage' : (1, 'number', 0, 0), - - 'paraStems' : (0, 'number', 1, 1), - 'paraStems.stemID' : (1, 'number', 0, 0), - - 'wordStems' : (0, 'number', 1, 1), - 'wordStems.stemID' : (1, 'number', 0, 0), - - 'empty' : (1, 'snippets', 1, 0), - - 'page' : (1, 'snippets', 1, 0), - 'page.pageid' : (1, 'scalar_text', 0, 0), - 'page.pagelabel' : (1, 'scalar_text', 0, 0), - 'page.type' : (1, 'scalar_text', 0, 0), - 'page.h' : (1, 'scalar_number', 0, 0), - 'page.w' : (1, 'scalar_number', 0, 0), - 'page.startID' : (1, 'scalar_number', 0, 0), - - 'group' : (1, 'snippets', 1, 0), - 'group.type' : (1, 'scalar_text', 0, 0), - 'group._tag' : (1, 'scalar_text', 0, 0), - 'group.orientation': (1, 'scalar_text', 0, 0), - - 'region' : (1, 'snippets', 1, 0), - 'region.type' : (1, 'scalar_text', 0, 0), - 'region.x' : (1, 'scalar_number', 0, 0), - 'region.y' : (1, 'scalar_number', 0, 0), - 'region.h' : (1, 'scalar_number', 0, 0), - 'region.w' : (1, 'scalar_number', 0, 0), - 'region.orientation' : (1, 'scalar_text', 0, 0), - - 'empty_text_region' : (1, 'snippets', 1, 0), - - 'img' : (1, 'snippets', 1, 0), - 'img.x' : (1, 'scalar_number', 0, 0), - 'img.y' : (1, 'scalar_number', 0, 0), - 'img.h' : (1, 'scalar_number', 0, 0), - 'img.w' : (1, 'scalar_number', 0, 0), - 'img.src' : (1, 'scalar_number', 0, 0), - 'img.color_src' : (1, 'scalar_number', 0, 0), - - 'paragraph' : (1, 'snippets', 1, 0), - 'paragraph.class' : (1, 'scalar_text', 0, 0), - 'paragraph.firstWord' : (1, 'scalar_number', 0, 0), - 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), - 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), - 'paragraph.gridSize' : (1, 'scalar_number', 0, 0), - 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), - - - 'word_semantic' : (1, 'snippets', 1, 1), - 'word_semantic.type' : (1, 'scalar_text', 0, 0), - 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0), - 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0), - - 'word' : (1, 'snippets', 1, 0), - 'word.type' : (1, 'scalar_text', 0, 0), - 'word.class' : (1, 'scalar_text', 0, 0), - 'word.firstGlyph' : (1, 'scalar_number', 0, 0), - 'word.lastGlyph' : (1, 'scalar_number', 0, 0), - - '_span' : (1, 'snippets', 1, 0), - '_span.firstWord' : (1, 'scalar_number', 0, 0), - '_span.lastWord' : (1, 'scalar_number', 0, 0), - '_span.gridSize' : (1, 'scalar_number', 0, 0), - '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0), - '_span.gridTopCenter' : (1, 'scalar_number', 0, 0), - '_span.gridBeginCenter' : (1, 'scalar_number', 0, 0), - '_span.gridEndCenter' : (1, 'scalar_number', 0, 0), - - 'span' : (1, 'snippets', 1, 0), - 'span.firstWord' : (1, 'scalar_number', 0, 0), - 'span.lastWord' : (1, 'scalar_number', 0, 0), - 'span.gridSize' : (1, 'scalar_number', 0, 0), - 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0), - 'span.gridTopCenter' : (1, 'scalar_number', 0, 0), - 'span.gridBeginCenter' : (1, 'scalar_number', 0, 0), - 'span.gridEndCenter' : (1, 'scalar_number', 0, 0), - - 'extratokens' : (1, 'snippets', 1, 0), - 'extratokens.type' : (1, 'scalar_text', 0, 0), - 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), - 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), - - 'glyph.h' : (1, 'number', 0, 0), - 'glyph.w' : (1, 'number', 0, 0), - 'glyph.use' : (1, 'number', 0, 0), - 'glyph.vtx' : (1, 'number', 0, 1), - 'glyph.len' : (1, 'number', 0, 1), - 'glyph.dpi' : (1, 'number', 0, 0), - 'vtx' : (0, 'number', 1, 1), - 'vtx.x' : (1, 'number', 0, 0), - 'vtx.y' : (1, 'number', 0, 0), - 'len' : (0, 'number', 1, 1), - 'len.n' : (1, 'number', 0, 0), - - 'book' : (1, 'snippets', 1, 0), - 'version' : (1, 'snippets', 1, 0), - 'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0), - 'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0), - 'version.Schema_id' : (1, 'scalar_text', 0, 0), - 'version.Schema_version' : (1, 'scalar_text', 0, 0), - 'version.Topaz_version' : (1, 'scalar_text', 0, 0), - 'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0), - 'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0), - 'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0), - 'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0), - 'version.chapterheaders' : (1, 'scalar_text', 0, 0), - 'version.creation_date' : (1, 'scalar_text', 0, 0), - 'version.header_footer' : (1, 'scalar_text', 0, 0), - 'version.init_from_ocr' : (1, 'scalar_text', 0, 0), - 'version.letter_insertion' : (1, 'scalar_text', 0, 0), - 'version.xmlinj_convert' : (1, 'scalar_text', 0, 0), - 'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0), - 'version.xmlinj_transform' : (1, 'scalar_text', 0, 0), - 'version.findlists' : (1, 'scalar_text', 0, 0), - 'version.page_num' : (1, 'scalar_text', 0, 0), - 'version.page_type' : (1, 'scalar_text', 0, 0), - 'version.bad_text' : (1, 'scalar_text', 0, 0), - 'version.glyph_mismatch' : (1, 'scalar_text', 0, 0), - 'version.margins' : (1, 'scalar_text', 0, 0), - 'version.staggered_lines' : (1, 'scalar_text', 0, 0), - 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0), - 'version.toc' : (1, 'scalar_text', 0, 0), - - 'stylesheet' : (1, 'snippets', 1, 0), - 'style' : (1, 'snippets', 1, 0), - 'style._tag' : (1, 'scalar_text', 0, 0), - 'style.type' : (1, 'scalar_text', 0, 0), - 'style._parent_type' : (1, 'scalar_text', 0, 0), - 'style.class' : (1, 'scalar_text', 0, 0), - 'style._after_class' : (1, 'scalar_text', 0, 0), - 'rule' : (1, 'snippets', 1, 0), - 'rule.attr' : (1, 'scalar_text', 0, 0), - 'rule.value' : (1, 'scalar_text', 0, 0), - - 'original' : (0, 'number', 1, 1), - 'original.pnum' : (1, 'number', 0, 0), - 'original.pid' : (1, 'text', 0, 0), - 'pages' : (0, 'number', 1, 1), - 'pages.ref' : (1, 'number', 0, 0), - 'pages.id' : (1, 'number', 0, 0), - 'startID' : (0, 'number', 1, 1), - 'startID.page' : (1, 'number', 0, 0), - 'startID.id' : (1, 'number', 0, 0), - - } - - - # full tag path record keeping routines - def tag_push(self, token): - self.tagpath.append(token) - def tag_pop(self): - if len(self.tagpath) > 0 : - self.tagpath.pop() - def tagpath_len(self): - return len(self.tagpath) - def get_tagpath(self, i): - cnt = len(self.tagpath) - if i < cnt : result = self.tagpath[i] - for j in xrange(i+1, cnt) : - result += '.' + self.tagpath[j] - return result - - - # list of absolute command byte values values that indicate - # various types of loop meachanisms typically used to generate vectors - - cmd_list = (0x76, 0x76) - - # peek at and return 1 byte that is ahead by i bytes - def peek(self, aheadi): - c = self.fo.read(aheadi) - if (len(c) == 0): - return None - self.fo.seek(-aheadi,1) - c = c[-1:] - return ord(c) - - - # get the next value from the file being processed - def getNext(self): - nbyte = self.peek(1); - if (nbyte == None): - return None - val = readEncodedNumber(self.fo) - return val - - - # format an arg by argtype - def formatArg(self, arg, argtype): - if (argtype == 'text') or (argtype == 'scalar_text') : - result = self.dict.lookup(arg) - elif (argtype == 'raw') or (argtype == 'number') or (argtype == 'scalar_number') : - result = arg - elif (argtype == 'snippets') : - result = arg - else : - print "Error Unknown argtype %s" % argtype - sys.exit(-2) - return result - - - # process the next tag token, recursively handling subtags, - # arguments, and commands - def procToken(self, token): - - known_token = False - self.tag_push(token) - - if self.debug : print 'Processing: ', self.get_tagpath(0) - cnt = self.tagpath_len() - for j in xrange(cnt): - tkn = self.get_tagpath(j) - if tkn in self.token_tags : - num_args = self.token_tags[tkn][0] - argtype = self.token_tags[tkn][1] - subtags = self.token_tags[tkn][2] - splcase = self.token_tags[tkn][3] - ntags = -1 - known_token = True - break - - if known_token : - - # handle subtags if present - subtagres = [] - if (splcase == 1): - # this type of tag uses of escape marker 0x74 indicate subtag count - if self.peek(1) == 0x74: - skip = readEncodedNumber(self.fo) - subtags = 1 - num_args = 0 - - if (subtags == 1): - ntags = readEncodedNumber(self.fo) - if self.debug : print 'subtags: ' + token + ' has ' + str(ntags) - for j in xrange(ntags): - val = readEncodedNumber(self.fo) - subtagres.append(self.procToken(self.dict.lookup(val))) - - # arguments can be scalars or vectors of text or numbers - argres = [] - if num_args > 0 : - firstarg = self.peek(1) - if (firstarg in self.cmd_list) and (argtype != 'scalar_number') and (argtype != 'scalar_text'): - # single argument is a variable length vector of data - arg = readEncodedNumber(self.fo) - argres = self.decodeCMD(arg,argtype) - else : - # num_arg scalar arguments - for i in xrange(num_args): - argres.append(self.formatArg(readEncodedNumber(self.fo), argtype)) - - # build the return tag - result = [] - tkn = self.get_tagpath(0) - result.append(tkn) - result.append(subtagres) - result.append(argtype) - result.append(argres) - self.tag_pop() - return result - - # all tokens that need to be processed should be in the hash - # table if it may indicate a problem, either new token - # or an out of sync condition - else: - result = [] - if (self.debug): - print 'Unknown Token:', token - self.tag_pop() - return result - - - # special loop used to process code snippets - # it is NEVER used to format arguments. - # builds the snippetList - def doLoop72(self, argtype): - cnt = readEncodedNumber(self.fo) - if self.debug : - result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n' - result += 'of the document is indicated by snippet number sets at the\n' - result += 'end of each snippet. \n' - print result - for i in xrange(cnt): - if self.debug: print 'Snippet:',str(i) - snippet = [] - snippet.append(i) - val = readEncodedNumber(self.fo) - snippet.append(self.procToken(self.dict.lookup(val))) - self.snippetList.append(snippet) - return - - - - # general loop code gracisouly submitted by "skindle" - thank you! - def doLoop76Mode(self, argtype, cnt, mode): - result = [] - adj = 0 - if mode & 1: - adj = readEncodedNumber(self.fo) - mode = mode >> 1 - x = [] - for i in xrange(cnt): - x.append(readEncodedNumber(self.fo) - adj) - for i in xrange(mode): - for j in xrange(1, cnt): - x[j] = x[j] + x[j - 1] - for i in xrange(cnt): - result.append(self.formatArg(x[i],argtype)) - return result - - - # dispatches loop commands bytes with various modes - # The 0x76 style loops are used to build vectors - - # This was all derived by trial and error and - # new loop types may exist that are not handled here - # since they did not appear in the test cases - - def decodeCMD(self, cmd, argtype): - if (cmd == 0x76): - - # loop with cnt, and mode to control loop styles - cnt = readEncodedNumber(self.fo) - mode = readEncodedNumber(self.fo) - - if self.debug : print 'Loop for', cnt, 'with mode', mode, ': ' - return self.doLoop76Mode(argtype, cnt, mode) - - if self.dbug: print "Unknown command", cmd - result = [] - return result - - - - # add full tag path to injected snippets - def updateName(self, tag, prefix): - name = tag[0] - subtagList = tag[1] - argtype = tag[2] - argList = tag[3] - nname = prefix + '.' + name - nsubtaglist = [] - for j in subtagList: - nsubtaglist.append(self.updateName(j,prefix)) - ntag = [] - ntag.append(nname) - ntag.append(nsubtaglist) - ntag.append(argtype) - ntag.append(argList) - return ntag - - - - # perform depth first injection of specified snippets into this one - def injectSnippets(self, snippet): - snipno, tag = snippet - name = tag[0] - subtagList = tag[1] - argtype = tag[2] - argList = tag[3] - nsubtagList = [] - if len(argList) > 0 : - for j in argList: - asnip = self.snippetList[j] - aso, atag = self.injectSnippets(asnip) - atag = self.updateName(atag, name) - nsubtagList.append(atag) - argtype='number' - argList=[] - if len(nsubtagList) > 0 : - subtagList.extend(nsubtagList) - tag = [] - tag.append(name) - tag.append(subtagList) - tag.append(argtype) - tag.append(argList) - snippet = [] - snippet.append(snipno) - snippet.append(tag) - return snippet - - - - # format the tag for output - def formatTag(self, node): - name = node[0] - subtagList = node[1] - argtype = node[2] - argList = node[3] - fullpathname = name.split('.') - nodename = fullpathname.pop() - ilvl = len(fullpathname) - indent = ' ' * (3 * ilvl) - rlst = [] - rlst.append(indent + '<' + nodename + '>') - if len(argList) > 0: - alst = [] - for j in argList: - if (argtype == 'text') or (argtype == 'scalar_text') : - alst.append(j + '|') - else : - alst.append(str(j) + ',') - argres = "".join(alst) - argres = argres[0:-1] - if argtype == 'snippets' : - rlst.append('snippets:' + argres) - else : - rlst.append(argres) - if len(subtagList) > 0 : - rlst.append('\n') - for j in subtagList: - if len(j) > 0 : - rlst.append(self.formatTag(j)) - rlst.append(indent + '\n') - else: - rlst.append('\n') - return "".join(rlst) - - - # flatten tag - def flattenTag(self, node): - name = node[0] - subtagList = node[1] - argtype = node[2] - argList = node[3] - rlst = [] - rlst.append(name) - if (len(argList) > 0): - alst = [] - for j in argList: - if (argtype == 'text') or (argtype == 'scalar_text') : - alst.append(j + '|') - else : - alst.append(str(j) + '|') - argres = "".join(alst) - argres = argres[0:-1] - if argtype == 'snippets' : - rlst.append('.snippets=' + argres) - else : - rlst.append('=' + argres) - rlst.append('\n') - for j in subtagList: - if len(j) > 0 : - rlst.append(self.flattenTag(j)) - return "".join(rlst) - - - # reduce create xml output - def formatDoc(self, flat_xml): - rlst = [] - for j in self.doc : - if len(j) > 0: - if flat_xml: - rlst.append(self.flattenTag(j)) - else: - rlst.append(self.formatTag(j)) - result = "".join(rlst) - if self.debug : print result - return result - - - - # main loop - parse the page.dat files - # to create structured document and snippets - - # FIXME: value at end of magic appears to be a subtags count - # but for what? For now, inject an 'info" tag as it is in - # every dictionary and seems close to what is meant - # The alternative is to special case the last _ "0x5f" to mean something - - def process(self): - - # peek at the first bytes to see what type of file it is - magic = self.fo.read(9) - if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'): - first_token = 'info' - elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'): - skip = self.fo.read(2) - first_token = 'info' - elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'): - first_token = 'info' - elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'): - skip = self.fo.read(3) - first_token = 'info' - else : - # other0.dat file - first_token = None - self.fo.seek(-9,1) - - - # main loop to read and build the document tree - while True: - - if first_token != None : - # use "inserted" first token 'info' for page and glyph files - tag = self.procToken(first_token) - if len(tag) > 0 : - self.doc.append(tag) - first_token = None - - v = self.getNext() - if (v == None): - break - - if (v == 0x72): - self.doLoop72('number') - elif (v > 0) and (v < self.dict.getSize()) : - tag = self.procToken(self.dict.lookup(v)) - if len(tag) > 0 : - self.doc.append(tag) - else: - if self.debug: - print "Main Loop: Unknown value: %x" % v - if (v == 0): - if (self.peek(1) == 0x5f): - skip = self.fo.read(1) - first_token = 'info' - - # now do snippet injection - if len(self.snippetList) > 0 : - if self.debug : print 'Injecting Snippets:' - snippet = self.injectSnippets(self.snippetList[0]) - snipno = snippet[0] - tag_add = snippet[1] - if self.debug : print self.formatTag(tag_add) - if len(tag_add) > 0: - self.doc.append(tag_add) - - # handle generation of xml output - xmlpage = self.formatDoc(self.flat_xml) - - return xmlpage - - -def fromData(dict, fname): - flat_xml = True - debug = False - pp = PageParser(fname, dict, debug, flat_xml) - xmlpage = pp.process() - return xmlpage - -def getXML(dict, fname): - flat_xml = False - debug = False - pp = PageParser(fname, dict, debug, flat_xml) - xmlpage = pp.process() - return xmlpage - -def usage(): - print 'Usage: ' - print ' convert2xml.py dict0000.dat infile.dat ' - print ' ' - print ' Options:' - print ' -h print this usage help message ' - print ' -d turn on debug output to check for potential errors ' - print ' --flat-xml output the flattened xml page description only ' - print ' ' - print ' This program will attempt to convert a page*.dat file or ' - print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. ' - print ' ' - print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump ' - print ' the *.dat files from a Topaz format e-book.' - -# -# Main -# - -def main(argv): - dictFile = "" - pageFile = "" - debug = False - flat_xml = False - printOutput = False - if len(argv) == 0: - printOutput = True - argv = sys.argv - - try: - opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"]) - - 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 =="-d": - debug=True - if o =="-h": - usage() - sys.exit(0) - if o =="--flat-xml": - flat_xml = True - - dictFile, pageFile = args[0], args[1] - - # read in the string table dictionary - dict = Dictionary(dictFile) - # dict.dumpDict() - - # create a page parser - pp = PageParser(pageFile, dict, debug, flat_xml) - - xmlpage = pp.process() - - if printOutput: - print xmlpage - return 0 - - return xmlpage - -if __name__ == '__main__': - sys.exit(main('')) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptepub.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptepub.py deleted file mode 100644 index e64c860..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptepub.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -sys.stdout=Unbuffered(sys.stdout) -import os - -import ineptepub -import ignobleepub -import zipfix -import re - -def main(argv=sys.argv): - args = argv[1:] - if len(args) != 3: - return -1 - infile = args[0] - outdir = args[1] - rscpath = args[2] - errlog = '' - - # first fix the epub to make sure we do not get errors - name, ext = os.path.splitext(os.path.basename(infile)) - bpath = os.path.dirname(infile) - zippath = os.path.join(bpath,name + '_temp.zip') - rv = zipfix.repairBook(infile, zippath) - if rv != 0: - print "Error while trying to fix epub" - return rv - - # determine a good name for the output file - outfile = os.path.join(outdir, name + '_nodrm.epub') - - rv = 1 - # first try with the Adobe adept epub - # try with any keyfiles (*.der) in the rscpath - files = os.listdir(rscpath) - filefilter = re.compile("\.der$", re.IGNORECASE) - files = filter(filefilter.search, files) - if files: - for filename in files: - keypath = os.path.join(rscpath, filename) - try: - rv = ineptepub.decryptBook(keypath, zippath, outfile) - if rv == 0: - break - except Exception, e: - errlog += str(e) - rv = 1 - pass - if rv == 0: - os.remove(zippath) - return 0 - - # still no luck - # now try with ignoble epub - # try with any keyfiles (*.b64) in the rscpath - files = os.listdir(rscpath) - filefilter = re.compile("\.b64$", re.IGNORECASE) - files = filter(filefilter.search, files) - if files: - for filename in files: - keypath = os.path.join(rscpath, filename) - try: - rv = ignobleepub.decryptBook(keypath, zippath, outfile) - if rv == 0: - break - except Exception, e: - errlog += str(e) - rv = 1 - pass - os.remove(zippath) - if rv != 0: - print errlog - return rv - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptpdb.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptpdb.py deleted file mode 100644 index 12b8c10..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptpdb.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -sys.stdout=Unbuffered(sys.stdout) -import os - -import erdr2pml - -def main(argv=sys.argv): - args = argv[1:] - if len(args) != 3: - return -1 - infile = args[0] - outdir = args[1] - rscpath = args[2] - rv = 1 - socialpath = os.path.join(rscpath,'sdrmlist.txt') - if os.path.exists(socialpath): - keydata = file(socialpath,'r').read() - keydata = keydata.rstrip(os.linesep) - ar = keydata.split(',') - for i in ar: - try: - name, cc8 = i.split(':') - except ValueError: - print ' Error parsing user supplied social drm data.' - return 1 - rv = erdr2pml.decryptBook(infile, outdir, name, cc8, True) - if rv == 0: - break - return rv - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptpdf.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptpdf.py deleted file mode 100644 index ddaeacd..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/decryptpdf.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -sys.stdout=Unbuffered(sys.stdout) -import os -import re -import ineptpdf - -def main(argv=sys.argv): - args = argv[1:] - if len(args) != 3: - return -1 - infile = args[0] - outdir = args[1] - rscpath = args[2] - errlog = '' - rv = 1 - - # determine a good name for the output file - name, ext = os.path.splitext(os.path.basename(infile)) - outfile = os.path.join(outdir, name + '_nodrm.pdf') - - # try with any keyfiles (*.der) in the rscpath - files = os.listdir(rscpath) - filefilter = re.compile("\.der$", re.IGNORECASE) - files = filter(filefilter.search, files) - if files: - for filename in files: - keypath = os.path.join(rscpath, filename) - try: - rv = ineptpdf.decryptBook(keypath, infile, outfile) - if rv == 0: - break - except Exception, e: - errlog += str(e) - rv = 1 - pass - if rv != 0: - print errlog - return rv - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/erdr2pml.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/erdr2pml.py deleted file mode 100644 index 8f958cd..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/erdr2pml.py +++ /dev/null @@ -1,526 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# -# erdr2pml.py -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# Changelog -# -# Based on ereader2html version 0.08 plus some later small fixes -# -# 0.01 - Initial version -# 0.02 - Support more eReader files. Support bold text and links. Fix PML decoder parsing bug. -# 0.03 - Fix incorrect variable usage at one place. -# 0.03b - enhancement by DeBockle (version 259 support) -# Custom version 0.03 - no change to eReader support, only usability changes -# - start of pep-8 indentation (spaces not tab), fix trailing blanks -# - version variable, only one place to change -# - added main routine, now callable as a library/module, -# means tools can add optional support for ereader2html -# - outdir is no longer a mandatory parameter (defaults based on input name if missing) -# - time taken output to stdout -# - Psyco support - reduces runtime by a factor of (over) 3! -# E.g. (~600Kb file) 90 secs down to 24 secs -# - newstyle classes -# - changed map call to list comprehension -# may not work with python 2.3 -# without Psyco this reduces runtime to 90% -# E.g. 90 secs down to 77 secs -# Psyco with map calls takes longer, do not run with map in Psyco JIT! -# - izip calls used instead of zip (if available), further reduction -# in run time (factor of 4.5). -# E.g. (~600Kb file) 90 secs down to 20 secs -# - Python 2.6+ support, avoid DeprecationWarning with sha/sha1 -# 0.04 - Footnote support, PML output, correct charset in html, support more PML tags -# - Feature change, dump out PML file -# - Added supprt for footnote tags. NOTE footnote ids appear to be bad (not usable) -# in some pdb files :-( due to the same id being used multiple times -# - Added correct charset encoding (pml is based on cp1252) -# - Added logging support. -# 0.05 - Improved type 272 support for sidebars, links, chapters, metainfo, etc -# 0.06 - Merge of 0.04 and 0.05. Improved HTML output -# Placed images in subfolder, so that it's possible to just -# drop the book.pml file onto DropBook to make an unencrypted -# copy of the eReader file. -# Using that with Calibre works a lot better than the HTML -# conversion in this code. -# 0.07 - Further Improved type 272 support for sidebars with all earlier fixes -# 0.08 - fixed typos, removed extraneous things -# 0.09 - fixed typos in first_pages to first_page to again support older formats -# 0.10 - minor cleanups -# 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook -# 0.12 - Fix added to prevent lowercasing of image names when the pml code itself uses a different case in the link name. -# 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 -# 0.17 - added support for pycrypto's DES as well -# 0.18 - on Windows try PyCrypto first and OpenSSL next -# 0.19 - Modify the interface to allow use of import -# 0.20 - modify to allow use inside new interface for calibre plugins -# 0.21 - Support eReader (drm) version 11. -# - Don't reject dictionary format. -# - Ignore sidebars for dictionaries (different format?) - -__version__='0.21' - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -Des = None -if sys.platform.startswith('win'): - # first try with pycrypto - if inCalibre: - from calibre_plugins.erdrpdb2pml import pycrypto_des - else: - import pycrypto_des - Des = pycrypto_des.load_pycrypto() - if Des == None: - # they try with openssl - if inCalibre: - from calibre_plugins.erdrpdb2pml import openssl_des - else: - import openssl_des - Des = openssl_des.load_libcrypto() -else: - # first try with openssl - if inCalibre: - from calibre_plugins.erdrpdb2pml import openssl_des - else: - import openssl_des - Des = openssl_des.load_libcrypto() - if Des == None: - # then try with pycrypto - if inCalibre: - from calibre_plugins.erdrpdb2pml import pycrypto_des - else: - import pycrypto_des - Des = pycrypto_des.load_pycrypto() - -# if that did not work then use pure python implementation -# of DES and try to speed it up with Psycho -if Des == None: - if inCalibre: - from calibre_plugins.erdrpdb2pml import python_des - else: - import python_des - Des = python_des.Des - # Import Psyco if available - try: - # http://psyco.sourceforge.net - import psyco - psyco.full() - except ImportError: - pass - -try: - from hashlib import sha1 -except ImportError: - # older Python release - import sha - sha1 = lambda s: sha.new(s) - -import cgi -import logging - -logging.basicConfig() -#logging.basicConfig(level=logging.DEBUG) - - -class Sectionizer(object): - bkType = "Book" - - def __init__(self, filename, ident): - self.contents = file(filename, 'rb').read() - self.header = self.contents[0:72] - self.num_sections, = struct.unpack('>H', self.contents[76:78]) - # Dictionary or normal content (TODO: Not hard-coded) - if self.header[0x3C:0x3C+8] != ident: - if self.header[0x3C:0x3C+8] == "PDctPPrs": - self.bkType = "Dict" - else: - raise ValueError('Invalid file format') - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - def loadSection(self, section): - if section + 1 == self.num_sections: - end_off = len(self.contents) - else: - end_off = self.sections[section + 1][0] - off = self.sections[section][0] - return self.contents[off:end_off] - -def sanitizeFileName(s): - r = '' - for c in s: - if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-": - r += c - return r - -def fixKey(key): - def fixByte(b): - return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) - return "".join([chr(fixByte(ord(a))) for a in key]) - -def deXOR(text, sp, table): - r='' - j = sp - for i in xrange(len(text)): - r += chr(ord(table[j]) ^ ord(text[i])) - j = j + 1 - if j == len(table): - j = 0 - return r - -class EreaderProcessor(object): - def __init__(self, sect, username, creditcard): - self.section_reader = sect.loadSection - data = self.section_reader(0) - version, = struct.unpack('>H', data[0:2]) - self.version = version - logging.info('eReader file format version %s', version) - if version != 272 and version != 260 and version != 259: - raise ValueError('incorrect eReader version %d (error 1)' % version) - data = self.section_reader(1) - self.data = data - des = Des(fixKey(data[0:8])) - cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:])) - if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200: - raise ValueError('incorrect eReader version (error 2)') - input = des.decrypt(data[-cookie_size:]) - def unshuff(data, shuf): - r = [''] * len(data) - j = 0 - for i in xrange(len(data)): - j = (j + shuf) % len(data) - r[j] = data[i] - assert len("".join(r)) == len(data) - return "".join(r) - r = unshuff(input[0:-8], cookie_shuf) - - def fixUsername(s): - r = '' - for c in s.lower(): - if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'): - r += c - return r - - user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff) - drm_sub_version = struct.unpack('>H', r[0:2])[0] - self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1 - self.num_image_pages = struct.unpack('>H', r[26:26+2])[0] - self.first_image_page = struct.unpack('>H', r[24:24+2])[0] - # Default values - self.num_footnote_pages = 0 - self.num_sidebar_pages = 0 - self.first_footnote_page = -1 - self.first_sidebar_page = -1 - if self.version == 272: - self.num_footnote_pages = struct.unpack('>H', r[46:46+2])[0] - self.first_footnote_page = struct.unpack('>H', r[44:44+2])[0] - if (sect.bkType == "Book"): - self.num_sidebar_pages = struct.unpack('>H', r[38:38+2])[0] - self.first_sidebar_page = struct.unpack('>H', r[36:36+2])[0] - # self.num_bookinfo_pages = struct.unpack('>H', r[34:34+2])[0] - # self.first_bookinfo_page = struct.unpack('>H', r[32:32+2])[0] - # self.num_chapter_pages = struct.unpack('>H', r[22:22+2])[0] - # self.first_chapter_page = struct.unpack('>H', r[20:20+2])[0] - # self.num_link_pages = struct.unpack('>H', r[30:30+2])[0] - # self.first_link_page = struct.unpack('>H', r[28:28+2])[0] - # self.num_xtextsize_pages = struct.unpack('>H', r[54:54+2])[0] - # self.first_xtextsize_page = struct.unpack('>H', r[52:52+2])[0] - - # **before** data record 1 was decrypted and unshuffled, it contained data - # to create an XOR table and which is used to fix footnote record 0, link records, chapter records, etc - self.xortable_offset = struct.unpack('>H', r[40:40+2])[0] - self.xortable_size = struct.unpack('>H', r[42:42+2])[0] - self.xortable = self.data[self.xortable_offset:self.xortable_offset + self.xortable_size] - else: - # Nothing needs to be done - pass - # self.num_bookinfo_pages = 0 - # self.num_chapter_pages = 0 - # self.num_link_pages = 0 - # self.num_xtextsize_pages = 0 - # self.first_bookinfo_page = -1 - # self.first_chapter_page = -1 - # self.first_link_page = -1 - # self.first_xtextsize_page = -1 - - logging.debug('self.num_text_pages %d', self.num_text_pages) - logging.debug('self.num_footnote_pages %d, self.first_footnote_page %d', self.num_footnote_pages , self.first_footnote_page) - logging.debug('self.num_sidebar_pages %d, self.first_sidebar_page %d', self.num_sidebar_pages , self.first_sidebar_page) - self.flags = struct.unpack('>L', r[4:8])[0] - reqd_flags = (1<<9) | (1<<7) | (1<<10) - if (self.flags & reqd_flags) != reqd_flags: - print "Flags: 0x%X" % self.flags - raise ValueError('incompatible eReader file') - des = Des(fixKey(user_key)) - if version == 259: - if drm_sub_version != 7: - raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version) - encrypted_key_sha = r[44:44+20] - encrypted_key = r[64:64+8] - elif version == 260: - if drm_sub_version != 13 and drm_sub_version != 11: - raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version) - if drm_sub_version == 13: - encrypted_key = r[44:44+8] - encrypted_key_sha = r[52:52+20] - else: - encrypted_key = r[64:64+8] - encrypted_key_sha = r[44:44+20] - elif version == 272: - encrypted_key = r[172:172+8] - encrypted_key_sha = r[56:56+20] - self.content_key = des.decrypt(encrypted_key) - if sha1(self.content_key).digest() != encrypted_key_sha: - raise ValueError('Incorrect Name and/or Credit Card') - - def getNumImages(self): - return self.num_image_pages - - def getImage(self, i): - sect = self.section_reader(self.first_image_page + i) - name = sect[4:4+32].strip('\0') - data = sect[62:] - return sanitizeFileName(name), data - - - # def getChapterNamePMLOffsetData(self): - # cv = '' - # if self.num_chapter_pages > 0: - # for i in xrange(self.num_chapter_pages): - # chaps = self.section_reader(self.first_chapter_page + i) - # j = i % self.xortable_size - # offname = deXOR(chaps, j, self.xortable) - # offset = struct.unpack('>L', offname[0:4])[0] - # name = offname[4:].strip('\0') - # cv += '%d|%s\n' % (offset, name) - # return cv - - # def getLinkNamePMLOffsetData(self): - # lv = '' - # if self.num_link_pages > 0: - # for i in xrange(self.num_link_pages): - # links = self.section_reader(self.first_link_page + i) - # j = i % self.xortable_size - # offname = deXOR(links, j, self.xortable) - # offset = struct.unpack('>L', offname[0:4])[0] - # name = offname[4:].strip('\0') - # lv += '%d|%s\n' % (offset, name) - # return lv - - # def getExpandedTextSizesData(self): - # ts = '' - # if self.num_xtextsize_pages > 0: - # tsize = deXOR(self.section_reader(self.first_xtextsize_page), 0, self.xortable) - # for i in xrange(self.num_text_pages): - # xsize = struct.unpack('>H', tsize[0:2])[0] - # ts += "%d\n" % xsize - # tsize = tsize[2:] - # return ts - - # def getBookInfo(self): - # bkinfo = '' - # if self.num_bookinfo_pages > 0: - # info = self.section_reader(self.first_bookinfo_page) - # bkinfo = deXOR(info, 0, self.xortable) - # bkinfo = bkinfo.replace('\0','|') - # bkinfo += '\n' - # return bkinfo - - def getText(self): - des = Des(fixKey(self.content_key)) - r = '' - for i in xrange(self.num_text_pages): - logging.debug('get page %d', i) - r += zlib.decompress(des.decrypt(self.section_reader(1 + i))) - - # now handle footnotes pages - if self.num_footnote_pages > 0: - r += '\n' - # the record 0 of the footnote section must pass through the Xor Table to make it useful - sect = self.section_reader(self.first_footnote_page) - fnote_ids = deXOR(sect, 0, self.xortable) - # the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated - des = Des(fixKey(self.content_key)) - for i in xrange(1,self.num_footnote_pages): - logging.debug('get footnotepage %d', i) - id_len = ord(fnote_ids[2]) - id = fnote_ids[3:3+id_len] - fmarker = '\n' % id - fmarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i))) - fmarker += '\n\n' - r += fmarker - fnote_ids = fnote_ids[id_len+4:] - - # TODO: Handle dictionary index (?) pages - which are also marked as - # sidebar_pages (?). For now dictionary sidebars are ignored - # For dictionaries - record 0 is null terminated strings, followed by - # blocks of around 62000 bytes and a final block. Not sure of the - # encoding - - # now handle sidebar pages - if self.num_sidebar_pages > 0: - r += '\n' - # the record 0 of the sidebar section must pass through the Xor Table to make it useful - sect = self.section_reader(self.first_sidebar_page) - sbar_ids = deXOR(sect, 0, self.xortable) - # the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated - des = Des(fixKey(self.content_key)) - for i in xrange(1,self.num_sidebar_pages): - id_len = ord(sbar_ids[2]) - id = sbar_ids[3:3+id_len] - smarker = '\n' % id - smarker += zlib.decompress(des.decrypt(self.section_reader(self.first_sidebar_page + i))) - smarker += '\n\n' - r += smarker - sbar_ids = sbar_ids[id_len+4:] - - return r - -def cleanPML(pml): - # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) - pml2 = pml - for k in xrange(128,256): - badChar = chr(k) - pml2 = pml2.replace(badChar, '\\a%03d' % k) - return pml2 - -def convertEreaderToPml(infile, name, cc, outdir): - if not os.path.exists(outdir): - os.makedirs(outdir) - bookname = os.path.splitext(os.path.basename(infile))[0] - print " Decoding File" - sect = Sectionizer(infile, 'PNRdPPrs') - er = EreaderProcessor(sect, name, cc) - - if er.getNumImages() > 0: - print " Extracting images" - imagedir = bookname + '_img/' - 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" - file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) - - # bkinfo = er.getBookInfo() - # if bkinfo != '': - # print " Extracting book meta information" - # file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo) - - - -def decryptBook(infile, outdir, name, cc, make_pmlz): - if make_pmlz : - # ignore specified outdir, use tempdir instead - outdir = tempfile.mkdtemp() - try: - print "Processing..." - convertEreaderToPml(infile, name, cc, outdir) - if make_pmlz : - import zipfile - import shutil - print " Creating PMLZ file" - zipname = infile[:-4] + '.pmlz' - myZipFile = zipfile.ZipFile(zipname,'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() - # remove temporary directory - shutil.rmtree(outdir, True) - print 'output is %s' % zipname - else : - print 'output in %s' % outdir - print "done" - except ValueError, e: - print "Error: %s" % e - return 1 - return 0 - - -def usage(): - print "Converts DRMed eReader books to PML Source" - print "Usage:" - print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number " - print " " - print "Options: " - print " -h prints this message" - print " --make-pmlz create PMLZ instead of using output directory" - print " " - print "Note:" - print " if ommitted, outdir defaults based on 'infile.pdb'" - print " It's enough to enter the last 8 digits of the credit card number" - return - - -def main(argv=None): - try: - opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"]) - except getopt.GetoptError, err: - print str(err) - usage() - return 1 - make_pmlz = False - for o, a in opts: - if o == "-h": - usage() - return 0 - elif o == "--make-pmlz": - make_pmlz = True - - print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__ - - if len(args)!=3 and len(args)!=4: - usage() - return 1 - - if len(args)==3: - infile, name, cc = args[0], args[1], args[2] - outdir = infile[:-4] + '_Source' - elif len(args)==4: - infile, outdir, name, cc = args[0], args[1], args[2], args[3] - - return decryptBook(infile, outdir, name, cc, make_pmlz) - - -if __name__ == "__main__": - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/getk4pcpids.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/getk4pcpids.py deleted file mode 100644 index cc8bcd4..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/getk4pcpids.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 1.00 - Initial version -# 1.01 - getPidList interface change - -__version__ = '1.01' - -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii -import kgenpids -import topazextract -import mobidedrm -from alfcrypto import Pukall_Cipher - -class DrmException(Exception): - pass - -def getK4PCpids(path_to_ebook): - # Return Kindle4PC PIDs. Assumes that the caller checked that we are not on Linux, which will raise an exception - - mobi = True - magic3 = file(path_to_ebook,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - if mobi: - mb = mobidedrm.MobiBook(path_to_ebook,False) - else: - mb = topazextract.TopazBook(path_to_ebook) - - md1, md2 = mb.getPIDMetaInfo() - - return kgenpids.getPidList(md1, md2) - - -def main(argv=sys.argv): - print ('getk4pcpids.py v%(__version__)s. ' - 'Copyright 2012 Apprentice Alf' % globals()) - - if len(argv)<2 or len(argv)>3: - print "Gets the possible book-specific PIDs from K4PC for a particular book" - print "Usage:" - print " %s []" % sys.argv[0] - return 1 - else: - infile = argv[1] - try: - pidlist = getK4PCpids(infile) - except DrmException, e: - print "Error: %s" % e - return 1 - pidstring = ','.join(pidlist) - print "Possible PIDs are: ", pidstring - if len(argv) is 3: - outfile = argv[2] - file(outfile, 'w').write(pidstring) - - return 0 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ignobleepub.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ignobleepub.py deleted file mode 100644 index 03aa91f..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ignobleepub.py +++ /dev/null @@ -1,337 +0,0 @@ -#! /usr/bin/python - -from __future__ import with_statement - -# ignobleepub.pyw, version 3.5 - -# To run this program install Python 2.6 from -# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignobleepub.pyw and double-click on it to run it. - -# Revision history: -# 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 -# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml -# 3.3 - On Windows try PyCrypto first and OpenSSL next -# 3.4 - Modify interace to allow use with import -# 3.5 - Fix for potential problem with PyCrypto - - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class IGNOBLEError(Exception): - pass - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - 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) - - 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]) - 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 IGNOBLEError('AES improper key used') - return - key = self._key = AES_KEY() - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) - if rv < 0: - raise IGNOBLEError('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 IGNOBLEError('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, '\x00'*16) - - def decrypt(self, data): - return self._aes.decrypt(data) - - return AES - -def _load_crypto(): - AES = None - cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) - if sys.platform.startswith('win'): - cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) - for loader in cryptolist: - try: - AES = loader() - break - except (ImportError, IGNOBLEError): - pass - return AES - -AES = _load_crypto() - - - -""" -Decrypt Barnes & Noble ADEPT encrypted EPUB books. -""" - - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - # self._aes = AES.new(bookkey, AES.MODE_CBC, '\x00'*16) - self._aes = AES(bookkey) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - path = path.encode('utf-8') - if path is not None: - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('bnepubkey.b64'): - self.keypath.insert(0, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N EPUB key file', - defaultextension='.b64', - filetypes=[('base64-encoded files', '.b64'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - - -def decryptBook(keypath, inpath, outpath): - with open(keypath, 'rb') as f: - keyb64 = f.read() - key = keyb64.decode('base64')[:16] - # aes = AES.new(key, AES.MODE_CBC, '\x00'*16) - aes = AES(key) - - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = aes.decrypt(bookkey.decode('base64')) - bookkey = bookkey[:-ord(bookkey[-1])] - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) - - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "Ignoble EPUB Decrypter", - "This script requires OpenSSL or PyCrypto, which must be installed " - "separately. Read the top-of-script comment for details.") - return 1 - root.title('Ignoble EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptepub.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptepub.py deleted file mode 100644 index 2bb32b1..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptepub.py +++ /dev/null @@ -1,478 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import with_statement - -# ineptepub.pyw, version 5.6 -# Copyright © 2009-2010 i♥cabbages - -# Released under the terms of the GNU General Public Licence, version 3 or -# later. - -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make sure to -# install the version for Python 2.6). Save this script file as -# ineptepub.pyw and double-click on it to run it. -# -# Mac OS X users: Save this script file as ineptepub.pyw. You can run this -# program from the command line (pythonw ineptepub.pyw) or by double-clicking -# it when it has been associated with PythonLauncher. - -# Revision history: -# 1 - Initial release -# 2 - Rename to INEPT, fix exit code -# 5 - Version bump to avoid (?) confusion; -# 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 -# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml -# 5.5 - On Windows try PyCrypto first, OpenSSL next -# 5.6 - Modify interface to allow use with import -# 5.7 - Fix for potential problem with PyCrypto - -""" -Decrypt Adobe ADEPT-encrypted EPUB books. -""" - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class ADEPTError(Exception): - pass - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - 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) - - RSA_NO_PADDING = 3 - AES_MAXNR = 14 - - c_char_pp = POINTER(c_char_p) - c_int_p = POINTER(c_int) - - class RSA(Structure): - pass - RSA_p = POINTER(RSA) - - 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 - - d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', - [RSA_p, c_char_pp, c_long]) - RSA_size = F(c_int, 'RSA_size', [RSA_p]) - RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', - [c_int, c_char_p, c_char_p, RSA_p, c_int]) - RSA_free = F(None, 'RSA_free', [RSA_p]) - 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 RSA(object): - def __init__(self, der): - buf = create_string_buffer(der) - pp = c_char_pp(cast(buf, c_char_p)) - rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) - if rsa is None: - raise ADEPTError('Error parsing ADEPT user key DER') - - def decrypt(self, from_): - rsa = self._rsa - to = create_string_buffer(RSA_size(rsa)) - dlen = RSA_private_decrypt(len(from_), from_, to, rsa, - RSA_NO_PADDING) - if dlen < 0: - raise ADEPTError('RSA decryption failed') - return to[:dlen] - - def __del__(self): - if self._rsa is not None: - RSA_free(self._rsa) - self._rsa = None - - 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: - 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, RSA) - -def _load_crypto_pycrypto(): - from Crypto.Cipher import AES as _AES - from Crypto.PublicKey import RSA as _RSA - - # ASN.1 parsing code from tlslite - class ASN1Error(Exception): - pass - - class ASN1Parser(object): - class Parser(object): - def __init__(self, bytes): - self.bytes = bytes - self.index = 0 - - def get(self, length): - if self.index + length > len(self.bytes): - raise ASN1Error("Error decoding ASN.1") - x = 0 - for count in range(length): - x <<= 8 - x |= self.bytes[self.index] - self.index += 1 - return x - - def getFixBytes(self, lengthBytes): - bytes = self.bytes[self.index : self.index+lengthBytes] - self.index += lengthBytes - return bytes - - def getVarBytes(self, lengthLength): - lengthBytes = self.get(lengthLength) - return self.getFixBytes(lengthBytes) - - def getFixList(self, length, lengthList): - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def getVarList(self, length, lengthLength): - lengthList = self.get(lengthLength) - if lengthList % length != 0: - raise ASN1Error("Error decoding ASN.1") - lengthList = int(lengthList/length) - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def startLengthCheck(self, lengthLength): - self.lengthCheck = self.get(lengthLength) - self.indexCheck = self.index - - def setLengthCheck(self, length): - self.lengthCheck = length - self.indexCheck = self.index - - def stopLengthCheck(self): - if (self.index - self.indexCheck) != self.lengthCheck: - raise ASN1Error("Error decoding ASN.1") - - def atLengthCheck(self): - if (self.index - self.indexCheck) < self.lengthCheck: - return False - elif (self.index - self.indexCheck) == self.lengthCheck: - return True - else: - raise ASN1Error("Error decoding ASN.1") - - def __init__(self, bytes): - p = self.Parser(bytes) - p.get(1) - self.length = self._getASN1Length(p) - self.value = p.getFixBytes(self.length) - - def getChild(self, which): - p = self.Parser(self.value) - for x in range(which+1): - markIndex = p.index - p.get(1) - length = self._getASN1Length(p) - p.getFixBytes(length) - return ASN1Parser(p.bytes[markIndex:p.index]) - - def _getASN1Length(self, p): - firstLength = p.get(1) - if firstLength<=127: - return firstLength - else: - lengthLength = firstLength & 0x7F - return p.get(lengthLength) - - class AES(object): - def __init__(self, key): - self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16) - - def decrypt(self, data): - return self._aes.decrypt(data) - - class RSA(object): - def __init__(self, der): - key = ASN1Parser([ord(x) for x in der]) - key = [key.getChild(x).value for x in xrange(1, 4)] - key = [self.bytesToNumber(v) for v in key] - self._rsa = _RSA.construct(key) - - def bytesToNumber(self, bytes): - total = 0L - for byte in bytes: - total = (total << 8) + byte - return total - - def decrypt(self, data): - return self._rsa.decrypt(data) - - return (AES, RSA) - -def _load_crypto(): - AES = RSA = None - cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) - if sys.platform.startswith('win'): - cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) - for loader in cryptolist: - try: - AES, RSA = loader() - break - except (ImportError, ADEPTError): - pass - return (AES, RSA) -AES, RSA = _load_crypto() - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - self._aes = AES(bookkey) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - if path is not None: - path = path.encode('utf-8') - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('adeptkey.der'): - self.keypath.insert(0, 'adeptkey.der') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT key file', - defaultextension='.der', filetypes=[('DER-encoded files', '.der'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - - -def decryptBook(keypath, inpath, outpath): - with open(keypath, 'rb') as f: - keyder = f.read() - rsa = RSA(keyder) - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = rsa.decrypt(bookkey.decode('base64')) - # Padded as per RSAES-PKCS1-v1_5 - if bookkey[-17] != '\x00': - raise ADEPTError('problem decrypting session key') - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be" \ - " installed separately. Read the top-of-script comment for" \ - " details." % (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) - - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "INEPT EPUB Decrypter", - "This script requires OpenSSL or PyCrypto, which must be" - " installed separately. Read the top-of-script comment for" - " details.") - return 1 - root.title('INEPT EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4mobidedrm.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4mobidedrm.py deleted file mode 100755 index 717b0d0..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4mobidedrm.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python - -from __future__ import with_statement - -# engine to remove drm from Kindle for Mac and Kindle for PC books -# for personal use for archiving and converting your ebooks - -# PLEASE DO NOT PIRATE EBOOKS! - -# We want all authors and publishers, and eBook stores to live -# long and prosperous lives but at the same time we just want to -# be able to read OUR books on whatever device we want and to keep -# readable for a long, long time - -# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, -# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates -# and many many others - - -__version__ = '4.4' - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -import os, csv, getopt -import string -import re -import traceback -import time - -buildXML = False - -class DrmException(Exception): - pass - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -if inCalibre: - from calibre_plugins.k4mobidedrm import mobidedrm - from calibre_plugins.k4mobidedrm import topazextract - from calibre_plugins.k4mobidedrm import kgenpids -else: - import mobidedrm - import topazextract - import kgenpids - - -# cleanup bytestring filenames -# borrowed from calibre from calibre/src/calibre/__init__.py -# added in removal of non-printing chars -# and removal of . at start -# convert underscores to spaces (we're OK with spaces in file names) -def cleanup_name(name): - _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') - substitute='_' - one = ''.join(char for char in name if char in string.printable) - one = _filename_sanitize.sub(substitute, one) - one = re.sub(r'\s', ' ', one).strip() - one = re.sub(r'^\.+$', '_', one) - one = one.replace('..', substitute) - # Windows doesn't like path components that end with a period - if one.endswith('.'): - one = one[:-1]+substitute - # Mac and Unix don't like file names that begin with a full stop - if len(one) > 0 and one[0] == '.': - one = substitute+one[1:] - one = one.replace('_',' ') - return one - -def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): - global buildXML - - - # handle the obvious cases at the beginning - if not os.path.isfile(infile): - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist" - return 1 - - starttime = time.time() - print "Starting decryptBook routine." - - - mobi = True - magic3 = file(infile,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(infile))[0] - - if mobi: - mb = mobidedrm.MobiBook(infile) - else: - mb = topazextract.TopazBook(infile) - - title = mb.getBookTitle() - print "Processing Book: ", title - filenametitle = cleanup_name(title) - outfilename = cleanup_name(bookname) - - # generate 'sensible' filename, that will sort with the original name, - # but is close to the name from the file. - outlength = len(outfilename) - comparelength = min(8,min(outlength,len(filenametitle))) - copylength = min(max(outfilename.find(' '),8),len(outfilename)) - if outlength==0: - outfilename = filenametitle - elif comparelength > 0: - if outfilename[:comparelength] == filenametitle[:comparelength]: - outfilename = filenametitle - else: - outfilename = outfilename[:copylength] + " " + filenametitle - - # avoid excessively long file names - if len(outfilename)>150: - outfilename = outfilename[:150] - - # build pid list - md1, md2 = mb.getPIDMetaInfo() - pids.extend(kgenpids.getPidList(md1, md2, k4, serials, kInfoFiles)) - - print "Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids)) - - - try: - mb.processBook(pids) - - except mobidedrm.DrmException, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - except topazextract.TpzDRMError, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - except Exception, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - - print "Successfully decrypted book after {0:.1f} seconds".format(time.time()-starttime) - - if mobi: - if mb.getPrintReplica(): - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4') - elif mb.getMobiVersion() >= 8: - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3') - else: - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') - mb.getMobiFile(outfile) - print "Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename + '_nodrm') - return 0 - - # topaz: - print " Creating NoDRM HTMLZ Archive" - zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz') - mb.getHTMLZip(zipname) - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') - mb.getSVGZip(zipname) - - if buildXML: - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') - mb.getXMLZip(zipname) - - # remove internal temporary directory of Topaz pieces - mb.cleanup() - print "Saved decrypted Topaz book parts after {0:.1f} seconds".format(time.time()-starttime) - return 0 - - -def usage(progname): - print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - -# -# Main -# -def main(argv=sys.argv): - progname = os.path.basename(argv[0]) - - k4 = False - kInfoFiles = [] - serials = [] - pids = [] - - print ('K4MobiDeDrm v%(__version__)s ' - 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) - - try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") - except getopt.GetoptError, err: - print str(err) - usage(progname) - sys.exit(2) - if len(args)<2: - usage(progname) - sys.exit(2) - - for o, a in opts: - if o == "-k": - if a == None : - raise DrmException("Invalid parameter for -k") - kInfoFiles.append(a) - if o == "-p": - if a == None : - raise DrmException("Invalid parameter for -p") - pids = a.split(',') - if o == "-s": - if a == None : - raise DrmException("Invalid parameter for -s") - serials = a.split(',') - - # try with built in Kindle Info files - k4 = True - if sys.platform.startswith('linux'): - k4 = False - kInfoFiles = None - infile = args[0] - outdir = args[1] - return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4mutils.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4mutils.py deleted file mode 100644 index 1fc08cb..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4mutils.py +++ /dev/null @@ -1,730 +0,0 @@ -# standlone set of Mac OSX specific routines needed for KindleBooks - -from __future__ import with_statement - -import sys -import os -import os.path -import re -import copy -import subprocess -from struct import pack, unpack, unpack_from - -class DrmException(Exception): - pass - - -# interface to needed routines in openssl's libcrypto -def _load_crypto_libcrypto(): - from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, addressof, string_at, cast - from ctypes.util import find_library - - libcrypto = find_library('crypto') - if libcrypto is None: - raise DrmException('libcrypto not found') - libcrypto = CDLL(libcrypto) - - # From OpenSSL's crypto aes header - # - # AES_ENCRYPT 1 - # AES_DECRYPT 0 - # AES_MAXNR 14 (in bytes) - # AES_BLOCK_SIZE 16 (in bytes) - # - # struct aes_key_st { - # unsigned long rd_key[4 *(AES_MAXNR + 1)]; - # int rounds; - # }; - # typedef struct aes_key_st AES_KEY; - # - # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); - # - # note: the ivec string, and output buffer are both mutable - # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, - # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc); - - 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]) - - # From OpenSSL's Crypto evp/p5_crpt2.c - # - # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, - # const unsigned char *salt, int saltlen, int iter, - # int keylen, unsigned char *out); - - PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', - [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) - - class LibCrypto(object): - def __init__(self): - self._blocksize = 0 - self._keyctx = None - self._iv = 0 - - def set_decrypt_key(self, userkey, iv): - self._blocksize = len(userkey) - if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : - raise DrmException('AES improper key used') - return - keyctx = self._keyctx = AES_KEY() - self._iv = iv - self._userkey = userkey - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) - if rv < 0: - raise DrmException('Failed to initialize AES key') - - def decrypt(self, data): - out = create_string_buffer(len(data)) - mutable_iv = create_string_buffer(self._iv, len(self._iv)) - keyctx = self._keyctx - rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) - if rv == 0: - raise DrmException('AES decryption failed') - return out.raw - - def keyivgen(self, passwd, salt, iter, keylen): - saltlen = len(salt) - passlen = len(passwd) - out = create_string_buffer(keylen) - rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) - return out.raw - return LibCrypto - -def _load_crypto(): - LibCrypto = None - try: - LibCrypto = _load_crypto_libcrypto() - except (ImportError, DrmException): - pass - return LibCrypto - -LibCrypto = _load_crypto() - -# -# Utility Routines -# - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" - -# For kinf approach of K4Mac 1.6.X or later -# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" -# For Mac they seem to re-use charMap2 here -charMap5 = charMap2 - -# new in K4M 1.9.X -testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD" - - -def encode(data, map): - result = "" - for char in data: - value = ord(char) - Q = (value ^ 0x80) // len(map) - R = value % len(map) - result += map[Q] - result += map[R] - return result - -# Hash the bytes in data and then encode the digest with the characters in map -def encodeHash(data,map): - return encode(MD5(data),map) - -# Decode the string in data with the characters in map. Returns the decoded bytes -def decode(data,map): - result = "" - for i in range (0,len(data)-1,2): - high = map.find(data[i]) - low = map.find(data[i+1]) - if (high == -1) or (low == -1) : - break - value = (((high * len(map)) ^ 0x80) & 0xFF) + low - result += pack("B",value) - return result - -# For K4M 1.6.X and later -# generate table of prime number less than or equal to int n -def primes(n): - if n==2: return [2] - elif n<2: return [] - s=range(3,n+1,2) - mroot = n ** 0.5 - half=(n+1)/2-1 - i=0 - m=3 - while m <= mroot: - if s[i]: - j=(m*m-3)/2 - s[j]=0 - while j 7: - print('Using Munged MAC Address for ID: '+mungedmac) - return mungedmac - sernum = GetVolumeSerialNumber() - if len(sernum) > 7: - print('Using Volume Serial Number for ID: '+sernum) - return sernum - diskpart = GetUserHomeAppSupKindleDirParitionName() - uuidnum = GetDiskPartitionUUID(diskpart) - if len(uuidnum) > 7: - print('Using Disk Partition UUID for ID: '+uuidnum) - return uuidnum - mungedmac = GetMACAddressMunged() - if len(mungedmac) > 7: - print('Using Munged MAC Address for ID: '+mungedmac) - return mungedmac - print('Using Fixed constant 9999999999 for ID.') - return '9999999999' - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used by Kindle for Mac versions < 1.6.0 -class CryptUnprotectData(object): - def __init__(self): - sernum = GetVolumeSerialNumber() - if sernum == '': - sernum = '9999999999' - sp = sernum + '!@#' + GetUserName() - passwdData = encode(SHA256(sp),charMap1) - salt = '16743' - self.crp = LibCrypto() - iter = 0x3e8 - keylen = 0x80 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext,charMap1) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.6.0 -class CryptUnprotectDataV2(object): - def __init__(self): - sp = GetUserName() + ':&%:' + GetIDString() - passwdData = encode(SHA256(sp),charMap5) - # salt generation as per the code - salt = 0x0512981d * 2 * 1 * 1 - salt = str(salt) + GetUserName() - salt = encode(salt,charMap5) - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap5) - return cleartext - - -# unprotect the new header blob in .kinf2011 -# used in Kindle for Mac Version >= 1.9.0 -def UnprotectHeaderData(encryptedData): - passwdData = 'header_key_data' - salt = 'HEADER.2011' - iter = 0x80 - keylen = 0x100 - crp = LibCrypto() - key_iv = crp.keyivgen(passwdData, salt, iter, keylen) - key = key_iv[0:32] - iv = key_iv[32:48] - crp.set_decrypt_key(key,iv) - cleartext = crp.decrypt(encryptedData) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.9.0 -class CryptUnprotectDataV3(object): - def __init__(self, entropy): - sp = GetUserName() + '+@#$%+' + GetIDString() - passwdData = encode(SHA256(sp),charMap2) - salt = entropy - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap2) - return cleartext - - -# Locate the .kindle-info files -def getKindleInfoFiles(): - # file searches can take a long time on some systems, so just look in known specific places. - kInfoFiles=[] - found = False - home = os.getenv('HOME') - # check for .kinf2011 file in new location (App Store Kindle for Mac) - testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kinf2011 file: ' + testpath) - found = True - # check for .kinf2011 files - testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kinf2011 file: ' + testpath) - found = True - # check for .rainier-2.1.1-kinf files - testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac rainier file: ' + testpath) - found = True - # check for .rainier-2.1.1-kinf files - testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kindle-info file: ' + testpath) - found = True - if not found: - print('No k4Mac kindle-info/rainier/kinf2011 files have been found.') - return kInfoFiles - -# determine type of kindle info provided and return a -# database of keynames and values -def getDBfromFile(kInfoFile): - names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"] - DB = {} - cnt = 0 - infoReader = open(kInfoFile, 'r') - hdr = infoReader.read(1) - data = infoReader.read() - - if data.find('[') != -1 : - - # older style kindle-info file - cud = CryptUnprotectData() - items = data.split('[') - for item in items: - if item != '': - keyhash, rawdata = item.split(':') - keyname = "unknown" - for name in names: - if encodeHash(name,charMap2) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - encryptedValue = decode(rawdata,charMap2) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - if cnt == 0: - DB = None - return DB - - if hdr == '/': - - # else newer style .kinf file used by K4Mac >= 1.6.0 - # the .kinf file uses "/" to separate it into records - # so remove the trailing "/" to make it easy to use split - data = data[:-1] - items = data.split('/') - cud = CryptUnprotectDataV2() - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # the raw keyhash string is also used to create entropy for the actual - # CryptProtectData Blob that represents that keys contents - # "entropy" not used for K4Mac only K4PC - # entropy = SHA1(keyhash) - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,charMap5) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the charMap5 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using charMap5 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the charMap5 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by charMap5 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using charMap5 to get the CryptProtect Data - encryptedValue = decode(encdata,charMap5) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB - - # the latest .kinf2011 version for K4M 1.9.1 - # put back the hdr char, it is needed - data = hdr + data - data = data[:-1] - items = data.split('/') - - # the headerblob is the encrypted information needed to build the entropy string - headerblob = items.pop(0) - encryptedValue = decode(headerblob, charMap1) - cleartext = UnprotectHeaderData(encryptedValue) - - # now extract the pieces in the same way - # this version is different from K4PC it scales the build number by multipying by 735 - pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) - for m in re.finditer(pattern, cleartext): - entropy = str(int(m.group(2)) * 0x2df) + m.group(4) - - cud = CryptUnprotectDataV3(entropy) - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # unlike K4PC the keyhash is not used in generating entropy - # entropy = SHA1(keyhash) + added_entropy - # entropy = added_entropy - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,testMap8) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the testMap8 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using testMap8 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the testMap8 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by testMap8 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using testMap8 to get the CryptProtect Data - encryptedValue = decode(encdata,testMap8) - cleartext = cud.decrypt(encryptedValue) - # print keyname - # print cleartext - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4pcutils.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4pcutils.py deleted file mode 100755 index 9f9ca07..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/k4pcutils.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/env python -# K4PC Windows specific routines - -from __future__ import with_statement - -import sys, os, re -from struct import pack, unpack, unpack_from - -from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ - create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ - string_at, Structure, c_void_p, cast - -import _winreg as winreg -MAX_PATH = 255 -kernel32 = windll.kernel32 -advapi32 = windll.advapi32 -crypt32 = windll.crypt32 - -import traceback - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# For K4PC 1.9.X -# use routines in alfcrypto: -# AES_cbc_encrypt -# AES_set_decrypt_key -# PKCS5_PBKDF2_HMAC_SHA1 - -from alfcrypto import AES_CBC, KeyIVGen - -def UnprotectHeaderData(encryptedData): - passwdData = 'header_key_data' - salt = 'HEADER.2011' - iter = 0x80 - keylen = 0x100 - key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen) - key = key_iv[0:32] - iv = key_iv[32:48] - aes=AES_CBC() - aes.set_decrypt_key(key, iv) - cleartext = aes.decrypt(encryptedData) - return cleartext - - -# simple primes table (<= n) calculator -def primes(n): - if n==2: return [2] - elif n<2: return [] - s=range(3,n+1,2) - mroot = n ** 0.5 - half=(n+1)/2-1 - i=0 - m=3 - while m <= mroot: - if s[i]: - j=(m*m-3)/2 - s[j]=0 - while j 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - - # the raw keyhash string is used to create entropy for the actual - # CryptProtectData Blob that represents that keys contents - entropy = SHA1(keyhash) - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,charMap5) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - # the charMap5 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using charMap5 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the charMap5 encoded contents seems to be: - # len(contents)-largest prime number <= int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by charMap5 - encdata = "".join(edlst) - contlen = len(encdata) - noffset = contlen - primes(int(contlen/3))[-1] - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using Map5 to get the CryptProtect Data - encryptedValue = decode(encdata,charMap5) - DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB - - # else newest .kinf2011 style .kinf file - # the .kinf file uses "/" to separate it into records - # so remove the trailing "/" to make it easy to use split - # need to put back the first char read because it it part - # of the added entropy blob - data = hdr + data[:-1] - items = data.split('/') - - # starts with and encoded and encrypted header blob - headerblob = items.pop(0) - encryptedValue = decode(headerblob, testMap1) - cleartext = UnprotectHeaderData(encryptedValue) - # now extract the pieces that form the added entropy - pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) - for m in re.finditer(pattern, cleartext): - added_entropy = m.group(2) + m.group(4) - - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - - # the sha1 of raw keyhash string is used to create entropy along - # with the added entropy provided above from the headerblob - entropy = SHA1(keyhash) + added_entropy - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - # key names now use the new testMap8 encoding - keyname = "unknown" - for name in names: - if encodeHash(name,testMap8) == keyhash: - keyname = name - break - - # the testMap8 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using testMap8 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the testMap8 encoded contents seems to be: - # len(contents)-largest prime number <= int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by testMap8 - # by moving noffset chars from the start of the - # string to the end of the string - encdata = "".join(edlst) - contlen = len(encdata) - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using new testMap8 to get the original CryptProtect Data - encryptedValue = decode(encdata,testMap8) - cleartext = CryptUnprotectData(encryptedValue, entropy, 1) - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/kgenpids.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/kgenpids.py deleted file mode 100644 index b0fbaa4..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/kgenpids.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python - -from __future__ import with_statement -import sys -import os, csv -import binascii -import zlib -import re -from struct import pack, unpack, unpack_from - -class DrmException(Exception): - pass - -global charMap1 -global charMap3 -global charMap4 - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -if inCalibre: - if sys.platform.startswith('win'): - from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - if sys.platform.startswith('darwin'): - from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString -else: - if sys.platform.startswith('win'): - from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - if sys.platform.startswith('darwin'): - from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - - -# Encode the bytes in data with the characters in map -def encode(data, map): - result = "" - for char in data: - value = ord(char) - Q = (value ^ 0x80) // len(map) - R = value % len(map) - result += map[Q] - result += map[R] - return result - -# Hash the bytes in data and then encode the digest with the characters in map -def encodeHash(data,map): - return encode(MD5(data),map) - -# Decode the string in data with the characters in map. Returns the decoded bytes -def decode(data,map): - result = "" - for i in range (0,len(data)-1,2): - high = map.find(data[i]) - low = map.find(data[i+1]) - if (high == -1) or (low == -1) : - break - value = (((high * len(map)) ^ 0x80) & 0xFF) + low - result += pack("B",value) - return result - -# -# PID generation routines -# - -# Returns two bit at offset from a bit field -def getTwoBitsFromBitField(bitField,offset): - byteNumber = offset // 4 - bitPosition = 6 - 2*(offset % 4) - return ord(bitField[byteNumber]) >> bitPosition & 3 - -# Returns the six bits at offset from a bit field -def getSixBitsFromBitField(bitField,offset): - offset *= 3 - value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) - return value - -# 8 bits to six bits encoding from hash to generate PID string -def encodePID(hash): - global charMap3 - PID = "" - for position in range (0,8): - PID += charMap3[getSixBitsFromBitField(hash,position)] - return PID - -# 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): - global charMap4 - 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 - -def crc32(s): - return (~binascii.crc32(s,-1))&0xFFFFFFFF - -# convert from 8 digit PID to 10 digit PID with checksum -def checksumPid(s): - global charMap4 - crc = crc32(s) - crc = crc ^ (crc >> 16) - res = s - l = len(charMap4) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += charMap4[pos%l] - crc >>= 8 - return res - - -# old kindle serial number to fixed pid -def pidFromSerial(s, l): - global charMap4 - crc = crc32(s) - arr1 = [0]*l - for i in xrange(len(s)): - arr1[i%l] ^= ord(s[i]) - crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] - for i in xrange(l): - arr1[i] ^= crc_bytes[i&3] - pid = "" - for i in xrange(l): - b = arr1[i] & 0xff - pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] - return pid - - -# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. -def getKindlePid(pidlst, rec209, token, serialnum): - # Compute book PID - pidHash = SHA1(serialnum+rec209+token) - bookPID = encodePID(pidHash) - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) - - # compute fixed pid for old pre 2.5 firmware update pid as well - bookPID = pidFromSerial(serialnum, 7) + "*" - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) - - return pidlst - - -# parse the Kindleinfo file to calculate the book pid. - -keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] - -def getK4Pids(pidlst, rec209, token, kInfoFile): - global charMap1 - kindleDatabase = None - try: - kindleDatabase = getDBfromFile(kInfoFile) - except Exception, message: - print(message) - kindleDatabase = None - pass - - if kindleDatabase == None : - return pidlst - - try: - # Get the Mazama Random number - MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] - - # Get the kindle account token - kindleAccountToken = kindleDatabase["kindle.account.tokens"] - except KeyError: - print "Keys not found in " + kInfoFile - return pidlst - - # Get the ID string used - encodedIDString = encodeHash(GetIDString(),charMap1) - - # Get the current user name - encodedUsername = encodeHash(GetUserName(),charMap1) - - # concat, hash and encode to calculate the DSN - DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1) - - # Compute the device PID (for which I can tell, is used for nothing). - table = generatePidEncryptionTable() - devicePID = generateDevicePID(table,DSN,4) - devicePID = checksumPid(devicePID) - pidlst.append(devicePID) - - # Compute book PIDs - - # book pid - pidHash = SHA1(DSN+kindleAccountToken+rec209+token) - bookPID = encodePID(pidHash) - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) - - # variant 1 - pidHash = SHA1(kindleAccountToken+rec209+token) - bookPID = encodePID(pidHash) - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) - - # variant 2 - pidHash = SHA1(DSN+rec209+token) - bookPID = encodePID(pidHash) - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) - - return pidlst - -def getPidList(md1, md2, k4 = True, serials=[], kInfoFiles=[]): - pidlst = [] - if kInfoFiles is None: - kInfoFiles = [] - if k4: - kInfoFiles.extend(getKindleInfoFiles()) - for infoFile in kInfoFiles: - try: - pidlst = getK4Pids(pidlst, md1, md2, infoFile) - except Exception, message: - print("Error getting PIDs from " + infoFile + ": " + message) - for serialnum in serials: - try: - pidlst = getKindlePid(pidlst, md1, md2, serialnum) - except Exception, message: - print("Error getting PIDs from " + serialnum + ": " + message) - return pidlst diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/mobidedrm.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/mobidedrm.py deleted file mode 100644 index cd993e1..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/mobidedrm.py +++ /dev/null @@ -1,460 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 0.01 - Initial version -# 0.02 - Huffdic compressed books were not properly decrypted -# 0.03 - Wasn't checking MOBI header length -# 0.04 - Wasn't sanity checking size of data record -# 0.05 - It seems that the extra data flags take two bytes not four -# 0.06 - And that low bit does mean something after all :-) -# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size -# 0.08 - ...and also not in Mobi header version < 6 -# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4! -# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre -# import filter it works when importing unencrypted files. -# Also now handles encrypted files that don't need a specific PID. -# 0.11 - use autoflushed stdout and proper return values -# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors -# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace -# and extra blank lines, converted CR/LF pairs at ends of each line, -# and other cosmetic fixes. -# 0.14 - Working out when the extra data flags are present has been problematic -# Versions 7 through 9 have tried to tweak the conditions, but have been -# only partially successful. Closer examination of lots of sample -# files reveals that a confusion has arisen because trailing data entries -# are not encrypted, but it turns out that the multibyte entries -# in utf8 file are encrypted. (Although neither kind gets compressed.) -# This knowledge leads to a simplification of the test for the -# trailing data byte flags - version 5 and higher AND header size >= 0xE4. -# 0.15 - Now outputs 'heartbeat', and is also quicker for long files. -# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility. -# 0.17 - added modifications to support its use as an imported python module -# both inside calibre and also in other places (ie K4DeDRM tools) -# 0.17a- disabled the standalone plugin feature since a plugin can not import -# a plugin -# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... -# Removed the disabled Calibre plug-in code -# Permit use of 8-digit PIDs -# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. -# 0.21 - Added support for multiple pids -# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface -# 0.23 - fixed problem with older files with no EXTH section -# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well -# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption -# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% -# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) -# 0.28 - slight additional changes to metadata token generation (None -> '') -# 0.29 - It seems that the ideas about when multibyte trailing characters were -# included in the encryption were wrong. They are for DOC compressed -# files, but they are not for HUFF/CDIC compress files! -# 0.30 - Modified interface slightly to work better with new calibre plugin style -# 0.31 - The multibyte encrytion info is true for version 7 files too. -# 0.32 - Added support for "Print Replica" Kindle ebooks -# 0.33 - Performance improvements for large files (concatenation) -# 0.34 - Performance improvements in decryption (libalfcrypto) -# 0.35 - add interface to get mobi_version -# 0.36 - fixed problem with TEXtREAd and getBookTitle interface -# 0.37 - Fixed double announcement for stand-alone operation - - -__version__ = '0.37' - -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii -from alfcrypto import Pukall_Cipher - -class DrmException(Exception): - pass - - -# -# MobiBook Utility Routines -# - -# Implementation of Pukall Cipher 1 -def PC1(key, src, decryption=True): - return Pukall_Cipher().PC1(key,src,decryption) -# sum1 = 0; -# sum2 = 0; -# keyXorVal = 0; -# if len(key)!=16: -# print "Bad key length!" -# return None -# wkey = [] -# for i in xrange(8): -# wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) -# dst = "" -# for i in xrange(len(src)): -# temp1 = 0; -# byteXorVal = 0; -# for j in xrange(8): -# temp1 ^= wkey[j] -# sum2 = (sum2+j)*20021 + sum1 -# sum1 = (temp1*346)&0xFFFF -# sum2 = (sum2+sum1)&0xFFFF -# temp1 = (temp1*20021+1)&0xFFFF -# byteXorVal ^= temp1 ^ sum2 -# curByte = ord(src[i]) -# if not decryption: -# keyXorVal = curByte * 257; -# curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF -# if decryption: -# keyXorVal = curByte * 257; -# for j in xrange(8): -# wkey[j] ^= keyXorVal; -# dst+=chr(curByte) -# return dst - -def checksumPid(s): - letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - crc = (~binascii.crc32(s,-1))&0xFFFFFFFF - crc = crc ^ (crc >> 16) - res = s - l = len(letters) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += letters[pos%l] - crc >>= 8 - return res - -def getSizeOfTrailingDataEntries(ptr, size, flags): - def getSizeOfTrailingDataEntry(ptr, size): - bitpos, result = 0, 0 - if size <= 0: - return result - while True: - v = ord(ptr[size-1]) - result |= (v & 0x7F) << bitpos - bitpos += 7 - size -= 1 - if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0): - return result - num = 0 - testflags = flags >> 1 - while testflags: - if testflags & 1: - num += getSizeOfTrailingDataEntry(ptr, size - num) - testflags >>= 1 - # Check the low bit to see if there's multibyte data present. - # if multibyte data is included in the encryped data, we'll - # have already cleared this flag. - if flags & 1: - num += (ord(ptr[size - num - 1]) & 0x3) + 1 - return num - - - -class MobiBook: - def loadSection(self, section): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - return self.data_file[off:endoff] - - def __init__(self, infile, announce = True): - if announce: - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) - - # initial sanity check on file - self.data_file = file(infile, 'rb').read() - self.mobi_data = '' - self.header = self.data_file[0:78] - if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") - self.magic = self.header[0x3C:0x3C+8] - self.crypto_type = -1 - - # build up section offset and flag info - self.num_sections, = struct.unpack('>H', self.header[76:78]) - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - - # parse information from section 0 - self.sect = self.loadSection(0) - self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) - self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2]) - - if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_codepage = 1252 - self.mobi_version = -1 - self.meta_array = {} - return - self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) - self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20]) - self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 - if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): - self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags - if (self.compression != 17480): - # multibyte utf8 data is included in the encryption for PalmDoc compression - # so clear that byte so that we leave it to be decrypted. - self.extra_data_flags &= 0xFFFE - - # if exth region exists parse it for metadata array - self.meta_array = {} - try: - exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' - if exth_flag & 0x40: - exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - content = exth[pos + 8: pos + size] - self.meta_array[type] = content - # reset the text to speech flag and clipping limit, if present - if type == 401 and size == 9: - # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) - elif type == 404 and size == 9: - # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - # print type, size, content, content.encode('hex') - pos += size - except: - self.meta_array = {} - pass - self.print_replica = False - - def getBookTitle(self): - codec_map = { - 1252 : 'windows-1252', - 65001 : 'utf-8', - } - title = '' - codec = 'windows-1252' - if self.magic == 'BOOKMOBI': - if 503 in self.meta_array: - title = self.meta_array[503] - else: - toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) - tend = toff + tlen - title = self.sect[toff:tend] - if self.mobi_codepage in codec_map.keys(): - codec = codec_map[self.mobi_codepage] - if title == '': - title = self.header[:32] - title = title.split("\0")[0] - return unicode(title, codec).encode('utf-8') - - def getPIDMetaInfo(self): - rec209 = '' - token = '' - if 209 in self.meta_array: - rec209 = self.meta_array[209] - data = rec209 - # The 209 data comes in five byte groups. Interpret the last four bytes - # of each group as a big endian unsigned integer to get a key value - # if that key exists in the meta_array, append its contents to the token - for i in xrange(0,len(data),5): - val, = struct.unpack('>I',data[i+1:i+5]) - sval = self.meta_array.get(val,'') - token += sval - return rec209, token - - def patch(self, off, new): - self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] - - def patchSection(self, section, new, in_off = 0): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - assert off + in_off + len(new) <= endoff - self.patch(off + in_off, new) - - def parseDRM(self, data, count, pidlist): - found_key = None - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" - for pid in pidlist: - bigpid = pid.ljust(16,'\0') - temp_key = PC1(keyvec1, bigpid, False) - temp_key_sum = sum(map(ord,temp_key)) & 0xff - found_key = None - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver and (flags & 0x1F) == 1: - found_key = finalkey - break - if found_key != None: - break - if not found_key: - # Then try the default encoding that doesn't require a PID - pid = "00000000" - temp_key = keyvec1 - temp_key_sum = sum(map(ord,temp_key)) & 0xff - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver: - found_key = finalkey - break - return [found_key,pid] - - def getMobiFile(self, outpath): - file(outpath,'wb').write(self.mobi_data) - - def getMobiVersion(self): - return self.mobi_version - - def getPrintReplica(self): - return self.print_replica - - def processBook(self, pidlist): - crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) - print 'Crypto Type is: ', crypto_type - self.crypto_type = crypto_type - if crypto_type == 0: - print "This book is not encrypted." - # we must still check for Print Replica - self.print_replica = (self.loadSection(1)[0:4] == '%MOP') - self.mobi_data = self.data_file - return - if crypto_type != 2 and crypto_type != 1: - raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) - if 406 in self.meta_array: - data406 = self.meta_array[406] - val406, = struct.unpack('>Q',data406) - if val406 != 0: - raise DrmException("Cannot decode library or rented ebooks.") - - goodpids = [] - for pid in pidlist: - if len(pid)==10: - if checksumPid(pid[0:-2]) != pid: - print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) - goodpids.append(pid[0:-2]) - elif len(pid)==8: - goodpids.append(pid) - - if self.crypto_type == 1: - t1_keyvec = "QDCVEPMU675RUBSZ" - if self.magic == 'TEXtREAd': - bookkey_data = self.sect[0x0E:0x0E+16] - elif self.mobi_version < 0: - bookkey_data = self.sect[0x90:0x90+16] - else: - bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] - pid = "00000000" - found_key = PC1(t1_keyvec, bookkey_data) - else : - # calculate the keys - drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) - if drm_count == 0: - raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") - found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) - if not found_key: - raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.") - # kill the drm keys - self.patchSection(0, "\0" * drm_size, drm_ptr) - # kill the drm pointers - self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) - - if pid=="00000000": - print "File has default encryption, no specific PID." - else: - print "File is encoded with PID "+checksumPid(pid)+"." - - # clear the crypto type - self.patchSection(0, "\0" * 2, 0xC) - - # decrypt sections - print "Decrypting. Please wait . . .", - mobidataList = [] - mobidataList.append(self.data_file[:self.sections[1][0]]) - for i in xrange(1, self.records+1): - data = self.loadSection(i) - extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) - if i%100 == 0: - print ".", - # print "record %d, extra_size %d" %(i,extra_size) - decoded_data = PC1(found_key, data[0:len(data) - extra_size]) - if i==1: - self.print_replica = (decoded_data[0:4] == '%MOP') - mobidataList.append(decoded_data) - if extra_size > 0: - mobidataList.append(data[-extra_size:]) - if self.num_sections > self.records+1: - mobidataList.append(self.data_file[self.sections[self.records+1][0]:]) - self.mobi_data = "".join(mobidataList) - print "done" - return - -def getUnencryptedBook(infile,pid,announce=True): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile,announce) - book.processBook([pid]) - return book.mobi_data - -def getUnencryptedBookWithList(infile,pidlist,announce=True): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile, announce) - book.processBook(pidlist) - return book.mobi_data - - -def main(argv=sys.argv): - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) - if len(argv)<3 or len(argv)>4: - print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks" - print "Usage:" - print " %s []" % sys.argv[0] - return 1 - else: - infile = argv[1] - outfile = argv[2] - if len(argv) is 4: - pidlist = argv[3].split(',') - else: - pidlist = {} - try: - stripped_file = getUnencryptedBookWithList(infile, pidlist, False) - file(outfile, 'wb').write(stripped_file) - except DrmException, e: - print "Error: %s" % e - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/openssl_des.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/openssl_des.py deleted file mode 100644 index a4a40ca..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/openssl_des.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/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/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/scrolltextwidget.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/scrolltextwidget.py deleted file mode 100644 index 98b4147..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/scrolltextwidget.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import Tkinter -import Tkconstants - -# basic scrolled text widget -class ScrolledText(Tkinter.Text): - def __init__(self, master=None, **kw): - self.frame = Tkinter.Frame(master) - self.vbar = Tkinter.Scrollbar(self.frame) - self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) - kw.update({'yscrollcommand': self.vbar.set}) - Tkinter.Text.__init__(self, self.frame, **kw) - self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) - self.vbar['command'] = self.yview - # Copy geometry methods of self.frame without overriding Text - # methods = hack! - text_meths = vars(Tkinter.Text).keys() - methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() - methods = set(methods).difference(text_meths) - for m in methods: - if m[0] != '_' and m != 'config' and m != 'configure': - setattr(self, m, getattr(self.frame, m)) - - def __str__(self): - return str(self.frame) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/scrolltextwidget.pyc b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/scrolltextwidget.pyc deleted file mode 100644 index 25af883..0000000 Binary files a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/scrolltextwidget.pyc and /dev/null differ diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/simpleprefs.pyc b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/simpleprefs.pyc deleted file mode 100644 index 71eb2bb..0000000 Binary files a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/simpleprefs.pyc and /dev/null differ diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/subasyncio.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/subasyncio.py deleted file mode 100644 index de084d3..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/subasyncio.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import os, sys -import signal -import threading -import subprocess -from subprocess import Popen, PIPE, STDOUT - -# **heavily** chopped up and modfied version of asyncproc.py -# to make it actually work on Windows as well as Mac/Linux -# For the original see: -# "http://www.lysator.liu.se/~bellman/download/" -# author is "Thomas Bellman " -# available under GPL version 3 or Later - -# create an asynchronous subprocess whose output can be collected in -# a non-blocking manner - -# What a mess! Have to use threads just to get non-blocking io -# in a cross-platform manner - -# luckily all thread use is hidden within this class - -class Process(object): - def __init__(self, *params, **kwparams): - if len(params) <= 3: - kwparams.setdefault('stdin', subprocess.PIPE) - if len(params) <= 4: - kwparams.setdefault('stdout', subprocess.PIPE) - if len(params) <= 5: - kwparams.setdefault('stderr', subprocess.PIPE) - self.__pending_input = [] - self.__collected_outdata = [] - self.__collected_errdata = [] - self.__exitstatus = None - self.__lock = threading.Lock() - self.__inputsem = threading.Semaphore(0) - self.__quit = False - - self.__process = subprocess.Popen(*params, **kwparams) - - if self.__process.stdin: - self.__stdin_thread = threading.Thread( - name="stdin-thread", - target=self.__feeder, args=(self.__pending_input, - self.__process.stdin)) - self.__stdin_thread.setDaemon(True) - self.__stdin_thread.start() - - if self.__process.stdout: - self.__stdout_thread = threading.Thread( - name="stdout-thread", - target=self.__reader, args=(self.__collected_outdata, - self.__process.stdout)) - self.__stdout_thread.setDaemon(True) - self.__stdout_thread.start() - - if self.__process.stderr: - self.__stderr_thread = threading.Thread( - name="stderr-thread", - target=self.__reader, args=(self.__collected_errdata, - self.__process.stderr)) - self.__stderr_thread.setDaemon(True) - self.__stderr_thread.start() - - def pid(self): - return self.__process.pid - - def kill(self, signal): - self.__process.send_signal(signal) - - # check on subprocess (pass in 'nowait') to act like poll - def wait(self, flag): - if flag.lower() == 'nowait': - rc = self.__process.poll() - else: - rc = self.__process.wait() - if rc != None: - if self.__process.stdin: - self.closeinput() - if self.__process.stdout: - self.__stdout_thread.join() - if self.__process.stderr: - self.__stderr_thread.join() - return self.__process.returncode - - def terminate(self): - if self.__process.stdin: - self.closeinput() - self.__process.terminate() - - # thread gets data from subprocess stdout - def __reader(self, collector, source): - while True: - data = os.read(source.fileno(), 65536) - self.__lock.acquire() - collector.append(data) - self.__lock.release() - if data == "": - source.close() - break - return - - # thread feeds data to subprocess stdin - def __feeder(self, pending, drain): - while True: - self.__inputsem.acquire() - self.__lock.acquire() - if not pending and self.__quit: - drain.close() - self.__lock.release() - break - data = pending.pop(0) - self.__lock.release() - drain.write(data) - - # non-blocking read of data from subprocess stdout - def read(self): - self.__lock.acquire() - outdata = "".join(self.__collected_outdata) - del self.__collected_outdata[:] - self.__lock.release() - return outdata - - # non-blocking read of data from subprocess stderr - def readerr(self): - self.__lock.acquire() - errdata = "".join(self.__collected_errdata) - del self.__collected_errdata[:] - self.__lock.release() - return errdata - - # non-blocking write to stdin of subprocess - def write(self, data): - if self.__process.stdin is None: - raise ValueError("Writing to process with stdin not a pipe") - self.__lock.acquire() - self.__pending_input.append(data) - self.__inputsem.release() - self.__lock.release() - - # close stdinput of subprocess - def closeinput(self): - self.__lock.acquire() - self.__quit = True - self.__inputsem.release() - self.__lock.release() diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/subasyncio.pyc b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/subasyncio.pyc deleted file mode 100644 index 9d50740..0000000 Binary files a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/subasyncio.pyc and /dev/null differ diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/topazextract.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/topazextract.py deleted file mode 100755 index bf2ad47..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/topazextract.py +++ /dev/null @@ -1,482 +0,0 @@ -#!/usr/bin/env python - -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 - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -buildXML = False - -import os, csv, getopt -import zlib, zipfile, tempfile, shutil -from struct import pack -from struct import unpack -from alfcrypto import Topaz_Cipher - -class TpzDRMError(Exception): - pass - - -# local support routines -if inCalibre: - from calibre_plugins.k4mobidedrm import kgenpids -else: - import kgenpids - -# recursive zip creation support routine -def zipUpDir(myzip, tdir, localname): - currentdir = tdir - if localname != "": - currentdir = os.path.join(currentdir,localname) - list = os.listdir(currentdir) - for file in list: - afilename = file - localfilePath = os.path.join(localname, afilename) - realfilePath = os.path.join(currentdir,file) - if os.path.isfile(realfilePath): - myzip.write(realfilePath, localfilePath) - elif os.path.isdir(realfilePath): - zipUpDir(myzip, tdir, localfilePath) - -# -# Utility routines -# - -# Get a 7 bit encoded number from file -def bookReadEncodedNumber(fo): - flag = False - data = ord(fo.read(1)) - if data == 0xFF: - flag = True - data = ord(fo.read(1)) - if data >= 0x80: - datax = (data & 0x7F) - while data >= 0x80 : - data = ord(fo.read(1)) - datax = (datax <<7) + (data & 0x7F) - data = datax - if flag: - data = -data - return data - -# Get a length prefixed string from file -def bookReadString(fo): - stringLength = bookReadEncodedNumber(fo) - return unpack(str(stringLength)+"s",fo.read(stringLength))[0] - -# -# crypto routines -# - -# Context initialisation for the Topaz Crypto -def topazCryptoInit(key): - return Topaz_Cipher().ctx_init(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): - return Topaz_Cipher().decrypt(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 data with the PID -def decryptRecord(data,PID): - ctx = topazCryptoInit(PID) - return topazCryptoDecrypt(data, ctx) - -# Try to decrypt a dkey record (contains the bookPID) -def decryptDkeyRecord(data,PID): - record = decryptRecord(data,PID) - fields = unpack("3sB8sB8s3s",record) - if fields[0] != "PID" or fields[5] != "pid" : - raise TpzDRMError("Didn't find PID magic numbers in record") - elif fields[1] != 8 or fields[3] != 8 : - raise TpzDRMError("Record didn't contain correct length fields") - elif fields[2] != PID : - raise TpzDRMError("Record didn't contain PID") - return fields[4] - -# Decrypt all 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 TpzDRMError: - pass - data = data[1+length:] - if len(records) == 0: - raise TpzDRMError("BookKey Not Found") - return records - - -class TopazBook: - def __init__(self, filename): - self.fo = file(filename, 'rb') - self.outdir = tempfile.mkdtemp() - # self.outdir = 'rawdat' - self.bookPayloadOffset = 0 - self.bookHeaderRecords = {} - self.bookMetadata = {} - self.bookKey = None - magic = unpack("4s",self.fo.read(4))[0] - if magic != 'TPZ0': - raise TpzDRMError("Parse Error : Invalid Header, not a Topaz file") - self.parseTopazHeaders() - self.parseMetadata() - - def parseTopazHeaders(self): - def bookReadHeaderRecordData(): - # Read and return the data of one header record at the current book file position - # [[offset,decompressedLength,compressedLength],...] - nbValues = bookReadEncodedNumber(self.fo) - values = [] - for i in range (0,nbValues): - values.append([bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo)]) - return values - def parseTopazHeaderRecord(): - # Read and parse one header record at the current book file position and return the associated data - # [[offset,decompressedLength,compressedLength],...] - if ord(self.fo.read(1)) != 0x63: - raise TpzDRMError("Parse Error : Invalid Header") - tag = bookReadString(self.fo) - record = bookReadHeaderRecordData() - return [tag,record] - nbRecords = bookReadEncodedNumber(self.fo) - for i in range (0,nbRecords): - result = parseTopazHeaderRecord() - # print result[0], result[1] - self.bookHeaderRecords[result[0]] = result[1] - if ord(self.fo.read(1)) != 0x64 : - raise TpzDRMError("Parse Error : Invalid Header") - self.bookPayloadOffset = self.fo.tell() - - def parseMetadata(self): - # Parse the metadata record from the book payload and return a list of [key,values] - self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords["metadata"][0][0]) - tag = bookReadString(self.fo) - if tag != "metadata" : - raise TpzDRMError("Parse Error : Record Names Don't Match") - flags = ord(self.fo.read(1)) - nbRecords = ord(self.fo.read(1)) - # print nbRecords - for i in range (0,nbRecords) : - keyval = bookReadString(self.fo) - content = bookReadString(self.fo) - # print keyval - # print content - self.bookMetadata[keyval] = content - return self.bookMetadata - - def getPIDMetaInfo(self): - keysRecord = self.bookMetadata.get('keys','') - keysRecordRecord = '' - if keysRecord != '': - keylst = keysRecord.split(',') - for keyval in keylst: - keysRecordRecord += self.bookMetadata.get(keyval,'') - return keysRecord, keysRecordRecord - - def getBookTitle(self): - title = '' - if 'Title' in self.bookMetadata: - title = self.bookMetadata['Title'] - return title - - def setBookKey(self, key): - self.bookKey = key - - def getBookPayloadRecord(self, name, index): - # Get a record in the book payload, given its name and index. - # decrypted and decompressed if necessary - encrypted = False - compressed = False - try: - recordOffset = self.bookHeaderRecords[name][index][0] - except: - raise TpzDRMError("Parse Error : Invalid Record, record not found") - - self.fo.seek(self.bookPayloadOffset + recordOffset) - - tag = bookReadString(self.fo) - if tag != name : - raise TpzDRMError("Parse Error : Invalid Record, record name doesn't match") - - recordIndex = bookReadEncodedNumber(self.fo) - if recordIndex < 0 : - encrypted = True - recordIndex = -recordIndex -1 - - if recordIndex != index : - raise TpzDRMError("Parse Error : Invalid Record, index doesn't match") - - if (self.bookHeaderRecords[name][index][2] > 0): - compressed = True - record = self.fo.read(self.bookHeaderRecords[name][index][2]) - else: - record = self.fo.read(self.bookHeaderRecords[name][index][1]) - - if encrypted: - if self.bookKey: - ctx = topazCryptoInit(self.bookKey) - record = topazCryptoDecrypt(record,ctx) - else : - raise TpzDRMError("Error: Attempt to decrypt without bookKey") - - if compressed: - record = zlib.decompress(record) - - return record - - def processBook(self, pidlst): - raw = 0 - fixedimage=True - try: - keydata = self.getBookPayloadRecord('dkey', 0) - except TpzDRMError, e: - print "no dkey record found, book may not be encrypted" - print "attempting to extrct files without a book key" - self.createBookDirectory() - self.extractFiles() - print "Successfully Extracted Topaz contents" - if inCalibre: - from calibre_plugins.k4mobidedrm import genbook - else: - import genbook - - rv = genbook.generateBook(self.outdir, raw, fixedimage) - if rv == 0: - print "\nBook Successfully generated" - return rv - - # try each pid to decode the file - bookKey = None - for pid in pidlst: - # use 8 digit pids here - pid = pid[0:8] - print "\nTrying: ", pid - bookKeys = [] - data = keydata - try: - bookKeys+=decryptDkeyRecords(data,pid) - except TpzDRMError, e: - pass - else: - bookKey = bookKeys[0] - print "Book Key Found!" - break - - if not bookKey: - raise TpzDRMError("Topaz Book. No key found in " + str(len(pidlst)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.") - - self.setBookKey(bookKey) - self.createBookDirectory() - self.extractFiles() - print "Successfully Extracted Topaz contents" - if inCalibre: - from calibre_plugins.k4mobidedrm import genbook - else: - import genbook - - rv = genbook.generateBook(self.outdir, raw, fixedimage) - if rv == 0: - print "\nBook Successfully generated" - return rv - - def createBookDirectory(self): - outdir = self.outdir - # create output directory structure - 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) - - def extractFiles(self): - outdir = self.outdir - for headerRecord in self.bookHeaderRecords: - name = headerRecord - if name != "dkey" : - ext = '.dat' - if name == 'img' : ext = '.jpg' - if name == 'color' : ext = '.jpg' - print "\nProcessing Section: %s " % name - for index in range (0,len(self.bookHeaderRecords[name])) : - fnum = "%04d" % index - fname = name + fnum + ext - destdir = outdir - if name == 'img': - destdir = os.path.join(outdir,'img') - if name == 'color': - destdir = os.path.join(outdir,'color_img') - if name == 'page': - destdir = os.path.join(outdir,'page') - if name == 'glyphs': - destdir = os.path.join(outdir,'glyphs') - outputFile = os.path.join(destdir,fname) - print ".", - record = self.getBookPayloadRecord(name,index) - if record != '': - file(outputFile, 'wb').write(record) - print " " - - def getHTMLZip(self, zipname): - htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html') - htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(self.outdir,'cover.jpg')): - htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg') - htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css') - zipUpDir(htmlzip, self.outdir, 'img') - htmlzip.close() - - def getSVGZip(self, zipname): - svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(svgzip, self.outdir, 'svg') - zipUpDir(svgzip, self.outdir, 'img') - svgzip.close() - - def getXMLZip(self, zipname): - xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(self.outdir,'xml') - zipUpDir(xmlzip, targetdir, '') - zipUpDir(xmlzip, self.outdir, 'img') - xmlzip.close() - - def cleanup(self): - if os.path.isdir(self.outdir): - shutil.rmtree(self.outdir, True) - -def usage(progname): - print "Removes DRM protection from Topaz ebooks and extract the contents" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - - -# Main -def main(argv=sys.argv): - global buildXML - progname = os.path.basename(argv[0]) - k4 = False - pids = [] - serials = [] - kInfoFiles = [] - - try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") - except getopt.GetoptError, err: - print str(err) - usage(progname) - return 1 - if len(args)<2: - usage(progname) - return 1 - - for o, a in opts: - if o == "-k": - if a == None : - print "Invalid parameter for -k" - return 1 - kInfoFiles.append(a) - if o == "-p": - if a == None : - print "Invalid parameter for -p" - return 1 - pids = a.split(',') - if o == "-s": - if a == None : - print "Invalid parameter for -s" - return 1 - serials = a.split(',') - k4 = True - - infile = args[0] - outdir = args[1] - - if not os.path.isfile(infile): - print "Input File Does Not Exist" - return 1 - - bookname = os.path.splitext(os.path.basename(infile))[0] - - tb = TopazBook(infile) - title = tb.getBookTitle() - print "Processing Book: ", title - keysRecord, keysRecordRecord = tb.getPIDMetaInfo() - pids.extend(kgenpids.getPidList(keysRecord, keysRecordRecord, k4, serials, kInfoFiles)) - - try: - print "Decrypting Book" - tb.processBook(pids) - - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz') - tb.getHTMLZip(zipname) - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') - tb.getSVGZip(zipname) - - if buildXML: - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_XML' + '.zip') - tb.getXMLZip(zipname) - - # removing internal temporary directory of pieces - tb.cleanup() - - except TpzDRMError, e: - print str(e) - # tb.cleanup() - return 1 - - except Exception, e: - print str(e) - # tb.cleanup - return 1 - - return 0 - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/zipfilerugged.py b/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/zipfilerugged.py deleted file mode 100644 index adf3c53..0000000 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/zipfilerugged.py +++ /dev/null @@ -1,1400 +0,0 @@ -""" -Read and write ZIP files. -""" -import struct, os, time, sys, shutil -import binascii, cStringIO, stat -import io -import re - -try: - import zlib # We may need its compression method - crc32 = zlib.crc32 -except ImportError: - zlib = None - crc32 = binascii.crc32 - -__all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile", - "ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile" ] - -class BadZipfile(Exception): - pass - - -class LargeZipFile(Exception): - """ - Raised when writing a zipfile, the zipfile requires ZIP64 extensions - and those extensions are disabled. - """ - -error = BadZipfile # The exception raised by this module - -ZIP64_LIMIT = (1 << 31) - 1 -ZIP_FILECOUNT_LIMIT = 1 << 16 -ZIP_MAX_COMMENT = (1 << 16) - 1 - -# constants for Zip file compression methods -ZIP_STORED = 0 -ZIP_DEFLATED = 8 -# Other ZIP compression methods not supported - -# Below are some formats and associated data for reading/writing headers using -# the struct module. The names and structures of headers/records are those used -# in the PKWARE description of the ZIP file format: -# http://www.pkware.com/documents/casestudies/APPNOTE.TXT -# (URL valid as of January 2008) - -# The "end of central directory" structure, magic number, size, and indices -# (section V.I in the format document) -structEndArchive = "<4s4H2LH" -stringEndArchive = "PK\005\006" -sizeEndCentDir = struct.calcsize(structEndArchive) - -_ECD_SIGNATURE = 0 -_ECD_DISK_NUMBER = 1 -_ECD_DISK_START = 2 -_ECD_ENTRIES_THIS_DISK = 3 -_ECD_ENTRIES_TOTAL = 4 -_ECD_SIZE = 5 -_ECD_OFFSET = 6 -_ECD_COMMENT_SIZE = 7 -# These last two indices are not part of the structure as defined in the -# spec, but they are used internally by this module as a convenience -_ECD_COMMENT = 8 -_ECD_LOCATION = 9 - -# The "central directory" structure, magic number, size, and indices -# of entries in the structure (section V.F in the format document) -structCentralDir = "<4s4B4HL2L5H2L" -stringCentralDir = "PK\001\002" -sizeCentralDir = struct.calcsize(structCentralDir) - -# indexes of entries in the central directory structure -_CD_SIGNATURE = 0 -_CD_CREATE_VERSION = 1 -_CD_CREATE_SYSTEM = 2 -_CD_EXTRACT_VERSION = 3 -_CD_EXTRACT_SYSTEM = 4 -_CD_FLAG_BITS = 5 -_CD_COMPRESS_TYPE = 6 -_CD_TIME = 7 -_CD_DATE = 8 -_CD_CRC = 9 -_CD_COMPRESSED_SIZE = 10 -_CD_UNCOMPRESSED_SIZE = 11 -_CD_FILENAME_LENGTH = 12 -_CD_EXTRA_FIELD_LENGTH = 13 -_CD_COMMENT_LENGTH = 14 -_CD_DISK_NUMBER_START = 15 -_CD_INTERNAL_FILE_ATTRIBUTES = 16 -_CD_EXTERNAL_FILE_ATTRIBUTES = 17 -_CD_LOCAL_HEADER_OFFSET = 18 - -# The "local file header" structure, magic number, size, and indices -# (section V.A in the format document) -structFileHeader = "<4s2B4HL2L2H" -stringFileHeader = "PK\003\004" -sizeFileHeader = struct.calcsize(structFileHeader) - -_FH_SIGNATURE = 0 -_FH_EXTRACT_VERSION = 1 -_FH_EXTRACT_SYSTEM = 2 -_FH_GENERAL_PURPOSE_FLAG_BITS = 3 -_FH_COMPRESSION_METHOD = 4 -_FH_LAST_MOD_TIME = 5 -_FH_LAST_MOD_DATE = 6 -_FH_CRC = 7 -_FH_COMPRESSED_SIZE = 8 -_FH_UNCOMPRESSED_SIZE = 9 -_FH_FILENAME_LENGTH = 10 -_FH_EXTRA_FIELD_LENGTH = 11 - -# The "Zip64 end of central directory locator" structure, magic number, and size -structEndArchive64Locator = "<4sLQL" -stringEndArchive64Locator = "PK\x06\x07" -sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator) - -# The "Zip64 end of central directory" record, magic number, size, and indices -# (section V.G in the format document) -structEndArchive64 = "<4sQ2H2L4Q" -stringEndArchive64 = "PK\x06\x06" -sizeEndCentDir64 = struct.calcsize(structEndArchive64) - -_CD64_SIGNATURE = 0 -_CD64_DIRECTORY_RECSIZE = 1 -_CD64_CREATE_VERSION = 2 -_CD64_EXTRACT_VERSION = 3 -_CD64_DISK_NUMBER = 4 -_CD64_DISK_NUMBER_START = 5 -_CD64_NUMBER_ENTRIES_THIS_DISK = 6 -_CD64_NUMBER_ENTRIES_TOTAL = 7 -_CD64_DIRECTORY_SIZE = 8 -_CD64_OFFSET_START_CENTDIR = 9 - -def _check_zipfile(fp): - try: - if _EndRecData(fp): - return True # file has correct magic number - except IOError: - pass - return False - -def is_zipfile(filename): - """Quickly see if a file is a ZIP file by checking the magic number. - - The filename argument may be a file or file-like object too. - """ - result = False - try: - if hasattr(filename, "read"): - result = _check_zipfile(fp=filename) - else: - with open(filename, "rb") as fp: - result = _check_zipfile(fp) - except IOError: - pass - return result - -def _EndRecData64(fpin, offset, endrec): - """ - Read the ZIP64 end-of-archive records and use that to update endrec - """ - fpin.seek(offset - sizeEndCentDir64Locator, 2) - data = fpin.read(sizeEndCentDir64Locator) - sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data) - if sig != stringEndArchive64Locator: - return endrec - - if diskno != 0 or disks != 1: - raise BadZipfile("zipfiles that span multiple disks are not supported") - - # Assume no 'zip64 extensible data' - fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2) - data = fpin.read(sizeEndCentDir64) - sig, sz, create_version, read_version, disk_num, disk_dir, \ - dircount, dircount2, dirsize, diroffset = \ - struct.unpack(structEndArchive64, data) - if sig != stringEndArchive64: - return endrec - - # Update the original endrec using data from the ZIP64 record - endrec[_ECD_SIGNATURE] = sig - endrec[_ECD_DISK_NUMBER] = disk_num - endrec[_ECD_DISK_START] = disk_dir - endrec[_ECD_ENTRIES_THIS_DISK] = dircount - endrec[_ECD_ENTRIES_TOTAL] = dircount2 - endrec[_ECD_SIZE] = dirsize - endrec[_ECD_OFFSET] = diroffset - return endrec - - -def _EndRecData(fpin): - """Return data from the "End of Central Directory" record, or None. - - The data is a list of the nine items in the ZIP "End of central dir" - record followed by a tenth item, the file seek offset of this record.""" - - # Determine file size - fpin.seek(0, 2) - filesize = fpin.tell() - - # Check to see if this is ZIP file with no archive comment (the - # "end of central directory" structure should be the last item in the - # file if this is the case). - try: - fpin.seek(-sizeEndCentDir, 2) - except IOError: - return None - data = fpin.read() - if data[0:4] == stringEndArchive and data[-2:] == "\000\000": - # the signature is correct and there's no comment, unpack structure - endrec = struct.unpack(structEndArchive, data) - endrec=list(endrec) - - # Append a blank comment and record start offset - endrec.append("") - endrec.append(filesize - sizeEndCentDir) - - # Try to read the "Zip64 end of central directory" structure - return _EndRecData64(fpin, -sizeEndCentDir, endrec) - - # Either this is not a ZIP file, or it is a ZIP file with an archive - # comment. Search the end of the file for the "end of central directory" - # record signature. The comment is the last item in the ZIP file and may be - # up to 64K long. It is assumed that the "end of central directory" magic - # number does not appear in the comment. - maxCommentStart = max(filesize - (1 << 16) - sizeEndCentDir, 0) - fpin.seek(maxCommentStart, 0) - data = fpin.read() - start = data.rfind(stringEndArchive) - if start >= 0: - # found the magic number; attempt to unpack and interpret - recData = data[start:start+sizeEndCentDir] - endrec = list(struct.unpack(structEndArchive, recData)) - comment = data[start+sizeEndCentDir:] - # check that comment length is correct - if endrec[_ECD_COMMENT_SIZE] == len(comment): - # Append the archive comment and start offset - endrec.append(comment) - endrec.append(maxCommentStart + start) - - # Try to read the "Zip64 end of central directory" structure - return _EndRecData64(fpin, maxCommentStart + start - filesize, - endrec) - - # Unable to find a valid end of central directory structure - return - - -class ZipInfo (object): - """Class with attributes describing each file in the ZIP archive.""" - - __slots__ = ( - 'orig_filename', - 'filename', - 'date_time', - 'compress_type', - 'comment', - 'extra', - 'create_system', - 'create_version', - 'extract_version', - 'reserved', - 'flag_bits', - 'volume', - 'internal_attr', - 'external_attr', - 'header_offset', - 'CRC', - 'compress_size', - 'file_size', - '_raw_time', - ) - - def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): - self.orig_filename = filename # Original file name in archive - - # Terminate the file name at the first null byte. Null bytes in file - # names are used as tricks by viruses in archives. - null_byte = filename.find(chr(0)) - if null_byte >= 0: - filename = filename[0:null_byte] - # This is used to ensure paths in generated ZIP files always use - # forward slashes as the directory separator, as required by the - # ZIP format specification. - if os.sep != "/" and os.sep in filename: - filename = filename.replace(os.sep, "/") - - self.filename = filename # Normalized file name - self.date_time = date_time # year, month, day, hour, min, sec - # Standard values: - self.compress_type = ZIP_STORED # Type of compression for the file - self.comment = "" # Comment for each file - self.extra = "" # ZIP extra data - if sys.platform == 'win32': - self.create_system = 0 # System which created ZIP archive - else: - # Assume everything else is unix-y - self.create_system = 3 # System which created ZIP archive - self.create_version = 20 # Version which created ZIP archive - self.extract_version = 20 # Version needed to extract archive - self.reserved = 0 # Must be zero - self.flag_bits = 0 # ZIP flag bits - self.volume = 0 # Volume number of file header - self.internal_attr = 0 # Internal attributes - self.external_attr = 0 # External file attributes - # Other attributes are set by class ZipFile: - # header_offset Byte offset to the file header - # CRC CRC-32 of the uncompressed file - # compress_size Size of the compressed file - # file_size Size of the uncompressed file - - def FileHeader(self): - """Return the per-file header as a string.""" - dt = self.date_time - dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] - dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) - if self.flag_bits & 0x08: - # Set these to zero because we write them after the file data - CRC = compress_size = file_size = 0 - else: - CRC = self.CRC - compress_size = self.compress_size - file_size = self.file_size - - extra = self.extra - - if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT: - # File is larger than what fits into a 4 byte integer, - # fall back to the ZIP64 extension - fmt = '= 24: - counts = unpack('> 1) & 0x7FFFFFFF) ^ poly - else: - crc = ((crc >> 1) & 0x7FFFFFFF) - table[i] = crc - return table - crctable = _GenerateCRCTable() - - def _crc32(self, ch, crc): - """Compute the CRC32 primitive on one byte.""" - return ((crc >> 8) & 0xffffff) ^ self.crctable[(crc ^ ord(ch)) & 0xff] - - def __init__(self, pwd): - self.key0 = 305419896 - self.key1 = 591751049 - self.key2 = 878082192 - for p in pwd: - self._UpdateKeys(p) - - def _UpdateKeys(self, c): - self.key0 = self._crc32(c, self.key0) - self.key1 = (self.key1 + (self.key0 & 255)) & 4294967295 - self.key1 = (self.key1 * 134775813 + 1) & 4294967295 - self.key2 = self._crc32(chr((self.key1 >> 24) & 255), self.key2) - - def __call__(self, c): - """Decrypt a single character.""" - c = ord(c) - k = self.key2 | 2 - c = c ^ (((k * (k^1)) >> 8) & 255) - c = chr(c) - self._UpdateKeys(c) - return c - -class ZipExtFile(io.BufferedIOBase): - """File-like object for reading an archive member. - Is returned by ZipFile.open(). - """ - - # Max size supported by decompressor. - MAX_N = 1 << 31 - 1 - - # Read from compressed files in 4k blocks. - MIN_READ_SIZE = 4096 - - # Search for universal newlines or line chunks. - PATTERN = re.compile(r'^(?P[^\r\n]+)|(?P\n|\r\n?)') - - def __init__(self, fileobj, mode, zipinfo, decrypter=None): - self._fileobj = fileobj - self._decrypter = decrypter - - self._compress_type = zipinfo.compress_type - self._compress_size = zipinfo.compress_size - self._compress_left = zipinfo.compress_size - - if self._compress_type == ZIP_DEFLATED: - self._decompressor = zlib.decompressobj(-15) - self._unconsumed = '' - - self._readbuffer = '' - self._offset = 0 - - self._universal = 'U' in mode - self.newlines = None - - # Adjust read size for encrypted files since the first 12 bytes - # are for the encryption/password information. - if self._decrypter is not None: - self._compress_left -= 12 - - self.mode = mode - self.name = zipinfo.filename - - def readline(self, limit=-1): - """Read and return a line from the stream. - - If limit is specified, at most limit bytes will be read. - """ - - if not self._universal and limit < 0: - # Shortcut common case - newline found in buffer. - i = self._readbuffer.find('\n', self._offset) + 1 - if i > 0: - line = self._readbuffer[self._offset: i] - self._offset = i - return line - - if not self._universal: - return io.BufferedIOBase.readline(self, limit) - - line = '' - while limit < 0 or len(line) < limit: - readahead = self.peek(2) - if readahead == '': - return line - - # - # Search for universal newlines or line chunks. - # - # The pattern returns either a line chunk or a newline, but not - # both. Combined with peek(2), we are assured that the sequence - # '\r\n' is always retrieved completely and never split into - # separate newlines - '\r', '\n' due to coincidental readaheads. - # - match = self.PATTERN.search(readahead) - newline = match.group('newline') - if newline is not None: - if self.newlines is None: - self.newlines = [] - if newline not in self.newlines: - self.newlines.append(newline) - self._offset += len(newline) - return line + '\n' - - chunk = match.group('chunk') - if limit >= 0: - chunk = chunk[: limit - len(line)] - - self._offset += len(chunk) - line += chunk - - return line - - def peek(self, n=1): - """Returns buffered bytes without advancing the position.""" - if n > len(self._readbuffer) - self._offset: - chunk = self.read(n) - self._offset -= len(chunk) - - # Return up to 512 bytes to reduce allocation overhead for tight loops. - return self._readbuffer[self._offset: self._offset + 512] - - def readable(self): - return True - - def read(self, n=-1): - """Read and return up to n bytes. - If the argument is omitted, None, or negative, data is read and returned until EOF is reached.. - """ - - buf = '' - while n < 0 or n is None or n > len(buf): - data = self.read1(n) - if len(data) == 0: - return buf - - buf += data - - return buf - - def read1(self, n): - """Read up to n bytes with at most one read() system call.""" - - # Simplify algorithm (branching) by transforming negative n to large n. - if n < 0 or n is None: - n = self.MAX_N - - # Bytes available in read buffer. - len_readbuffer = len(self._readbuffer) - self._offset - - # Read from file. - if self._compress_left > 0 and n > len_readbuffer + len(self._unconsumed): - nbytes = n - len_readbuffer - len(self._unconsumed) - nbytes = max(nbytes, self.MIN_READ_SIZE) - nbytes = min(nbytes, self._compress_left) - - data = self._fileobj.read(nbytes) - self._compress_left -= len(data) - - if data and self._decrypter is not None: - data = ''.join(map(self._decrypter, data)) - - if self._compress_type == ZIP_STORED: - self._readbuffer = self._readbuffer[self._offset:] + data - self._offset = 0 - else: - # Prepare deflated bytes for decompression. - self._unconsumed += data - - # Handle unconsumed data. - if (len(self._unconsumed) > 0 and n > len_readbuffer and - self._compress_type == ZIP_DEFLATED): - data = self._decompressor.decompress( - self._unconsumed, - max(n - len_readbuffer, self.MIN_READ_SIZE) - ) - - self._unconsumed = self._decompressor.unconsumed_tail - if len(self._unconsumed) == 0 and self._compress_left == 0: - data += self._decompressor.flush() - - self._readbuffer = self._readbuffer[self._offset:] + data - self._offset = 0 - - # Read from buffer. - data = self._readbuffer[self._offset: self._offset + n] - self._offset += len(data) - return data - - - -class ZipFile: - """ Class with methods to open, read, write, close, list zip files. - - z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=False) - - file: Either the path to the file, or a file-like object. - If it is a path, the file will be opened and closed by ZipFile. - mode: The mode can be either read "r", write "w" or append "a". - compression: ZIP_STORED (no compression) or ZIP_DEFLATED (requires zlib). - allowZip64: if True ZipFile will create files with ZIP64 extensions when - needed, otherwise it will raise an exception when this would - be necessary. - - """ - - fp = None # Set here since __del__ checks it - - def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False): - """Open the ZIP file with mode read "r", write "w" or append "a".""" - if mode not in ("r", "w", "a"): - raise RuntimeError('ZipFile() requires mode "r", "w", or "a"') - - if compression == ZIP_STORED: - pass - elif compression == ZIP_DEFLATED: - if not zlib: - raise RuntimeError,\ - "Compression requires the (missing) zlib module" - else: - raise RuntimeError, "That compression method is not supported" - - self._allowZip64 = allowZip64 - self._didModify = False - self.debug = 0 # Level of printing: 0 through 3 - self.NameToInfo = {} # Find file info given name - self.filelist = [] # List of ZipInfo instances for archive - self.compression = compression # Method of compression - self.mode = key = mode.replace('b', '')[0] - self.pwd = None - self.comment = '' - - # Check if we were passed a file-like object - if isinstance(file, basestring): - self._filePassed = 0 - self.filename = file - modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'} - try: - self.fp = open(file, modeDict[mode]) - except IOError: - if mode == 'a': - mode = key = 'w' - self.fp = open(file, modeDict[mode]) - else: - raise - else: - self._filePassed = 1 - self.fp = file - self.filename = getattr(file, 'name', None) - - if key == 'r': - self._GetContents() - elif key == 'w': - pass - elif key == 'a': - try: # See if file is a zip file - self._RealGetContents() - # seek to start of directory and overwrite - self.fp.seek(self.start_dir, 0) - except BadZipfile: # file is not a zip file, just append - self.fp.seek(0, 2) - else: - if not self._filePassed: - self.fp.close() - self.fp = None - raise RuntimeError, 'Mode must be "r", "w" or "a"' - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.close() - - def _GetContents(self): - """Read the directory, making sure we close the file if the format - is bad.""" - try: - self._RealGetContents() - except BadZipfile: - if not self._filePassed: - self.fp.close() - self.fp = None - raise - - def _RealGetContents(self): - """Read in the table of contents for the ZIP file.""" - fp = self.fp - endrec = _EndRecData(fp) - if not endrec: - raise BadZipfile, "File is not a zip file" - if self.debug > 1: - print endrec - size_cd = endrec[_ECD_SIZE] # bytes in central directory - offset_cd = endrec[_ECD_OFFSET] # offset of central directory - self.comment = endrec[_ECD_COMMENT] # archive comment - - # "concat" is zero, unless zip was concatenated to another file - concat = endrec[_ECD_LOCATION] - size_cd - offset_cd - if endrec[_ECD_SIGNATURE] == stringEndArchive64: - # If Zip64 extension structures are present, account for them - concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator) - - if self.debug > 2: - inferred = concat + offset_cd - print "given, inferred, offset", offset_cd, inferred, concat - # self.start_dir: Position of start of central directory - self.start_dir = offset_cd + concat - fp.seek(self.start_dir, 0) - data = fp.read(size_cd) - fp = cStringIO.StringIO(data) - total = 0 - while total < size_cd: - centdir = fp.read(sizeCentralDir) - if centdir[0:4] != stringCentralDir: - raise BadZipfile, "Bad magic number for central directory" - centdir = struct.unpack(structCentralDir, centdir) - if self.debug > 2: - print centdir - filename = fp.read(centdir[_CD_FILENAME_LENGTH]) - # Create ZipInfo instance to store file information - x = ZipInfo(filename) - x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH]) - x.comment = fp.read(centdir[_CD_COMMENT_LENGTH]) - x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET] - (x.create_version, x.create_system, x.extract_version, x.reserved, - x.flag_bits, x.compress_type, t, d, - x.CRC, x.compress_size, x.file_size) = centdir[1:12] - x.volume, x.internal_attr, x.external_attr = centdir[15:18] - # Convert date/time code to (year, month, day, hour, min, sec) - x._raw_time = t - x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F, - t>>11, (t>>5)&0x3F, (t&0x1F) * 2 ) - - x._decodeExtra() - x.header_offset = x.header_offset + concat - x.filename = x._decodeFilename() - self.filelist.append(x) - self.NameToInfo[x.filename] = x - - # update total bytes read from central directory - total = (total + sizeCentralDir + centdir[_CD_FILENAME_LENGTH] - + centdir[_CD_EXTRA_FIELD_LENGTH] - + centdir[_CD_COMMENT_LENGTH]) - - if self.debug > 2: - print "total", total - - - def namelist(self): - """Return a list of file names in the archive.""" - l = [] - for data in self.filelist: - l.append(data.filename) - return l - - def infolist(self): - """Return a list of class ZipInfo instances for files in the - archive.""" - return self.filelist - - def printdir(self): - """Print a table of contents for the zip file.""" - print "%-46s %19s %12s" % ("File Name", "Modified ", "Size") - for zinfo in self.filelist: - date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6] - print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size) - - def testzip(self): - """Read all the files and check the CRC.""" - chunk_size = 2 ** 20 - for zinfo in self.filelist: - try: - # Read by chunks, to avoid an OverflowError or a - # MemoryError with very large embedded files. - f = self.open(zinfo.filename, "r") - while f.read(chunk_size): # Check CRC-32 - pass - except BadZipfile: - return zinfo.filename - - def getinfo(self, name): - """Return the instance of ZipInfo given 'name'.""" - info = self.NameToInfo.get(name) - if info is None: - raise KeyError( - 'There is no item named %r in the archive' % name) - - return info - - def setpassword(self, pwd): - """Set default password for encrypted files.""" - self.pwd = pwd - - def read(self, name, pwd=None): - """Return file bytes (as a string) for name.""" - return self.open(name, "r", pwd).read() - - def open(self, name, mode="r", pwd=None): - """Return file-like object for 'name'.""" - if mode not in ("r", "U", "rU"): - raise RuntimeError, 'open() requires mode "r", "U", or "rU"' - if not self.fp: - raise RuntimeError, \ - "Attempt to read ZIP archive that was already closed" - - # Only open a new file for instances where we were not - # given a file object in the constructor - if self._filePassed: - zef_file = self.fp - else: - zef_file = open(self.filename, 'rb') - - # Make sure we have an info object - if isinstance(name, ZipInfo): - # 'name' is already an info object - zinfo = name - else: - # Get info object for name - zinfo = self.getinfo(name) - - zef_file.seek(zinfo.header_offset, 0) - - # Skip the file header: - fheader = zef_file.read(sizeFileHeader) - if fheader[0:4] != stringFileHeader: - raise BadZipfile, "Bad magic number for file header" - - fheader = struct.unpack(structFileHeader, fheader) - fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) - if fheader[_FH_EXTRA_FIELD_LENGTH]: - zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) - - if fname != zinfo.orig_filename: - raise BadZipfile, \ - 'File name in directory "%s" and header "%s" differ.' % ( - zinfo.orig_filename, fname) - - # check for encrypted flag & handle password - is_encrypted = zinfo.flag_bits & 0x1 - zd = None - if is_encrypted: - if not pwd: - pwd = self.pwd - if not pwd: - raise RuntimeError, "File %s is encrypted, " \ - "password required for extraction" % name - - zd = _ZipDecrypter(pwd) - # The first 12 bytes in the cypher stream is an encryption header - # used to strengthen the algorithm. The first 11 bytes are - # completely random, while the 12th contains the MSB of the CRC, - # or the MSB of the file time depending on the header type - # and is used to check the correctness of the password. - bytes = zef_file.read(12) - h = map(zd, bytes[0:12]) - if zinfo.flag_bits & 0x8: - # compare against the file type from extended local headers - check_byte = (zinfo._raw_time >> 8) & 0xff - else: - # compare against the CRC otherwise - check_byte = (zinfo.CRC >> 24) & 0xff - if ord(h[11]) != check_byte: - raise RuntimeError("Bad password for file", name) - - return ZipExtFile(zef_file, mode, zinfo, zd) - - def extract(self, member, path=None, pwd=None): - """Extract a member from the archive to the current working directory, - using its full name. Its file information is extracted as accurately - as possible. `member' may be a filename or a ZipInfo object. You can - specify a different directory using `path'. - """ - if not isinstance(member, ZipInfo): - member = self.getinfo(member) - - if path is None: - path = os.getcwd() - - return self._extract_member(member, path, pwd) - - def extractall(self, path=None, members=None, pwd=None): - """Extract all members from the archive to the current working - directory. `path' specifies a different directory to extract to. - `members' is optional and must be a subset of the list returned - by namelist(). - """ - if members is None: - members = self.namelist() - - for zipinfo in members: - self.extract(zipinfo, path, pwd) - - def _extract_member(self, member, targetpath, pwd): - """Extract the ZipInfo object 'member' to a physical - file on the path targetpath. - """ - # build the destination pathname, replacing - # forward slashes to platform specific separators. - # Strip trailing path separator, unless it represents the root. - if (targetpath[-1:] in (os.path.sep, os.path.altsep) - and len(os.path.splitdrive(targetpath)[1]) > 1): - targetpath = targetpath[:-1] - - # don't include leading "/" from file name if present - if member.filename[0] == '/': - targetpath = os.path.join(targetpath, member.filename[1:]) - else: - targetpath = os.path.join(targetpath, member.filename) - - targetpath = os.path.normpath(targetpath) - - # Create all upper directories if necessary. - upperdirs = os.path.dirname(targetpath) - if upperdirs and not os.path.exists(upperdirs): - os.makedirs(upperdirs) - - if member.filename[-1] == '/': - if not os.path.isdir(targetpath): - os.mkdir(targetpath) - return targetpath - - source = self.open(member, pwd=pwd) - target = file(targetpath, "wb") - shutil.copyfileobj(source, target) - source.close() - target.close() - - return targetpath - - def _writecheck(self, zinfo): - """Check for errors before writing a file to the archive.""" - if zinfo.filename in self.NameToInfo: - if self.debug: # Warning for duplicate names - print "Duplicate name:", zinfo.filename - if self.mode not in ("w", "a"): - raise RuntimeError, 'write() requires mode "w" or "a"' - if not self.fp: - raise RuntimeError, \ - "Attempt to write ZIP archive that was already closed" - if zinfo.compress_type == ZIP_DEFLATED and not zlib: - raise RuntimeError, \ - "Compression requires the (missing) zlib module" - if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED): - raise RuntimeError, \ - "That compression method is not supported" - if zinfo.file_size > ZIP64_LIMIT: - if not self._allowZip64: - raise LargeZipFile("Filesize would require ZIP64 extensions") - if zinfo.header_offset > ZIP64_LIMIT: - if not self._allowZip64: - raise LargeZipFile("Zipfile size would require ZIP64 extensions") - - def write(self, filename, arcname=None, compress_type=None): - """Put the bytes from filename into the archive under the name - arcname.""" - if not self.fp: - raise RuntimeError( - "Attempt to write to ZIP archive that was already closed") - - st = os.stat(filename) - isdir = stat.S_ISDIR(st.st_mode) - mtime = time.localtime(st.st_mtime) - date_time = mtime[0:6] - # Create ZipInfo instance to store file information - if arcname is None: - arcname = filename - arcname = os.path.normpath(os.path.splitdrive(arcname)[1]) - while arcname[0] in (os.sep, os.altsep): - arcname = arcname[1:] - if isdir: - arcname += '/' - zinfo = ZipInfo(arcname, date_time) - zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes - if compress_type is None: - zinfo.compress_type = self.compression - else: - zinfo.compress_type = compress_type - - zinfo.file_size = st.st_size - zinfo.flag_bits = 0x00 - zinfo.header_offset = self.fp.tell() # Start of header bytes - - self._writecheck(zinfo) - self._didModify = True - - if isdir: - zinfo.file_size = 0 - zinfo.compress_size = 0 - zinfo.CRC = 0 - self.filelist.append(zinfo) - self.NameToInfo[zinfo.filename] = zinfo - self.fp.write(zinfo.FileHeader()) - return - - with open(filename, "rb") as fp: - # Must overwrite CRC and sizes with correct data later - zinfo.CRC = CRC = 0 - zinfo.compress_size = compress_size = 0 - zinfo.file_size = file_size = 0 - self.fp.write(zinfo.FileHeader()) - if zinfo.compress_type == ZIP_DEFLATED: - cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, - zlib.DEFLATED, -15) - else: - cmpr = None - while 1: - buf = fp.read(1024 * 8) - if not buf: - break - file_size = file_size + len(buf) - CRC = crc32(buf, CRC) & 0xffffffff - if cmpr: - buf = cmpr.compress(buf) - compress_size = compress_size + len(buf) - self.fp.write(buf) - if cmpr: - buf = cmpr.flush() - compress_size = compress_size + len(buf) - self.fp.write(buf) - zinfo.compress_size = compress_size - else: - zinfo.compress_size = file_size - zinfo.CRC = CRC - zinfo.file_size = file_size - # Seek backwards and write CRC and file sizes - position = self.fp.tell() # Preserve current position in file - self.fp.seek(zinfo.header_offset + 14, 0) - self.fp.write(struct.pack(" ZIP64_LIMIT \ - or zinfo.compress_size > ZIP64_LIMIT: - extra.append(zinfo.file_size) - extra.append(zinfo.compress_size) - file_size = 0xffffffff - compress_size = 0xffffffff - else: - file_size = zinfo.file_size - compress_size = zinfo.compress_size - - if zinfo.header_offset > ZIP64_LIMIT: - extra.append(zinfo.header_offset) - header_offset = 0xffffffffL - else: - header_offset = zinfo.header_offset - - extra_data = zinfo.extra - if extra: - # Append a ZIP64 field to the extra's - extra_data = struct.pack( - '>sys.stderr, (structCentralDir, - stringCentralDir, create_version, - zinfo.create_system, extract_version, zinfo.reserved, - zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, - zinfo.CRC, compress_size, file_size, - len(zinfo.filename), len(extra_data), len(zinfo.comment), - 0, zinfo.internal_attr, zinfo.external_attr, - header_offset) - raise - self.fp.write(centdir) - self.fp.write(filename) - self.fp.write(extra_data) - self.fp.write(zinfo.comment) - - pos2 = self.fp.tell() - # Write end-of-zip-archive record - centDirCount = count - centDirSize = pos2 - pos1 - centDirOffset = pos1 - if (centDirCount >= ZIP_FILECOUNT_LIMIT or - centDirOffset > ZIP64_LIMIT or - centDirSize > ZIP64_LIMIT): - # Need to write the ZIP64 end-of-archive records - zip64endrec = struct.pack( - structEndArchive64, stringEndArchive64, - 44, 45, 45, 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset) - self.fp.write(zip64endrec) - - zip64locrec = struct.pack( - structEndArchive64Locator, - stringEndArchive64Locator, 0, pos2, 1) - self.fp.write(zip64locrec) - centDirCount = min(centDirCount, 0xFFFF) - centDirSize = min(centDirSize, 0xFFFFFFFF) - centDirOffset = min(centDirOffset, 0xFFFFFFFF) - - # check for valid comment length - if len(self.comment) >= ZIP_MAX_COMMENT: - if self.debug > 0: - msg = 'Archive comment is too long; truncating to %d bytes' \ - % ZIP_MAX_COMMENT - self.comment = self.comment[:ZIP_MAX_COMMENT] - - endrec = struct.pack(structEndArchive, stringEndArchive, - 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset, len(self.comment)) - self.fp.write(endrec) - self.fp.write(self.comment) - self.fp.flush() - - if not self._filePassed: - self.fp.close() - self.fp = None - - -class PyZipFile(ZipFile): - """Class to create ZIP archives with Python library files and packages.""" - - def writepy(self, pathname, basename = ""): - """Add all files from "pathname" to the ZIP archive. - - If pathname is a package directory, search the directory and - all package subdirectories recursively for all *.py and enter - the modules into the archive. If pathname is a plain - directory, listdir *.py and enter all modules. Else, pathname - must be a Python *.py file and the module will be put into the - archive. Added modules are always module.pyo or module.pyc. - This method will compile the module.py into module.pyc if - necessary. - """ - dir, name = os.path.split(pathname) - if os.path.isdir(pathname): - initname = os.path.join(pathname, "__init__.py") - if os.path.isfile(initname): - # This is a package directory, add it - if basename: - basename = "%s/%s" % (basename, name) - else: - basename = name - if self.debug: - print "Adding package in", pathname, "as", basename - fname, arcname = self._get_codename(initname[0:-3], basename) - if self.debug: - print "Adding", arcname - self.write(fname, arcname) - dirlist = os.listdir(pathname) - dirlist.remove("__init__.py") - # Add all *.py files and package subdirectories - for filename in dirlist: - path = os.path.join(pathname, filename) - root, ext = os.path.splitext(filename) - if os.path.isdir(path): - if os.path.isfile(os.path.join(path, "__init__.py")): - # This is a package directory, add it - self.writepy(path, basename) # Recursive call - elif ext == ".py": - fname, arcname = self._get_codename(path[0:-3], - basename) - if self.debug: - print "Adding", arcname - self.write(fname, arcname) - else: - # This is NOT a package directory, add its files at top level - if self.debug: - print "Adding files from directory", pathname - for filename in os.listdir(pathname): - path = os.path.join(pathname, filename) - root, ext = os.path.splitext(filename) - if ext == ".py": - fname, arcname = self._get_codename(path[0:-3], - basename) - if self.debug: - print "Adding", arcname - self.write(fname, arcname) - else: - if pathname[-3:] != ".py": - raise RuntimeError, \ - 'Files added with writepy() must end with ".py"' - fname, arcname = self._get_codename(pathname[0:-3], basename) - if self.debug: - print "Adding file", arcname - self.write(fname, arcname) - - def _get_codename(self, pathname, basename): - """Return (filename, archivename) for the path. - - Given a module name path, return the correct file path and - archive name, compiling if necessary. For example, given - /python/lib/string, return (/python/lib/string.pyc, string). - """ - file_py = pathname + ".py" - file_pyc = pathname + ".pyc" - file_pyo = pathname + ".pyo" - if os.path.isfile(file_pyo) and \ - os.stat(file_pyo).st_mtime >= os.stat(file_py).st_mtime: - fname = file_pyo # Use .pyo file - elif not os.path.isfile(file_pyc) or \ - os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime: - import py_compile - if self.debug: - print "Compiling", file_py - try: - py_compile.compile(file_py, file_pyc, None, True) - except py_compile.PyCompileError,err: - print err.msg - fname = file_pyc - else: - fname = file_pyc - archivename = os.path.split(fname)[1] - if basename: - archivename = "%s/%s" % (basename, archivename) - return (fname, archivename) - - -def main(args = None): - import textwrap - USAGE=textwrap.dedent("""\ - Usage: - zipfile.py -l zipfile.zip # Show listing of a zipfile - zipfile.py -t zipfile.zip # Test if a zipfile is valid - zipfile.py -e zipfile.zip target # Extract zipfile into target dir - zipfile.py -c zipfile.zip src ... # Create zipfile from sources - """) - if args is None: - args = sys.argv[1:] - - if not args or args[0] not in ('-l', '-c', '-e', '-t'): - print USAGE - sys.exit(1) - - if args[0] == '-l': - if len(args) != 2: - print USAGE - sys.exit(1) - zf = ZipFile(args[1], 'r') - zf.printdir() - zf.close() - - elif args[0] == '-t': - if len(args) != 2: - print USAGE - sys.exit(1) - zf = ZipFile(args[1], 'r') - zf.testzip() - print "Done testing" - - elif args[0] == '-e': - if len(args) != 3: - print USAGE - sys.exit(1) - - zf = ZipFile(args[1], 'r') - out = args[2] - for path in zf.namelist(): - if path.startswith('./'): - tgt = os.path.join(out, path[2:]) - else: - tgt = os.path.join(out, path) - - tgtdir = os.path.dirname(tgt) - if not os.path.exists(tgtdir): - os.makedirs(tgtdir) - with open(tgt, 'wb') as fp: - fp.write(zf.read(path)) - zf.close() - - elif args[0] == '-c': - if len(args) < 3: - print USAGE - sys.exit(1) - - def addToZip(zf, path, zippath): - if os.path.isfile(path): - zf.write(path, zippath, ZIP_DEFLATED) - elif os.path.isdir(path): - for nm in os.listdir(path): - addToZip(zf, - os.path.join(path, nm), os.path.join(zippath, nm)) - # else: ignore - - zf = ZipFile(args[1], 'w', allowZip64=True) - for src in args[2:]: - addToZip(zf, src, os.path.basename(src)) - - zf.close() - -if __name__ == "__main__": - main() diff --git a/DeDRM_Applications/Windows/DeDRM_ReadMe.txt b/DeDRM_Applications/Windows/DeDRM_ReadMe.txt deleted file mode 100644 index 2c73c84..0000000 --- a/DeDRM_Applications/Windows/DeDRM_ReadMe.txt +++ /dev/null @@ -1,50 +0,0 @@ -ReadMe_DeDRM_v5.4.1_WinApp ------------------------ - -DeDRM_v5.4.1_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program that remembers preferences and settings. - -It should work out of the box with Kindle for PC ebooks and Adobe Adept epub and pdf ebooks. - -To remove the DRM from standalone Kindle ebooks, eReader pdb ebooks, Barnes and Noble epubs, and Mobipocket ebooks requires the user to double-click the DeDRM_Drop_Target and set some additional Preferences including: - -eInk Kindle: 16 digit Serial Number -Barnes & Noble: key file (bnepubkey.b64) -eReader Social DRM: Name:Last 8 digits of CC number -MobiPocket: 10 digit PID - -Once these preferences have been set, the user can simply drag and drop ebooks onto the DeDRM_Drop_Target to remove the DRM. - -This program requires that a 32 bit version of Python 2.X (tested with Python 2.5 through Python 2.7) and PyCrypto be installed on your computer before it will work. See below for where to get theese programs for Windows. - -Installation ------------- - -0. If you don't already have a correct version of Python and PyCrypto installed, follow the "Installing Python on Windows" and "Installing PyCrypto on Windows" sections below before continuing. - -1. Drag the DeDRM_5.4.1 folder from tools_v5.4.1/DeDRM_Applications/Windows to your "My Documents" folder. - -2. Open the DeDRM_5.4.1 folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop. - -3. To set the preferences simply double-click on your just created short-cut. - - - -Installing Python on Windows ----------------------------- -I strongly recommend fully installing ActiveState’s Active Python, free Community Edition for Windows (x86) 32 bits. This is a free, full version of the Python. It comes with some important additional modules that are not included in the bare-bones version from www.python.org unless you choose to install everything. - -1. Download ActivePython 2.7.X for Windows (x86) (or later 2.7 version for Windows (x86) ) from http://www.activestate.com/activepython/downloads. Do not download the ActivePython 2.7.X for Windows (64-bit, x64) verson, even if you are running 64-bit Windows. - -2. When it has finished downloading, run the installer. Accept the default options. - - -Installing PyCrypto on Windows ------------------------------- -PyCrypto is a set of encryption/decryption routines that work with Python. The sources are freely available, and compiled versions are available from several sources. You must install a version that is for 32-bit Windows and Python 2.7. I recommend the installer linked from Michael Foord’s blog. - -1. Download PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto - -2. When it has finished downloading, unzip it. This will produce a file “pycrypto-2.1.0.win32-py2.7.exe”. - -3. Double-click “pycrypto-2.1.0.win32-py2.7.exe” to run it. Accept the default options. - diff --git a/DeDRM_Macintosh_Application/DeDRM ReadMe.rtf b/DeDRM_Macintosh_Application/DeDRM ReadMe.rtf new file mode 100644 index 0000000..8813e94 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM ReadMe.rtf @@ -0,0 +1,76 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Verdana;\f2\fnil\fcharset128 HiraKakuProN-W3; +} +{\colortbl;\red255\green255\blue255;} +\paperw11900\paperh16840\margl1440\margr1440\vieww18160\viewh16520\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc + +\f0\b\fs24 \cf0 DeDRM ReadMe +\b0 \ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural +\cf0 \ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qj +\cf0 DeDRM is an application that packs all of the python dm-removal software into one easy to use program that remembers preferences and settings.\ +It works without manual configuration with Kindle for Mac ebooks and Adobe Adept (including nook) ePub and PDF ebooks.\ +\ +To remove the DRM of Kindle ebooks from eInk Kindles, eReader pdb ebooks, Barnes & Noble ePubs, or Mobipocket ebooks, you must first run DeDRM application (by double-clicking it) and set some additional Preferences including:\ +\ +eInk Kindle (not Kindle Fire): 16 digit Serial Number\ +Barnes & Noble ePub: Name and CC number or key file (bnepubkey.b64)\ +eReader: Name and last 8 digits of CC number\ +Mobipocket: 10 digit PID\ +\ +A final preference is the destination folder for the DRM-free copies of your ebooks that the application produces. This can be either the same folder as the original ebook, or a folder of your choice.\ +\ +Once these preferences have been set, you can drag and drop ebooks (or folders of ebooks) onto the DeDRM droplet to remove the DRM.\ +\ +This program requires Mac OS X 10.4 or above. It will not work on Mac OS X 10.3 or earlier.\ +\ +\ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural + +\b \cf0 Installation +\b0 \ +Mac OS X 10.4 +\i only +\i0 : You +\i must +\i0 first install Python 2.7.3 from http://python.org/. At the time of writing, the direct download link is http://www.python.org/ftp/python/2.7.3/python-2.7.3-macosx10.3.dmg.\ +\ +Mac OS X 10.5 and above: You do +\i not +\i0 need to install Python.\ +\ +Drag the DeDRM application from from the DeDRM_Application_Macintosh folder (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\ +\ +\ + +\b Use +\b0 \ +1. To set the preferences, double-click the application and follow the instructions in the dialogs.\ +2. Drag & Drop DRMed ebooks or folders of DRMed ebooks onto the application icon when it is not running.\ +\ +\ + +\b Troubleshooting\ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural + +\b0 \cf0 A log is created on your desktop (DeDRM.log) containing detailed information from all the scripts. If you have any problems decrypting your ebooks, copy the contents of this log in a comment at Apprentice Alf's blog.\ +http://apprenticealf.wordpress.com/\ +\ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural + +\b \cf0 Credits\ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural + +\b0 \cf0 The mobidedrm and erdr2pml scripts were created by The Dark Reverser\ +The i +\f1 \CocoaLigature0 gnobleepub, ignoblekeygen, ineptepub and adobe key scripts were created by +\f0 \CocoaLigature1 i +\f2 \CocoaLigature0 \uc0\u9829 +\f1 cabbages\ +The k4mobidedrm script and supporting scripts were written by some_updates with help from DiapDealer and Apprentice Alf, based on code by Bart Simpson (aka Skindle), CMBDTC and clarknova \ +The alfcrypto library was created by some_updates\ +The ePub encryption detection script was adapted by Apprentice Alf from a script by Paul Durrant\ +The DeDRM all-in-one AppleScript was created by Apprentice Alf\ +} \ No newline at end of file diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist similarity index 82% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Info.plist rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist index d73d063..d8c82fa 100644 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Info.plist +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist @@ -24,17 +24,19 @@ CFBundleExecutable droplet CFBundleGetInfoString - DeDRM 5.4.1. AppleScript written 2010–2012 by Apprentice Alf and others. + DeDRM AppleScript 6.0.4. Written 2010–2013 by Apprentice Alf and others. CFBundleIconFile DeDRM + CFBundleIdentifier + com.apple.ScriptEditor.id.707CCCD5-0C6C-4BEB-B67C-B6E866ADE85A CFBundleInfoDictionaryVersion 6.0 CFBundleName - DeDRM 5.4.1 + DeDRM CFBundlePackageType APPL CFBundleShortVersionString - 5.4.1 + 6.0.4 CFBundleSignature dplt LSRequiresCarbon @@ -48,9 +50,9 @@ name ScriptWindowState positionOfDivider - 0 + 0.0 savedFrame - 287 405 800 473 0 0 1440 878 + 85 100 1002 753 0 0 1440 878 selectedTabView event log diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/MacOS/droplet b/DeDRM_Macintosh_Application/DeDRM.app/Contents/MacOS/droplet new file mode 100755 index 0000000..5436333 Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/MacOS/droplet differ diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/PkgInfo b/DeDRM_Macintosh_Application/DeDRM.app/Contents/PkgInfo similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/PkgInfo rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/PkgInfo diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress Source.zip b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress Source.zip similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress Source.zip rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress Source.zip diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress new file mode 100755 index 0000000..ca47eb4 Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress differ diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/DeDRM Progress.icns b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/DeDRM Progress.icns similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/DeDRM Progress.icns rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/DeDRM Progress.icns diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM.icns b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM.icns similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM.icns rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM.icns diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Adobe Digital Editions Key_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Adobe Digital Editions Key_Help.htm new file mode 100644 index 0000000..b258afe --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Adobe Digital Editions Key_Help.htm @@ -0,0 +1,60 @@ + + + + + + +Managing Adobe Digital Editions Keys + + + + + +

Managing Adobe Digital Editions Keys

+ + +

If you have upgraded from an earlier version of the plugin, any existing Adobe Digital Editions keys will have been automatically imported, so you might not need to do any more configuration. In addition, on Windows and Mac, the default Adobe Digital Editions key is added the first time the plugin is run. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog prompting you to enter a key name for the default Adobe Digital Editions key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys.
  • +
+ +

Click the OK button to create and store the Adobe Digital Editions key for the current installation of Adobe Digital Editions. Or Cancel if you don’t want to create the key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Linux Users: WINEPREFIX

+ +

Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Adobe Digital Editions under Wine, and your wine installation containing Adobe Digital Editions isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.der’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the adobekey.pyw script running under Wine on Linux systems.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes will only be saved permanently when you click OK in the main configuration dialog.

+ + + + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Barnes and Noble Key_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Barnes and Noble Key_Help.htm new file mode 100644 index 0000000..ac1b693 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Barnes and Noble Key_Help.htm @@ -0,0 +1,57 @@ + + + + + + +Managing Barnes and Noble Keys + + + + + +

Managing Barnes and Noble Keys

+ + +

If you have upgraded from an earlier version of the plugin, any existing Barnes and Noble keys will have been automatically imported, so you might not need to do any more configuration. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.
  • +
  • Your Name: This is the name used by Barnes and Noble to generate your encryption key. Seemingly at random, Barnes and Noble choose one of three places from which to take this name. Most commonly, it’s your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. Sometimes it is the the name used in the default shipping address, and sometimes it’s the name listed for the active credit card. If these names are different in your Barnes and Noble account preferences, I suggest creating one key for each version of your name. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that’s stored in the preferences.
  • +
  • Credit Card#: this is the default credit card number that was on file with Barnes and Noble at the time of download of the ebook to be de-DRMed. Just enter the 16 (15 for American Express) digits. As with the name, this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that’s stored in the preferences.
  • +
+ +

Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.b64’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.b64’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the original i♥cabbages script.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_EInk Kindle Serial Number_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_EInk Kindle Serial Number_Help.htm new file mode 100644 index 0000000..e79abd7 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_EInk Kindle Serial Number_Help.htm @@ -0,0 +1,43 @@ + + + + + + +Managing eInk Kindle serial numbers + + + + + +

Managing eInk Kindle serial numbers

+ +

If you have upgraded from an earlier version of the plugin, any existing eInk Kindle serial numbers will have been automatically imported, so you might not need to do any more configuration.

+ +

Please note that Kindle serial numbers are only valid keys for eInk Kindles like the Kindle Touch and PaperWhite. The Kindle Fire and Fire HD do not use their serial number for DRM and it is useless to enter those serial numbers.

+ +

Creating New Kindle serial numbers:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering a new Kindle serial number.

+
    +
  • Eink Kindle Serial Number: this is the unique serial number of your device. It usually starts with a ‘B’ or a ‘9’ and is sixteen characters long. For a reference of where to find serial numbers and their ranges, please refere to this mobileread wiki page.
  • +
+ +

Click the OK button to save the serial number. Or Cancel if you didn’t want to enter a serial number.

+ +

Deleting Kindle serial numbers:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted Kindle serial number from the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Once done creating/deleting serial numbers, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm new file mode 100644 index 0000000..f497a0b --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm @@ -0,0 +1,73 @@ + + + + + + +DeDRM Plugin Configuration + + + + + +

DeDRM Plugin (v6.0.0)

+ +

This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.

+ +

Installation

+

You have obviously managed to install the plugin, as otherwise you wouldn’t be reading this help file. However, you should also delete any older DeDRM plugins, as this DeDRM plugin replaces the five older plugins: Kindle and Mobipocket DeDRM (K4MobiDeDRM), Ignoble Epub DeDRM (ignobleepub), Inept Epub DeDRM (ineptepub), Inept PDF DeDRM (ineptepub) and eReader PDB 2 PML (eReaderPDB2PML).

+ +

Configuration

+

On Windows and Mac, the keys for ebooks downloaded for Kindle for Mac/PC and Adobe Digital Editions are automatically generated. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC and/or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. On Linux, keys for Kindle for PC and Adobe Digital Editions need to be generated separately (see the Linux section below)

+ +

If you have other DRMed ebooks, you will need to enter extra configuration information. The buttons in this dialog will open individual configuration dialogs that will allow you to enter the needed information, depending on the type and source of your DRMed eBooks. Additional help on the information required is available in each of the the dialogs.

+ +

If you have used previous versions of the various DeDRM plugins on this machine, you may find that some of the configuration dialogs already contain the information you entered through those previous plugins.

+ +

When you have finished entering your configuration information, you must click the OK button to save it. If you click the Cancel button, all your changes in all the configuration dialogs will be lost.

+ +

Troubleshooting:

+ +

If you find that it’s not working for you , you can save a lot of time by trying to add the ebook to Calibre in debug mode. This will print out a lot of helpful info that can be copied into any online help requests.

+ +

Open a command prompt (terminal window) and type "calibre-debug -g" (without the quotes). Calibre will launch, and you can can add the problem ebook the usual way. The debug info will be output to the original command prompt (terminal window). Copy the resulting output and paste it into the comment you make at my blog.

+

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 find the option to install the command line tools.

+ +

Credits:

+
    +
  • The Dark Reverser for the Mobipocket and eReader scripts
  • +
  • i♥cabbages for the Adobe Digital Editions scripts
  • +
  • Skindle aka Bart Simpson for the Amazon Kindle for PC script
  • +
  • CMBDTC for Amazon Topaz DRM removal script
  • +
  • some_updates, clarknova and Bart Simpson for Amazon Topaz conversion scripts
  • +
  • DiapDealer for the first calibre plugin versions of the tools
  • +
  • some_updates, DiapDealer, Apprentice Alf and mdlnx for Amazon Kindle/Mobipocket tools
  • +
  • some_updates for the DeDRM all-in-one Python tool
  • +
  • Apprentice Alf for the DeDRM all-in-one AppleScript tool
  • +
  • Apprentice Alf for the DeDRM all-in-one calibre plugin
  • +
  • And probably many more.
  • +
+ +

For additional help read the FAQs at Apprentice Alf’s Blog and ask questions in the comments section of the first post.

+ +

Linux Systems Only

+

Generating decryption keys for Adobe Digital Editions and Kindle for PC

+

If you install Kindle for PC and/or Adobe Digital Editions in Wine, you will be able to download DRMed ebooks to them under Wine. To be able to remove the DRM, you will need to generate key files and add them in the plugin's customisation dialogs.

+ +

To generate the key files you will need to install Python and PyCrypto under the same Wine setup as your Kindle for PC and/or Adobe Digital Editions installations. (Kindle for PC, Python and Pycrypto installation instructions in the ReadMe.)

+ +

Once everything's installed under Wine, you'll need to run the adobekey.pyw script (for Adobe Digital Editions) and kindlekey.pyw (For Kindle for PC) using the python installation in your Wine system. The scripts can be found in Other_Tools/Key_Retrieval_Scripts.

+ +

Each script will create a key file in the same folder as the script. Copy the key files to your Linux system and then load the key files using the Adobe Digital Editions ebooks dialog and the Kindle for Mac/PC ebooks dialog.

+ + + + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Kindle for Mac and PC Key_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Kindle for Mac and PC Key_Help.htm new file mode 100644 index 0000000..35f1a50 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Kindle for Mac and PC Key_Help.htm @@ -0,0 +1,59 @@ + + + + + + +Managing Kindle for Mac/PC Keys + + + + + +

Managing Kindle for Mac/PC Keys

+ + +

If you have upgraded from an earlier version of the plugin, any existing Kindle for Mac/PC keys will have been automatically imported, so you might not need to do any more configuration. In addition, on Windows and Mac, the default Kindle for Mac/PC key is added the first time the plugin is run. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog prompting you to enter a key name for the default Kindle for Mac/PC key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys.
  • +
+ +

Click the OK button to create and store the Kindle for Mac/PC key for the current installation of Kindle for Mac/PC. Or Cancel if you don’t want to create the key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Linux Users: WINEPREFIX

+ +

Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Kindle for PC under Wine, and your wine installation containing Kindle for PC isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.k4i’ key files. Key files might come from being exported from this plugin, or may have been generated using the kindlekey.pyw script running under Wine on Linux systems.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Mobipocket PID_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Mobipocket PID_Help.htm new file mode 100644 index 0000000..00aeeca --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Mobipocket PID_Help.htm @@ -0,0 +1,42 @@ + + + + + + +Managing Mobipocket PIDs + + + + + +

Managing Mobipocket PIDs

+ +

If you have upgraded from an earlier version of the plugin, any existing Mobipocket PIDs will have been automatically imported, so you might not need to do any more configuration.

+ + +

Creating New Mobipocket PIDs:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering a new Mobipocket PID.

+
    +
  • PID: this is a PID used to decrypt your Mobipocket ebooks. It is eight or ten characters long. Mobipocket PIDs are usualy displayed in the About screen of your Mobipocket device.
  • +
+ +

Click the OK button to save the PID. Or Cancel if you didn’t want to enter a PID.

+ +

Deleting Mobipocket PIDs:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted Mobipocket PID from the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Once done creating/deleting PIDs, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_eReader Key_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_eReader Key_Help.htm new file mode 100644 index 0000000..c1c78ad --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_eReader Key_Help.htm @@ -0,0 +1,56 @@ + + + + + + +Managing eReader Keys + + + + + +

Managing eReader Keys

+ +

If you have upgraded from an earlier version of the plugin, any existing eReader (Fictionwise ‘.pdb’) keys will have been automatically imported, so you might not need to do any more configuration. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.
  • +
  • Your Name: This is the name used by Fictionwise to generate your encryption key. Since Fictionwise has now closed down, you might not have easy access to this. It was often the name on the Credit Card used at Fictionwise.
  • +
  • Credit Card#: this is the default credit card number that was on file with Fictionwise at the time of download of the ebook to be de-DRMed. Just enter the last 8 digits of the number. As with the name, this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that’s stored in the preferences.
  • +
+ +

Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.b63’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.b63’ key files that have previously been exported.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt new file mode 100644 index 0000000..a99828f Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py new file mode 100644 index 0000000..1c931a4 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement +__license__ = 'GPL v3' +__docformat__ = 'restructuredtext en' + + +# Released under the terms of the GNU General Public Licence, version 3 +# +# +# Requires Calibre version 0.7.55 or higher. +# +# All credit given to i♥cabbages and The Dark Reverser for the original standalone scripts. +# We had the much easier job of converting them to a calibre plugin. +# +# This plugin is meant to decrypt eReader PDBs, Adobe Adept ePubs, Barnes & Noble ePubs, +# Adobe Adept PDFs, Amazon Kindle and Mobipocket files without having to +# install any dependencies... other than having calibre installed, of course. +# +# Configuration: +# Check out the plugin's configuration settings by clicking the "Customize plugin" +# button when you have the "DeDRM" plugin highlighted (under Preferences-> +# Plugins->File type plugins). Once you have the configuration dialog open, you'll +# see a Help link on the top right-hand side. +# +# Revision history: +# 6.0.0 - Initial release +# 6.0.1 - Bug Fixes for Windows App, Kindle for Mac and Windows Adobe Digital Editions +# 6.0.2 - Restored call to Wine to get Kindle for PC keys + +""" +Decrypt DRMed ebooks. +""" + +PLUGIN_NAME = u"DeDRM" +PLUGIN_VERSION_TUPLE = (6, 0, 2) +PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE]) +# Include an html helpfile in the plugin's zipfile with the following name. +RESOURCE_NAME = PLUGIN_NAME + '_Help.htm' + +import sys, os, re +import time +import zipfile +import traceback +from zipfile import ZipFile + +class DeDRMError(Exception): + pass + +from calibre.customize import FileTypePlugin +from calibre.constants import iswindows, isosx +from calibre.gui2 import is_ok_to_use_qt +from calibre.utils.config import config_dir + + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get safely +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +class DeDRM(FileTypePlugin): + name = PLUGIN_NAME + description = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts." + supported_platforms = ['linux', 'osx', 'windows'] + author = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages" + version = PLUGIN_VERSION_TUPLE + minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions. + file_types = set(['epub','pdf','pdb','prc','mobi','azw','azw1','azw3','azw4','tpz']) + on_import = True + priority = 600 + + def initialize(self): + # convert old preferences, if necessary. + try: + from calibre_plugins.dedrm.prefs import convertprefs + convertprefs() + except: + traceback.print_exc() + + """ + Dynamic modules can't be imported/loaded from a zipfile... so this routine + runs whenever the plugin gets initialized. This will extract the appropriate + library for the target OS and copy it to the 'alfcrypto' subdirectory of + calibre's configuration directory. That 'alfcrypto' directory is then + inserted into the syspath (as the very first entry) in the run function + so the CDLL stuff will work in the alfcrypto.py script. + """ + try: + if iswindows: + names = [u"alfcrypto.dll",u"alfcrypto64.dll"] + elif isosx: + names = [u"libalfcrypto.dylib"] + else: + names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"] + lib_dict = self.load_resources(names) + self.pluginsdir = os.path.join(config_dir,u"plugins") + if not os.path.exists(self.pluginsdir): + os.mkdir(self.pluginsdir) + self.maindir = os.path.join(self.pluginsdir,u"DeDRM") + if not os.path.exists(self.maindir): + os.mkdir(self.maindir) + self.helpdir = os.path.join(self.maindir,u"help") + if not os.path.exists(self.helpdir): + os.mkdir(self.helpdir) + self.alfdir = os.path.join(self.maindir,u"libraryfiles") + if not os.path.exists(self.alfdir): + os.mkdir(self.alfdir) + for entry, data in lib_dict.items(): + file_path = os.path.join(self.alfdir, entry) + open(file_path,'wb').write(data) + except Exception, e: + traceback.print_exc() + raise + + def ePubDecrypt(self,path_to_ebook): + # Create a TemporaryPersistent file to work with. + # Check original epub archive for zip errors. + import calibre_plugins.dedrm.zipfix + + inf = self.temporary_file(u".epub") + try: + print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION) + fr = zipfix.fixZip(path_to_ebook, inf.name) + fr.fix() + except Exception, e: + print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]) + raise Exception(e) + + # import the decryption keys + import calibre_plugins.dedrm.prefs as prefs + dedrmprefs = prefs.DeDRM_Prefs() + + # import the Barnes & Noble ePub handler + import calibre_plugins.dedrm.ignobleepub as ignobleepub + + + #check the book + if ignobleepub.ignobleBook(inf.name): + print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + + # Attempt to decrypt epub with each encryption key (generated or provided). + for keyname, userkey in dedrmprefs['bandnkeys'].items(): + keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked) + of = self.temporary_file(u".epub") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + result = ignobleepub.decryptBook(userkey, inf.name, of.name) + + of.close() + + if result == 0: + # Decryption was successful. + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime) + + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + # import the Adobe Adept ePub handler + import calibre_plugins.dedrm.ineptepub as ineptepub + + if ineptepub.adeptBook(inf.name): + print u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + + # Attempt to decrypt epub with each encryption key (generated or provided). + for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): + userkey = userkeyhex.decode('hex') + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) + of = self.temporary_file(u".epub") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptepub.decryptBook(userkey, inf.name, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was successful. + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime) + + # perhaps we need to get a new default ADE key + print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) + + # get the default Adobe keys + defaultkeys = [] + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.adobekey import adeptkeys + + defaultkeys = adeptkeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.join(self.alfdir,u"adobekey.py") + defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix']) + + self.default_key = default_keys[0] + except: + traceback.print_exc() + self.default_key = u"" + + newkeys = [] + for keyvalue in defaultkeys: + if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values(): + newkeys.append(keyvalue) + + if len(newkeys) > 0: + try: + for i,userkey in enumerate(newkeys): + print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + of = self.temporary_file(u".epub") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptepub.decryptBook(userkey, inf.name, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was a success + # Store the new successful key in the defaults + print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + try: + dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) + dedrmprefs.writeprefs() + except: + traceback.print_exc() + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + except Exception, e: + pass + + # Something went wrong with decryption. + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + # Not a Barnes & Noble nor an Adobe Adept + # Import the fixed epub. + print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + return inf.name + + def PDFDecrypt(self,path_to_ebook): + import calibre_plugins.dedrm.prefs as prefs + import calibre_plugins.dedrm.ineptpdf + + dedrmprefs = prefs.DeDRM_Prefs() + # Attempt to decrypt epub with each encryption key (generated or provided). + print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): + userkey = userkeyhex.decode('hex') + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) + of = self.temporary_file(u".pdf") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was successful. + # Return the modified PersistentTemporary file to calibre. + return of.name + + # perhaps we need to get a new default ADE key + print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) + + # get the default Adobe keys + defaultkeys = [] + + if iswindows or isosx: + import calibre_plugins.dedrm.adobekey as adobe + + try: + defaultkeys = adobe.adeptkeys() + except: + pass + else: + # linux + try: + from wineutils import WineGetKeys + + scriptpath = os.join(self.alfdir,u"adobekey.py") + defaultkeys = self.WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix']) + except: + pass + + newkeys = [] + for keyvalue in defaultkeys: + if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values(): + newkeys.append(keyvalue) + + if len(newkeys) > 0: + try: + for i,userkey in enumerate(newkeys): + print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + of = self.temporary_file(u".pdf") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptepdf.decryptBook(userkey, inf.name, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was a success + # Store the new successful key in the defaults + print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + try: + dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) + dedrmprefs.writeprefs() + except: + traceback.print_exc() + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + except Exception, e: + pass + + # Something went wrong with decryption. + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + + def KindleMobiDecrypt(self,path_to_ebook): + + # add the alfcrypto directory to sys.path so alfcrypto.py + # will be able to locate the custom lib(s) for CDLL import. + sys.path.insert(0, self.alfdir) + # Had to move this import here so the custom libs can be + # extracted to the appropriate places beforehand these routines + # look for them. + import calibre_plugins.dedrm.prefs as prefs + import calibre_plugins.dedrm.k4mobidedrm + + dedrmprefs = prefs.DeDRM_Prefs() + pids = dedrmprefs['pids'] + serials = dedrmprefs['serials'] + kindleDatabases = dedrmprefs['kindlekeys'].items() + + try: + book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime) + except Exception, e: + decoded = False + # perhaps we need to get a new default Kindle for Mac/PC key + defaultkeys = [] + print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]) + print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.kindlekey import kindlekeys + + defaultkeys = kindlekeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.join(self.alfdir,u"kindlekey.py") + defaultkeys = self.WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix']) + except: + pass + + newkeys = {} + for i,keyvalue in enumerate(defaultkeys): + keyname = u"default_key_{0:d}".format(i+1) + if keyvalue not in dedrmprefs['kindlekeys'].values(): + newkeys[keyname] = keyvalue + if len(newkeys) > 0: + print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys") + try: + book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime) + decoded = True + # store the new successful keys in the defaults + print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys") + for keyvalue in newkeys.values(): + dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue) + dedrmprefs.writeprefs() + except Exception, e: + pass + if not decoded: + #if you reached here then no luck raise and exception + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + traceback.print_exc() + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{4}” after {3:.1f} seconds with error: {2}\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0],time.time()-self.starttime,os.path.basename(path_to_ebook))) + + of = self.temporary_file(book.getBookExtension()) + book.getFile(of.name) + of.close() + book.cleanup() + return of.name + + + def eReaderDecrypt(self,path_to_ebook): + + import calibre_plugins.dedrm.prefs as prefs + import calibre_plugins.dedrm.erdr2pml + + dedrmrefs = prefs.DeDRM_Prefs() + # Attempt to decrypt epub with each encryption key (generated or provided). + for keyname, userkey in dedrmprefs['ereaderkeys'].items(): + keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked) + of = self.temporary_file(u".pmlz") + + # Give the userkey, ebook and TemporaryPersistent file to the decryption function. + result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex')) + + of.close() + + # Decryption was successful return the modified PersistentTemporary + # file to Calibre's import process. + if result == 0: + return of.name + + print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime) + + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + + def run(self, path_to_ebook): + + # make sure any unicode output gets converted safely with 'replace' + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + + print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + self.starttime = time.time() + + booktype = os.path.splitext(path_to_ebook)[1].lower()[1:] + if booktype in ['prc','mobi','azw','azw1','azw3','azw4','tpz']: + # Kindle/Mobipocket + decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook) + elif booktype == 'pdb': + # eReader + decrypted_ebook = self.eReaderDecrypt(path_to_ebook) + pass + elif booktype == 'pdf': + # Adobe Adept PDF (hopefully) + decrypted_ebook = self.PDFDecrypt(path_to_ebook) + pass + elif booktype == 'epub': + # Adobe Adept or B&N ePub + decrypted_ebook = self.ePubDecrypt(path_to_ebook) + else: + print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype) + return path_to_ebook + print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + return decrypted_ebook + + def is_customizable(self): + # return true to allow customization via the Plugin->Preferences. + return True + + def config_widget(self): + import calibre_plugins.dedrm.config as config + return config.ConfigWidget(self.plugin_path, self.alfdir) + + def save_settings(self, config_widget): + config_widget.save_settings() diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptkey.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/adobekey.py old mode 100755 new mode 100644 similarity index 64% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptkey.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/adobekey.py index 723b7c6..bfa542b --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptkey.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/adobekey.py @@ -1,25 +1,31 @@ -#! /usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import with_statement -# ineptkey.pyw, version 5.6 +# adobekey.pyw, version 5.7 # Copyright © 2009-2010 i♥cabbages -# Released under the terms of the GNU General Public Licence, version 3 or -# later. +# Released under the terms of the GNU General Public Licence, version 3 +# -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make certain -# to install the version for Python 2.6). Then save this script file as -# ineptkey.pyw and double-click on it to run it. It will create a file named -# adeptkey.der in the same directory. This is your ADEPT user key. +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python. +# We recommend ActiveState Python 2.7.X for Windows (x86) from +# http://www.activestate.com/activepython/downloads. +# You must also install PyCrypto from +# http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make certain to install the version for Python 2.7). +# Then save this script file as adobekey.pyw and double-click on it to run it. +# It will create a file named adobekey_1.der in in the same directory as the script. +# This is your Adobe Digital Editions user key. # -# Mac OS X users: Save this script file as ineptkey.pyw. You can run this -# program from the command line (pythonw ineptkey.pyw) or by double-clicking +# Mac OS X users: Save this script file as adobekey.pyw. You can run this +# program from the command line (python adobekey.pyw) or by double-clicking # it when it has been associated with PythonLauncher. It will create a file -# named adeptkey.der in the same directory. This is your ADEPT user key. +# named adobekey_1.der in the same directory as the script. +# This is your Adobe Digital Editions user key. # Revision history: # 1 - Initial release, for Adobe Digital Editions 1.7 @@ -30,24 +36,44 @@ # 4.2 - added old 1.7.1 processing # 4.3 - better key search # 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 - 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 # 5.2 - added support for output of key to a particular file # 5.3 - On Windows try PyCrypto first, OpenSSL next # 5.4 - Modify interface to allow use of import # 5.5 - Fix for potential problem with PyCrypto -# 5.6 - Revise to allow use in Plugins to eliminate need for duplicate code +# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code +# 5.7 - Unicode support added, renamed adobekey from ineptkey +# 5.8 - Added getkey interface for Windows DeDRM application +# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 6.0 - Work if TkInter is missing """ Retrieve Adobe ADEPT user key. """ __license__ = 'GPL v3' - -import sys -import os -import struct +__version__ = '6.0' + +import sys, os, struct, getopt + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) try: from calibre.constants import iswindows, isosx @@ -55,6 +81,44 @@ iswindows = sys.platform.startswith('win') isosx = sys.platform.startswith('darwin') +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"adobekey.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + class ADEPTError(Exception): pass @@ -80,13 +144,13 @@ 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', @@ -296,7 +360,7 @@ def CryptUnprotectData(indata, entropy): return CryptUnprotectData CryptUnprotectData = CryptUnprotectData() - def retrieve_keys(): + def adeptkeys(): if AES is None: raise ADEPTError("PyCrypto or OpenSSL must be installed") root = GetSystemDirectory().split('\\')[0] + '\\' @@ -308,9 +372,9 @@ def retrieve_keys(): cuser = winreg.HKEY_CURRENT_USER try: regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH) + device = winreg.QueryValueEx(regkey, 'key')[0] except WindowsError: raise ADEPTError("Adobe Digital Editions not activated") - device = winreg.QueryValueEx(regkey, 'key')[0] keykey = CryptUnprotectData(device, entropy) userkey = None keys = [] @@ -339,11 +403,13 @@ def retrieve_keys(): aes = AES(keykey) userkey = aes.decrypt(userkey) userkey = userkey[26:-ord(userkey[-1])] + #print "found key:",userkey.encode('hex') keys.append(userkey) if len(keys) == 0: raise ADEPTError('Could not locate privateLicenseKey') + print u"Found {0:d} keys".format(len(keys)) return keys - + elif isosx: import xml.etree.ElementTree as etree @@ -353,6 +419,9 @@ def retrieve_keys(): 'enc': 'http://www.w3.org/2001/04/xmlenc#'} def findActivationDat(): + import warnings + warnings.filterwarnings('ignore', category=FutureWarning) + home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"' cmdline = cmdline.encode(sys.getfilesystemencoding()) @@ -360,6 +429,7 @@ def findActivationDat(): out1, out2 = p2.communicate() reslst = out1.split('\n') cnt = len(reslst) + ActDatPath = "activation.dat" for j in xrange(cnt): resline = reslst[j] pp = resline.find('activation.dat') @@ -370,10 +440,10 @@ def findActivationDat(): return ActDatPath return None - def retrieve_keys(): + def adeptkeys(): actpath = findActivationDat() if actpath is None: - raise ADEPTError("Could not locate ADE activation") + raise ADEPTError("Could not find ADE activation.dat file.") tree = etree.parse(actpath) adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey')) @@ -383,75 +453,151 @@ def retrieve_keys(): return [userkey] else: - def retrieve_keys(keypath): + def adeptkeys(): raise ADEPTError("This script only supports Windows and Mac OS X.") return [] - -def retrieve_key(keypath): - keys = retrieve_keys() - with open(keypath, 'wb') as f: - f.write(keys[0]) - return True - -def extractKeyfile(keypath): - try: - success = retrieve_key(keypath) - except ADEPTError, e: - print "Key generation Error: " + str(e) - return 1 - except Exception, e: - print "General Error: " + str(e) - return 1 - if not success: - return 1 - return 0 +# interface for Python DeDRM +def getkey(outpath): + keys = adeptkeys() + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'wb') as keyfileout: + keyfileout.write(keys[0]) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + print u"Saved a key to {0}".format(outfile) + return True + return False + +def usage(progname): + print u"Finds, decrypts and saves the default Adobe Adept encryption key(s)." + print u"Keys are saved to the current directory, or a specified output directory." + print u"If a file name is passed instead of a directory, only the first key is saved, in that file." + print u"Usage:" + print u" {0:s} [-h] []".format(progname) + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__) -def cli_main(argv=sys.argv): - keypath = argv[1] - return extractKeyfile(keypath) + try: + opts, args = getopt.getopt(argv[1:], "h") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + + for o, a in opts: + if o == "-h": + usage(progname) + sys.exit(0) + + if len(args) > 1: + usage(progname) + sys.exit(2) + + if len(args) == 1: + # save to the specified file or directory + outpath = args[0] + if not os.path.isabs(outpath): + outpath = os.path.abspath(outpath) + else: + # save to the same directory as the script + outpath = os.path.dirname(argv[0]) + + # make sure the outpath is the + outpath = os.path.realpath(os.path.normpath(outpath)) + + keys = adeptkeys() + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'wb') as keyfileout: + keyfileout.write(keys[0]) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + print u"Saved a key to {0}".format(outfile) + else: + print u"Could not retrieve Adobe Adept key." + return 0 -def main(argv=sys.argv): - import Tkinter - import Tkconstants - import tkMessageBox - import traceback +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() class ExceptionDialog(Tkinter.Frame): def __init__(self, root, text): Tkinter.Frame.__init__(self, root, border=5) - label = Tkinter.Label(self, text="Unexpected error:", + label = Tkinter.Label(self, text=u"Unexpected error:", anchor=Tkconstants.W, justify=Tkconstants.LEFT) label.pack(fill=Tkconstants.X, expand=0) self.text = Tkinter.Text(self) self.text.pack(fill=Tkconstants.BOTH, expand=1) - + self.text.insert(Tkconstants.END, text) + argv=unicode_argv() root = Tkinter.Tk() root.withdraw() - progname = os.path.basename(argv[0]) - keypath = os.path.abspath("adeptkey.der") + progpath, progname = os.path.split(argv[0]) success = False try: - success = retrieve_key(keypath) + keys = adeptkeys() + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(progpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + success = True + tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) except ADEPTError, e: - tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) + tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) except Exception: root.wm_state('normal') - root.title('ADEPT Key') + root.title(progname) text = traceback.format_exc() ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) root.mainloop() if not success: return 1 - tkMessageBox.showinfo( - "ADEPT Key", "Key successfully retrieved to %s" % (keypath)) return 0 if __name__ == '__main__': if len(sys.argv) > 1: sys.exit(cli_main()) - sys.exit(main()) + sys.exit(gui_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/aescbc.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/aescbc.py similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/aescbc.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/aescbc.py diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.dll b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.dll similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.dll rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.dll diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.py old mode 100644 new mode 100755 similarity index 91% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.py index e25a0c8..036ba10 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.py @@ -1,11 +1,18 @@ -#! /usr/bin/env python +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# crypto library mainly by some_updates + +# pbkdf2.py pbkdf2 code taken from pbkdf2.py +# pbkdf2.py Copyright © 2004 Matt Johnston +# pbkdf2.py Copyright © 2009 Daniel Holth +# pbkdf2.py This code may be freely used and modified for any purpose. import sys, os import hmac from struct import pack import hashlib - # interface to needed routines libalfcrypto def _load_libalfcrypto(): import ctypes @@ -26,11 +33,15 @@ def _load_libalfcrypto(): name_of_lib = 'libalfcrypto32.so' else: name_of_lib = 'libalfcrypto64.so' - - libalfcrypto = sys.path[0] + os.sep + name_of_lib + # hard code to local location for libalfcrypto + libalfcrypto = os.path.join(sys.path[0],name_of_lib) if not os.path.isfile(libalfcrypto): - raise Exception('libalfcrypto not found') + libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib) + if not os.path.isfile(libalfcrypto): + libalfcrypto = os.path.join('.',name_of_lib) + if not os.path.isfile(libalfcrypto): + raise Exception('libalfcrypto not found at %s' % libalfcrypto) libalfcrypto = CDLL(libalfcrypto) @@ -55,7 +66,7 @@ def F(restype, name, argtypes): # # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); # - # + # # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, # const unsigned long length, const AES_KEY *key, # unsigned char *ivec, const int enc); @@ -147,7 +158,7 @@ def decrypt(self, data, ctx=None): topazCryptoDecrypt(ctx, data, out, len(data)) return out.raw - print "Using Library AlfCrypto DLL/DYLIB/SO" + print u"Using Library AlfCrypto DLL/DYLIB/SO" return (AES_CBC, Pukall_Cipher, Topaz_Cipher) @@ -164,8 +175,7 @@ def PC1(self, key, src, decryption=True): sum2 = 0; keyXorVal = 0; if len(key)!=16: - print "Bad key length!" - return None + raise Exception('Pukall_Cipher: Bad key length.') wkey = [] for i in xrange(8): wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) @@ -234,6 +244,7 @@ def decrypt(self, data): cleartext = self.aes.decrypt(iv + data) return cleartext + print u"Using Library AlfCrypto Python" return (AES_CBC, Pukall_Cipher, Topaz_Cipher) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto64.dll b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto64.dll similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto64.dll rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto64.dll diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto_src.zip b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto_src.zip similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto_src.zip rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto_src.zip diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py new file mode 100644 index 0000000..f159a9f --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py @@ -0,0 +1,907 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +__license__ = 'GPL v3' + +# Standard Python modules. +import os, traceback + +# PyQT4 modules (part of calibre). +from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, + QGroupBox, QPushButton, QListWidget, QListWidgetItem, + QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl, QString) +from PyQt4 import QtGui + +from zipfile import ZipFile + +# calibre modules and constants. +from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url, + choose_dir, choose_files) +from calibre.utils.config import dynamic, config_dir, JSONConfig +from calibre.constants import iswindows, isosx + +# modules from this plugin's zipfile. +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION +from calibre_plugins.dedrm.__init__ import RESOURCE_NAME as help_file_name +from calibre_plugins.dedrm.utilities import uStrCmp + +import calibre_plugins.dedrm.prefs as prefs + +class ConfigWidget(QWidget): + def __init__(self, plugin_path, alfdir): + QWidget.__init__(self) + + self.plugin_path = plugin_path + self.alfdir = alfdir + + # get the prefs + self.dedrmprefs = prefs.DeDRM_Prefs() + + # make a local copy + self.tempdedrmprefs = {} + self.tempdedrmprefs['bandnkeys'] = self.dedrmprefs['bandnkeys'].copy() + self.tempdedrmprefs['adeptkeys'] = self.dedrmprefs['adeptkeys'].copy() + self.tempdedrmprefs['ereaderkeys'] = self.dedrmprefs['ereaderkeys'].copy() + self.tempdedrmprefs['kindlekeys'] = self.dedrmprefs['kindlekeys'].copy() + self.tempdedrmprefs['pids'] = list(self.dedrmprefs['pids']) + self.tempdedrmprefs['serials'] = list(self.dedrmprefs['serials']) + self.tempdedrmprefs['adobewineprefix'] = self.dedrmprefs['adobewineprefix'] + self.tempdedrmprefs['kindlewineprefix'] = self.dedrmprefs['kindlewineprefix'] + + # Start Qt Gui dialog layout + layout = QVBoxLayout(self) + self.setLayout(layout) + + help_layout = QHBoxLayout() + layout.addLayout(help_layout) + # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked. + help_label = QLabel('Plugin Help', self) + help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) + help_label.setAlignment(Qt.AlignRight) + help_label.linkActivated.connect(self.help_link_activated) + help_layout.addWidget(help_label) + + keys_group_box = QGroupBox(_('Configuration:'), self) + layout.addWidget(keys_group_box) + keys_group_box_layout = QHBoxLayout() + keys_group_box.setLayout(keys_group_box_layout) + + + button_layout = QVBoxLayout() + keys_group_box_layout.addLayout(button_layout) + self.bandn_button = QtGui.QPushButton(self) + self.bandn_button.setToolTip(_(u"Click to manage keys for Barnes and Noble ebooks")) + self.bandn_button.setText(u"Barnes and Noble ebooks") + self.bandn_button.clicked.connect(self.bandn_keys) + self.kindle_serial_button = QtGui.QPushButton(self) + self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks")) + self.kindle_serial_button.setText(u"eInk Kindle ebooks") + self.kindle_serial_button.clicked.connect(self.kindle_serials) + self.kindle_key_button = QtGui.QPushButton(self) + self.kindle_key_button.setToolTip(_(u"Click to manage keys for Kindle for Mac/PC ebooks")) + self.kindle_key_button.setText(u"Kindle for Mac/PC ebooks") + self.kindle_key_button.clicked.connect(self.kindle_keys) + self.adept_button = QtGui.QPushButton(self) + self.adept_button.setToolTip(_(u"Click to manage keys for Adobe Digital Editions ebooks")) + self.adept_button.setText(u"Adobe Digital Editions ebooks") + self.adept_button.clicked.connect(self.adept_keys) + self.mobi_button = QtGui.QPushButton(self) + self.mobi_button.setToolTip(_(u"Click to manage PIDs for Mobipocket ebooks")) + self.mobi_button.setText(u"Mobipocket ebooks") + self.mobi_button.clicked.connect(self.mobi_keys) + self.ereader_button = QtGui.QPushButton(self) + self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks")) + self.ereader_button.setText(u"eReader ebooks") + self.ereader_button.clicked.connect(self.ereader_keys) + button_layout.addWidget(self.kindle_serial_button) + button_layout.addWidget(self.bandn_button) + button_layout.addWidget(self.mobi_button) + button_layout.addWidget(self.ereader_button) + button_layout.addWidget(self.adept_button) + button_layout.addWidget(self.kindle_key_button) + + self.resize(self.sizeHint()) + + def kindle_serials(self): + d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog) + d.exec_() + + def kindle_keys(self): + if isosx or iswindows: + d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i') + else: + # linux + d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix']) + d.exec_() + self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix() + + def adept_keys(self): + if isosx or iswindows: + d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der') + else: + # linux + d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix']) + d.exec_() + self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix() + + def mobi_keys(self): + d = ManageKeysDialog(self,u"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog) + d.exec_() + + def bandn_keys(self): + d = ManageKeysDialog(self,u"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64') + d.exec_() + + def ereader_keys(self): + d = ManageKeysDialog(self,u"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63') + d.exec_() + + def help_link_activated(self, url): + def get_help_file_resource(): + # Copy the HTML helpfile to the plugin directory each time the + # link is clicked in case the helpfile is updated in newer plugins. + file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name) + with open(file_path,'w') as f: + f.write(self.load_resource(help_file_name)) + return file_path + url = 'file:///' + get_help_file_resource() + open_url(QUrl(url)) + + def save_settings(self): + self.dedrmprefs.set('bandnkeys', self.tempdedrmprefs['bandnkeys']) + self.dedrmprefs.set('adeptkeys', self.tempdedrmprefs['adeptkeys']) + self.dedrmprefs.set('ereaderkeys', self.tempdedrmprefs['ereaderkeys']) + self.dedrmprefs.set('kindlekeys', self.tempdedrmprefs['kindlekeys']) + self.dedrmprefs.set('pids', self.tempdedrmprefs['pids']) + self.dedrmprefs.set('serials', self.tempdedrmprefs['serials']) + self.dedrmprefs.set('adobewineprefix', self.tempdedrmprefs['adobewineprefix']) + self.dedrmprefs.set('kindlewineprefix', self.tempdedrmprefs['kindlewineprefix']) + self.dedrmprefs.set('configured', True) + self.dedrmprefs.writeprefs() + + def load_resource(self, name): + with ZipFile(self.plugin_path, 'r') as zf: + if name in zf.namelist(): + return zf.read(name) + return "" + + + +class ManageKeysDialog(QDialog): + def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u"", wineprefix = None): + QDialog.__init__(self,parent) + self.parent = parent + self.key_type_name = key_type_name + self.plugin_keys = plugin_keys + self.create_key = create_key + self.keyfile_ext = keyfile_ext + self.import_key = (keyfile_ext != u"") + self.binary_file = (keyfile_ext == u".der") + self.json_file = (keyfile_ext == u".k4i") + self.wineprefix = wineprefix + + self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name)) + + # Start Qt Gui dialog layout + layout = QVBoxLayout(self) + self.setLayout(layout) + + help_layout = QHBoxLayout() + layout.addLayout(help_layout) + # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked. + help_label = QLabel('Help', self) + help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) + help_label.setAlignment(Qt.AlignRight) + help_label.linkActivated.connect(self.help_link_activated) + help_layout.addWidget(help_label) + + keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self) + layout.addWidget(keys_group_box) + keys_group_box_layout = QHBoxLayout() + keys_group_box.setLayout(keys_group_box_layout) + + self.listy = QListWidget(self) + self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name)) + self.listy.setSelectionMode(QAbstractItemView.SingleSelection) + self.populate_list() + keys_group_box_layout.addWidget(self.listy) + + button_layout = QVBoxLayout() + keys_group_box_layout.addLayout(button_layout) + self._add_key_button = QtGui.QToolButton(self) + self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name)) + self._add_key_button.setIcon(QIcon(I('plus.png'))) + self._add_key_button.clicked.connect(self.add_key) + button_layout.addWidget(self._add_key_button) + + self._delete_key_button = QtGui.QToolButton(self) + self._delete_key_button.setToolTip(_(u"Delete highlighted key")) + self._delete_key_button.setIcon(QIcon(I('list_remove.png'))) + self._delete_key_button.clicked.connect(self.delete_key) + button_layout.addWidget(self._delete_key_button) + + if type(self.plugin_keys) == dict and self.import_key: + self._rename_key_button = QtGui.QToolButton(self) + self._rename_key_button.setToolTip(_(u"Rename highlighted key")) + self._rename_key_button.setIcon(QIcon(I('edit-select-all.png'))) + self._rename_key_button.clicked.connect(self.rename_key) + button_layout.addWidget(self._rename_key_button) + + self.export_key_button = QtGui.QToolButton(self) + self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext)) + self.export_key_button.setIcon(QIcon(I('save.png'))) + self.export_key_button.clicked.connect(self.export_key) + button_layout.addWidget(self.export_key_button) + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + button_layout.addItem(spacerItem) + + if self.wineprefix is not None: + layout.addSpacing(5) + wineprefix_layout = QHBoxLayout() + layout.addLayout(wineprefix_layout) + wineprefix_layout.setAlignment(Qt.AlignCenter) + self.wp_label = QLabel(u"WINEPREFIX:") + wineprefix_layout.addWidget(self.wp_label) + self.wp_lineedit = QLineEdit(self) + wineprefix_layout.addWidget(self.wp_lineedit) + self.wp_label.setBuddy(self.wp_lineedit) + self.wp_lineedit.setText(self.wineprefix) + + layout.addSpacing(5) + migrate_layout = QHBoxLayout() + layout.addLayout(migrate_layout) + if self.import_key: + migrate_layout.setAlignment(Qt.AlignJustify) + self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self) + self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext)) + self.migrate_btn.clicked.connect(self.migrate_wrapper) + migrate_layout.addWidget(self.migrate_btn) + migrate_layout.addStretch() + self.button_box = QDialogButtonBox(QDialogButtonBox.Close) + self.button_box.rejected.connect(self.close) + migrate_layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + def getwineprefix(self): + if self.wineprefix is not None: + return unicode(self.wp_lineedit.text().toUtf8(), 'utf8').strip() + return u"" + + def populate_list(self): + if type(self.plugin_keys) == dict: + for key in self.plugin_keys.keys(): + self.listy.addItem(QListWidgetItem(key)) + else: + for key in self.plugin_keys: + self.listy.addItem(QListWidgetItem(key)) + + def add_key(self): + d = self.create_key(self) + d.exec_() + + if d.result() != d.Accepted: + # New key generation cancelled. + return + new_key_value = d.key_value + if type(self.plugin_keys) == dict: + if new_key_value in self.plugin_keys.values(): + old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] + info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), + u"The new {1} is the same as the existing {1} named {0} and has not been added.".format(old_key_name,self.key_type_name), show=True) + return + self.plugin_keys[d.key_name] = new_key_value + else: + if new_key_value in self.plugin_keys: + info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), + u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True) + return + + self.plugin_keys.append(d.key_value) + self.listy.clear() + self.populate_list() + + def rename_key(self): + if not self.listy.currentItem(): + errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name) + r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + return + + d = RenameKeyDialog(self) + d.exec_() + + if d.result() != d.Accepted: + # rename cancelled or moot. + return + keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8') + if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named {0} to {1}?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): + return + self.plugin_keys[d.key_name] = self.plugin_keys[keyname] + del self.plugin_keys[keyname] + + self.listy.clear() + self.populate_list() + + def delete_key(self): + if not self.listy.currentItem(): + return + keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8') + if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} {0}?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): + return + if type(self.plugin_keys) == dict: + del self.plugin_keys[keyname] + else: + self.plugin_keys.remove(keyname) + + self.listy.clear() + self.populate_list() + + def help_link_activated(self, url): + def get_help_file_resource(): + # Copy the HTML helpfile to the plugin directory each time the + # link is clicked in case the helpfile is updated in newer plugins. + help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name) + file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name) + with open(file_path,'w') as f: + f.write(self.parent.load_resource(help_file_name)) + return file_path + url = 'file:///' + get_help_file_resource() + open_url(QUrl(url)) + + def migrate_files(self): + dynamic[PLUGIN_NAME + u"config_dir"] = config_dir + files = choose_files(self, PLUGIN_NAME + u"config_dir", + u"Select {0} files to import".format(self.key_type_name), [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])], False) + counter = 0 + skipped = 0 + if files: + for filename in files: + fpath = os.path.join(config_dir, filename) + filename = os.path.basename(filename) + new_key_name = os.path.splitext(os.path.basename(filename))[0] + with open(fpath,'rb') as keyfile: + new_key_value = keyfile.read() + if self.binary_file: + new_key_value = new_key_value.encode('hex') + elif self.json_file: + new_key_value = json.loads(new_key_value) + match = False + for key in self.plugin_keys.keys(): + if uStrCmp(new_key_name, key, True): + skipped += 1 + msg = u"A key with the name {0} already exists!\nSkipping key file {1}.\nRename the existing key and import again".format(new_key_name,filename) + inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(msg), show_copy_button=False, show=True) + match = True + break + if not match: + if new_key_value in self.plugin_keys.values(): + old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] + skipped += 1 + info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + u"The key in file {0} is the same as the existing key {1} and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True) + else: + counter += 1 + self.plugin_keys[new_key_name] = new_key_value + + msg = u"" + if counter+skipped > 1: + if counter > 0: + msg += u"Imported {0:d} key {1}. ".format(counter, u"file" if counter == 1 else u"files") + if skipped > 0: + msg += u"Skipped {0:d} key {1}.".format(skipped, u"file" if counter == 1 else u"files") + inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(msg), show_copy_button=False, show=True) + return counter > 0 + + def migrate_wrapper(self): + if self.migrate_files(): + self.listy.clear() + self.populate_list() + + def export_key(self): + if not self.listy.currentItem(): + errmsg = u"No keyfile selected to export. Highlight a keyfile first." + r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + return + filter = QString(u"{0} Files (*.{1})".format(self.key_type_name, self.keyfile_ext)) + keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8') + if dynamic.get(PLUGIN_NAME + 'save_dir'): + defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), u"{0}.{1}".format(keyname , self.keyfile_ext)) + else: + defaultname = os.path.join(os.path.expanduser('~'), u"{0}.{1}".format(keyname , self.keyfile_ext)) + filename = unicode(QtGui.QFileDialog.getSaveFileName(self, u"Save {0} File as...".format(self.key_type_name), defaultname, + u"{0} Files (*.{1})".format(self.key_type_name,self.keyfile_ext), filter)) + if filename: + dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0] + with file(filename, 'w') as fname: + if self.binary_file: + fname.write(self.plugin_keys[keyname].decode('hex')) + elif self.json_file: + fname.write(json.dumps(self.plugin_keys[keyname])) + else: + fname.write(self.plugin_keys[keyname]) + + + + +class RenameKeyDialog(QDialog): + def __init__(self, parent=None,): + print repr(self), repr(parent) + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox('', self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + data_group_box_layout.addWidget(QLabel('New Key Name:', self)) + self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self) + self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name)) + data_group_box_layout.addWidget(self.key_ledit) + + layout.addSpacing(20) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + def accept(self): + if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace(): + errmsg = u"Key name field cannot be empty!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + if len(self.key_ledit.text()) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()): + # Same exact name ... do nothing. + return QDialog.reject(self) + for k in self.parent.plugin_keys.keys(): + if (uStrCmp(self.key_ledit.text(), k, True) and + not uStrCmp(k, self.parent.listy.currentItem().text(), True)): + errmsg = u"The key name {0} is already being used.".format(self.key_ledit.text()) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + QDialog.accept(self) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + + + + + + + +class AddBandNKeyDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(_(u"

Enter an identifying name for this new key.

" + + u"

It should be something that will help you remember " + + u"what personal information was used to create it.")) + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + name_group = QHBoxLayout() + data_group_box_layout.addLayout(name_group) + name_group.addWidget(QLabel(u"Your Name:", self)) + self.name_ledit = QLineEdit(u"", self) + self.name_ledit.setToolTip(_(u"

Enter your name as it appears in your B&N " + + u"account or on your credit card.

" + + u"

It will only be used to generate this " + + u"one-time key and won\'t be stored anywhere " + + u"in calibre or on your computer.

" + + u"

(ex: Jonathan Smith)")) + name_group.addWidget(self.name_ledit) + name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) + name_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(name_disclaimer_label) + + ccn_group = QHBoxLayout() + data_group_box_layout.addLayout(ccn_group) + ccn_group.addWidget(QLabel(u"Credit Card#:", self)) + self.cc_ledit = QLineEdit(u"", self) + self.cc_ledit.setToolTip(_(u"

Enter the full credit card number on record " + + u"in your B&N account.

" + + u"

No spaces or dashes... just the numbers. " + + u"This number will only be used to generate this " + + u"one-time key and won\'t be stored anywhere in " + + u"calibre or on your computer.")) + ccn_group.addWidget(self.cc_ledit) + ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) + ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(ccn_disclaimer_label) + layout.addSpacing(10) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key + return generate_bandn_key(self.user_name,self.cc_number) + + @property + def user_name(self): + return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','') + + @property + def cc_number(self): + return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','') + + + def accept(self): + if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if not self.cc_number.isdigit(): + errmsg = u"Numbers only in the credit card number field!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + +class AddEReaderDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + name_group = QHBoxLayout() + data_group_box_layout.addLayout(name_group) + name_group.addWidget(QLabel(u"Your Name:", self)) + self.name_ledit = QLineEdit(u"", self) + self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)") + name_group.addWidget(self.name_ledit) + name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) + name_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(name_disclaimer_label) + + ccn_group = QHBoxLayout() + data_group_box_layout.addLayout(ccn_group) + ccn_group.addWidget(QLabel(u"Credit Card#:", self)) + self.cc_ledit = QLineEdit(u"", self) + self.cc_ledit.setToolTip(u"

Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.") + ccn_group.addWidget(self.cc_ledit) + ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) + ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(ccn_disclaimer_label) + layout.addSpacing(10) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key + return generate_ereader_key(self.user_name,self.cc_number).encode('hex') + + @property + def user_name(self): + return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','') + + @property + def cc_number(self): + return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','') + + + def accept(self): + if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if not self.cc_number.isdigit(): + errmsg = u"Numbers only in the credit card number field!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddAdeptDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.adobekey import adeptkeys + + defaultkeys = adeptkeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.path.join(parent.parent.alfdir,u"adobekey.py") + defaultkeys = WineGetKeys(scriptpath, u".der",parent.getwineprefix()) + + self.default_key = defaultkeys[0] + except: + traceback.print_exc() + self.default_key = u"" + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + if len(self.default_key)>0: + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit(u"default_key", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for the current default Adobe Digital Editions key.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + self.button_box.accepted.connect(self.accept) + else: + default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self) + default_key_error.setAlignment(Qt.AlignHCenter) + layout.addWidget(default_key_error) + # if no default, bot buttons do the same + self.button_box.accepted.connect(self.reject) + + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return self.default_key.encode('hex') + + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddKindleDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.kindlekey import kindlekeys + + defaultkeys = kindlekeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py") + defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix()) + + self.default_key = defaultkeys[0] + except: + traceback.print_exc() + self.default_key = u"" + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + if len(self.default_key)>0: + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit(u"default_key", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for the current default Kindle for Mac/PC key.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + self.button_box.accepted.connect(self.accept) + else: + default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self) + default_key_error.setAlignment(Qt.AlignHCenter) + layout.addWidget(default_key_error) + # if no default, bot buttons do the same + self.button_box.accepted.connect(self.reject) + + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return self.default_key + + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddSerialDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) != 16: + errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name)) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddPIDDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"PID:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog." + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) != 8 and len(self.key_name) != 10: + errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name)) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + diff --git a/Other_Tools/KindleBooks/lib/convert2xml.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py old mode 100644 new mode 100755 similarity index 92% rename from Other_Tools/KindleBooks/lib/convert2xml.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py index c412d7b..101c45a --- a/Other_Tools/KindleBooks/lib/convert2xml.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py @@ -230,6 +230,7 @@ def __init__(self, filename, dict, debug, flat_xml): 'empty' : (1, 'snippets', 1, 0), 'page' : (1, 'snippets', 1, 0), + 'page.class' : (1, 'scalar_text', 0, 0), 'page.pageid' : (1, 'scalar_text', 0, 0), 'page.pagelabel' : (1, 'scalar_text', 0, 0), 'page.type' : (1, 'scalar_text', 0, 0), @@ -238,11 +239,13 @@ def __init__(self, filename, dict, debug, flat_xml): 'page.startID' : (1, 'scalar_number', 0, 0), 'group' : (1, 'snippets', 1, 0), + 'group.class' : (1, 'scalar_text', 0, 0), 'group.type' : (1, 'scalar_text', 0, 0), 'group._tag' : (1, 'scalar_text', 0, 0), 'group.orientation': (1, 'scalar_text', 0, 0), 'region' : (1, 'snippets', 1, 0), + 'region.class' : (1, 'scalar_text', 0, 0), 'region.type' : (1, 'scalar_text', 0, 0), 'region.x' : (1, 'scalar_number', 0, 0), 'region.y' : (1, 'scalar_number', 0, 0), @@ -252,13 +255,16 @@ def __init__(self, filename, dict, debug, flat_xml): 'empty_text_region' : (1, 'snippets', 1, 0), - 'img' : (1, 'snippets', 1, 0), - 'img.x' : (1, 'scalar_number', 0, 0), - 'img.y' : (1, 'scalar_number', 0, 0), - 'img.h' : (1, 'scalar_number', 0, 0), - 'img.w' : (1, 'scalar_number', 0, 0), - 'img.src' : (1, 'scalar_number', 0, 0), - 'img.color_src' : (1, 'scalar_number', 0, 0), + 'img' : (1, 'snippets', 1, 0), + 'img.x' : (1, 'scalar_number', 0, 0), + 'img.y' : (1, 'scalar_number', 0, 0), + 'img.h' : (1, 'scalar_number', 0, 0), + 'img.w' : (1, 'scalar_number', 0, 0), + 'img.src' : (1, 'scalar_number', 0, 0), + 'img.color_src' : (1, 'scalar_number', 0, 0), + 'img.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'img.gridEndCenter' : (1, 'scalar_number', 0, 0), + 'img.image_type' : (1, 'scalar_number', 0, 0), 'paragraph' : (1, 'snippets', 1, 0), 'paragraph.class' : (1, 'scalar_text', 0, 0), @@ -267,15 +273,20 @@ def __init__(self, filename, dict, debug, flat_xml): 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), 'paragraph.gridSize' : (1, 'scalar_number', 0, 0), 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), 'word_semantic' : (1, 'snippets', 1, 1), 'word_semantic.type' : (1, 'scalar_text', 0, 0), + 'word_semantic.class' : (1, 'scalar_text', 0, 0), 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0), 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0), 'word' : (1, 'snippets', 1, 0), 'word.type' : (1, 'scalar_text', 0, 0), @@ -284,6 +295,7 @@ def __init__(self, filename, dict, debug, flat_xml): 'word.lastGlyph' : (1, 'scalar_number', 0, 0), '_span' : (1, 'snippets', 1, 0), + '_span.class' : (1, 'scalar_text', 0, 0), '_span.firstWord' : (1, 'scalar_number', 0, 0), '_span.lastWord' : (1, 'scalar_number', 0, 0), '_span.gridSize' : (1, 'scalar_number', 0, 0), @@ -302,6 +314,7 @@ def __init__(self, filename, dict, debug, flat_xml): 'span.gridEndCenter' : (1, 'scalar_number', 0, 0), 'extratokens' : (1, 'snippets', 1, 0), + 'extratokens.class' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), @@ -347,16 +360,18 @@ def __init__(self, filename, dict, debug, flat_xml): 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0), 'version.toc' : (1, 'scalar_text', 0, 0), - 'stylesheet' : (1, 'snippets', 1, 0), - 'style' : (1, 'snippets', 1, 0), - 'style._tag' : (1, 'scalar_text', 0, 0), - 'style.type' : (1, 'scalar_text', 0, 0), - 'style._parent_type' : (1, 'scalar_text', 0, 0), - 'style.class' : (1, 'scalar_text', 0, 0), - 'style._after_class' : (1, 'scalar_text', 0, 0), - 'rule' : (1, 'snippets', 1, 0), - 'rule.attr' : (1, 'scalar_text', 0, 0), - 'rule.value' : (1, 'scalar_text', 0, 0), + 'stylesheet' : (1, 'snippets', 1, 0), + 'style' : (1, 'snippets', 1, 0), + 'style._tag' : (1, 'scalar_text', 0, 0), + 'style.type' : (1, 'scalar_text', 0, 0), + 'style._after_type' : (1, 'scalar_text', 0, 0), + 'style._parent_type' : (1, 'scalar_text', 0, 0), + 'style._after_parent_type' : (1, 'scalar_text', 0, 0), + 'style.class' : (1, 'scalar_text', 0, 0), + 'style._after_class' : (1, 'scalar_text', 0, 0), + 'rule' : (1, 'snippets', 1, 0), + 'rule.attr' : (1, 'scalar_text', 0, 0), + 'rule.value' : (1, 'scalar_text', 0, 0), 'original' : (0, 'number', 1, 1), 'original.pnum' : (1, 'number', 0, 0), diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/description.rtfd/TXT.rtf b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/description.rtfd/TXT.rtf new file mode 100644 index 0000000..d868171 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/description.rtfd/TXT.rtf @@ -0,0 +1,4 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf370 +{\fonttbl} +{\colortbl;\red255\green255\blue255;} +} \ No newline at end of file diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/droplet.rsrc b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/droplet.rsrc similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/droplet.rsrc rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/droplet.rsrc diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/encodebase64.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/encodebase64.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/epubtest.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/epubtest.py new file mode 100644 index 0000000..11f1427 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/epubtest.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# This is a python script. You need a Python interpreter to run it. +# For example, ActiveState Python, which exists for windows. +# +# Changelog drmcheck +# 1.00 - Initial version, with code from various other scripts +# 1.01 - Moved authorship announcement to usage section. +# +# Changelog epubtest +# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf +# 1.01 - Added routine for use by Windows DeDRM +# +# Written in 2011 by Paul Durrant +# Released with unlicense. See http://unlicense.org/ +# +############################################################################# +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +############################################################################# +# +# It's still polite to give attribution if you do reuse this code. +# + +from __future__ import with_statement + +__version__ = '1.01' + +import sys, struct, os +import zlib +import zipfile +import xml.etree.ElementTree as etree + +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"epubtest.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +_FILENAME_LEN_OFFSET = 26 +_EXTRA_LEN_OFFSET = 28 +_FILENAME_OFFSET = 30 +_MAX_SIZE = 64 * 1024 + + +def uncompress(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(file, zi): + # get file name length and exta data length to find start of file data + local_header_offset = zi.header_offset + + file.seek(local_header_offset + _FILENAME_LEN_OFFSET) + leninfo = file.read(2) + local_name_length, = struct.unpack(' 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] Des = None -if sys.platform.startswith('win'): +if iswindows: # first try with pycrypto if inCalibre: - from calibre_plugins.erdrpdb2pml import pycrypto_des + from calibre_plugins.dedrm import pycrypto_des else: import pycrypto_des Des = pycrypto_des.load_pycrypto() if Des == None: # they try with openssl if inCalibre: - from calibre_plugins.erdrpdb2pml import openssl_des + from calibre_plugins.dedrm import openssl_des else: import openssl_des Des = openssl_des.load_libcrypto() else: # first try with openssl if inCalibre: - from calibre_plugins.erdrpdb2pml import openssl_des + from calibre_plugins.dedrm import openssl_des else: import openssl_des Des = openssl_des.load_libcrypto() if Des == None: # then try with pycrypto if inCalibre: - from calibre_plugins.erdrpdb2pml import pycrypto_des + from calibre_plugins.dedrm import pycrypto_des else: import pycrypto_des Des = pycrypto_des.load_pycrypto() @@ -116,7 +170,7 @@ def __getattr__(self, attr): # of DES and try to speed it up with Psycho if Des == None: if inCalibre: - from calibre_plugins.erdrpdb2pml import python_des + from calibre_plugins.dedrm import python_des else: import python_des Des = python_des.Des @@ -168,17 +222,30 @@ def loadSection(self, section): off = self.sections[section][0] return self.contents[off:end_off] -def sanitizeFileName(s): - r = '' - for c in s: - if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-": - r += c - return r +# cleanup unicode filenames +# borrowed from calibre from calibre/src/calibre/__init__.py +# added in removal of control (<32) chars +# and removal of . at start and end +# and with some (heavily edited) code from Paul Durrant's kindlenamer.py +def sanitizeFileName(name): + # substitute filename unfriendly characters + name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'") + # delete control characters + name = u"".join(char for char in name if ord(char)>=32) + # white space to single space, delete leading and trailing while space + name = re.sub(ur"\s", u" ", name).strip() + # remove leading dots + while len(name)>0 and name[0] == u".": + name = name[1:] + # remove trailing dots (Windows doesn't like them) + if name.endswith(u'.'): + name = name[:-1] + return name def fixKey(key): def fixByte(b): return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) - return "".join([chr(fixByte(ord(a))) for a in key]) + return "".join([chr(fixByte(ord(a))) for a in key]) def deXOR(text, sp, table): r='' @@ -191,7 +258,7 @@ def deXOR(text, sp, table): return r class EreaderProcessor(object): - def __init__(self, sect, username, creditcard): + def __init__(self, sect, user_key): self.section_reader = sect.loadSection data = self.section_reader(0) version, = struct.unpack('>H', data[0:2]) @@ -212,18 +279,10 @@ def unshuff(data, shuf): for i in xrange(len(data)): j = (j + shuf) % len(data) r[j] = data[i] - assert len("".join(r)) == len(data) + assert len("".join(r)) == len(data) return "".join(r) r = unshuff(input[0:-8], cookie_shuf) - def fixUsername(s): - r = '' - for c in s.lower(): - if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'): - r += c - return r - - user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff) drm_sub_version = struct.unpack('>H', r[0:2])[0] self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1 self.num_image_pages = struct.unpack('>H', r[26:26+2])[0] @@ -302,7 +361,7 @@ def getImage(self, i): sect = self.section_reader(self.first_image_page + i) name = sect[4:4+32].strip('\0') data = sect[62:] - return sanitizeFileName(name), data + return sanitizeFileName(unicode(name,'windows-1252')), data # def getChapterNamePMLOffsetData(self): @@ -399,60 +458,53 @@ def getText(self): return r def cleanPML(pml): - # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) + # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) pml2 = pml for k in xrange(128,256): badChar = chr(k) pml2 = pml2.replace(badChar, '\\a%03d' % k) return pml2 -def convertEreaderToPml(infile, name, cc, outdir): - if not os.path.exists(outdir): - os.makedirs(outdir) +def decryptBook(infile, outpath, make_pmlz, user_key): bookname = os.path.splitext(os.path.basename(infile))[0] - print " Decoding File" - sect = Sectionizer(infile, 'PNRdPPrs') - er = EreaderProcessor(sect, name, cc) - - if er.getNumImages() > 0: - print " Extracting images" - imagedir = bookname + '_img/' - 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" - file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) - - # bkinfo = er.getBookInfo() - # if bkinfo != '': - # print " Extracting book meta information" - # file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo) - - - -def decryptBook(infile, outdir, name, cc, make_pmlz): - if make_pmlz : - # ignore specified outdir, use tempdir instead + if make_pmlz: + # outpath is actually pmlz name + pmlzname = outpath outdir = tempfile.mkdtemp() + imagedirpath = os.path.join(outdir,u"images") + else: + pmlzname = None + outdir = outpath + imagedirpath = os.path.join(outdir,bookname + u"_img") + try: - print "Processing..." - convertEreaderToPml(infile, name, cc, outdir) - if make_pmlz : + if not os.path.exists(outdir): + os.makedirs(outdir) + print u"Decoding File" + sect = Sectionizer(infile, 'PNRdPPrs') + er = EreaderProcessor(sect, user_key) + + if er.getNumImages() > 0: + print u"Extracting images" + 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 u"Extracting pml" + pml_string = er.getText() + pmlfilename = bookname + ".pml" + file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) + if pmlzname is not None: import zipfile import shutil - print " Creating PMLZ file" - zipname = infile[:-4] + '.pmlz' - myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False) + print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname)) + myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False) list = os.listdir(outdir) - for file in list: - localname = file - filePath = os.path.join(outdir,file) + for filename in list: + localname = filename + filePath = os.path.join(outdir,filename) if os.path.isfile(filePath): myZipFile.write(filePath, localname) elif os.path.isdir(filePath): @@ -466,36 +518,48 @@ def decryptBook(infile, outdir, name, cc, make_pmlz): myZipFile.close() # remove temporary directory shutil.rmtree(outdir, True) - print 'output is %s' % zipname + print u"Output is {0}".format(pmlzname) else : - print 'output in %s' % outdir + print u"Output is in {0}".format(outdir) print "done" except ValueError, e: - print "Error: %s" % e + print u"Error: {0}".format(e) + traceback.print_exc() return 1 return 0 def usage(): - print "Converts DRMed eReader books to PML Source" - print "Usage:" - print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number " - print " " - print "Options: " - print " -h prints this message" - print " --make-pmlz create PMLZ instead of using output directory" - print " " - print "Note:" - print " if ommitted, outdir defaults based on 'infile.pdb'" - print " It's enough to enter the last 8 digits of the credit card number" + print u"Converts DRMed eReader books to PML Source" + print u"Usage:" + print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number" + print u" " + print u"Options: " + print u" -h prints this message" + print u" -p create PMLZ instead of source folder" + print u" --make-pmlz create PMLZ instead of source folder" + print u" " + print u"Note:" + print u" if outpath is ommitted, creates source in 'infile_Source' folder" + print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'" + print u" if source folder created, images are in infile_img folder" + print u" if pmlz file created, images are in images folder" + print u" It's enough to enter the last 8 digits of the credit card number" return +def getuser_key(name,cc): + newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9') + cc = cc.replace(" ","") + return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff) -def main(argv=None): +def cli_main(): + print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__) + + argv=unicode_argv() try: - opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"]) + opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"]) except getopt.GetoptError, err: - print str(err) + print err.args[0] usage() return 1 make_pmlz = False @@ -503,24 +567,31 @@ def main(argv=None): if o == "-h": usage() return 0 + elif o == "-p": + make_pmlz = True elif o == "--make-pmlz": make_pmlz = True - print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__ - if len(args)!=3 and len(args)!=4: usage() return 1 if len(args)==3: - infile, name, cc = args[0], args[1], args[2] - outdir = infile[:-4] + '_Source' + infile, name, cc = args + if make_pmlz: + outpath = os.path.splitext(infile)[0] + u".pmlz" + else: + outpath = os.path.splitext(infile)[0] + u"_Source" elif len(args)==4: - infile, outdir, name, cc = args[0], args[1], args[2], args[3] + infile, outpath, name, cc = args + + print getuser_key(name,cc).encode('hex') - return decryptBook(infile, outdir, name, cc, make_pmlz) + return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc)) if __name__ == "__main__": - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) + diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/flatxml2html.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.py old mode 100644 new mode 100755 similarity index 98% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/flatxml2html.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.py index e5647f4..4d83368 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/flatxml2html.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.py @@ -387,10 +387,14 @@ def getParaDescription(self, start, end, regtype): ws_last = int(argres) elif name.endswith('word.class'): - (cname, space) = argres.split('-',1) - if space == '' : space = '0' - if (cname == 'spaceafter') and (int(space) > 0) : - word_class = 'sa' + # we only handle spaceafter word class + try: + (cname, space) = argres.split('-',1) + if space == '' : space = '0' + if (cname == 'spaceafter') and (int(space) > 0) : + word_class = 'sa' + except: + pass elif name.endswith('word.img.src'): result.append(('img' + word_class, int(argres))) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2svg.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2svg.py similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2svg.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2svg.py diff --git a/Other_Tools/KindleBooks/lib/genbook.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py old mode 100644 new mode 100755 similarity index 98% rename from Other_Tools/KindleBooks/lib/genbook.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py index 9733887..3ed925d --- a/Other_Tools/KindleBooks/lib/genbook.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py @@ -29,10 +29,10 @@ class TpzDRMError(Exception): inCalibre = False if inCalibre : - from calibre_plugins.k4mobidedrm import convert2xml - from calibre_plugins.k4mobidedrm import flatxml2html - from calibre_plugins.k4mobidedrm import flatxml2svg - from calibre_plugins.k4mobidedrm import stylexml2css + from calibre_plugins.dedrm import convert2xml + from calibre_plugins.dedrm import flatxml2html + from calibre_plugins.dedrm import flatxml2svg + from calibre_plugins.dedrm import stylexml2css else : import convert2xml import flatxml2html @@ -117,7 +117,7 @@ def lookup(self,val): self.pos = val return self.stable[self.pos] else: - print "Error - %d outside of string table limits" % val + print "Error: %d outside of string table limits" % val raise TpzDRMError('outside or string table limits') # sys.exit(-1) def getSize(self): diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.py new file mode 100644 index 0000000..ac73d1e --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# ignobleepub.pyw, version 3.8 +# Copyright © 2009-2010 by i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make sure to +# install the version for Python 2.6). Save this script file as +# ineptepub.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ineptepub.pyw. You can run this +# program from the command line (pythonw ineptepub.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. + +# Revision history: +# 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 +# 3.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml +# 3.3 - On Windows try PyCrypto first, OpenSSL next +# 3.4 - Modify interface to allow use with import +# 3.5 - Fix for potential problem with PyCrypto +# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code +# 3.7 - Tweaked to match ineptepub more closely +# 3.8 - Fixed to retain zip file metadata (e.g. file modification date) +# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 4.0 - Work if TkInter is missing + +""" +Decrypt Barnes & Noble encrypted ePub books. +""" + +__license__ = 'GPL v3' +__version__ = "4.0" + +import sys +import os +import traceback +import zlib +import zipfile +from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED +from contextlib import closing +import xml.etree.ElementTree as etree + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + return [u"ineptepub.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + + +class IGNOBLEError(Exception): + pass + +def _load_crypto_libcrypto(): + from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, cast + from ctypes.util import find_library + + if iswindows: + libcrypto = find_library('libeay32') + else: + libcrypto = find_library('crypto') + + if libcrypto is None: + raise IGNOBLEError('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 IGNOBLEError('AES improper key used') + return + key = self._key = AES_KEY() + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) + if rv < 0: + raise IGNOBLEError('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 IGNOBLEError('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, '\x00'*16) + + def decrypt(self, data): + return self._aes.decrypt(data) + + return AES + +def _load_crypto(): + AES = None + cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) + if sys.platform.startswith('win'): + cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) + for loader in cryptolist: + try: + AES = loader() + break + except (ImportError, IGNOBLEError): + pass + return AES + +AES = _load_crypto() + +META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +class Decryptor(object): + def __init__(self, bookkey, encryption): + enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) + self._aes = AES(bookkey) + encryption = etree.fromstring(encryption) + self._encrypted = encrypted = set() + expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), + enc('CipherReference')) + for elem in encryption.findall(expr): + path = elem.get('URI', None) + if path is not None: + path = path.encode('utf-8') + encrypted.add(path) + + def decompress(self, bytes): + dc = zlib.decompressobj(-15) + bytes = dc.decompress(bytes) + ex = dc.decompress('Z') + dc.flush() + if ex: + bytes = bytes + ex + return bytes + + def decrypt(self, path, data): + if path in self._encrypted: + data = self._aes.decrypt(data)[16:] + data = data[:-ord(data[-1])] + data = self.decompress(data) + return data + +# check file to make check whether it's probably an Adobe Adept encrypted ePub +def ignobleBook(inpath): + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + return False + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) == 64: + return True + except: + # if we couldn't check, assume it is + return True + return False + +def decryptBook(keyb64, inpath, outpath): + if AES is None: + raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.") + key = keyb64.decode('base64')[:16] + aes = AES(key) + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + print u"{0:s} is DRM-free.".format(os.path.basename(inpath)) + return 1 + for name in META_NAMES: + namelist.remove(name) + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) != 64: + print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)) + return 1 + bookkey = aes.decrypt(bookkey.decode('base64')) + bookkey = bookkey[:-ord(bookkey[-1])] + encryption = inf.read('META-INF/encryption.xml') + decryptor = Decryptor(bookkey[-16:], encryption) + kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) + with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: + zi = ZipInfo('mimetype') + zi.compress_type=ZIP_STORED + try: + # if the mimetype is present, get its info, including time-stamp + oldzi = inf.getinfo('mimetype') + # copy across fields to be preserved + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, inf.read('mimetype')) + for path in namelist: + data = inf.read(path) + zi = ZipInfo(path) + zi.compress_type=ZIP_DEFLATED + try: + # get the file info, including time-stamp + oldzi = inf.getinfo(path) + # copy across useful fields + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, decryptor.decrypt(path, data)) + except: + print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()) + return 2 + return 0 + + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + if len(argv) != 4: + print u"usage: {0} ".format(progname) + return 1 + keypath, inpath, outpath = argv[1:] + userkey = open(keypath,'rb').read() + result = decryptBook(userkey, inpath, outpath) + if result == 0: + print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) + return result + +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text=u"Select files for decryption") + 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=u"Key file").grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists(u"bnepubkey.b64"): + self.keypath.insert(0, u"bnepubkey.b64") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text=u"Input file").grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text=u"Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text=u"Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title=u"Select Barnes & Noble \'.b64\' key file", + defaultextension=u".b64", + filetypes=[('base64-encoded files', '.b64'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title=u"Select B&N-encrypted ePub file to decrypt", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title=u"Select unencrypted ePub file to produce", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = u"Specified key file does not exist" + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = u"Specified input file does not exist" + return + if not outpath: + self.status['text'] = u"Output file not specified" + return + if inpath == outpath: + self.status['text'] = u"Must have different input and output files" + return + userkey = open(keypath,'rb').read() + self.status['text'] = u"Decrypting..." + try: + decrypt_status = decryptBook(userkey, inpath, outpath) + except Exception, e: + self.status['text'] = u"Error: {0}".format(e.args[0]) + return + if decrypt_status == 0: + self.status['text'] = u"File successfully decrypted" + else: + self.status['text'] = u"The was an error decrypting the file." + + root = Tkinter.Tk() + root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__)) + root.resizable(True, False) + root.minsize(300, 0) + DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) + root.mainloop() + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignoblekeygen.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.py old mode 100755 new mode 100644 similarity index 53% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignoblekeygen.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.py index e2c50e2..5118c87 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignoblekeygen.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.py @@ -1,13 +1,27 @@ -#! /usr/bin/python +#!/usr/bin/env python +# -*- coding: utf-8 -*- from __future__ import with_statement -# ignoblekeygen.pyw, version 2.4 +# ignoblekeygen.pyw, version 2.5 +# Copyright © 2009-2010 i♥cabbages -# To run this program install Python 2.6 from -# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignoblekeygen.pyw and double-click on it to run it. +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python. +# We recommend ActiveState Python 2.7.X for Windows (x86) from +# http://www.activestate.com/activepython/downloads. +# You must also install PyCrypto from +# http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make certain to install the version for Python 2.7). +# Then save this script file as ignoblekeygen.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this +# program from the command line (python ignoblekeygen.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. # Revision history: # 1 - Initial release @@ -16,36 +30,97 @@ # 2.2 - On Windows try PyCrypto first and then OpenSSL next # 2.3 - Modify interface to allow use of import # 2.4 - Improvements to UI and now works in plugins +# 2.5 - Additional improvement for unicode and plugin support +# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 2.7 - Work if TkInter is missing """ Generate Barnes & Noble EPUB user key from name and credit card number. """ __license__ = 'GPL v3' +__version__ = "2.7" import sys import os import hashlib +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"ignoblekeygen.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] -# use openssl's libcrypt if it exists in place of pycrypto -# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages class IGNOBLEError(Exception): pass - def _load_crypto_libcrypto(): from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ Structure, c_ulong, create_string_buffer, cast from ctypes.util import find_library - if sys.platform.startswith('win'): + if iswindows: libcrypto = find_library('libeay32') else: libcrypto = find_library('crypto') + if libcrypto is None: - print 'libcrypto not found' raise IGNOBLEError('libcrypto not found') libcrypto = CDLL(libcrypto) @@ -70,6 +145,7 @@ def F(restype, name, argtypes): 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, iv): self._blocksize = len(userkey) @@ -88,7 +164,6 @@ def encrypt(self, data): return AES - def _load_crypto_pycrypto(): from Crypto.Cipher import AES as _AES @@ -120,25 +195,31 @@ def normalize_name(name): return ''.join(x for x in name.lower() if x != ' ') -def generate_keyfile(name, ccn, outpath): +def generate_key(name, ccn): # remove spaces and case from name and CC numbers. + if type(name)==unicode: + name = name.encode('utf-8') + if type(ccn)==unicode: + ccn = ccn.encode('utf-8') + name = normalize_name(name) + '\x00' ccn = normalize_name(ccn) + '\x00' - + name_sha = hashlib.sha1(name).digest()[:16] ccn_sha = hashlib.sha1(ccn).digest()[:16] both_sha = hashlib.sha1(name + ccn).digest() aes = AES(ccn_sha, name_sha) crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c)) userkey = hashlib.sha1(crypt).digest() - with open(outpath, 'wb') as f: - f.write(userkey.encode('base64')) - return userkey + return userkey.encode('base64') -def cli_main(argv=sys.argv): +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() progname = os.path.basename(argv[0]) if AES is None: print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ @@ -146,54 +227,58 @@ def cli_main(argv=sys.argv): (progname,) return 1 if len(argv) != 4: - print "usage: %s NAME CC# OUTFILE" % (progname,) + print u"usage: {0} ".format(progname) return 1 - name, ccn, outpath = argv[1:] - generate_keyfile(name, ccn, outpath) + name, ccn, keypath = argv[1:] + userkey = generate_key(name, ccn) + open(keypath,'wb').write(userkey) return 0 def gui_main(): - import Tkinter - import Tkconstants - import tkFileDialog - import tkMessageBox + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() class DecryptionDialog(Tkinter.Frame): def __init__(self, root): Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Enter parameters') + self.status = Tkinter.Label(self, text=u"Enter parameters") 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='Account Name').grid(row=0) + Tkinter.Label(body, text=u"Account Name").grid(row=0) self.name = Tkinter.Entry(body, width=40) self.name.grid(row=0, column=1, sticky=sticky) - Tkinter.Label(body, text='CC#').grid(row=1) + Tkinter.Label(body, text=u"CC#").grid(row=1) self.ccn = Tkinter.Entry(body, width=40) self.ccn.grid(row=1, column=1, sticky=sticky) - Tkinter.Label(body, text='Output file').grid(row=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) self.keypath = Tkinter.Entry(body, width=40) self.keypath.grid(row=2, column=1, sticky=sticky) - self.keypath.insert(2, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) + self.keypath.insert(2, u"bnepubkey.b64") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button.grid(row=2, column=2) buttons = Tkinter.Frame(self) buttons.pack() botton = Tkinter.Button( - buttons, text="Generate", width=10, command=self.generate) + buttons, text=u"Generate", width=10, command=self.generate) botton.pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) + buttons, text=u"Quit", width=10, command=self.quit) button.pack(side=Tkconstants.RIGHT) - + def get_keypath(self): keypath = tkFileDialog.asksaveasfilename( - parent=None, title='Select B&N EPUB key file to produce', - defaultextension='.b64', + parent=None, title=u"Select B&N ePub key file to produce", + defaultextension=u".b64", filetypes=[('base64-encoded files', '.b64'), ('All Files', '.*')]) if keypath: @@ -201,27 +286,28 @@ def get_keypath(self): self.keypath.delete(0, Tkconstants.END) self.keypath.insert(0, keypath) return - + def generate(self): name = self.name.get() ccn = self.ccn.get() keypath = self.keypath.get() if not name: - self.status['text'] = 'Name not specified' + self.status['text'] = u"Name not specified" return if not ccn: - self.status['text'] = 'Credit card number not specified' + self.status['text'] = u"Credit card number not specified" return if not keypath: - self.status['text'] = 'Output keyfile path not specified' + self.status['text'] = u"Output keyfile path not specified" return - self.status['text'] = 'Generating...' + self.status['text'] = u"Generating..." try: - generate_keyfile(name, ccn, keypath) + userkey = generate_key(name, ccn) except Exception, e: - self.status['text'] = 'Error: ' + str(e) + self.status['text'] = u"Error: (0}".format(e.args[0]) return - self.status['text'] = 'Keyfile successfully generated' + open(keypath,'wb').write(userkey) + self.status['text'] = u"Keyfile successfully generated" root = Tkinter.Tk() if AES is None: @@ -231,7 +317,7 @@ def generate(self): "This script requires OpenSSL or PyCrypto, which must be installed " "separately. Read the top-of-script comment for details.") return 1 - root.title('Ignoble EPUB Keyfile Generator') + root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__)) root.resizable(True, False) root.minsize(300, 0) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.py new file mode 100644 index 0000000..225ffa7 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.py @@ -0,0 +1,594 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# ineptepub.pyw, version 5.9 +# Copyright © 2009-2010 by i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make sure to +# install the version for Python 2.6). Save this script file as +# ineptepub.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ineptepub.pyw. You can run this +# program from the command line (pythonw ineptepub.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. + +# Revision history: +# 1 - Initial release +# 2 - Rename to INEPT, fix exit code +# 5 - Version bump to avoid (?) confusion; +# 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 +# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml +# 5.5 - On Windows try PyCrypto first, OpenSSL next +# 5.6 - Modify interface to allow use with import +# 5.7 - Fix for potential problem with PyCrypto +# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code +# 5.9 - Fixed to retain zip file metadata (e.g. file modification date) +# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 6.1 - Work if TkInter is missing + +""" +Decrypt Adobe Digital Editions encrypted ePub books. +""" + +__license__ = 'GPL v3' +__version__ = "6.1" + +import sys +import os +import traceback +import zlib +import zipfile +from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED +from contextlib import closing +import xml.etree.ElementTree as etree + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + return [u"ineptepub.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + + +class ADEPTError(Exception): + pass + +def _load_crypto_libcrypto(): + from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, cast + from ctypes.util import find_library + + if iswindows: + libcrypto = find_library('libeay32') + else: + libcrypto = find_library('crypto') + + if libcrypto is None: + raise ADEPTError('libcrypto not found') + libcrypto = CDLL(libcrypto) + + RSA_NO_PADDING = 3 + AES_MAXNR = 14 + + c_char_pp = POINTER(c_char_p) + c_int_p = POINTER(c_int) + + class RSA(Structure): + pass + RSA_p = POINTER(RSA) + + 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 + + d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', + [RSA_p, c_char_pp, c_long]) + RSA_size = F(c_int, 'RSA_size', [RSA_p]) + RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', + [c_int, c_char_p, c_char_p, RSA_p, c_int]) + RSA_free = F(None, 'RSA_free', [RSA_p]) + 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 RSA(object): + def __init__(self, der): + buf = create_string_buffer(der) + pp = c_char_pp(cast(buf, c_char_p)) + rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) + if rsa is None: + raise ADEPTError('Error parsing ADEPT user key DER') + + def decrypt(self, from_): + rsa = self._rsa + to = create_string_buffer(RSA_size(rsa)) + dlen = RSA_private_decrypt(len(from_), from_, to, rsa, + RSA_NO_PADDING) + if dlen < 0: + raise ADEPTError('RSA decryption failed') + return to[:dlen] + + def __del__(self): + if self._rsa is not None: + RSA_free(self._rsa) + self._rsa = None + + 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: + 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, RSA) + +def _load_crypto_pycrypto(): + from Crypto.Cipher import AES as _AES + from Crypto.PublicKey import RSA as _RSA + + # ASN.1 parsing code from tlslite + class ASN1Error(Exception): + pass + + class ASN1Parser(object): + class Parser(object): + def __init__(self, bytes): + self.bytes = bytes + self.index = 0 + + def get(self, length): + if self.index + length > len(self.bytes): + raise ASN1Error("Error decoding ASN.1") + x = 0 + for count in range(length): + x <<= 8 + x |= self.bytes[self.index] + self.index += 1 + return x + + def getFixBytes(self, lengthBytes): + bytes = self.bytes[self.index : self.index+lengthBytes] + self.index += lengthBytes + return bytes + + def getVarBytes(self, lengthLength): + lengthBytes = self.get(lengthLength) + return self.getFixBytes(lengthBytes) + + def getFixList(self, length, lengthList): + l = [0] * lengthList + for x in range(lengthList): + l[x] = self.get(length) + return l + + def getVarList(self, length, lengthLength): + lengthList = self.get(lengthLength) + if lengthList % length != 0: + raise ASN1Error("Error decoding ASN.1") + lengthList = int(lengthList/length) + l = [0] * lengthList + for x in range(lengthList): + l[x] = self.get(length) + return l + + def startLengthCheck(self, lengthLength): + self.lengthCheck = self.get(lengthLength) + self.indexCheck = self.index + + def setLengthCheck(self, length): + self.lengthCheck = length + self.indexCheck = self.index + + def stopLengthCheck(self): + if (self.index - self.indexCheck) != self.lengthCheck: + raise ASN1Error("Error decoding ASN.1") + + def atLengthCheck(self): + if (self.index - self.indexCheck) < self.lengthCheck: + return False + elif (self.index - self.indexCheck) == self.lengthCheck: + return True + else: + raise ASN1Error("Error decoding ASN.1") + + def __init__(self, bytes): + p = self.Parser(bytes) + p.get(1) + self.length = self._getASN1Length(p) + self.value = p.getFixBytes(self.length) + + def getChild(self, which): + p = self.Parser(self.value) + for x in range(which+1): + markIndex = p.index + p.get(1) + length = self._getASN1Length(p) + p.getFixBytes(length) + return ASN1Parser(p.bytes[markIndex:p.index]) + + def _getASN1Length(self, p): + firstLength = p.get(1) + if firstLength<=127: + return firstLength + else: + lengthLength = firstLength & 0x7F + return p.get(lengthLength) + + class AES(object): + def __init__(self, key): + self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16) + + def decrypt(self, data): + return self._aes.decrypt(data) + + class RSA(object): + def __init__(self, der): + key = ASN1Parser([ord(x) for x in der]) + key = [key.getChild(x).value for x in xrange(1, 4)] + key = [self.bytesToNumber(v) for v in key] + self._rsa = _RSA.construct(key) + + def bytesToNumber(self, bytes): + total = 0L + for byte in bytes: + total = (total << 8) + byte + return total + + def decrypt(self, data): + return self._rsa.decrypt(data) + + return (AES, RSA) + +def _load_crypto(): + AES = RSA = None + cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) + if sys.platform.startswith('win'): + cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) + for loader in cryptolist: + try: + AES, RSA = loader() + break + except (ImportError, ADEPTError): + pass + return (AES, RSA) + +AES, RSA = _load_crypto() + +META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +class Decryptor(object): + def __init__(self, bookkey, encryption): + enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) + self._aes = AES(bookkey) + encryption = etree.fromstring(encryption) + self._encrypted = encrypted = set() + expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), + enc('CipherReference')) + for elem in encryption.findall(expr): + path = elem.get('URI', None) + if path is not None: + path = path.encode('utf-8') + encrypted.add(path) + + def decompress(self, bytes): + dc = zlib.decompressobj(-15) + bytes = dc.decompress(bytes) + ex = dc.decompress('Z') + dc.flush() + if ex: + bytes = bytes + ex + return bytes + + def decrypt(self, path, data): + if path in self._encrypted: + data = self._aes.decrypt(data)[16:] + data = data[:-ord(data[-1])] + data = self.decompress(data) + return data + +# check file to make check whether it's probably an Adobe Adept encrypted ePub +def adeptBook(inpath): + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + return False + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) == 172: + return True + except: + # if we couldn't check, assume it is + return True + return False + +def decryptBook(userkey, inpath, outpath): + if AES is None: + raise ADEPTError(u"PyCrypto or OpenSSL must be installed.") + rsa = RSA(userkey) + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + print u"{0:s} is DRM-free.".format(os.path.basename(inpath)) + return 1 + for name in META_NAMES: + namelist.remove(name) + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) != 172: + print u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)) + return 1 + bookkey = rsa.decrypt(bookkey.decode('base64')) + # Padded as per RSAES-PKCS1-v1_5 + if bookkey[-17] != '\x00': + print u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)) + return 2 + encryption = inf.read('META-INF/encryption.xml') + decryptor = Decryptor(bookkey[-16:], encryption) + kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) + with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: + zi = ZipInfo('mimetype') + zi.compress_type=ZIP_STORED + try: + # if the mimetype is present, get its info, including time-stamp + oldzi = inf.getinfo('mimetype') + # copy across fields to be preserved + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, inf.read('mimetype')) + for path in namelist: + data = inf.read(path) + zi = ZipInfo(path) + zi.compress_type=ZIP_DEFLATED + try: + # get the file info, including time-stamp + oldzi = inf.getinfo(path) + # copy across useful fields + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, decryptor.decrypt(path, data)) + except: + print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()) + return 2 + return 0 + + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + if len(argv) != 4: + print u"usage: {0} ".format(progname) + return 1 + keypath, inpath, outpath = argv[1:] + userkey = open(keypath,'rb').read() + result = decryptBook(userkey, inpath, outpath) + if result == 0: + print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) + return result + +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text=u"Select files for decryption") + 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=u"Key file").grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists(u"adeptkey.der"): + self.keypath.insert(0, u"adeptkey.der") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text=u"Input file").grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text=u"Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text=u"Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title=u"Select Adobe Adept \'.der\' key file", + defaultextension=u".der", + filetypes=[('Adobe Adept DER-encoded files', '.der'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title=u"Select unencrypted ePub file to produce", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = u"Specified key file does not exist" + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = u"Specified input file does not exist" + return + if not outpath: + self.status['text'] = u"Output file not specified" + return + if inpath == outpath: + self.status['text'] = u"Must have different input and output files" + return + userkey = open(keypath,'rb').read() + self.status['text'] = u"Decrypting..." + try: + decrypt_status = decryptBook(userkey, inpath, outpath) + except Exception, e: + self.status['text'] = u"Error: {0}".format(e.args[0]) + return + if decrypt_status == 0: + self.status['text'] = u"File successfully decrypted" + else: + self.status['text'] = u"The was an error decrypting the file." + + root = Tkinter.Tk() + root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__)) + root.resizable(True, False) + root.minsize(300, 0) + DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) + root.mainloop() + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptpdf.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py old mode 100755 new mode 100644 similarity index 88% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptpdf.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py index 20721d1..797db60 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptpdf.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py @@ -1,13 +1,25 @@ -#! /usr/bin/env python -# ineptpdf.pyw, version 7.11 +#! /usr/bin/python +# -*- coding: utf-8 -*- from __future__ import with_statement -# To run this program install Python 2.6 from http://www.python.org/download/ -# and OpenSSL (already installed on Mac OS X and Linux) OR -# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ineptpdf.pyw and double-click on it to run it. +# ineptpdf.pyw, version 7.11 +# Copyright © 2009-2010 by i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make sure to +# install the version for Python 2.6). Save this script file as +# ineptpdf.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this +# program from the command line (pythonw ineptpdf.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. # Revision history: # 1 - Initial release @@ -36,12 +48,17 @@ # 7.9 - Bug fix for some session key errors when len(bookkey) > length required # 7.10 - Various tweaks to fix minor problems. # 7.11 - More tweaks to fix minor problems. +# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code +# 7.13 - Fixed erroneous mentions of ineptepub +# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 8.0 - Work if TkInter is missing """ Decrypts Adobe ADEPT-encrypted PDF files. """ __license__ = 'GPL v3' +__version__ = "8.0" import sys import os @@ -51,10 +68,63 @@ import hashlib from itertools import chain, islice import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + return [u"ineptpdf.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + class ADEPTError(Exception): pass @@ -1520,9 +1590,7 @@ def initialize_standard(self, password, docid, param): def initialize_ebx(self, password, docid, param): self.is_printable = self.is_modifiable = self.is_extractable = True - with open(password, 'rb') as f: - keyder = f.read() - rsa = RSA(keyder) + rsa = RSA(password) length = int_value(param.get('Length', 0)) / 8 rights = str_value(param.get('ADEPT_LICENSE')).decode('base64') rights = zlib.decompress(rights, -15) @@ -1907,14 +1975,14 @@ def do_keyword(self, pos, token): ### My own code, for which there is none else to blame class PDFSerializer(object): - def __init__(self, inf, keypath): + def __init__(self, inf, userkey): global GEN_XREF_STM, gen_xref_stm gen_xref_stm = GEN_XREF_STM > 1 self.version = inf.read(8) inf.seek(0) self.doc = doc = PDFDocument() parser = PDFParser(doc, inf) - doc.initialize(keypath) + doc.initialize(userkey) self.objids = objids = set() for xref in reversed(doc.xrefs): trailer = xref.trailer @@ -2097,142 +2165,150 @@ def serialize_indirect(self, objid, obj): self.write('endobj\n') -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - ltext='Select file for decryption\n' - self.status = Tkinter.Label(self, text=ltext) - self.status.pack(fill=Tkconstants.X, expand=1) - body = Tkinter.Frame(self) - body.pack(fill=Tkconstants.X, expand=1) - sticky = Tkconstants.E + Tkconstants.W - body.grid_columnconfigure(1, weight=2) - Tkinter.Label(body, text='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('adeptkey.der'): - self.keypath.insert(0, 'adeptkey.der') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - - - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT key file', - defaultextension='.der', filetypes=[('DER-encoded files', '.der'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(os.path.realpath(keypath)) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT encrypted PDF file to decrypt', - defaultextension='.pdf', filetypes=[('PDF files', '.pdf'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(os.path.realpath(inpath)) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted PDF file to produce', - defaultextension='.pdf', filetypes=[('PDF files', '.pdf'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(os.path.realpath(outpath)) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - # keyfile doesn't exist - self.status['text'] = 'Specified Adept key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - # patch for non-ascii characters - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Processing ...' - try: - cli_main(argv) - except Exception, a: - self.status['text'] = 'Error: ' + str(a) - return - self.status['text'] = 'File successfully decrypted.\n'+\ - 'Close this window or decrypt another pdf file.' - return -def decryptBook(keypath, inpath, outpath): +def decryptBook(userkey, inpath, outpath): + if RSA is None: + raise ADEPTError(u"PyCrypto or OpenSSL must be installed.") with open(inpath, 'rb') as inf: try: - serializer = PDFSerializer(inf, keypath) + serializer = PDFSerializer(inf, userkey) except: - print "Error serializing pdf. Probably wrong key." - return 1 + print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath)) + return 2 # hope this will fix the 'bad file descriptor' problem with open(outpath, 'wb') as outf: - # help construct to make sure the method runs to the end + # help construct to make sure the method runs to the end try: serializer.dump(outf) - except: - print "error writing pdf." - return 1 + except Exception, e: + print u"error writing pdf: {0}".format(e.args[0]) + return 2 return 0 -def cli_main(argv=sys.argv): +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() progname = os.path.basename(argv[0]) - if RSA is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) + print u"usage: {0} ".format(progname) return 1 keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) + userkey = open(keypath,'rb').read() + result = decryptBook(userkey, inpath, outpath) + if result == 0: + print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) + return result def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text=u"Select files for decryption") + 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=u"Key file").grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists(u"adeptkey.der"): + self.keypath.insert(0, u"adeptkey.der") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text=u"Input file").grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text=u"Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text=u"Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title=u"Select Adobe Adept \'.der\' key file", + defaultextension=u".der", + filetypes=[('Adobe Adept DER-encoded files', '.der'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt", + defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title=u"Select unencrypted PDF file to produce", + defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = u"Specified key file does not exist" + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = u"Specified input file does not exist" + return + if not outpath: + self.status['text'] = u"Output file not specified" + return + if inpath == outpath: + self.status['text'] = u"Must have different input and output files" + return + userkey = open(keypath,'rb').read() + self.status['text'] = u"Decrypting..." + try: + decrypt_status = decryptBook(userkey, inpath, outpath) + except Exception, e: + self.status['text'] = u"Error; {0}".format(e.args[0]) + return + if decrypt_status == 0: + self.status['text'] = u"File successfully decrypted" + else: + self.status['text'] = u"The was an error decrypting the file." + + root = Tkinter.Tk() if RSA is None: root.withdraw() @@ -2241,7 +2317,7 @@ def gui_main(): "This script requires OpenSSL or PyCrypto, which must be installed " "separately. Read the top-of-script comment for details.") return 1 - root.title('INEPT PDF Decrypter') + root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__)) root.resizable(True, False) root.minsize(370, 0) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py new file mode 100644 index 0000000..0e426a1 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# ignobleepub.pyw, version 3.6 +# Copyright © 2009-2012 by DiapDealer et al. + +# engine to remove drm from Kindle for Mac and Kindle for PC books +# for personal use for archiving and converting your ebooks + +# PLEASE DO NOT PIRATE EBOOKS! + +# We want all authors and publishers, and eBook stores to live +# long and prosperous lives but at the same time we just want to +# be able to read OUR books on whatever device we want and to keep +# readable for a long, long time + +# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, +# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates +# and many many others +# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump +# from which this script borrows most unashamedly. + + +# Changelog +# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code +# 1.1 - Adds support for additional kindle.info files +# 1.2 - Better error handling for older Mobipocket +# 1.3 - Don't try to decrypt Topaz books +# 1.7 - Add support for Topaz books and Kindle serial numbers. Split code. +# 1.9 - Tidy up after Topaz, minor exception changes +# 2.1 - Topaz fix and filename sanitizing +# 2.2 - Topaz Fix and minor Mac code fix +# 2.3 - More Topaz fixes +# 2.4 - K4PC/Mac key generation fix +# 2.6 - Better handling of non-K4PC/Mac ebooks +# 2.7 - Better trailing bytes handling in mobidedrm +# 2.8 - Moved parsing of kindle.info files to mac & pc util files. +# 3.1 - Updated for new calibre interface. Now __init__ in plugin. +# 3.5 - Now support Kindle for PC/Mac 1.6 +# 3.6 - Even better trailing bytes handling in mobidedrm +# 3.7 - Add support for Amazon Print Replica ebooks. +# 3.8 - Improved Topaz support +# 4.1 - Improved Topaz support and faster decryption with alfcrypto +# 4.2 - Added support for Amazon's KF8 format ebooks +# 4.4 - Linux calls to Wine added, and improved configuration dialog +# 4.5 - Linux works again without Wine. Some Mac key file search changes +# 4.6 - First attempt to handle unicode properly +# 4.7 - Added timing reports, and changed search for Mac key files +# 4.8 - Much better unicode handling, matching the updated inept and ignoble scripts +# - Moved back into plugin, __init__ in plugin now only contains plugin code. +# 4.9 - Missed some invalid characters in cleanup_name +# 5.0 - Extraction of info from Kindle for PC/Mac moved into kindlekey.py +# - tweaked GetDecryptedBook interface to leave passed parameters unchanged +# 5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 5.2 - Fixed error in command line processing of unicode arguments + +__version__ = '5.2' + + +import sys, os, re +import csv +import getopt +import re +import traceback +import time +import htmlentitydefs +import json + +class DrmException(Exception): + pass + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + from calibre_plugins.dedrm import mobidedrm + from calibre_plugins.dedrm import topazextract + from calibre_plugins.dedrm import kgenpids +else: + import mobidedrm + import topazextract + import kgenpids + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +# cleanup unicode filenames +# borrowed from calibre from calibre/src/calibre/__init__.py +# added in removal of control (<32) chars +# and removal of . at start and end +# and with some (heavily edited) code from Paul Durrant's kindlenamer.py +def cleanup_name(name): + # substitute filename unfriendly characters + name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"") + # delete control characters + name = u"".join(char for char in name if ord(char)>=32) + # white space to single space, delete leading and trailing while space + name = re.sub(ur"\s", u" ", name).strip() + # remove leading dots + while len(name)>0 and name[0] == u".": + name = name[1:] + # remove trailing dots (Windows doesn't like them) + if name.endswith(u'.'): + name = name[:-1] + return name + +# must be passed unicode +def unescape(text): + def fixup(m): + text = m.group(0) + if text[:2] == u"&#": + # character reference + try: + if text[:3] == u"&#x": + return unichr(int(text[3:-1], 16)) + else: + return unichr(int(text[2:-1])) + except ValueError: + pass + else: + # named entity + try: + text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) + except KeyError: + pass + return text # leave as is + return re.sub(u"&#?\w+;", fixup, text) + +def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()): + # handle the obvious cases at the beginning + if not os.path.isfile(infile): + raise DRMException (u"Input file does not exist.") + + mobi = True + magic3 = open(infile,'rb').read(3) + if magic3 == 'TPZ': + mobi = False + + if mobi: + mb = mobidedrm.MobiBook(infile) + else: + mb = topazextract.TopazBook(infile) + + bookname = unescape(mb.getBookTitle()) + print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()) + + # copy list of pids + totalpids = list(pids) + # extend PID list with book-specific PIDs + md1, md2 = mb.getPIDMetaInfo() + totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases)) + print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids)) + + try: + mb.processBook(totalpids) + except: + mb.cleanup + raise + + print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime) + return mb + + +# kDatabaseFiles is a list of files created by kindlekey +def decryptBook(infile, outdir, kDatabaseFiles, serials, pids): + starttime = time.time() + kDatabases = [] + for dbfile in kDatabaseFiles: + kindleDatabase = {} + try: + with open(dbfile, 'r') as keyfilein: + kindleDatabase = json.loads(keyfilein.read()) + kDatabases.append([dbfile,kindleDatabase]) + except Exception, e: + print u"Error getting database from file {0:s}: {1:s}".format(dbfile,e) + traceback.print_exc() + + + + try: + book = GetDecryptedBook(infile, kDatabases, serials, pids, starttime) + except Exception, e: + print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime) + traceback.print_exc() + return 1 + + # if we're saving to the same folder as the original, use file name_ + # if to a different folder, use book name + if os.path.normcase(os.path.normpath(outdir)) == os.path.normcase(os.path.normpath(os.path.dirname(infile))): + outfilename = os.path.splitext(os.path.basename(infile))[0] + else: + outfilename = cleanup_name(book.getBookTitle()) + + # avoid excessively long file names + if len(outfilename)>150: + outfilename = outfilename[:150] + + outfilename = outfilename+u"_nodrm" + outfile = os.path.join(outdir, outfilename + book.getBookExtension()) + + book.getFile(outfile) + print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename) + + if book.getBookType()==u"Topaz": + zipname = os.path.join(outdir, outfilename + u"_SVG.zip") + book.getSVGZip(zipname) + print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename) + + # remove internal temporary directory of Topaz pieces + book.cleanup() + return 0 + + +def usage(progname): + print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks" + print u"Usage:" + print u" {0} [-k ] [-p ] [-s ] ".format(progname) + +# +# Main +# +def cli_main(): + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__) + + try: + opts, args = getopt.getopt(argv[1:], "k:p:s:") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + if len(args)<2: + usage(progname) + sys.exit(2) + + infile = args[0] + outdir = args[1] + kDatabaseFiles = [] + serials = [] + pids = [] + + for o, a in opts: + if o == "-k": + if a == None : + raise DrmException("Invalid parameter for -k") + kDatabaseFiles.append(a) + if o == "-p": + if a == None : + raise DrmException("Invalid parameter for -p") + pids = a.split(',') + if o == "-s": + if a == None : + raise DrmException("Invalid parameter for -s") + serials = a.split(',') + + # try with built in Kindle Info files if not on Linux + k4 = not sys.platform.startswith('linux') + + return decryptBook(infile, outdir, kDatabaseFiles, serials, pids) + + +if __name__ == '__main__': + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/Other_Tools/KindleBooks/lib/kgenpids.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py similarity index 67% rename from Other_Tools/KindleBooks/lib/kgenpids.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py index b0fbaa4..dd88797 100644 --- a/Other_Tools/KindleBooks/lib/kgenpids.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- from __future__ import with_statement import sys @@ -7,6 +8,7 @@ import zlib import re from struct import pack, unpack, unpack_from +import traceback class DrmException(Exception): pass @@ -15,28 +17,10 @@ class DrmException(Exception): global charMap3 global charMap4 -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False -if inCalibre: - if sys.platform.startswith('win'): - from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - if sys.platform.startswith('darwin'): - from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString -else: - if sys.platform.startswith('win'): - from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - if sys.platform.startswith('darwin'): - from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" +charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' +charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' # crypto digestroutines import hashlib @@ -54,7 +38,7 @@ def SHA1(message): # Encode the bytes in data with the characters in map def encode(data, map): - result = "" + result = '' for char in data: value = ord(char) Q = (value ^ 0x80) // len(map) @@ -69,14 +53,14 @@ def encodeHash(data,map): # Decode the string in data with the characters in map. Returns the decoded bytes def decode(data,map): - result = "" + result = '' for i in range (0,len(data)-1,2): high = map.find(data[i]) low = map.find(data[i+1]) if (high == -1) or (low == -1) : break value = (((high * len(map)) ^ 0x80) & 0xFF) + low - result += pack("B",value) + result += pack('B',value) return result # @@ -98,7 +82,7 @@ def getSixBitsFromBitField(bitField,offset): # 8 bits to six bits encoding from hash to generate PID string def encodePID(hash): global charMap3 - PID = "" + PID = '' for position in range (0,8): PID += charMap3[getSixBitsFromBitField(hash,position)] return PID @@ -129,7 +113,7 @@ def generatePidSeed(table,dsn) : def generateDevicePID(table,dsn,nbRoll): global charMap4 seed = generatePidSeed(table,dsn) - pidAscii = "" + 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): @@ -176,53 +160,56 @@ def pidFromSerial(s, l): # Parse the EXTH header records and use the Kindle serial number to calculate the book pid. -def getKindlePid(pidlst, rec209, token, serialnum): +def getKindlePids(rec209, token, serialnum): + pids=[] + + if isinstance(serialnum,unicode): + serialnum = serialnum.encode('ascii') + # Compute book PID pidHash = SHA1(serialnum+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) # compute fixed pid for old pre 2.5 firmware update pid as well - bookPID = pidFromSerial(serialnum, 7) + "*" - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + kindlePID = pidFromSerial(serialnum, 7) + "*" + kindlePID = checksumPid(kindlePID) + pids.append(kindlePID) - return pidlst + return pids # parse the Kindleinfo file to calculate the book pid. -keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] +keynames = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber'] -def getK4Pids(pidlst, rec209, token, kInfoFile): +def getK4Pids(rec209, token, kindleDatabase): global charMap1 - kindleDatabase = None - try: - kindleDatabase = getDBfromFile(kInfoFile) - except Exception, message: - print(message) - kindleDatabase = None - pass - - if kindleDatabase == None : - return pidlst + pids = [] try: # Get the Mazama Random number - MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] + MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex').encode('ascii') # Get the kindle account token - kindleAccountToken = kindleDatabase["kindle.account.tokens"] + kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex').encode('ascii') + + # Get the IDString used to decode the Kindle Info file + IDString = (kindleDatabase[1])['IDString'].decode('hex').encode('ascii') + + # Get the UserName stored when the Kindle Info file was decoded + UserName = (kindleDatabase[1])['UserName'].decode('hex').encode('ascii') + except KeyError: - print "Keys not found in " + kInfoFile - return pidlst + print u"Keys not found in the database {0}.".format(kindleDatabase[0]) + return pids # Get the ID string used - encodedIDString = encodeHash(GetIDString(),charMap1) + encodedIDString = encodeHash(IDString,charMap1) # Get the current user name - encodedUsername = encodeHash(GetUserName(),charMap1) + encodedUsername = encodeHash(UserName,charMap1) # concat, hash and encode to calculate the DSN DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1) @@ -231,7 +218,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): table = generatePidEncryptionTable() devicePID = generateDevicePID(table,DSN,4) devicePID = checksumPid(devicePID) - pidlst.append(devicePID) + pids.append(devicePID) # Compute book PIDs @@ -239,36 +226,42 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): pidHash = SHA1(DSN+kindleAccountToken+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) # variant 1 pidHash = SHA1(kindleAccountToken+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) # variant 2 pidHash = SHA1(DSN+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) - return pidlst + return pids -def getPidList(md1, md2, k4 = True, serials=[], kInfoFiles=[]): +def getPidList(md1, md2, serials=[], kDatabases=[]): pidlst = [] - if kInfoFiles is None: - kInfoFiles = [] - if k4: - kInfoFiles.extend(getKindleInfoFiles()) - for infoFile in kInfoFiles: + + if kDatabases is None: + kDatabases = [] + if serials is None: + serials = [] + + for kDatabase in kDatabases: try: - pidlst = getK4Pids(pidlst, md1, md2, infoFile) - except Exception, message: - print("Error getting PIDs from " + infoFile + ": " + message) + pidlst.extend(getK4Pids(md1, md2, kDatabase)) + except Exception, e: + print u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]) + traceback.print_exc() + for serialnum in serials: try: - pidlst = getKindlePid(pidlst, md1, md2, serialnum) - except Exception, message: - print("Error getting PIDs from " + serialnum + ": " + message) + pidlst.extend(getKindlePids(md1, md2, serialnum)) + except Exception, e: + print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]) + traceback.print_exc() + return pidlst diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlekey.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlekey.py new file mode 100644 index 0000000..f58e973 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlekey.py @@ -0,0 +1,1918 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# kindlekey.py +# Copyright © 2010-2013 by some_updates and Apprentice Alf +# +# Currently requires alfcrypto.py which requires the alfcrypto library + +# Revision history: +# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc. +# 1.1 - Added Tkinter to match adobekey.py +# 1.2 - Fixed testing of successful retrieval on Mac +# 1.3 - Added getkey interface for Windows DeDRM application +# Simplified some of the Kindle for Mac code. +# 1.4 - Remove dependency on alfcrypto +# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 1.6 - Fixed a problem getting the disk serial numbers +# 1.7 - Work if TkInter is missing +# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names + + +""" +Retrieve Kindle for PC/Mac user key. +""" + +__license__ = 'GPL v3' +__version__ = '1.8' + +import sys, os, re +from struct import pack, unpack, unpack_from +import json +import getopt + +# Routines common to Mac and PC + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"kindlekey.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +class DrmException(Exception): + pass + +# crypto digestroutines +import hashlib + +def MD5(message): + ctx = hashlib.md5() + ctx.update(message) + return ctx.digest() + +def SHA1(message): + ctx = hashlib.sha1() + ctx.update(message) + return ctx.digest() + +def SHA256(message): + ctx = hashlib.sha256() + ctx.update(message) + return ctx.digest() + +# For K4M/PC 1.6.X and later +# generate table of prime number less than or equal to int n +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # no more data expected from caller + finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) + if len(finalBytes) > 0: + ctBlock = self.encryptBlock(finalBytes) + self.encryptBlockCount += 1 + cipherText += ctBlock + self.resetEncrypt() + return cipherText + + def decrypt(self, cipherText, more = None): + """ Decrypt a string and return a string """ + self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt + + numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) + if more == None: # no more calls to decrypt, should have all the data + if numExtraBytes != 0: + raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' + + # hold back some bytes in case last decrypt has zero len + if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : + numBlocks -= 1 + numExtraBytes = self.blockSize + + plainText = '' + for i in range(numBlocks): + bStart = i*self.blockSize + ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) + self.decryptBlockCount += 1 + plainText += ptBlock + + if numExtraBytes > 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # last decrypt remove padding + plainText = self.padding.removePad(plainText, self.blockSize) + self.resetDecrypt() + return plainText + + + class Pad: + def __init__(self): + pass # eventually could put in calculation of min and max size extension + + class padWithPadLen(Pad): + """ Pad a binary string with the length of the padding """ + + def addPad(self, extraBytes, blockSize): + """ Add padding to a binary string to make it an even multiple + of the block size """ + blocks, numExtraBytes = divmod(len(extraBytes), blockSize) + padLength = blockSize - numExtraBytes + return extraBytes + padLength*chr(padLength) + + def removePad(self, paddedBinaryString, blockSize): + """ Remove padding from a binary string """ + if not(0 6 and i%Nk == 4 : + temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) + w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) + return w + + Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! + 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, + 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) + + #------------------------------------- + def AddRoundKey(algInstance, keyBlock): + """ XOR the algorithm state with a block of key material """ + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] ^= keyBlock[column][row] + #------------------------------------- + + def SubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = Sbox[algInstance.state[column][row]] + + def InvSubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] + + Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, + 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, + 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, + 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, + 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, + 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, + 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, + 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, + 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, + 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, + 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, + 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, + 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, + 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, + 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, + 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, + 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) + + InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) + + #------------------------------------- + """ For each block size (Nb), the ShiftRow operation shifts row i + by the amount Ci. Note that row 0 is not shifted. + Nb C1 C2 C3 + ------------------- """ + shiftOffset = { 4 : ( 0, 1, 2, 3), + 5 : ( 0, 1, 2, 3), + 6 : ( 0, 1, 2, 3), + 7 : ( 0, 1, 2, 4), + 8 : ( 0, 1, 3, 4) } + def ShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] + def InvShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] + #------------------------------------- + def MixColumns(a): + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) + Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + + def InvMixColumns(a): + """ Mix the four bytes of every column in a linear way + This is the opposite operation of Mixcolumn """ + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) + Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) + Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) + Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + + #------------------------------------- + def mul(a, b): + """ Multiply two elements of GF(2^m) + needed for MixColumn and InvMixColumn """ + if (a !=0 and b!=0): + return Alogtable[(Logtable[a] + Logtable[b])%255] + else: + return 0 + + Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, + 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, + 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, + 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, + 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, + 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, + 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, + 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, + 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, + 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, + 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, + 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) + + Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, + 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, + 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, + 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, + 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, + 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, + 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, + 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, + 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, + 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) + + + + + """ + AES Encryption Algorithm + The AES algorithm is just Rijndael algorithm restricted to the default + blockSize of 128 bits. + """ + + class AES(Rijndael): + """ The AES algorithm is the Rijndael block cipher restricted to block + sizes of 128 bits and key sizes of 128, 192 or 256 bits + """ + def __init__(self, key = None, padding = padWithPadLen(), keySize=16): + """ Initialize AES, keySize is in bytes """ + if not (keySize == 16 or keySize == 24 or keySize == 32) : + raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' + + Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) + + self.name = 'AES' + + + """ + CBC mode of encryption for block ciphers. + This algorithm mode wraps any BlockCipher to make a + Cipher Block Chaining mode. + """ + from random import Random # should change to crypto.random!!! + + + class CBC(BlockCipher): + """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode + algorithms. The initialization (IV) is automatic if set to None. Padding + is also automatic based on the Pad class used to initialize the algorithm + """ + def __init__(self, blockCipherInstance, padding = padWithPadLen()): + """ CBC algorithms are created by initializing with a BlockCipher instance """ + self.baseCipher = blockCipherInstance + self.name = self.baseCipher.name + '_CBC' + self.blockSize = self.baseCipher.blockSize + self.keySize = self.baseCipher.keySize + self.padding = padding + self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! + self.r = Random() # for IV generation, currently uses + # mediocre standard distro version <---------------- + import time + newSeed = time.ctime()+str(self.r) # seed with instance location + self.r.seed(newSeed) # to make unique + self.reset() + + def setKey(self, key): + self.baseCipher.setKey(key) + + # Overload to reset both CBC state and the wrapped baseCipher + def resetEncrypt(self): + BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) + self.baseCipher.resetEncrypt() # reset base cipher encrypt state + + def resetDecrypt(self): + BlockCipher.resetDecrypt(self) # reset CBC state (super class) + self.baseCipher.resetDecrypt() # reset base cipher decrypt state + + def encrypt(self, plainText, iv=None, more=None): + """ CBC encryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.encryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to encrypt' + + return BlockCipher.encrypt(self,plainText, more=more) + + def decrypt(self, cipherText, iv=None, more=None): + """ CBC decryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.decryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to decrypt' + + return BlockCipher.decrypt(self, cipherText, more=more) + + def encryptBlock(self, plainTextBlock): + """ CBC block encryption, IV is set with 'encrypt' """ + auto_IV = '' + if self.encryptBlockCount == 0: + if self.iv == None: + # generate IV and use + self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) + self.prior_encr_CT_block = self.iv + auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic + else: # application provided IV + assert(len(self.iv) == self.blockSize ),'IV must be same length as block' + self.prior_encr_CT_block = self.iv + """ encrypt the prior CT XORed with the PT """ + ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) + self.prior_encr_CT_block = ct + return auto_IV+ct + + def decryptBlock(self, encryptedBlock): + """ Decrypt a single block """ + + if self.decryptBlockCount == 0: # first call, process IV + if self.iv == None: # auto decrypt IV? + self.prior_CT_block = encryptedBlock + return '' + else: + assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" + self.prior_CT_block = self.iv + + dct = self.baseCipher.decryptBlock(encryptedBlock) + """ XOR the prior decrypted CT with the prior CT """ + dct_XOR_priorCT = xor( self.prior_CT_block, dct ) + + self.prior_CT_block = encryptedBlock + + return dct_XOR_priorCT + + + """ + AES_CBC Encryption Algorithm + """ + + class aescbc_AES_CBC(CBC): + """ AES encryption in CBC feedback mode """ + def __init__(self, key=None, padding=padWithPadLen(), keySize=16): + CBC.__init__( self, AES(key, noPadding(), keySize), padding) + self.name = 'AES_CBC' + + class AES_CBC(object): + def __init__(self): + self._key = None + self._iv = None + self.aes = None + + def set_decrypt_key(self, userkey, iv): + self._key = userkey + self._iv = iv + self.aes = aescbc_AES_CBC(userkey, noPadding(), len(userkey)) + + def decrypt(self, data): + iv = self._iv + cleartext = self.aes.decrypt(iv + data) + return cleartext + + import hmac + + class KeyIVGen(object): + # this only exists in openssl so we will use pure python implementation instead + # 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]) + def pbkdf2(self, passwd, salt, iter, keylen): + + def xorstr( a, b ): + if len(a) != len(b): + raise Exception("xorstr(): lengths differ") + return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) + + def prf( h, data ): + hm = h.copy() + hm.update( data ) + return hm.digest() + + def pbkdf2_F( h, salt, itercount, blocknum ): + U = prf( h, salt + pack('>i',blocknum ) ) + T = U + for i in range(2, itercount+1): + U = prf( h, U ) + T = xorstr( T, U ) + return T + + sha = hashlib.sha1 + digest_size = sha().digest_size + # l - number of output blocks to produce + l = keylen / digest_size + if keylen % digest_size != 0: + l += 1 + h = hmac.new( passwd, None, sha ) + T = "" + for i in range(1, l+1): + T += pbkdf2_F( h, salt, iter, i ) + return T[0: keylen] + + def UnprotectHeaderData(encryptedData): + passwdData = 'header_key_data' + salt = 'HEADER.2011' + iter = 0x80 + keylen = 0x100 + key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen) + key = key_iv[0:32] + iv = key_iv[32:48] + aes=AES_CBC() + aes.set_decrypt_key(key, iv) + cleartext = aes.decrypt(encryptedData) + return cleartext + + # Various character maps used to decrypt kindle info values. + # Probably supposed to act as obfuscation + charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" + charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" + # New maps in K4PC 1.9.0 + testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" + testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG" + testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD" + + # interface with Windows OS Routines + 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 GetIDString(): + vsn = GetVolumeSerialNumber() + #print('Using Volume Serial Number for ID: '+vsn) + return vsn + + def getLastError(): + GetLastError = kernel32.GetLastError + GetLastError.argtypes = None + GetLastError.restype = c_uint + def getLastError(): + return GetLastError() + return getLastError + getLastError = getLastError() + + def GetUserName(): + GetUserNameW = advapi32.GetUserNameW + GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] + GetUserNameW.restype = c_uint + def GetUserName(): + buffer = create_unicode_buffer(2) + size = c_uint(len(buffer)) + while not GetUserNameW(buffer, byref(size)): + errcd = getLastError() + if errcd == 234: + # bad wine implementation up through wine 1.3.21 + return "AlternateUserName" + 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, flags): + 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, flags, byref(outdata)): + # raise DrmException("Failed to Unprotect Data") + return 'failed' + return string_at(outdata.pbData, outdata.cbData) + return CryptUnprotectData + CryptUnprotectData = CryptUnprotectData() + + + # Locate all of the kindle-info style files and return as list + def getKindleInfoFiles(): + kInfoFiles = [] + # some 64 bit machines do not have the proper registry key for some reason + # or the pythonn interface to the 32 vs 64 bit registry is broken + path = "" + if 'LOCALAPPDATA' in os.environ.keys(): + path = os.environ['LOCALAPPDATA'] + else: + # User Shell Folders show take precedent over Shell Folders if present + try: + regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\") + path = winreg.QueryValueEx(regkey, 'Local AppData')[0] + if not os.path.isdir(path): + path = "" + try: + regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") + path = winreg.QueryValueEx(regkey, 'Local AppData')[0] + if not os.path.isdir(path): + path = "" + except RegError: + pass + except RegError: + pass + + found = False + if path == "": + print ('Could not find the folder in which to look for kinfoFiles.') + else: + print('searching for kinfoFiles in ' + path) + + # look for (K4PC 1.9.0 and later) .kinf2011 file + kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file + kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.6-1.8 kinf file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for (K4PC 1.5.0 and later) rainier.2.1.1.kinf file + kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.5 kinf file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for original (earlier than K4PC 1.5.0) kindle-info files + kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC kindle.info file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + if not found: + print('No K4PC kindle.info/kinf/kinf2011 files have been found.') + return kInfoFiles + + + # determine type of kindle info provided and return a + # database of keynames and values + def getDBfromFile(kInfoFile): + names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF'] + DB = {} + with open(kInfoFile, 'rb') as infoReader: + hdr = infoReader.read(1) + data = infoReader.read() + + if data.find('{') != -1 : + # older style kindle-info file + items = data.split('{') + for item in items: + if item != '': + keyhash, rawdata = item.split(':') + keyname = "unknown" + for name in names: + if encodeHash(name,charMap2) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + encryptedValue = decode(rawdata,charMap2) + DB[keyname] = CryptUnprotectData(encryptedValue, "", 0) + elif hdr == '/': + # else rainier-2-1-1 .kinf file + # the .kinf file uses "/" to separate it into records + # so remove the trailing "/" to make it easy to use split + data = data[:-1] + items = data.split('/') + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the raw keyhash string is used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = "unknown" + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents)-largest prime number <= int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using Map5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + else: + # else newest .kinf2011 style .kinf file + # the .kinf file uses "/" to separate it into records + # so remove the trailing "/" to make it easy to use split + # need to put back the first char read because it it part + # of the added entropy blob + data = hdr + data[:-1] + items = data.split('/') + + # starts with and encoded and encrypted header blob + headerblob = items.pop(0) + encryptedValue = decode(headerblob, testMap1) + cleartext = UnprotectHeaderData(encryptedValue) + # now extract the pieces that form the added entropy + pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) + for m in re.finditer(pattern, cleartext): + added_entropy = m.group(2) + m.group(4) + + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the sha1 of raw keyhash string is used to create entropy along + # with the added entropy provided above from the headerblob + entropy = SHA1(keyhash) + added_entropy + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + # key names now use the new testMap8 encoding + keyname = "unknown" + for name in names: + if encodeHash(name,testMap8) == keyhash: + keyname = name + break + + # the testMap8 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using testMap8 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the testMap8 encoded contents seems to be: + # len(contents)-largest prime number <= int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by testMap8 + # by moving noffset chars from the start of the + # string to the end of the string + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using new testMap8 to get the original CryptProtect Data + encryptedValue = decode(encdata,testMap8) + cleartext = CryptUnprotectData(encryptedValue, entropy, 1) + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1")) + # store values used in decryption + DB['IDString'] = GetIDString() + DB['UserName'] = GetUserName() + else: + DB = {} + return DB +elif isosx: + import copy + import subprocess + + # interface to needed routines in openssl's libcrypto + def _load_crypto_libcrypto(): + from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, addressof, string_at, cast + from ctypes.util import find_library + + libcrypto = find_library('crypto') + if libcrypto is None: + raise DrmException(u"libcrypto not found") + libcrypto = CDLL(libcrypto) + + # From OpenSSL's crypto aes header + # + # AES_ENCRYPT 1 + # AES_DECRYPT 0 + # AES_MAXNR 14 (in bytes) + # AES_BLOCK_SIZE 16 (in bytes) + # + # struct aes_key_st { + # unsigned long rd_key[4 *(AES_MAXNR + 1)]; + # int rounds; + # }; + # typedef struct aes_key_st AES_KEY; + # + # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); + # + # note: the ivec string, and output buffer are both mutable + # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc); + + 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]) + + # From OpenSSL's Crypto evp/p5_crpt2.c + # + # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, + # const unsigned char *salt, int saltlen, int iter, + # int keylen, unsigned char *out); + + PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', + [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) + + class LibCrypto(object): + def __init__(self): + self._blocksize = 0 + self._keyctx = None + self._iv = 0 + + def set_decrypt_key(self, userkey, iv): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise DrmException(u"AES improper key used") + return + keyctx = self._keyctx = AES_KEY() + self._iv = iv + self._userkey = userkey + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) + if rv < 0: + raise DrmException(u"Failed to initialize AES key") + + def decrypt(self, data): + out = create_string_buffer(len(data)) + mutable_iv = create_string_buffer(self._iv, len(self._iv)) + keyctx = self._keyctx + rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) + if rv == 0: + raise DrmException(u"AES decryption failed") + return out.raw + + def keyivgen(self, passwd, salt, iter, keylen): + saltlen = len(salt) + passlen = len(passwd) + out = create_string_buffer(keylen) + rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) + return out.raw + return LibCrypto + + def _load_crypto(): + LibCrypto = None + try: + LibCrypto = _load_crypto_libcrypto() + except (ImportError, DrmException): + pass + return LibCrypto + + LibCrypto = _load_crypto() + + # Various character maps used to decrypt books. Probably supposed to act as obfuscation + charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' + charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM' + + # For kinf approach of K4Mac 1.6.X or later + # On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE' + # For Mac they seem to re-use charMap2 here + charMap5 = charMap2 + + # new in K4M 1.9.X + testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD' + + # uses a sub process to get the Hard Drive Serial Number using ioreg + # returns serial numbers of all internal hard drive drives + def GetVolumesSerialNumbers(): + sernums = [] + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + sernums.append(sernum.strip()) + cmdline = '/usr/sbin/ioreg -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('\"Serial Number\" = \"') + if pp >= 0: + sernum = resline[pp+19:-1] + sernums.append(sernum.strip()) + return sernums + + def GetUserHomeAppSupKindleDirParitionName(): + home = os.getenv('HOME') + dpath = home + '/Library' + cmdline = '/sbin/mount' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + disk = '' + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + if resline.startswith('/dev'): + (devpart, mpath) = resline.split(' on ') + dpart = devpart[5:] + pp = mpath.find('(') + if pp >= 0: + mpath = mpath[:pp-1] + if dpath.startswith(mpath): + disk = dpart + return disk + + # uses a sub process to get the UUID of the specified disk partition using ioreg + def GetDiskPartitionUUIDs(diskpart): + uuids = [] + uuidnum = os.getenv('MYUUIDNUMBER') + if uuidnum != None: + uuids.append(strip(uuidnum)) + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + bsdname = None + uuidnum = None + foundIt = False + nest = 0 + uuidnest = -1 + partnest = -2 + for j in xrange(cnt): + resline = reslst[j] + if resline.find('{') >= 0: + nest += 1 + if resline.find('}') >= 0: + nest -= 1 + pp = resline.find('\"UUID\" = \"') + if pp >= 0: + uuidnum = resline[pp+10:-1] + uuidnum = uuidnum.strip() + uuidnest = nest + if partnest == uuidnest and uuidnest > 0: + foundIt = True + break + bb = resline.find('\"BSD Name\" = \"') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == diskpart): + partnest = nest + else : + partnest = -2 + if partnest == uuidnest and partnest > 0: + foundIt = True + break + if nest == 0: + partnest = -2 + uuidnest = -1 + uuidnum = None + bsdname = None + if foundIt: + uuids.append(uuidnum) + return uuids + + def GetMACAddressesMunged(): + macnums = [] + macnum = os.getenv('MYMACNUM') + if macnum != None: + macnums.append(macnum) + cmdline = '/sbin/ifconfig en0' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + macnum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('ether ') + if pp >= 0: + macnum = resline[pp+6:-1] + macnum = macnum.strip() + # print 'original mac', macnum + # now munge it up the way Kindle app does + # by xoring it with 0xa5 and swapping elements 3 and 4 + maclst = macnum.split(':') + n = len(maclst) + if n != 6: + fountIt = False + break + for i in range(6): + maclst[i] = int('0x' + maclst[i], 0) + mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + mlst[5] = maclst[5] ^ 0xa5 + mlst[4] = maclst[3] ^ 0xa5 + mlst[3] = maclst[4] ^ 0xa5 + mlst[2] = maclst[2] ^ 0xa5 + mlst[1] = maclst[1] ^ 0xa5 + mlst[0] = maclst[0] ^ 0xa5 + macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5]) + foundIt = True + break + if foundIt: + macnums.append(macnum) + return macnums + + + # uses unix env to get username instead of using sysctlbyname + def GetUserName(): + username = os.getenv('USER') + return username + + def GetIDStrings(): + # Return all possible ID Strings + strings = [] + strings.extend(GetMACAddressesMunged()) + strings.extend(GetVolumesSerialNumbers()) + diskpart = GetUserHomeAppSupKindleDirParitionName() + strings.extend(GetDiskPartitionUUIDs(diskpart)) + strings.append('9999999999') + #print strings + return strings + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used by Kindle for Mac versions < 1.6.0 + class CryptUnprotectData(object): + def __init__(self, IDString): + sp = IDString + '!@#' + GetUserName() + passwdData = encode(SHA256(sp),charMap1) + salt = '16743' + self.crp = LibCrypto() + iter = 0x3e8 + keylen = 0x80 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext,charMap1) + return cleartext + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used for Kindle for Mac Versions >= 1.6.0 + class CryptUnprotectDataV2(object): + def __init__(self, IDString): + sp = GetUserName() + ':&%:' + IDString + passwdData = encode(SHA256(sp),charMap5) + # salt generation as per the code + salt = 0x0512981d * 2 * 1 * 1 + salt = str(salt) + GetUserName() + salt = encode(salt,charMap5) + self.crp = LibCrypto() + iter = 0x800 + keylen = 0x400 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext, charMap5) + return cleartext + + + # unprotect the new header blob in .kinf2011 + # used in Kindle for Mac Version >= 1.9.0 + def UnprotectHeaderData(encryptedData): + passwdData = 'header_key_data' + salt = 'HEADER.2011' + iter = 0x80 + keylen = 0x100 + crp = LibCrypto() + key_iv = crp.keyivgen(passwdData, salt, iter, keylen) + key = key_iv[0:32] + iv = key_iv[32:48] + crp.set_decrypt_key(key,iv) + cleartext = crp.decrypt(encryptedData) + return cleartext + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used for Kindle for Mac Versions >= 1.9.0 + class CryptUnprotectDataV3(object): + def __init__(self, entropy, IDString): + sp = GetUserName() + '+@#$%+' + IDString + passwdData = encode(SHA256(sp),charMap2) + salt = entropy + self.crp = LibCrypto() + iter = 0x800 + keylen = 0x400 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext, charMap2) + return cleartext + + + # Locate the .kindle-info files + def getKindleInfoFiles(): + # file searches can take a long time on some systems, so just look in known specific places. + kInfoFiles=[] + found = False + home = os.getenv('HOME') + # check for .kinf2011 file in new location (App Store Kindle for Mac) + testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kinf2011 file: ' + testpath) + found = True + # check for .kinf2011 files from 1.10 + testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kinf2011 file: ' + testpath) + found = True + # check for .rainier-2.1.1-kinf files from 1.6 + testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac rainier file: ' + testpath) + found = True + # check for .kindle-info files from 1.4 + testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + # check for .kindle-info file from 1.2.2 + testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + # check for .kindle-info file from 1.0 beta 1 (27214) + testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + if not found: + print('No k4Mac kindle-info/rainier/kinf2011 files have been found.') + return kInfoFiles + + # determine type of kindle info provided and return a + # database of keynames and values + def getDBfromFile(kInfoFile): + names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF'] + with open(kInfoFile, 'rb') as infoReader: + filehdr = infoReader.read(1) + filedata = infoReader.read() + + IDStrings = GetIDStrings() + for IDString in IDStrings: + DB = {} + #print "trying IDString:",IDString + try: + hdr = filehdr + data = filedata + if data.find('[') != -1 : + # older style kindle-info file + cud = CryptUnprotectData(IDString) + items = data.split('[') + for item in items: + if item != '': + keyhash, rawdata = item.split(':') + keyname = 'unknown' + for name in names: + if encodeHash(name,charMap2) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + encryptedValue = decode(rawdata,charMap2) + cleartext = cud.decrypt(encryptedValue) + if len(cleartext) > 0: + DB[keyname] = cleartext + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + elif hdr == '/': + # else newer style .kinf file used by K4Mac >= 1.6.0 + # the .kinf file uses '/' to separate it into records + # so remove the trailing '/' to make it easy to use split + data = data[:-1] + items = data.split('/') + cud = CryptUnprotectDataV2(IDString) + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + keyname = 'unknown' + + # the raw keyhash string is also used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + # 'entropy' not used for K4Mac only K4PC + # entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = 'unknown' + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split 'about' 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = ''.join(edlst) + contlen = len(encdata) + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using charMap5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + cleartext = cud.decrypt(encryptedValue) + if len(cleartext) > 0: + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + else: + # the latest .kinf2011 version for K4M 1.9.1 + # put back the hdr char, it is needed + data = hdr + data + data = data[:-1] + items = data.split('/') + + # the headerblob is the encrypted information needed to build the entropy string + headerblob = items.pop(0) + encryptedValue = decode(headerblob, charMap1) + cleartext = UnprotectHeaderData(encryptedValue) + + # now extract the pieces in the same way + # this version is different from K4PC it scales the build number by multipying by 735 + pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) + for m in re.finditer(pattern, cleartext): + entropy = str(int(m.group(2)) * 0x2df) + m.group(4) + + cud = CryptUnprotectDataV3(entropy,IDString) + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + keyname = 'unknown' + + # unlike K4PC the keyhash is not used in generating entropy + # entropy = SHA1(keyhash) + added_entropy + # entropy = added_entropy + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = 'unknown' + for name in names: + if encodeHash(name,testMap8) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + + # the testMap8 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using testMap8 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the testMap8 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split 'about' 2/3rds of the way through) + + # move first offsets chars to end to align for decode by testMap8 + encdata = ''.join(edlst) + contlen = len(encdata) + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using testMap8 to get the CryptProtect Data + encryptedValue = decode(encdata,testMap8) + cleartext = cud.decrypt(encryptedValue) + # print keyname + # print cleartext + if len(cleartext) > 0: + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + except: + pass + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + # store values used in decryption + print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName()) + DB['IDString'] = IDString + DB['UserName'] = GetUserName() + else: + print u"Couldn't decrypt file." + DB = {} + return DB +else: + def getDBfromFile(kInfoFile): + raise DrmException(u"This script only runs under Windows or Mac OS X.") + return {} + +def kindlekeys(files = []): + keys = [] + if files == []: + files = getKindleInfoFiles() + for file in files: + key = getDBfromFile(file) + if key: + # convert all values to hex, just in case. + for keyname in key: + key[keyname]=key[keyname].encode('hex') + keys.append(key) + return keys + +# interface for Python DeDRM +# returns single key or multiple keys, depending on path or file passed in +def getkey(outpath, files=[]): + keys = kindlekeys(files) + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(keys[0])) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(key)) + print u"Saved a key to {0}".format(outfile) + return True + return False + +def usage(progname): + print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys." + print u"Keys are saved to the current directory, or a specified output directory." + print u"If a file name is passed instead of a directory, only the first key is saved, in that file." + print u"Usage:" + print u" {0:s} [-h] [-k ] []".format(progname) + + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"{0} v{1}\nCopyright © 2010-2013 some_updates and Apprentice Alf".format(progname,__version__) + + try: + opts, args = getopt.getopt(argv[1:], "hk:") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + + files = [] + for o, a in opts: + if o == "-h": + usage(progname) + sys.exit(0) + if o == "-k": + files = [a] + + if len(args) > 1: + usage(progname) + sys.exit(2) + + if len(args) == 1: + # save to the specified file or directory + outpath = args[0] + if not os.path.isabs(outpath): + outpath = os.path.abspath(outpath) + else: + # save to the same directory as the script + outpath = os.path.dirname(argv[0]) + + # make sure the outpath is the + outpath = os.path.realpath(os.path.normpath(outpath)) + + if not getkey(outpath, files): + print u"Could not retrieve Kindle for Mac/PC key." + return 0 + + +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class ExceptionDialog(Tkinter.Frame): + def __init__(self, root, text): + Tkinter.Frame.__init__(self, root, border=5) + label = Tkinter.Label(self, text=u"Unexpected error:", + anchor=Tkconstants.W, justify=Tkconstants.LEFT) + label.pack(fill=Tkconstants.X, expand=0) + self.text = Tkinter.Text(self) + self.text.pack(fill=Tkconstants.BOTH, expand=1) + + self.text.insert(Tkconstants.END, text) + + + argv=unicode_argv() + root = Tkinter.Tk() + root.withdraw() + progpath, progname = os.path.split(argv[0]) + success = False + try: + keys = kindlekeys() + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(progpath,u"kindlekey{0:d}.k4i".format(keycount)) + if not os.path.exists(outfile): + break + + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(key)) + success = True + tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) + except DrmException, e: + tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) + except Exception: + root.wm_state('normal') + root.title(progname) + text = traceback.format_exc() + ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) + root.mainloop() + if not success: + return 1 + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlepid.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlepid.py new file mode 100644 index 0000000..8bbcf69 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlepid.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Mobipocket PID calculator v0.4 for Amazon Kindle. +# Copyright (c) 2007, 2009 Igor Skochinsky +# History: +# 0.1 Initial release +# 0.2 Added support for generating PID for iPhone (thanks to mbp) +# 0.3 changed to autoflush stdout, fixed return code usage +# 0.3 updated for unicode +# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs. +# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility + +import sys +import binascii + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"kindlepid.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +if sys.hexversion >= 0x3000000: + print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.' + sys.exit(2) + +letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' + +def crc32(s): + return (~binascii.crc32(s,-1))&0xFFFFFFFF + +def checksumPid(s): + crc = crc32(s) + 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 pidFromSerial(s, l): + crc = crc32(s) + + arr1 = [0]*l + for i in xrange(len(s)): + arr1[i%l] ^= ord(s[i]) + + crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] + for i in xrange(l): + arr1[i] ^= crc_bytes[i&3] + + pid = '' + for i in xrange(l): + b = arr1[i] & 0xff + pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] + + return pid + +def cli_main(): + print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky" + argv=unicode_argv() + if len(argv)==2: + serial = argv[1] + else: + print u"Usage: kindlepid.py /" + return 1 + if len(serial)==16: + if serial.startswith("B") or serial.startswith("9"): + print u"Kindle serial number detected" + else: + print u"Warning: unrecognized serial number. Please recheck input." + return 1 + pid = pidFromSerial(serial.encode("utf-8"),7)+'*' + print u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)) + return 0 + elif len(serial)==40: + print u"iPhone serial number (UDID) detected" + pid = pidFromSerial(serial.encode("utf-8"),8) + print u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)) + return 0 + print u"Warning: unrecognized serial number. Please recheck input." + return 1 + + +if __name__ == "__main__": + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto.dylib b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto.dylib rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto32.so b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto32.so similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto32.so rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto32.so diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto64.so b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto64.so similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto64.so rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto64.so diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py similarity index 66% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/mobidedrm.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py index cd993e1..7b69edc 100644 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py @@ -1,5 +1,11 @@ -#!/usr/bin/python +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# mobidedrm.py, version 0.38 +# Copyright © 2008 The Dark Reverser # +# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf + # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # @@ -59,26 +65,81 @@ # 0.35 - add interface to get mobi_version # 0.36 - fixed problem with TEXtREAd and getBookTitle interface # 0.37 - Fixed double announcement for stand-alone operation +# 0.38 - Unicode used wherever possible, cope with absent alfcrypto +# 0.39 - Fixed problem with TEXtREAd and getBookType interface +# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 0.41 - Fixed potential unicode problem in command line calls -__version__ = '0.37' +__version__ = u"0.41" import sys - -class Unbuffered: +import os +import struct +import binascii +try: + from alfcrypto import Pukall_Cipher +except: + print u"AlfCrypto not found. Using python PC1 implementation." + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: def __init__(self, stream): self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) -import os -import struct -import binascii -from alfcrypto import Pukall_Cipher +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = 'utf-8' + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + class DrmException(Exception): pass @@ -90,40 +151,45 @@ class DrmException(Exception): # Implementation of Pukall Cipher 1 def PC1(key, src, decryption=True): - return Pukall_Cipher().PC1(key,src,decryption) -# 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 + # if we can get it from alfcrypto, use that + try: + return Pukall_Cipher().PC1(key,src,decryption) + except NameError: + pass + + # use slow python version, since Pukall_Cipher didn't load + sum1 = 0; + sum2 = 0; + keyXorVal = 0; + if len(key)!=16: + DrmException (u"PC1: Bad key length") + 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" + letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' crc = (~binascii.crc32(s,-1))&0xFFFFFFFF crc = crc ^ (crc >> 16) res = s @@ -171,17 +237,24 @@ def loadSection(self, section): off = self.sections[section][0] return self.data_file[off:endoff] - def __init__(self, infile, announce = True): - if announce: - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) + def cleanup(self): + # to match function in Topaz book + pass + + def __init__(self, infile): + print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__) + + try: + from alfcrypto import Pukall_Cipher + except: + print u"AlfCrypto not found. Using python PC1 implementation." # initial sanity check on file self.data_file = file(infile, 'rb').read() self.mobi_data = '' self.header = self.data_file[0:78] if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") + raise DrmException(u"Invalid file format") self.magic = self.header[0x3C:0x3C+8] self.crypto_type = -1 @@ -198,35 +271,37 @@ def __init__(self, infile, announce = True): self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2]) + # det default values before PalmDoc test + self.print_replica = False + self.extra_data_flags = 0 + self.meta_array = {} + self.mobi_length = 0 + self.mobi_codepage = 1252 + self.mobi_version = -1 + if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_codepage = 1252 - self.mobi_version = -1 - self.meta_array = {} + print u"PalmDoc format book detected." return + self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20]) self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 + print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length) if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags + print u"Extra Data Flags: {0:d}".format(self.extra_data_flags) if (self.compression != 17480): # multibyte utf8 data is included in the encryption for PalmDoc compression # so clear that byte so that we leave it to be decrypted. self.extra_data_flags &= 0xFFFE # if exth region exists parse it for metadata array - self.meta_array = {} try: exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' + exth = '' if exth_flag & 0x40: exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): + if (len(exth) >= 12) and (exth[:4] == 'EXTH'): nitems, = struct.unpack('>I', exth[8:12]) pos = 12 for i in xrange(nitems): @@ -236,16 +311,14 @@ def __init__(self, infile, announce = True): # reset the text to speech flag and clipping limit, if present if type == 401 and size == 9: # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) + self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8) elif type == 404 and size == 9: # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) + self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8) # print type, size, content, content.encode('hex') pos += size except: - self.meta_array = {} pass - self.print_replica = False def getBookTitle(self): codec_map = { @@ -265,8 +338,8 @@ def getBookTitle(self): codec = codec_map[self.mobi_codepage] if title == '': title = self.header[:32] - title = title.split("\0")[0] - return unicode(title, codec).encode('utf-8') + title = title.split('\0')[0] + return unicode(title, codec) def getPIDMetaInfo(self): rec209 = '' @@ -297,7 +370,7 @@ def patchSection(self, section, new, in_off = 0): def parseDRM(self, data, count, pidlist): found_key = None - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" + keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96' for pid in pidlist: bigpid = pid.ljust(16,'\0') temp_key = PC1(keyvec1, bigpid, False) @@ -315,7 +388,7 @@ def parseDRM(self, data, count, pidlist): break if not found_key: # Then try the default encoding that doesn't require a PID - pid = "00000000" + pid = '00000000' temp_key = keyvec1 temp_key_sum = sum(map(ord,temp_key)) & 0xff for i in xrange(count): @@ -328,82 +401,92 @@ def parseDRM(self, data, count, pidlist): break return [found_key,pid] - def getMobiFile(self, outpath): + def getFile(self, outpath): file(outpath,'wb').write(self.mobi_data) - def getMobiVersion(self): - return self.mobi_version - - def getPrintReplica(self): - return self.print_replica + def getBookType(self): + if self.print_replica: + return u"Print Replica" + if self.mobi_version >= 8: + return u"Kindle Format 8" + if self.mobi_version >= 0: + return u"Mobipocket {0:d}".format(self.mobi_version) + return u"PalmDoc" + + def getBookExtension(self): + if self.print_replica: + return u".azw4" + if self.mobi_version >= 8: + return u".azw3" + return u".mobi" def processBook(self, pidlist): crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) - print 'Crypto Type is: ', crypto_type + print u"Crypto Type is: {0:d}".format(crypto_type) self.crypto_type = crypto_type if crypto_type == 0: - print "This book is not encrypted." + print u"This book is not encrypted." # we must still check for Print Replica self.print_replica = (self.loadSection(1)[0:4] == '%MOP') self.mobi_data = self.data_file return if crypto_type != 2 and crypto_type != 1: - raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) + raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type)) if 406 in self.meta_array: data406 = self.meta_array[406] val406, = struct.unpack('>Q',data406) if val406 != 0: - raise DrmException("Cannot decode library or rented ebooks.") + raise DrmException(u"Cannot decode library or rented ebooks.") goodpids = [] for pid in pidlist: if len(pid)==10: if checksumPid(pid[0:-2]) != pid: - print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) + print u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])) goodpids.append(pid[0:-2]) elif len(pid)==8: goodpids.append(pid) if self.crypto_type == 1: - t1_keyvec = "QDCVEPMU675RUBSZ" + t1_keyvec = 'QDCVEPMU675RUBSZ' if self.magic == 'TEXtREAd': bookkey_data = self.sect[0x0E:0x0E+16] elif self.mobi_version < 0: bookkey_data = self.sect[0x90:0x90+16] else: bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] - pid = "00000000" + pid = '00000000' found_key = PC1(t1_keyvec, bookkey_data) else : # calculate the keys drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) if drm_count == 0: - raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") + raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.") found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) if not found_key: - raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.") + raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids))) # kill the drm keys - self.patchSection(0, "\0" * drm_size, drm_ptr) + self.patchSection(0, '\0' * drm_size, drm_ptr) # kill the drm pointers - self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) + self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8) - if pid=="00000000": - print "File has default encryption, no specific PID." + if pid=='00000000': + print u"File has default encryption, no specific key needed." else: - print "File is encoded with PID "+checksumPid(pid)+"." + print u"File is encoded with PID {0}.".format(checksumPid(pid)) # clear the crypto type self.patchSection(0, "\0" * 2, 0xC) # decrypt sections - print "Decrypting. Please wait . . .", + print u"Decrypting. Please wait . . .", mobidataList = [] mobidataList.append(self.data_file[:self.sections[1][0]]) for i in xrange(1, self.records+1): data = self.loadSection(i) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: - print ".", + print u".", # print "record %d, extra_size %d" %(i,extra_size) decoded_data = PC1(found_key, data[0:len(data) - extra_size]) if i==1: @@ -414,31 +497,25 @@ def processBook(self, pidlist): if self.num_sections > self.records+1: mobidataList.append(self.data_file[self.sections[self.records+1][0]:]) self.mobi_data = "".join(mobidataList) - print "done" + print u"done" return -def getUnencryptedBook(infile,pid,announce=True): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile,announce) - book.processBook([pid]) - return book.mobi_data - -def getUnencryptedBookWithList(infile,pidlist,announce=True): +def getUnencryptedBook(infile,pidlist): if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile, announce) + raise DrmException(u"Input File Not Found.") + book = MobiBook(infile) book.processBook(pidlist) return book.mobi_data -def main(argv=sys.argv): - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) +def cli_main(): + argv=unicode_argv() + progname = os.path.basename(argv[0]) if len(argv)<3 or len(argv)>4: - print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks" - print "Usage:" - print " %s []" % sys.argv[0] + print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__) + print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks" + print u"Usage:" + print u" {0} []".format(progname) return 1 else: infile = argv[1] @@ -446,15 +523,17 @@ def main(argv=sys.argv): if len(argv) is 4: pidlist = argv[3].split(',') else: - pidlist = {} + pidlist = [] try: - stripped_file = getUnencryptedBookWithList(infile, pidlist, False) + stripped_file = getUnencryptedBook(infile, pidlist) file(outfile, 'wb').write(stripped_file) except DrmException, e: - print "Error: %s" % e + print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0]) return 1 return 0 -if __name__ == "__main__": - sys.exit(main()) +if __name__ == '__main__': + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/openssl_des.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py old mode 100755 new mode 100644 similarity index 98% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/openssl_des.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py index a4a40ca..9a84e58 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/openssl_des.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py @@ -65,7 +65,7 @@ def F(restype, name, argtypes): class DES(object): def __init__(self, key): if len(key) != 8 : - raise Error('DES improper key used') + raise Exception('DES improper key used') return self.key = key self.keyschedule = DES_KEY_SCHEDULE() diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/plugin-import-name-dedrm.txt b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/plugin-import-name-dedrm.txt new file mode 100644 index 0000000..e69de29 diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py new file mode 100755 index 0000000..2c8c665 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +from __future__ import with_statement +__license__ = 'GPL v3' + +# Standard Python modules. +import os, sys, re, hashlib +import json +import traceback + +from calibre.utils.config import dynamic, config_dir, JSONConfig +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION +from calibre.constants import iswindows, isosx + +class DeDRM_Prefs(): + def __init__(self): + JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json') + self.dedrmprefs = JSONConfig(JSON_PATH) + + self.dedrmprefs.defaults['configured'] = False + self.dedrmprefs.defaults['bandnkeys'] = {} + self.dedrmprefs.defaults['adeptkeys'] = {} + self.dedrmprefs.defaults['ereaderkeys'] = {} + self.dedrmprefs.defaults['kindlekeys'] = {} + self.dedrmprefs.defaults['pids'] = [] + self.dedrmprefs.defaults['serials'] = [] + self.dedrmprefs.defaults['adobewineprefix'] = "" + self.dedrmprefs.defaults['kindlewineprefix'] = "" + + # initialise + # we must actually set the prefs that are dictionaries and lists + # to empty dictionaries and lists, otherwise we are unable to add to them + # as then it just adds to the (memory only) dedrmprefs.defaults versions! + if self.dedrmprefs['bandnkeys'] == {}: + self.dedrmprefs['bandnkeys'] = {} + if self.dedrmprefs['adeptkeys'] == {}: + self.dedrmprefs['adeptkeys'] = {} + if self.dedrmprefs['ereaderkeys'] == {}: + self.dedrmprefs['ereaderkeys'] = {} + if self.dedrmprefs['kindlekeys'] == {}: + self.dedrmprefs['kindlekeys'] = {} + if self.dedrmprefs['pids'] == []: + self.dedrmprefs['pids'] = [] + if self.dedrmprefs['serials'] == []: + self.dedrmprefs['serials'] = [] + + def __getitem__(self,kind = None): + if kind is not None: + return self.dedrmprefs[kind] + return self.dedrmprefs + + def set(self, kind, value): + self.dedrmprefs[kind] = value + + def writeprefs(self,value = True): + self.dedrmprefs['configured'] = value + + def addnamedvaluetoprefs(self, prefkind, keyname, keyvalue): + try: + if keyvalue not in self.dedrmprefs[prefkind].values(): + # ensure that the keyname is unique + # by adding a number (starting with 2) to the name if it is not + namecount = 1 + newname = keyname + while newname in self.dedrmprefs[prefkind]: + namecount += 1 + newname = "{0:s}_{1:d}".format(keyname,namecount) + # add to the preferences + self.dedrmprefs[prefkind][newname] = keyvalue + return (True, newname) + except: + traceback.print_exc() + pass + return (False, keyname) + + def addvaluetoprefs(self, prefkind, prefsvalue): + # ensure the keyvalue isn't already in the preferences + try: + if prefsvalue not in self.dedrmprefs[prefkind]: + self.dedrmprefs[prefkind].append(prefsvalue) + return True + except: + traceback.print_exc() + return False + + +def convertprefs(always = False): + + def parseIgnobleString(keystuff): + from calibre_plugins.dedrm.ignoblekeygen import generate_key + userkeys = [] + ar = keystuff.split(':') + for keystring in ar: + try: + name, ccn = keystring.split(',') + # Generate Barnes & Noble EPUB user key from name and credit card number. + keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:]) + keyvalue = generate_key(name, ccn) + userkeys.append([keyname,keyvalue]) + except Exception, e: + traceback.print_exc() + print e.args[0] + pass + return userkeys + + def parseeReaderString(keystuff): + from calibre_plugins.dedrm.erdr2pml import getuser_key + userkeys = [] + ar = keystuff.split(':') + for keystring in ar: + try: + name, cc = keystring.split(',') + # Generate eReader user key from name and credit card number. + keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:]) + keyvalue = getuser_key(name,cc).encode('hex') + userkeysappend([keyname,keyvalue]) + except Exception, e: + traceback.print_exc() + print e.args[0] + pass + return userkeys + + def parseKindleString(keystuff): + pids = [] + serials = [] + ar = keystuff.split(',') + for keystring in ar: + keystring = str(keystring).strip().replace(" ","") + if len(keystring) == 10 or len(keystring) == 8 and keystring not in pids: + pids.append(keystring) + elif len(keystring) == 16 and keystring[0] == 'B' and keystring not in serials: + serials.append(keystring) + return (pids,serials) + + def getConfigFiles(extension, encoding = None): + # get any files with extension 'extension' in the config dir + userkeys = [] + files = [f for f in os.listdir(config_dir) if f.endswith(extension)] + for filename in files: + try: + fpath = os.path.join(config_dir, filename) + key = os.path.splitext(filename)[0] + value = open(fpath, 'rb').read() + if encoding is not None: + value = value.encode(encoding) + userkeys.append([key,value]) + except: + traceback.print_exc() + pass + return userkeys + + dedrmprefs = DeDRM_Prefs() + + if (not always) and dedrmprefs['configured']: + # We've already converted old preferences, + # and we're not being forced to do it again, so just return + return + + + print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION) + + IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM" + EREADERPLUGINNAME = "eReader PDB 2 PML" + OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM" + + # get prefs from older tools + kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM")) + ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm")) + + # Handle the old ignoble plugin's customization string by converting the + # old string to stored keys... get that personal data out of plain sight. + from calibre.customize.ui import config + sc = config['plugin_customization'] + val = sc.pop(IGNOBLEPLUGINNAME, None) + if val is not None: + print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) + priorkeycount = len(dedrmprefs['bandnkeys']) + userkeys = parseIgnobleString(str(val)) + for keypair in userkeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) + addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount + print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # Handle the old eReader plugin's customization string by converting the + # old string to stored keys... get that personal data out of plain sight. + val = sc.pop(EREADERPLUGINNAME, None) + if val is not None: + print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) + priorkeycount = len(dedrmprefs['ereaderkeys']) + userkeys = parseeReaderString(str(val)) + for keypair in userkeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value) + addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount + print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get old Kindle plugin configuration string + val = sc.pop(OLDKINDLEPLUGINNAME, None) + if val is not None: + print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) + priorpidcount = len(dedrmprefs['pids']) + priorserialcount = len(dedrmprefs['serials']) + pids, serials = parseKindleString(val) + for pid in pids: + dedrmprefs.addvaluetoprefs('pids',pid) + for serial in serials: + dedrmprefs.addvaluetoprefs('serials',serial) + addedpidcount = len(dedrmprefs['pids']) - priorpidcount + addedserialcount = len(dedrmprefs['serials']) - priorserialcount + print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # copy the customisations back into calibre preferences, as we've now removed the nasty plaintext + config['plugin_customization'] = sc + + # get any .b64 files in the config dir + priorkeycount = len(dedrmprefs['bandnkeys']) + bandnfilekeys = getConfigFiles('.b64') + for keypair in bandnfilekeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) + addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount + if addedkeycount > 0: + print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, ignoblecount, u"key file" if ignoblecount==1 else u"key files") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get any .der files in the config dir + priorkeycount = len(dedrmprefs['adeptkeys']) + adeptfilekeys = getConfigFiles('.der','hex') + ineptcount = addConfigFiles('.der', 'adeptkeys') + for keypair in adeptfilekeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value) + addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount + if addedkeycount > 0: + print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, ineptcount, u"keyfile" if ineptcount==1 else u"keyfiles") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get ignoble json prefs + if 'keys' in ignobleprefs: + priorkeycount = len(dedrmprefs['bandnkeys']) + for name in ignobleprefs['keys']: + value = ignobleprefs['keys'][name] + dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) + addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount + # no need to delete old prefs, since they contain no recoverable private data + if addedkeycount > 0: + print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get kindle json prefs + priorpidcount = len(dedrmprefs['pids']) + priorserialcount = len(dedrmprefs['serials']) + if 'pids' in kindleprefs: + pids, serials = parseKindleString(kindleprefs['pids']) + for pid in pids: + dedrmprefs.addvaluetoprefs('pids',pid) + if 'serials' in kindleprefs: + pids, serials = parseKindleString(kindleprefs['serials']) + for serial in serials: + dedrmprefs.addvaluetoprefs('serials',serial) + addedpidcount = len(dedrmprefs['pids']) - priorpidcount + if addedpidcount > 0: + print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs") + addedserialcount = len(dedrmprefs['serials']) - priorserialcount + if addedserialcount > 0: + print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers") + try: + if kindleprefs['wineprefix'] != "": + dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix']) + dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix']) + print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix']) + except: + traceback.print_exc() + + + # Make the json write all the prefs to disk + dedrmprefs.writeprefs() + print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/pycrypto_des.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pycrypto_des.py similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/pycrypto_des.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pycrypto_des.py diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/python_des.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/python_des.py similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/python_des.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/python_des.py diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py new file mode 100644 index 0000000..3be643f --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import sys +import os +import re +import ineptepub +import ignobleepub +import epubtest +import zipfix +import ineptpdf +import erdr2pml +import k4mobidedrm +import traceback + +def decryptepub(infile, outdir, rscpath): + errlog = '' + + # first fix the epub to make sure we do not get errors + name, ext = os.path.splitext(os.path.basename(infile)) + bpath = os.path.dirname(infile) + zippath = os.path.join(bpath,name + '_temp.zip') + rv = zipfix.repairBook(infile, zippath) + if rv != 0: + print "Error while trying to fix epub" + return rv + + # determine a good name for the output file + outfile = os.path.join(outdir, name + '_nodrm.epub') + + rv = 1 + # first try with the Adobe adept epub + if ineptepub.adeptBook(zippath): + # try with any keyfiles (*.der) in the rscpath + files = os.listdir(rscpath) + filefilter = re.compile("\.der$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + keypath = os.path.join(rscpath, filename) + userkey = open(keypath,'rb').read() + try: + rv = ineptepub.decryptBook(userkey, zippath, outfile) + if rv == 0: + print "Decrypted Adobe ePub with key file {0}".format(filename) + break + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + # now try with ignoble epub + elif ignobleepub.ignobleBook(zippath): + # try with any keyfiles (*.b64) in the rscpath + files = os.listdir(rscpath) + filefilter = re.compile("\.b64$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + keypath = os.path.join(rscpath, filename) + userkey = open(keypath,'r').read() + #print userkey + try: + rv = ignobleepub.decryptBook(userkey, zippath, outfile) + if rv == 0: + print "Decrypted B&N ePub with key file {0}".format(filename) + break + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + else: + encryption = epubtest.encryption(zippath) + if encryption == "Unencrypted": + print "{0} is not DRMed.".format(name) + rv = 0 + else: + print "{0} has an unknown encryption.".format(name) + + os.remove(zippath) + if rv != 0: + print errlog + return rv + + +def decryptpdf(infile, outdir, rscpath): + errlog = '' + rv = 1 + + # determine a good name for the output file + name, ext = os.path.splitext(os.path.basename(infile)) + outfile = os.path.join(outdir, name + '_nodrm.pdf') + + # try with any keyfiles (*.der) in the rscpath + files = os.listdir(rscpath) + filefilter = re.compile("\.der$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + keypath = os.path.join(rscpath, filename) + userkey = open(keypath,'rb').read() + try: + rv = ineptpdf.decryptBook(userkey, infile, outfile) + if rv == 0: + break + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + + if rv != 0: + print errlog + return rv + + +def decryptpdb(infile, outdir, rscpath): + outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz" + outpath = os.path.join(outdir, outname) + rv = 1 + socialpath = os.path.join(rscpath,'sdrmlist.txt') + if os.path.exists(socialpath): + keydata = file(socialpath,'r').read() + keydata = keydata.rstrip(os.linesep) + ar = keydata.split(',') + for i in ar: + try: + name, cc8 = i.split(':') + except ValueError: + print ' Error parsing user supplied social drm data.' + return 1 + try: + rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8)) + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + + if rv == 0: + break + return rv + + +def decryptk4mobi(infile, outdir, rscpath): + rv = 1 + pidnums = [] + pidspath = os.path.join(rscpath,'pidlist.txt') + if os.path.exists(pidspath): + pidstr = file(pidspath,'r').read() + pidstr = pidstr.rstrip(os.linesep) + pidstr = pidstr.strip() + if pidstr != '': + pidnums = pidstr.split(',') + serialnums = [] + serialnumspath = os.path.join(rscpath,'seriallist.txt') + if os.path.exists(serialnumspath): + serialstr = file(serialnumspath,'r').read() + serialstr = serialstr.rstrip(os.linesep) + serialstr = serialstr.strip() + if serialstr != '': + serialnums = serialstr.split(',') + kDatabaseFiles = [] + files = os.listdir(rscpath) + filefilter = re.compile("\.k4i$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + dpath = os.path.join(rscpath,filename) + kDatabaseFiles.append(dpath) + try: + rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, serialnums, pidnums) + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + + return rv diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/stylexml2css.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/stylexml2css.py old mode 100755 new mode 100644 similarity index 95% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/stylexml2css.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/stylexml2css.py index 2347f6a..c111850 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/stylexml2css.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/stylexml2css.py @@ -10,6 +10,7 @@ from struct import pack from struct import unpack +debug = False class DocParser(object): def __init__(self, flatxml, fontsize, ph, pw): @@ -113,7 +114,9 @@ def process(self): # process each style converting what you can + if debug: print ' ', 'Processing styles.' for j in xrange(stylecnt): + if debug: print ' ', 'Processing style %d' %(j) start = styleList[j] end = styleList[j+1] @@ -132,6 +135,8 @@ def process(self): else : sclass = '' + if debug: print 'sclass', sclass + # check for any "after class" specifiers (pos, aftclass) = self.findinDoc('style._after_class',start,end) if aftclass != None: @@ -140,6 +145,8 @@ def process(self): else : aftclass = '' + if debug: print 'aftclass', aftclass + cssargs = {} while True : @@ -147,6 +154,9 @@ def process(self): (pos1, attr) = self.findinDoc('style.rule.attr', start, end) (pos2, val) = self.findinDoc('style.rule.value', start, end) + if debug: print 'attr', attr + if debug: print 'val', val + if attr == None : break if (attr == 'display') or (attr == 'pos') or (attr == 'align'): @@ -164,7 +174,7 @@ def process(self): scale = self.pw elif attr == 'line-space': scale = self.fontsize * 2.0 - + if val == "": val = 0 @@ -179,6 +189,7 @@ def process(self): if aftclass != "" : keep = False if keep : + if debug: print 'keeping style' # make sure line-space does not go below 100% or above 300% since # it can be wacky in some styles if 'line-space' in cssargs: @@ -256,7 +267,9 @@ def convert2CSS(flatxml, fontsize, ph, pw): # create a document parser dp = DocParser(flatxml, fontsize, ph, pw) + if debug: print ' ', 'Created DocParser.' csspage = dp.process() + if debug: print ' ', 'Processed DocParser.' return csspage diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/topazextract.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py old mode 100755 new mode 100644 similarity index 56% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/topazextract.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py index bf2ad47..97f6583 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/topazextract.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py @@ -1,43 +1,97 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -class Unbuffered: +# topazextract.py +# Mostly written by some_updates based on code from many others + +# Changelog +# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 5.0 - Fixed potential unicode problem with command line interface + +__version__ = '5.0' + +import sys +import os, csv, getopt +import zlib, zipfile, tempfile, shutil +import traceback +from struct import pack +from struct import unpack +from alfcrypto import Topaz_Cipher + +class SafeUnbuffered: def __init__(self, stream): self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) -import sys +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = 'utf-8' + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +#global switch +debug = False if 'calibre' in sys.modules: inCalibre = True + from calibre_plugins.dedrm import kgenpids else: inCalibre = False + import kgenpids -buildXML = False - -import os, csv, getopt -import zlib, zipfile, tempfile, shutil -from struct import pack -from struct import unpack -from alfcrypto import Topaz_Cipher -class TpzDRMError(Exception): +class DrmException(Exception): pass -# local support routines -if inCalibre: - from calibre_plugins.k4mobidedrm import kgenpids -else: - import kgenpids - # recursive zip creation support routine def zipUpDir(myzip, tdir, localname): currentdir = tdir - if localname != "": + if localname != u"": currentdir = os.path.join(currentdir,localname) list = os.listdir(currentdir) for file in list: @@ -73,7 +127,7 @@ def bookReadEncodedNumber(fo): # Get a length prefixed string from file def bookReadString(fo): stringLength = bookReadEncodedNumber(fo) - return unpack(str(stringLength)+"s",fo.read(stringLength))[0] + return unpack(str(stringLength)+'s',fo.read(stringLength))[0] # # crypto routines @@ -112,13 +166,13 @@ def decryptRecord(data,PID): # Try to decrypt a dkey record (contains the bookPID) def decryptDkeyRecord(data,PID): record = decryptRecord(data,PID) - fields = unpack("3sB8sB8s3s",record) - if fields[0] != "PID" or fields[5] != "pid" : - raise TpzDRMError("Didn't find PID magic numbers in record") + fields = unpack('3sB8sB8s3s',record) + if fields[0] != 'PID' or fields[5] != 'pid' : + raise DrmException(u"Didn't find PID magic numbers in record") elif fields[1] != 8 or fields[3] != 8 : - raise TpzDRMError("Record didn't contain correct length fields") + raise DrmException(u"Record didn't contain correct length fields") elif fields[2] != PID : - raise TpzDRMError("Record didn't contain PID") + raise DrmException(u"Record didn't contain PID") return fields[4] # Decrypt all dkey records (contain the book PID) @@ -131,11 +185,11 @@ def decryptDkeyRecords(data,PID): try: key = decryptDkeyRecord(data[1:length+1],PID) records.append(key) - except TpzDRMError: + except DrmException: pass data = data[1+length:] if len(records) == 0: - raise TpzDRMError("BookKey Not Found") + raise DrmException(u"BookKey Not Found") return records @@ -148,9 +202,9 @@ def __init__(self, filename): self.bookHeaderRecords = {} self.bookMetadata = {} self.bookKey = None - magic = unpack("4s",self.fo.read(4))[0] + magic = unpack('4s',self.fo.read(4))[0] if magic != 'TPZ0': - raise TpzDRMError("Parse Error : Invalid Header, not a Topaz file") + raise DrmException(u"Parse Error : Invalid Header, not a Topaz file") self.parseTopazHeaders() self.parseMetadata() @@ -159,6 +213,7 @@ def bookReadHeaderRecordData(): # Read and return the data of one header record at the current book file position # [[offset,decompressedLength,compressedLength],...] nbValues = bookReadEncodedNumber(self.fo) + if debug: print "%d records in header " % nbValues, values = [] for i in range (0,nbValues): values.append([bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo)]) @@ -167,33 +222,34 @@ def parseTopazHeaderRecord(): # Read and parse one header record at the current book file position and return the associated data # [[offset,decompressedLength,compressedLength],...] if ord(self.fo.read(1)) != 0x63: - raise TpzDRMError("Parse Error : Invalid Header") + raise DrmException(u"Parse Error : Invalid Header") tag = bookReadString(self.fo) record = bookReadHeaderRecordData() return [tag,record] nbRecords = bookReadEncodedNumber(self.fo) + if debug: print "Headers: %d" % nbRecords for i in range (0,nbRecords): result = parseTopazHeaderRecord() - # print result[0], result[1] + if debug: print result[0], ": ", result[1] self.bookHeaderRecords[result[0]] = result[1] if ord(self.fo.read(1)) != 0x64 : - raise TpzDRMError("Parse Error : Invalid Header") + raise DrmException(u"Parse Error : Invalid Header") self.bookPayloadOffset = self.fo.tell() def parseMetadata(self): # Parse the metadata record from the book payload and return a list of [key,values] - self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords["metadata"][0][0]) + self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0]) tag = bookReadString(self.fo) - if tag != "metadata" : - raise TpzDRMError("Parse Error : Record Names Don't Match") + if tag != 'metadata' : + raise DrmException(u"Parse Error : Record Names Don't Match") flags = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1)) - # print nbRecords + if debug: print "Metadata Records: %d" % nbRecords for i in range (0,nbRecords) : keyval = bookReadString(self.fo) content = bookReadString(self.fo) - # print keyval - # print content + if debug: print keyval + if debug: print content self.bookMetadata[keyval] = content return self.bookMetadata @@ -210,7 +266,7 @@ def getBookTitle(self): title = '' if 'Title' in self.bookMetadata: title = self.bookMetadata['Title'] - return title + return title.decode('utf-8') def setBookKey(self, key): self.bookKey = key @@ -223,13 +279,13 @@ def getBookPayloadRecord(self, name, index): try: recordOffset = self.bookHeaderRecords[name][index][0] except: - raise TpzDRMError("Parse Error : Invalid Record, record not found") + raise DrmException("Parse Error : Invalid Record, record not found") self.fo.seek(self.bookPayloadOffset + recordOffset) tag = bookReadString(self.fo) if tag != name : - raise TpzDRMError("Parse Error : Invalid Record, record name doesn't match") + raise DrmException("Parse Error : Invalid Record, record name doesn't match") recordIndex = bookReadEncodedNumber(self.fo) if recordIndex < 0 : @@ -237,7 +293,7 @@ def getBookPayloadRecord(self, name, index): recordIndex = -recordIndex -1 if recordIndex != index : - raise TpzDRMError("Parse Error : Invalid Record, index doesn't match") + raise DrmException("Parse Error : Invalid Record, index doesn't match") if (self.bookHeaderRecords[name][index][2] > 0): compressed = True @@ -250,7 +306,7 @@ def getBookPayloadRecord(self, name, index): ctx = topazCryptoInit(self.bookKey) record = topazCryptoDecrypt(record,ctx) else : - raise TpzDRMError("Error: Attempt to decrypt without bookKey") + raise DrmException("Error: Attempt to decrypt without bookKey") if compressed: record = zlib.decompress(record) @@ -262,20 +318,20 @@ def processBook(self, pidlst): fixedimage=True try: keydata = self.getBookPayloadRecord('dkey', 0) - except TpzDRMError, e: - print "no dkey record found, book may not be encrypted" - print "attempting to extrct files without a book key" + except DrmException, e: + print u"no dkey record found, book may not be encrypted" + print u"attempting to extrct files without a book key" self.createBookDirectory() self.extractFiles() - print "Successfully Extracted Topaz contents" + print u"Successfully Extracted Topaz contents" if inCalibre: - from calibre_plugins.k4mobidedrm import genbook + from calibre_plugins.dedrm import genbook else: import genbook rv = genbook.generateBook(self.outdir, raw, fixedimage) if rv == 0: - print "\nBook Successfully generated" + print u"Book Successfully generated." return rv # try each pid to decode the file @@ -283,33 +339,33 @@ def processBook(self, pidlst): for pid in pidlst: # use 8 digit pids here pid = pid[0:8] - print "\nTrying: ", pid + print u"Trying: {0}".format(pid) bookKeys = [] data = keydata try: bookKeys+=decryptDkeyRecords(data,pid) - except TpzDRMError, e: + except DrmException, e: pass else: bookKey = bookKeys[0] - print "Book Key Found!" + print u"Book Key Found! ({0})".format(bookKey.encode('hex')) break if not bookKey: - raise TpzDRMError("Topaz Book. No key found in " + str(len(pidlst)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.") + raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(pidlst))) self.setBookKey(bookKey) self.createBookDirectory() self.extractFiles() - print "Successfully Extracted Topaz contents" + print u"Successfully Extracted Topaz contents" if inCalibre: - from calibre_plugins.k4mobidedrm import genbook + from calibre_plugins.dedrm import genbook else: import genbook rv = genbook.generateBook(self.outdir, raw, fixedimage) if rv == 0: - print "\nBook Successfully generated" + print u"Book Successfully generated" return rv def createBookDirectory(self): @@ -317,16 +373,16 @@ def createBookDirectory(self): # create output directory structure if not os.path.exists(outdir): os.makedirs(outdir) - destdir = os.path.join(outdir,'img') + destdir = os.path.join(outdir,u"img") if not os.path.exists(destdir): os.makedirs(destdir) - destdir = os.path.join(outdir,'color_img') + destdir = os.path.join(outdir,u"color_img") if not os.path.exists(destdir): os.makedirs(destdir) - destdir = os.path.join(outdir,'page') + destdir = os.path.join(outdir,u"page") if not os.path.exists(destdir): os.makedirs(destdir) - destdir = os.path.join(outdir,'glyphs') + destdir = os.path.join(outdir,u"glyphs") if not os.path.exists(destdir): os.makedirs(destdir) @@ -334,149 +390,149 @@ def extractFiles(self): outdir = self.outdir for headerRecord in self.bookHeaderRecords: name = headerRecord - if name != "dkey" : - ext = '.dat' - if name == 'img' : ext = '.jpg' - if name == 'color' : ext = '.jpg' - print "\nProcessing Section: %s " % name + if name != 'dkey': + ext = u".dat" + if name == 'img': ext = u".jpg" + if name == 'color' : ext = u".jpg" + print u"Processing Section: {0}\n. . .".format(name), for index in range (0,len(self.bookHeaderRecords[name])) : - fnum = "%04d" % index - fname = name + fnum + ext + fname = u"{0}{1:04d}{2}".format(name,index,ext) destdir = outdir if name == 'img': - destdir = os.path.join(outdir,'img') + destdir = os.path.join(outdir,u"img") if name == 'color': - destdir = os.path.join(outdir,'color_img') + destdir = os.path.join(outdir,u"color_img") if name == 'page': - destdir = os.path.join(outdir,'page') + destdir = os.path.join(outdir,u"page") if name == 'glyphs': - destdir = os.path.join(outdir,'glyphs') + destdir = os.path.join(outdir,u"glyphs") outputFile = os.path.join(destdir,fname) - print ".", + print u".", record = self.getBookPayloadRecord(name,index) if record != '': file(outputFile, 'wb').write(record) - print " " + print u" " - def getHTMLZip(self, zipname): + def getFile(self, zipname): htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html') - htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(self.outdir,'cover.jpg')): - htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg') - htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css') - zipUpDir(htmlzip, self.outdir, 'img') + htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html") + htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf") + if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")): + htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg") + htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css") + zipUpDir(htmlzip, self.outdir, u"img") htmlzip.close() + def getBookType(self): + return u"Topaz" + + def getBookExtension(self): + return u".htmlz" + def getSVGZip(self, zipname): svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(svgzip, self.outdir, 'svg') - zipUpDir(svgzip, self.outdir, 'img') + svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml") + zipUpDir(svgzip, self.outdir, u"svg") + zipUpDir(svgzip, self.outdir, u"img") svgzip.close() - def getXMLZip(self, zipname): - xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(self.outdir,'xml') - zipUpDir(xmlzip, targetdir, '') - zipUpDir(xmlzip, self.outdir, 'img') - xmlzip.close() - def cleanup(self): if os.path.isdir(self.outdir): shutil.rmtree(self.outdir, True) def usage(progname): - print "Removes DRM protection from Topaz ebooks and extract the contents" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - + print u"Removes DRM protection from Topaz ebooks and extracts the contents" + print u"Usage:" + print u" {0} [-k ] [-p ] [-s ] ".format(progname) # Main -def main(argv=sys.argv): - global buildXML +def cli_main(): + argv=unicode_argv() progname = os.path.basename(argv[0]) - k4 = False - pids = [] - serials = [] - kInfoFiles = [] + print u"TopazExtract v{0}.".format(__version__) try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") + opts, args = getopt.getopt(argv[1:], "k:p:s:x") except getopt.GetoptError, err: - print str(err) + print u"Error in options or arguments: {0}".format(err.args[0]) usage(progname) return 1 if len(args)<2: usage(progname) return 1 + infile = args[0] + outdir = args[1] + if not os.path.isfile(infile): + print u"Input File {0} Does Not Exist.".format(infile) + return 1 + + if not os.path.exists(outdir): + print u"Output Directory {0} Does Not Exist.".format(outdir) + return 1 + + kDatabaseFiles = [] + serials = [] + pids = [] + for o, a in opts: - if o == "-k": + if o == '-k': if a == None : - print "Invalid parameter for -k" - return 1 - kInfoFiles.append(a) - if o == "-p": + raise DrmException("Invalid parameter for -k") + kDatabaseFiles.append(a) + if o == '-p': if a == None : - print "Invalid parameter for -p" - return 1 + raise DrmException("Invalid parameter for -p") pids = a.split(',') - if o == "-s": + if o == '-s': if a == None : - print "Invalid parameter for -s" - return 1 - serials = a.split(',') - k4 = True - - infile = args[0] - outdir = args[1] - - if not os.path.isfile(infile): - print "Input File Does Not Exist" - return 1 + raise DrmException("Invalid parameter for -s") + serials = [serial.replace(" ","") for serial in a.split(',')] bookname = os.path.splitext(os.path.basename(infile))[0] tb = TopazBook(infile) title = tb.getBookTitle() - print "Processing Book: ", title - keysRecord, keysRecordRecord = tb.getPIDMetaInfo() - pids.extend(kgenpids.getPidList(keysRecord, keysRecordRecord, k4, serials, kInfoFiles)) + print u"Processing Book: {0}".format(title) + md1, md2 = tb.getPIDMetaInfo() + pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles)) try: - print "Decrypting Book" + print u"Decrypting Book" tb.processBook(pids) - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz') - tb.getHTMLZip(zipname) + print u" Creating HTML ZIP Archive" + zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz") + tb.getFile(zipname) - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') + print u" Creating SVG ZIP Archive" + zipname = os.path.join(outdir, bookname + u"_SVG.zip") tb.getSVGZip(zipname) - if buildXML: - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_XML' + '.zip') - tb.getXMLZip(zipname) - # removing internal temporary directory of pieces tb.cleanup() - except TpzDRMError, e: - print str(e) - # tb.cleanup() + except DrmException, e: + print u"Decryption failed\n{0}".format(traceback.format_exc()) + + try: + tb.cleanup() + except: + pass return 1 except Exception, e: - print str(e) - # tb.cleanup + print u"Decryption failed\m{0}".format(traceback.format_exc()) + try: + tb.cleanup() + except: + pass return 1 return 0 if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/utilities.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/utilities.py new file mode 100644 index 0000000..4ebb301 --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/utilities.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +__license__ = 'GPL v3' + +DETAILED_MESSAGE = \ +'You have personal information stored in this plugin\'s customization '+ \ +'string from a previous version of this plugin.\n\n'+ \ +'This new version of the plugin can convert that info '+ \ +'into key data that the new plugin can then use (which doesn\'t '+ \ +'require personal information to be stored/displayed in an insecure '+ \ +'manner like the old plugin did).\n\nIf you choose NOT to migrate this data at this time '+ \ +'you will be prompted to save that personal data to a file elsewhere; and you\'ll have '+ \ +'to manually re-configure this plugin with your information.\n\nEither way... ' + \ +'this new version of the plugin will not be responsible for storing that personal '+ \ +'info in plain sight any longer.' + +def uStrCmp (s1, s2, caseless=False): + import unicodedata as ud + str1 = s1 if isinstance(s1, unicode) else unicode(s1) + str2 = s2 if isinstance(s2, unicode) else unicode(s2) + if caseless: + return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower()) + else: + return ud.normalize('NFC', str1) == ud.normalize('NFC', str2) + +def parseCustString(keystuff): + userkeys = [] + ar = keystuff.split(':') + for i in ar: + try: + name, ccn = i.split(',') + # Generate Barnes & Noble EPUB user key from name and credit card number. + userkeys.append(generate_key(name, ccn)) + except: + pass + return userkeys diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/wineutils.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/wineutils.py new file mode 100755 index 0000000..f8d5f7a --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/wineutils.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +__license__ = 'GPL v3' + +# Standard Python modules. +import os, sys, re, hashlib +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION + +def WineGetKeys(scriptpath, extension, wineprefix=""): + import subprocess + from subprocess import Popen, PIPE, STDOUT + + import subasyncio + from subasyncio import Process + + if extension == u".k4i": + import json + + basepath, script = os.path.split(scriptpath) + print u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script) + + outdirpath = os.path.join(basepath, u"winekeysdir") + if not os.path.exists(outdirpath): + os.mkdir(outdirpath) + + if wineprefix != "" and os.path.exists(wineprefix): + cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix) + else: + cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath) + print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline) + + try: + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) + result = p2.wait("wait") + except Exception, e: + print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]) + return [] + + winekeys = [] + # get any files with extension in the output dir + files = [f for f in os.listdir(outdirpath) if f.endswith(extension)] + for filename in files: + try: + fpath = os.path.join(outdirpath, filename) + with open(fpath, 'rb') as keyfile: + if extension == u".k4i": + new_key_value = json.loads(keyfile.read()) + else: + new_key_value = keyfile.read() + winekeys.append(new_key_value) + except: + print u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename) + traceback.print_exc() + os.remove(fpath) + print u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files") + return winekeys diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/zipfilerugged.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfilerugged.py similarity index 99% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/zipfilerugged.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfilerugged.py index adf3c53..4a55a69 100644 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/zipfilerugged.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfilerugged.py @@ -354,7 +354,7 @@ def _encodeFilenameFlags(self): def _decodeFilename(self): if self.flag_bits & 0x800: try: - print "decoding filename",self.filename + #print "decoding filename",self.filename return self.filename.decode('utf-8') except: return self.filename diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/zipfix.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfix.py similarity index 69% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/zipfix.py rename to DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfix.py index c7921f2..8ddfae3 100755 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/zipfix.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfix.py @@ -1,4 +1,22 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# zipfix.py, version 1.1 +# Copyright © 2010-2013 by some_updates, DiapDealer and Apprentice Alf + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Revision history: +# 1.0 - Initial release +# 1.1 - Updated to handle zip file metadata correctly + +""" +Re-write zip (or ePub) fixing problems with file names (and mimetype entry). +""" + +__license__ = 'GPL v3' +__version__ = "1.1" import sys import zlib @@ -27,14 +45,10 @@ def __init__(self, zinput, zoutput): self.ztype = 'zip' if zinput.lower().find('.epub') >= 0 : self.ztype = 'epub' - print "opening input" self.inzip = zipfilerugged.ZipFile(zinput,'r') - print "opening outout" self.outzip = zipfilerugged.ZipFile(zoutput,'w') - print "opening input as raw file" # open the input zip for reading only as a raw file self.bzf = file(zinput,'rb') - print "finished initialising" def getlocalname(self, zi): local_header_offset = zi.header_offset @@ -99,25 +113,41 @@ def fix(self): # if epub write mimetype file first, with no compression if self.ztype == 'epub': - nzinfo = ZipInfo('mimetype', compress_type=zipfilerugged.ZIP_STORED) - self.outzip.writestr(nzinfo, _MIMETYPE) + # first get a ZipInfo with current time and no compression + mimeinfo = ZipInfo('mimetype',compress_type=zipfilerugged.ZIP_STORED) + mimeinfo.internal_attr = 1 # text file + try: + # if the mimetype is present, get its info, including time-stamp + oldmimeinfo = self.inzip.getinfo('mimetype') + # copy across useful fields + mimeinfo.date_time = oldmimeinfo.date_time + mimeinfo.comment = oldmimeinfo.comment + mimeinfo.extra = oldmimeinfo.extra + mimeinfo.internal_attr = oldmimeinfo.internal_attr + mimeinfo.external_attr = oldmimeinfo.external_attr + mimeinfo.create_system = oldmimeinfo.create_system + except: + pass + self.outzip.writestr(mimeinfo, _MIMETYPE) # write the rest of the files for zinfo in self.inzip.infolist(): - if zinfo.filename != "mimetype" or self.ztype == '.zip': + if zinfo.filename != "mimetype" or self.ztype != 'epub': data = None - nzinfo = zinfo try: data = self.inzip.read(zinfo.filename) except zipfilerugged.BadZipfile or zipfilerugged.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 + zinfo.filename = local_name + + # create new ZipInfo with only the useful attributes from the old info + nzinfo = ZipInfo(zinfo.filename, zinfo.date_time, compress_type=zinfo.compress_type) + nzinfo.comment=zinfo.comment + nzinfo.extra=zinfo.extra + nzinfo.internal_attr=zinfo.internal_attr + nzinfo.external_attr=zinfo.external_attr + nzinfo.create_system=zinfo.create_system self.outzip.writestr(nzinfo,data) self.bzf.close() diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_Drop_Target.bat b/DeDRM_Windows_Application/DeDRM_App/DeDRM_Drop_Target.bat new file mode 100644 index 0000000..fa4e7cc --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_Drop_Target.bat @@ -0,0 +1 @@ +chcp 65001 > nul && set PWD="%~dp0" && cd /d "%~dp0DeDRM_lib" && start /min python DeDRM_App.pyw %* diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/DeDRM_app.pyw b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_App.pyw similarity index 69% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/DeDRM_app.pyw rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_App.pyw index a0ef90d..f00b934 100644 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/DeDRM_app.pyw +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_App.pyw @@ -1,10 +1,28 @@ #!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +# -*- coding: utf-8 -*- + +# DeDRM.pyw, version 6.0.1 +# Copyright 2010-2013 some_updates and Apprentice Alf + +# Revision history: +# 6.0.0 - Release along with unified plugin +# 6.0.1 - Bug Fixes for Windows App +# 6.0.2 - Fixed problem with spaces in paths and the bat file +# 6.0.3 - Fix for Windows non-ascii user names +# 6.0.4 - Fix for other potential unicode problems + +__version__ = '6.0.4' import sys import os, os.path -sys.path.append(sys.path[0]+os.sep+'lib') -os.environ['PYTHONIOENCODING'] = "utf-8" +sys.path.append(os.path.join(sys.path[0],"lib")) +import sys, os +import codecs + +from argv_utils import add_cp65001_codec, set_utf8_default_encoding, unicode_argv +add_cp65001_codec() +set_utf8_default_encoding() + import shutil import Tkinter @@ -13,15 +31,32 @@ import Tkconstants import tkFileDialog from scrolltextwidget import ScrolledText from activitybar import ActivityBar -import subprocess -from subprocess import Popen, PIPE, STDOUT -import subasyncio -from subasyncio import Process +if sys.platform.startswith("win"): + from askfolder_ed import AskFolder import re import simpleprefs +import traceback + +from Queue import Full +from Queue import Empty +from multiprocessing import Process, Queue +from scriptinterface import decryptepub, decryptpdb, decryptpdf, decryptk4mobi -__version__ = '5.4.1' + +# Wrap a stream so that output gets flushed immediately +# and appended to shared queue +class QueuedUTF8Stream: + def __init__(self, stream, q): + self.stream = stream + self.encoding = 'utf-8' + self.q = q + def write(self, data): + if isinstance(data,unicode): + data = data.encode('utf-8',"replace") + self.q.put(data) + def __getattr__(self, attr): + return getattr(self.stream, attr) class DrmException(Exception): pass @@ -32,6 +67,7 @@ class MainApp(Tk): self.withdraw() self.dnd = dnd self.apphome = apphome + # preference settings # [dictionary key, file in preferences directory where info is stored] description = [ ['pids' , 'pidlist.txt' ], @@ -52,12 +88,20 @@ class MainApp(Tk): def getPreferences(self): prefs = self.po.getPreferences() prefdir = prefs['dir'] - keyfile = os.path.join(prefdir,'adeptkey.der') - if not os.path.exists(keyfile): - import ineptkey + adeptkeyfile = os.path.join(prefdir,'adeptkey.der') + if not os.path.exists(adeptkeyfile): + import adobekey + try: + adobekey.getkey(adeptkeyfile) + except: + pass + kindlekeyfile = os.path.join(prefdir,'kindlekey.k4i') + if not os.path.exists(kindlekeyfile): + import kindlekey try: - ineptkey.extractKeyfile(keyfile) + kindlekey.getkey(kindlekeyfile) except: + traceback.print_exc() pass return prefs @@ -105,7 +149,7 @@ class PrefsDialog(Toplevel): sticky = Tkconstants.E + Tkconstants.W body.grid_columnconfigure(1, weight=2) - Tkinter.Label(body, text='Adept Key file (adeptkey.der)').grid(row=0, sticky=Tkconstants.E) + Tkinter.Label(body, text='Adobe Key file (adeptkey.der)').grid(row=0, sticky=Tkconstants.E) self.adkpath = Tkinter.Entry(body, width=50) self.adkpath.grid(row=0, column=1, sticky=sticky) prefdir = self.prefs_array['dir'] @@ -116,30 +160,26 @@ class PrefsDialog(Toplevel): button = Tkinter.Button(body, text="...", command=self.get_adkpath) button.grid(row=0, column=2) - Tkinter.Label(body, text='Barnes and Noble Key file (bnepubkey.b64)').grid(row=1, sticky=Tkconstants.E) - self.bnkpath = Tkinter.Entry(body, width=50) - self.bnkpath.grid(row=1, column=1, sticky=sticky) + Tkinter.Label(body, text='Kindle Key file (kindlekey.k4i)').grid(row=1, sticky=Tkconstants.E) + self.kkpath = Tkinter.Entry(body, width=50) + self.kkpath.grid(row=1, column=1, sticky=sticky) prefdir = self.prefs_array['dir'] - keyfile = os.path.join(prefdir,'bnepubkey.b64') + keyfile = os.path.join(prefdir,'kindlekey.k4i') if os.path.isfile(keyfile): path = keyfile - self.bnkpath.insert(0, path) - button = Tkinter.Button(body, text="...", command=self.get_bnkpath) + self.kkpath.insert(1, path) + button = Tkinter.Button(body, text="...", command=self.get_kkpath) button.grid(row=1, column=2) - Tkinter.Label(body, text='Additional kindle.info or .kinf file').grid(row=2, sticky=Tkconstants.E) - self.altinfopath = Tkinter.Entry(body, width=50) - self.altinfopath.grid(row=2, column=1, sticky=sticky) + Tkinter.Label(body, text='Barnes and Noble Key file (bnepubkey.b64)').grid(row=2, sticky=Tkconstants.E) + self.bnkpath = Tkinter.Entry(body, width=50) + self.bnkpath.grid(row=2, column=1, sticky=sticky) prefdir = self.prefs_array['dir'] - path = '' - infofile = os.path.join(prefdir,'kindle.info') - ainfofile = os.path.join(prefdir,'.kinf') - if os.path.isfile(infofile): - path = infofile - elif os.path.isfile(ainfofile): - path = ainfofile - self.altinfopath.insert(0, path) - button = Tkinter.Button(body, text="...", command=self.get_altinfopath) + keyfile = os.path.join(prefdir,'bnepubkey.b64') + if os.path.isfile(keyfile): + path = keyfile + self.bnkpath.insert(2, path) + button = Tkinter.Button(body, text="...", command=self.get_bnkpath) button.grid(row=2, column=2) Tkinter.Label(body, text='Mobipocket PID list\n(8 or 10 characters, comma separated)').grid(row=3, sticky=Tkconstants.E) @@ -149,7 +189,7 @@ class PrefsDialog(Toplevel): self.pidnums.set(self.prefs_array['pids']) self.pidinfo.grid(row=3, column=1, sticky=sticky) - Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, first character B, comma separated)').grid(row=4, sticky=Tkconstants.E) + Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, comma separated)').grid(row=4, sticky=Tkconstants.E) self.sernums = Tkinter.StringVar() self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums) if 'serials' in self.prefs_array: @@ -218,9 +258,16 @@ class PrefsDialog(Toplevel): def get_outpath(self): cpath = self.outpath.get() - outpath = tkFileDialog.askdirectory( - parent=None, title='Folder to Store Unencrypted file(s) into', - initialdir=cpath, initialfile=None) + if sys.platform.startswith("win"): + # tk_chooseDirectory is horribly broken for unicode paths + # on windows - bug has been reported but not fixed for years + # workaround by using our own unicode aware version + outpath = AskFolder(message="Choose the folder for DRM-free ebooks", + defaultLocation=cpath) + else: + outpath = tkFileDialog.askdirectory( + parent=None, title='Choose the folder for DRM-free ebooks', + initialdir=cpath, initialfile=None) if outpath: outpath = os.path.normpath(outpath) self.outpath.delete(0, Tkconstants.END) @@ -237,6 +284,16 @@ class PrefsDialog(Toplevel): self.adkpath.insert(0, adkpath) return + def get_kkpath(self): + cpath = self.kkpath.get() + kkpath = tkFileDialog.askopenfilename(initialdir = cpath, parent=None, title='Select Kindle Key file', + defaultextension='.k4i', filetypes=[('Kindle Key file', '.k4i'), ('All Files', '.*')]) + if kkpath: + kkpath = os.path.normpath(kkpath) + self.kkpath.delete(0, Tkconstants.END) + self.kkpath.insert(0, kkpath) + return + def get_bnkpath(self): cpath = self.bnkpath.get() bnkpath = tkFileDialog.askopenfilename(initialdir = cpath, parent=None, title='Select Barnes and Noble Key file', @@ -247,31 +304,20 @@ class PrefsDialog(Toplevel): self.bnkpath.insert(0, bnkpath) return - def get_altinfopath(self): - cpath = self.altinfopath.get() - altinfopath = tkFileDialog.askopenfilename(parent=None, title='Select Alternative kindle.info or .kinf File', - defaultextension='.info', filetypes=[('Kindle Info', '.info'),('Kindle KInf','.kinf')('All Files', '.*')], - initialdir=cpath) - if altinfopath: - altinfopath = os.path.normpath(altinfopath) - self.altinfopath.delete(0, Tkconstants.END) - self.altinfopath.insert(0, altinfopath) - return - def get_bookpath(self): cpath = self.bookpath.get() bookpath = tkFileDialog.askopenfilename(parent=None, title='Select eBook for DRM Removal', - filetypes=[('ePub Files','.epub'), - ('Kindle','.azw'), - ('Kindle','.azw1'), - ('Kindle','.azw3'), - ('Kindle','.azw4'), - ('Kindle','.tpz'), - ('Kindle','.mobi'), - ('Kindle','.prc'), - ('eReader','.pdb'), - ('PDF','.pdf'), - ('All Files', '.*')], + filetypes=[('All Files', '.*'), + ('ePub Files','.epub'), + ('Kindle','.azw'), + ('Kindle','.azw1'), + ('Kindle','.azw3'), + ('Kindle','.azw4'), + ('Kindle','.tpz'), + ('Kindle','.mobi'), + ('Kindle','.prc'), + ('eReader','.pdb'), + ('PDF','.pdf')], initialdir=cpath) if bookpath: bookpath = os.path.normpath(bookpath) @@ -297,10 +343,15 @@ class PrefsDialog(Toplevel): bnkpath = self.bnkpath.get() if os.path.dirname(bnkpath) != prefdir: new_prefs['bnkfile'] = bnkpath - altinfopath = self.altinfopath.get() - if os.path.dirname(altinfopath) != prefdir: - new_prefs['kinfofile'] = altinfopath + kkpath = self.kkpath.get() + if os.path.dirname(kkpath) != prefdir: + new_prefs['kindlefile'] = kkpath self.master.setPreferences(new_prefs) + # and update internal copies + self.prefs_array['pids'] = new_prefs['pids'] + self.prefs_array['serials'] = new_prefs['serials'] + self.prefs_array['sdrms'] = new_prefs['sdrms'] + self.prefs_array['outdir'] = new_prefs['outdir'] def doit(self): self.disablebuttons() @@ -324,10 +375,10 @@ class ConvDialog(Toplevel): self.filenames = filenames self.interval = 50 self.p2 = None + self.q = Queue() self.running = 'inactive' self.numgood = 0 self.numbad = 0 - self.log = '' self.status = Tkinter.Label(self, text='DeDRM processing...') self.status.pack(fill=Tkconstants.X, expand=1) body = Tkinter.Frame(self) @@ -350,6 +401,8 @@ class ConvDialog(Toplevel): self.qbutton.pack(side=Tkconstants.BOTTOM) self.status['text'] = '' + self.logfile = open(os.path.join(os.path.expanduser('~'),'DeDRM.log'),'w') + def show(self): self.deiconify() self.tkraise() @@ -375,19 +428,19 @@ class ConvDialog(Toplevel): if len(self.filenames) > 0: filename = self.filenames.pop(0) if filename == None: - msg = '\nComplete: ' + msg = 'Complete: ' msg += 'Successes: %d, ' % self.numgood msg += 'Failures: %d\n' % self.numbad self.showCmdOutput(msg) if self.numbad == 0: self.after(2000,self.conversion_done()) - logfile = os.path.join(rscpath,'dedrm.log') - file(logfile,'w').write(self.log) + self.logfile.write("DeDRM v{0}: {1}".format(__version__,msg)) + self.logfile.close() return infile = filename bname = os.path.basename(infile) - msg = 'Processing: ' + bname + ' ... ' - self.log += msg + msg = 'Processing: ' + bname + '...' + self.logfile.write("DeDRM v{0}: {1}\n".format(__version__,msg)) self.showCmdOutput(msg) outdir = os.path.dirname(filename) if 'outdir' in self.prefs_array: @@ -398,10 +451,10 @@ class ConvDialog(Toplevel): if rv == 0: self.bar.start() self.running = 'active' - self.processPipe() + self.processQueue() else: msg = 'Unknown File: ' + bname + '\n' - self.log += msg + self.logfile.write("DeDRM v{0}: {1}".format(__version__,msg)) self.showCmdOutput(msg) self.numbad += 1 @@ -409,7 +462,7 @@ class ConvDialog(Toplevel): # kill any still running subprocess self.running = 'stopped' if self.p2 != None: - if (self.p2.wait('nowait') == None): + if (self.p2.exitcode == None): self.p2.terminate() self.conversion_done() @@ -425,131 +478,110 @@ class ConvDialog(Toplevel): # read from subprocess pipe without blocking # invoked every interval via the widget "after" # option being used, so need to reset it for the next time - def processPipe(self): + def processQueue(self): if self.p2 == None: # nothing to wait for so just return return - poll = self.p2.wait('nowait') + poll = self.p2.exitcode + #print "processing", poll + done = False + text = '' + while not done: + try: + data = self.q.get_nowait() + text += data + except Empty: + done = True + if text != '': + self.logfile.write(text) if poll != None: self.bar.stop() if poll == 0: msg = 'Success\n' self.numgood += 1 - text = self.p2.read() - text += self.p2.readerr() - self.log += text - self.log += msg - if poll != 0: + else: msg = 'Failed\n' - text = self.p2.read() - text += self.p2.readerr() - msg += text - msg += '\n' self.numbad += 1 - self.log += msg + self.p2.join() + self.logfile.write("DeDRM v{0}: {1}\n".format(__version__,msg)) self.showCmdOutput(msg) self.p2 = None self.running = 'inactive' self.after(50,self.processBooks) return # make sure we get invoked again by event loop after interval - self.stext.after(self.interval,self.processPipe) + self.stext.after(self.interval,self.processQueue) return def decrypt_ebook(self, infile, outdir, rscpath): - apphome = self.apphome + q = self.q rv = 1 name, ext = os.path.splitext(os.path.basename(infile)) ext = ext.lower() if ext == '.epub': - self.p2 = processEPUB(apphome, infile, outdir, rscpath) + self.p2 = Process(target=processEPUB, args=(q, infile, outdir, rscpath)) + self.p2.start() return 0 if ext == '.pdb': - self.p2 = processPDB(apphome, infile, outdir, rscpath) + self.p2 = Process(target=processPDB, args=(q, infile, outdir, rscpath)) + self.p2.start() return 0 if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.tpz']: - self.p2 = processK4MOBI(apphome, infile, outdir, rscpath) + self.p2 = Process(target=processK4MOBI,args=(q, infile, outdir, rscpath)) + self.p2.start() return 0 if ext == '.pdf': - self.p2 = processPDF(apphome, infile, outdir, rscpath) + self.p2 = Process(target=processPDF, args=(q, infile, outdir, rscpath)) + self.p2.start() return 0 return rv -# run as a subprocess via pipes and collect stdout, stderr, and return value -def runit(apphome, ncmd, nparms): - pengine = sys.executable - if pengine is None or pengine == '': - pengine = 'python' - pengine = os.path.normpath(pengine) - cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" ' - # if sys.platform.startswith('win'): - # search_path = os.environ['PATH'] - # search_path = search_path.lower() - # if search_path.find('python') < 0: - # # if no python hope that win registry finds what is associated with py extension - # cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" ' - cmdline += nparms - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) - return p2 - -def processK4MOBI(apphome, infile, outdir, rscpath): - cmd = os.path.join('lib','k4mobidedrm.py') - parms = '' - pidnums = '' - pidspath = os.path.join(rscpath,'pidlist.txt') - if os.path.exists(pidspath): - pidnums = file(pidspath,'r').read() - pidnums = pidnums.rstrip(os.linesep) - if pidnums != '': - parms += '-p "' + pidnums + '" ' - serialnums = '' - serialnumspath = os.path.join(rscpath,'seriallist.txt') - if os.path.exists(serialnumspath): - serialnums = file(serialnumspath,'r').read() - serialnums = serialnums.rstrip(os.linesep) - if serialnums != '': - parms += '-s "' + serialnums + '" ' - - files = os.listdir(rscpath) - filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE) - files = filter(filefilter.search, files) - if files: - for filename in files: - dpath = os.path.join(rscpath,filename) - parms += '-k "' + dpath + '" ' - parms += '"' + infile +'" "' + outdir + '"' - p2 = runit(apphome, cmd, parms) - return p2 - -def processPDF(apphome, infile, outdir, rscpath): - cmd = os.path.join('lib','decryptpdf.py') - parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"' - p2 = runit(apphome, cmd, parms) - return p2 - -def processEPUB(apphome, infile, outdir, rscpath): - # invoke routine to check both Adept and Barnes and Noble - cmd = os.path.join('lib','decryptepub.py') - parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"' - p2 = runit(apphome, cmd, parms) - return p2 - -def processPDB(apphome, infile, outdir, rscpath): - cmd = os.path.join('lib','decryptpdb.py') - parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"' - p2 = runit(apphome, cmd, parms) - return p2 - - -def main(argv=sys.argv): - apphome = os.path.dirname(sys.argv[0]) +# child process starts here +def processK4MOBI(q, infile, outdir, rscpath): + add_cp65001_codec() + set_utf8_default_encoding() + sys.stdout = QueuedUTF8Stream(sys.stdout, q) + sys.stderr = QueuedUTF8Stream(sys.stderr, q) + rv = decryptk4mobi(infile, outdir, rscpath) + sys.exit(rv) + +# child process starts here +def processPDF(q, infile, outdir, rscpath): + add_cp65001_codec() + set_utf8_default_encoding() + sys.stdout = QueuedUTF8Stream(sys.stdout, q) + sys.stderr = QueuedUTF8Stream(sys.stderr, q) + rv = decryptpdf(infile, outdir, rscpath) + sys.exit(rv) + +# child process starts here +def processEPUB(q, infile, outdir, rscpath): + add_cp65001_codec() + set_utf8_default_encoding() + sys.stdout = QueuedUTF8Stream(sys.stdout, q) + sys.stderr = QueuedUTF8Stream(sys.stderr, q) + rv = decryptepub(infile, outdir, rscpath) + sys.exit(rv) + +# child process starts here +def processPDB(q, infile, outdir, rscpath): + add_cp65001_codec() + set_utf8_default_encoding() + sys.stdout = QueuedUTF8Stream(sys.stdout, q) + sys.stderr = QueuedUTF8Stream(sys.stderr, q) + rv = decryptpdb(infile, outdir, rscpath) + sys.exit(rv) + + +def main(): + argv=unicode_argv() + apphome = os.path.dirname(argv[0]) apphome = os.path.abspath(apphome) # windows may pass a spurious quoted null string as argv[1] from bat file # simply work around this until we can figure out a better way to handle things - if len(argv) == 2: + if sys.platform.startswith('win') and len(argv) == 2: temp = argv[1] temp = temp.strip('"') temp = temp.strip() @@ -563,11 +595,10 @@ def main(argv=sys.argv): else : # processing books via drag and drop dnd = True # build a list of the files to be processed + # note all filenames and paths have been utf-8 encoded infilelst = argv[1:] filenames = [] for infile in infilelst: - infile = infile.decode(sys.getfilesystemencoding()) - print infile infile = infile.replace('"','') infile = os.path.abspath(infile) if os.path.isdir(infile): diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Adobe Digital Editions Key_Help.htm b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Adobe Digital Editions Key_Help.htm new file mode 100644 index 0000000..b258afe --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Adobe Digital Editions Key_Help.htm @@ -0,0 +1,60 @@ + + + + + + +Managing Adobe Digital Editions Keys + + + + + +

Managing Adobe Digital Editions Keys

+ + +

If you have upgraded from an earlier version of the plugin, any existing Adobe Digital Editions keys will have been automatically imported, so you might not need to do any more configuration. In addition, on Windows and Mac, the default Adobe Digital Editions key is added the first time the plugin is run. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog prompting you to enter a key name for the default Adobe Digital Editions key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys.
  • +
+ +

Click the OK button to create and store the Adobe Digital Editions key for the current installation of Adobe Digital Editions. Or Cancel if you don’t want to create the key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Linux Users: WINEPREFIX

+ +

Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Adobe Digital Editions under Wine, and your wine installation containing Adobe Digital Editions isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.der’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the adobekey.pyw script running under Wine on Linux systems.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes will only be saved permanently when you click OK in the main configuration dialog.

+ + + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Barnes and Noble Key_Help.htm b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Barnes and Noble Key_Help.htm new file mode 100644 index 0000000..ac1b693 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Barnes and Noble Key_Help.htm @@ -0,0 +1,57 @@ + + + + + + +Managing Barnes and Noble Keys + + + + + +

Managing Barnes and Noble Keys

+ + +

If you have upgraded from an earlier version of the plugin, any existing Barnes and Noble keys will have been automatically imported, so you might not need to do any more configuration. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.
  • +
  • Your Name: This is the name used by Barnes and Noble to generate your encryption key. Seemingly at random, Barnes and Noble choose one of three places from which to take this name. Most commonly, it’s your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. Sometimes it is the the name used in the default shipping address, and sometimes it’s the name listed for the active credit card. If these names are different in your Barnes and Noble account preferences, I suggest creating one key for each version of your name. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that’s stored in the preferences.
  • +
  • Credit Card#: this is the default credit card number that was on file with Barnes and Noble at the time of download of the ebook to be de-DRMed. Just enter the 16 (15 for American Express) digits. As with the name, this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that’s stored in the preferences.
  • +
+ +

Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.b64’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.b64’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the original i♥cabbages script.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_EInk Kindle Serial Number_Help.htm b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_EInk Kindle Serial Number_Help.htm new file mode 100644 index 0000000..e79abd7 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_EInk Kindle Serial Number_Help.htm @@ -0,0 +1,43 @@ + + + + + + +Managing eInk Kindle serial numbers + + + + + +

Managing eInk Kindle serial numbers

+ +

If you have upgraded from an earlier version of the plugin, any existing eInk Kindle serial numbers will have been automatically imported, so you might not need to do any more configuration.

+ +

Please note that Kindle serial numbers are only valid keys for eInk Kindles like the Kindle Touch and PaperWhite. The Kindle Fire and Fire HD do not use their serial number for DRM and it is useless to enter those serial numbers.

+ +

Creating New Kindle serial numbers:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering a new Kindle serial number.

+
    +
  • Eink Kindle Serial Number: this is the unique serial number of your device. It usually starts with a ‘B’ or a ‘9’ and is sixteen characters long. For a reference of where to find serial numbers and their ranges, please refere to this mobileread wiki page.
  • +
+ +

Click the OK button to save the serial number. Or Cancel if you didn’t want to enter a serial number.

+ +

Deleting Kindle serial numbers:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted Kindle serial number from the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Once done creating/deleting serial numbers, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Help.htm b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Help.htm new file mode 100644 index 0000000..f497a0b --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Help.htm @@ -0,0 +1,73 @@ + + + + + + +DeDRM Plugin Configuration + + + + + +

DeDRM Plugin (v6.0.0)

+ +

This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.

+ +

Installation

+

You have obviously managed to install the plugin, as otherwise you wouldn’t be reading this help file. However, you should also delete any older DeDRM plugins, as this DeDRM plugin replaces the five older plugins: Kindle and Mobipocket DeDRM (K4MobiDeDRM), Ignoble Epub DeDRM (ignobleepub), Inept Epub DeDRM (ineptepub), Inept PDF DeDRM (ineptepub) and eReader PDB 2 PML (eReaderPDB2PML).

+ +

Configuration

+

On Windows and Mac, the keys for ebooks downloaded for Kindle for Mac/PC and Adobe Digital Editions are automatically generated. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC and/or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. On Linux, keys for Kindle for PC and Adobe Digital Editions need to be generated separately (see the Linux section below)

+ +

If you have other DRMed ebooks, you will need to enter extra configuration information. The buttons in this dialog will open individual configuration dialogs that will allow you to enter the needed information, depending on the type and source of your DRMed eBooks. Additional help on the information required is available in each of the the dialogs.

+ +

If you have used previous versions of the various DeDRM plugins on this machine, you may find that some of the configuration dialogs already contain the information you entered through those previous plugins.

+ +

When you have finished entering your configuration information, you must click the OK button to save it. If you click the Cancel button, all your changes in all the configuration dialogs will be lost.

+ +

Troubleshooting:

+ +

If you find that it’s not working for you , you can save a lot of time by trying to add the ebook to Calibre in debug mode. This will print out a lot of helpful info that can be copied into any online help requests.

+ +

Open a command prompt (terminal window) and type "calibre-debug -g" (without the quotes). Calibre will launch, and you can can add the problem ebook the usual way. The debug info will be output to the original command prompt (terminal window). Copy the resulting output and paste it into the comment you make at my blog.

+

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 find the option to install the command line tools.

+ +

Credits:

+
    +
  • The Dark Reverser for the Mobipocket and eReader scripts
  • +
  • i♥cabbages for the Adobe Digital Editions scripts
  • +
  • Skindle aka Bart Simpson for the Amazon Kindle for PC script
  • +
  • CMBDTC for Amazon Topaz DRM removal script
  • +
  • some_updates, clarknova and Bart Simpson for Amazon Topaz conversion scripts
  • +
  • DiapDealer for the first calibre plugin versions of the tools
  • +
  • some_updates, DiapDealer, Apprentice Alf and mdlnx for Amazon Kindle/Mobipocket tools
  • +
  • some_updates for the DeDRM all-in-one Python tool
  • +
  • Apprentice Alf for the DeDRM all-in-one AppleScript tool
  • +
  • Apprentice Alf for the DeDRM all-in-one calibre plugin
  • +
  • And probably many more.
  • +
+ +

For additional help read the FAQs at Apprentice Alf’s Blog and ask questions in the comments section of the first post.

+ +

Linux Systems Only

+

Generating decryption keys for Adobe Digital Editions and Kindle for PC

+

If you install Kindle for PC and/or Adobe Digital Editions in Wine, you will be able to download DRMed ebooks to them under Wine. To be able to remove the DRM, you will need to generate key files and add them in the plugin's customisation dialogs.

+ +

To generate the key files you will need to install Python and PyCrypto under the same Wine setup as your Kindle for PC and/or Adobe Digital Editions installations. (Kindle for PC, Python and Pycrypto installation instructions in the ReadMe.)

+ +

Once everything's installed under Wine, you'll need to run the adobekey.pyw script (for Adobe Digital Editions) and kindlekey.pyw (For Kindle for PC) using the python installation in your Wine system. The scripts can be found in Other_Tools/Key_Retrieval_Scripts.

+ +

Each script will create a key file in the same folder as the script. Copy the key files to your Linux system and then load the key files using the Adobe Digital Editions ebooks dialog and the Kindle for Mac/PC ebooks dialog.

+ + + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Kindle for Mac and PC Key_Help.htm b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Kindle for Mac and PC Key_Help.htm new file mode 100644 index 0000000..35f1a50 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Kindle for Mac and PC Key_Help.htm @@ -0,0 +1,59 @@ + + + + + + +Managing Kindle for Mac/PC Keys + + + + + +

Managing Kindle for Mac/PC Keys

+ + +

If you have upgraded from an earlier version of the plugin, any existing Kindle for Mac/PC keys will have been automatically imported, so you might not need to do any more configuration. In addition, on Windows and Mac, the default Kindle for Mac/PC key is added the first time the plugin is run. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog prompting you to enter a key name for the default Kindle for Mac/PC key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys.
  • +
+ +

Click the OK button to create and store the Kindle for Mac/PC key for the current installation of Kindle for Mac/PC. Or Cancel if you don’t want to create the key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Linux Users: WINEPREFIX

+ +

Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Kindle for PC under Wine, and your wine installation containing Kindle for PC isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.k4i’ key files. Key files might come from being exported from this plugin, or may have been generated using the kindlekey.pyw script running under Wine on Linux systems.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Mobipocket PID_Help.htm b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Mobipocket PID_Help.htm new file mode 100644 index 0000000..00aeeca --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Mobipocket PID_Help.htm @@ -0,0 +1,42 @@ + + + + + + +Managing Mobipocket PIDs + + + + + +

Managing Mobipocket PIDs

+ +

If you have upgraded from an earlier version of the plugin, any existing Mobipocket PIDs will have been automatically imported, so you might not need to do any more configuration.

+ + +

Creating New Mobipocket PIDs:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering a new Mobipocket PID.

+
    +
  • PID: this is a PID used to decrypt your Mobipocket ebooks. It is eight or ten characters long. Mobipocket PIDs are usualy displayed in the About screen of your Mobipocket device.
  • +
+ +

Click the OK button to save the PID. Or Cancel if you didn’t want to enter a PID.

+ +

Deleting Mobipocket PIDs:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted Mobipocket PID from the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Once done creating/deleting PIDs, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_eReader Key_Help.htm b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_eReader Key_Help.htm new file mode 100644 index 0000000..c1c78ad --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_eReader Key_Help.htm @@ -0,0 +1,56 @@ + + + + + + +Managing eReader Keys + + + + + +

Managing eReader Keys

+ +

If you have upgraded from an earlier version of the plugin, any existing eReader (Fictionwise ‘.pdb’) keys will have been automatically imported, so you might not need to do any more configuration. Continue reading for key generation and management instructions.

+ +

Creating New Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.

+
    +
  • Unique Key Name: this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.
  • +
  • Your Name: This is the name used by Fictionwise to generate your encryption key. Since Fictionwise has now closed down, you might not have easy access to this. It was often the name on the Credit Card used at Fictionwise.
  • +
  • Credit Card#: this is the default credit card number that was on file with Fictionwise at the time of download of the ebook to be de-DRMed. Just enter the last 8 digits of the number. As with the name, this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that’s stored in the preferences.
  • +
+ +

Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.

+

New keys are checked against the current list of keys before being added, and duplicates are discarded.

+ +

Deleting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.

+ +

Renaming Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..

+ +

Exporting Keys:

+ +

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.b63’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.

+ +

Importing Existing Keyfiles:

+ +

At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.b63’ key files that have previously been exported.

+ +

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+ + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py new file mode 100644 index 0000000..1c931a4 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement +__license__ = 'GPL v3' +__docformat__ = 'restructuredtext en' + + +# Released under the terms of the GNU General Public Licence, version 3 +# +# +# Requires Calibre version 0.7.55 or higher. +# +# All credit given to i♥cabbages and The Dark Reverser for the original standalone scripts. +# We had the much easier job of converting them to a calibre plugin. +# +# This plugin is meant to decrypt eReader PDBs, Adobe Adept ePubs, Barnes & Noble ePubs, +# Adobe Adept PDFs, Amazon Kindle and Mobipocket files without having to +# install any dependencies... other than having calibre installed, of course. +# +# Configuration: +# Check out the plugin's configuration settings by clicking the "Customize plugin" +# button when you have the "DeDRM" plugin highlighted (under Preferences-> +# Plugins->File type plugins). Once you have the configuration dialog open, you'll +# see a Help link on the top right-hand side. +# +# Revision history: +# 6.0.0 - Initial release +# 6.0.1 - Bug Fixes for Windows App, Kindle for Mac and Windows Adobe Digital Editions +# 6.0.2 - Restored call to Wine to get Kindle for PC keys + +""" +Decrypt DRMed ebooks. +""" + +PLUGIN_NAME = u"DeDRM" +PLUGIN_VERSION_TUPLE = (6, 0, 2) +PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE]) +# Include an html helpfile in the plugin's zipfile with the following name. +RESOURCE_NAME = PLUGIN_NAME + '_Help.htm' + +import sys, os, re +import time +import zipfile +import traceback +from zipfile import ZipFile + +class DeDRMError(Exception): + pass + +from calibre.customize import FileTypePlugin +from calibre.constants import iswindows, isosx +from calibre.gui2 import is_ok_to_use_qt +from calibre.utils.config import config_dir + + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get safely +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +class DeDRM(FileTypePlugin): + name = PLUGIN_NAME + description = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts." + supported_platforms = ['linux', 'osx', 'windows'] + author = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages" + version = PLUGIN_VERSION_TUPLE + minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions. + file_types = set(['epub','pdf','pdb','prc','mobi','azw','azw1','azw3','azw4','tpz']) + on_import = True + priority = 600 + + def initialize(self): + # convert old preferences, if necessary. + try: + from calibre_plugins.dedrm.prefs import convertprefs + convertprefs() + except: + traceback.print_exc() + + """ + Dynamic modules can't be imported/loaded from a zipfile... so this routine + runs whenever the plugin gets initialized. This will extract the appropriate + library for the target OS and copy it to the 'alfcrypto' subdirectory of + calibre's configuration directory. That 'alfcrypto' directory is then + inserted into the syspath (as the very first entry) in the run function + so the CDLL stuff will work in the alfcrypto.py script. + """ + try: + if iswindows: + names = [u"alfcrypto.dll",u"alfcrypto64.dll"] + elif isosx: + names = [u"libalfcrypto.dylib"] + else: + names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"] + lib_dict = self.load_resources(names) + self.pluginsdir = os.path.join(config_dir,u"plugins") + if not os.path.exists(self.pluginsdir): + os.mkdir(self.pluginsdir) + self.maindir = os.path.join(self.pluginsdir,u"DeDRM") + if not os.path.exists(self.maindir): + os.mkdir(self.maindir) + self.helpdir = os.path.join(self.maindir,u"help") + if not os.path.exists(self.helpdir): + os.mkdir(self.helpdir) + self.alfdir = os.path.join(self.maindir,u"libraryfiles") + if not os.path.exists(self.alfdir): + os.mkdir(self.alfdir) + for entry, data in lib_dict.items(): + file_path = os.path.join(self.alfdir, entry) + open(file_path,'wb').write(data) + except Exception, e: + traceback.print_exc() + raise + + def ePubDecrypt(self,path_to_ebook): + # Create a TemporaryPersistent file to work with. + # Check original epub archive for zip errors. + import calibre_plugins.dedrm.zipfix + + inf = self.temporary_file(u".epub") + try: + print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION) + fr = zipfix.fixZip(path_to_ebook, inf.name) + fr.fix() + except Exception, e: + print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]) + raise Exception(e) + + # import the decryption keys + import calibre_plugins.dedrm.prefs as prefs + dedrmprefs = prefs.DeDRM_Prefs() + + # import the Barnes & Noble ePub handler + import calibre_plugins.dedrm.ignobleepub as ignobleepub + + + #check the book + if ignobleepub.ignobleBook(inf.name): + print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + + # Attempt to decrypt epub with each encryption key (generated or provided). + for keyname, userkey in dedrmprefs['bandnkeys'].items(): + keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked) + of = self.temporary_file(u".epub") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + result = ignobleepub.decryptBook(userkey, inf.name, of.name) + + of.close() + + if result == 0: + # Decryption was successful. + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime) + + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + # import the Adobe Adept ePub handler + import calibre_plugins.dedrm.ineptepub as ineptepub + + if ineptepub.adeptBook(inf.name): + print u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + + # Attempt to decrypt epub with each encryption key (generated or provided). + for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): + userkey = userkeyhex.decode('hex') + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) + of = self.temporary_file(u".epub") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptepub.decryptBook(userkey, inf.name, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was successful. + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime) + + # perhaps we need to get a new default ADE key + print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) + + # get the default Adobe keys + defaultkeys = [] + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.adobekey import adeptkeys + + defaultkeys = adeptkeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.join(self.alfdir,u"adobekey.py") + defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix']) + + self.default_key = default_keys[0] + except: + traceback.print_exc() + self.default_key = u"" + + newkeys = [] + for keyvalue in defaultkeys: + if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values(): + newkeys.append(keyvalue) + + if len(newkeys) > 0: + try: + for i,userkey in enumerate(newkeys): + print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + of = self.temporary_file(u".epub") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptepub.decryptBook(userkey, inf.name, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was a success + # Store the new successful key in the defaults + print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + try: + dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) + dedrmprefs.writeprefs() + except: + traceback.print_exc() + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + except Exception, e: + pass + + # Something went wrong with decryption. + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + # Not a Barnes & Noble nor an Adobe Adept + # Import the fixed epub. + print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + return inf.name + + def PDFDecrypt(self,path_to_ebook): + import calibre_plugins.dedrm.prefs as prefs + import calibre_plugins.dedrm.ineptpdf + + dedrmprefs = prefs.DeDRM_Prefs() + # Attempt to decrypt epub with each encryption key (generated or provided). + print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): + userkey = userkeyhex.decode('hex') + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) + of = self.temporary_file(u".pdf") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was successful. + # Return the modified PersistentTemporary file to calibre. + return of.name + + # perhaps we need to get a new default ADE key + print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) + + # get the default Adobe keys + defaultkeys = [] + + if iswindows or isosx: + import calibre_plugins.dedrm.adobekey as adobe + + try: + defaultkeys = adobe.adeptkeys() + except: + pass + else: + # linux + try: + from wineutils import WineGetKeys + + scriptpath = os.join(self.alfdir,u"adobekey.py") + defaultkeys = self.WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix']) + except: + pass + + newkeys = [] + for keyvalue in defaultkeys: + if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values(): + newkeys.append(keyvalue) + + if len(newkeys) > 0: + try: + for i,userkey in enumerate(newkeys): + print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + of = self.temporary_file(u".pdf") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptepdf.decryptBook(userkey, inf.name, of.name) + except: + result = 1 + + of.close() + + if result == 0: + # Decryption was a success + # Store the new successful key in the defaults + print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) + try: + dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) + dedrmprefs.writeprefs() + except: + traceback.print_exc() + # Return the modified PersistentTemporary file to calibre. + return of.name + + print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + except Exception, e: + pass + + # Something went wrong with decryption. + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + + def KindleMobiDecrypt(self,path_to_ebook): + + # add the alfcrypto directory to sys.path so alfcrypto.py + # will be able to locate the custom lib(s) for CDLL import. + sys.path.insert(0, self.alfdir) + # Had to move this import here so the custom libs can be + # extracted to the appropriate places beforehand these routines + # look for them. + import calibre_plugins.dedrm.prefs as prefs + import calibre_plugins.dedrm.k4mobidedrm + + dedrmprefs = prefs.DeDRM_Prefs() + pids = dedrmprefs['pids'] + serials = dedrmprefs['serials'] + kindleDatabases = dedrmprefs['kindlekeys'].items() + + try: + book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime) + except Exception, e: + decoded = False + # perhaps we need to get a new default Kindle for Mac/PC key + defaultkeys = [] + print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]) + print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.kindlekey import kindlekeys + + defaultkeys = kindlekeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.join(self.alfdir,u"kindlekey.py") + defaultkeys = self.WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix']) + except: + pass + + newkeys = {} + for i,keyvalue in enumerate(defaultkeys): + keyname = u"default_key_{0:d}".format(i+1) + if keyvalue not in dedrmprefs['kindlekeys'].values(): + newkeys[keyname] = keyvalue + if len(newkeys) > 0: + print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys") + try: + book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime) + decoded = True + # store the new successful keys in the defaults + print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys") + for keyvalue in newkeys.values(): + dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue) + dedrmprefs.writeprefs() + except Exception, e: + pass + if not decoded: + #if you reached here then no luck raise and exception + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + traceback.print_exc() + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{4}” after {3:.1f} seconds with error: {2}\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0],time.time()-self.starttime,os.path.basename(path_to_ebook))) + + of = self.temporary_file(book.getBookExtension()) + book.getFile(of.name) + of.close() + book.cleanup() + return of.name + + + def eReaderDecrypt(self,path_to_ebook): + + import calibre_plugins.dedrm.prefs as prefs + import calibre_plugins.dedrm.erdr2pml + + dedrmrefs = prefs.DeDRM_Prefs() + # Attempt to decrypt epub with each encryption key (generated or provided). + for keyname, userkey in dedrmprefs['ereaderkeys'].items(): + keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) + print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked) + of = self.temporary_file(u".pmlz") + + # Give the userkey, ebook and TemporaryPersistent file to the decryption function. + result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex')) + + of.close() + + # Decryption was successful return the modified PersistentTemporary + # file to Calibre's import process. + if result == 0: + return of.name + + print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime) + + print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime)) + + + def run(self, path_to_ebook): + + # make sure any unicode output gets converted safely with 'replace' + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + + print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) + self.starttime = time.time() + + booktype = os.path.splitext(path_to_ebook)[1].lower()[1:] + if booktype in ['prc','mobi','azw','azw1','azw3','azw4','tpz']: + # Kindle/Mobipocket + decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook) + elif booktype == 'pdb': + # eReader + decrypted_ebook = self.eReaderDecrypt(path_to_ebook) + pass + elif booktype == 'pdf': + # Adobe Adept PDF (hopefully) + decrypted_ebook = self.PDFDecrypt(path_to_ebook) + pass + elif booktype == 'epub': + # Adobe Adept or B&N ePub + decrypted_ebook = self.ePubDecrypt(path_to_ebook) + else: + print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype) + return path_to_ebook + print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) + return decrypted_ebook + + def is_customizable(self): + # return true to allow customization via the Plugin->Preferences. + return True + + def config_widget(self): + import calibre_plugins.dedrm.config as config + return config.ConfigWidget(self.plugin_path, self.alfdir) + + def save_settings(self, config_widget): + config_widget.save_settings() diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/activitybar.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/activitybar.py similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/activitybar.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/activitybar.py diff --git a/Other_Tools/Adobe_ePub_Tools/ineptkey_v5.6.pyw b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/adobekey.py old mode 100755 new mode 100644 similarity index 64% rename from Other_Tools/Adobe_ePub_Tools/ineptkey_v5.6.pyw rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/adobekey.py index 723b7c6..bfa542b --- a/Other_Tools/Adobe_ePub_Tools/ineptkey_v5.6.pyw +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/adobekey.py @@ -1,25 +1,31 @@ -#! /usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import with_statement -# ineptkey.pyw, version 5.6 +# adobekey.pyw, version 5.7 # Copyright © 2009-2010 i♥cabbages -# Released under the terms of the GNU General Public Licence, version 3 or -# later. +# Released under the terms of the GNU General Public Licence, version 3 +# -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make certain -# to install the version for Python 2.6). Then save this script file as -# ineptkey.pyw and double-click on it to run it. It will create a file named -# adeptkey.der in the same directory. This is your ADEPT user key. +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python. +# We recommend ActiveState Python 2.7.X for Windows (x86) from +# http://www.activestate.com/activepython/downloads. +# You must also install PyCrypto from +# http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make certain to install the version for Python 2.7). +# Then save this script file as adobekey.pyw and double-click on it to run it. +# It will create a file named adobekey_1.der in in the same directory as the script. +# This is your Adobe Digital Editions user key. # -# Mac OS X users: Save this script file as ineptkey.pyw. You can run this -# program from the command line (pythonw ineptkey.pyw) or by double-clicking +# Mac OS X users: Save this script file as adobekey.pyw. You can run this +# program from the command line (python adobekey.pyw) or by double-clicking # it when it has been associated with PythonLauncher. It will create a file -# named adeptkey.der in the same directory. This is your ADEPT user key. +# named adobekey_1.der in the same directory as the script. +# This is your Adobe Digital Editions user key. # Revision history: # 1 - Initial release, for Adobe Digital Editions 1.7 @@ -30,24 +36,44 @@ # 4.2 - added old 1.7.1 processing # 4.3 - better key search # 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 - 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 # 5.2 - added support for output of key to a particular file # 5.3 - On Windows try PyCrypto first, OpenSSL next # 5.4 - Modify interface to allow use of import # 5.5 - Fix for potential problem with PyCrypto -# 5.6 - Revise to allow use in Plugins to eliminate need for duplicate code +# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code +# 5.7 - Unicode support added, renamed adobekey from ineptkey +# 5.8 - Added getkey interface for Windows DeDRM application +# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 6.0 - Work if TkInter is missing """ Retrieve Adobe ADEPT user key. """ __license__ = 'GPL v3' - -import sys -import os -import struct +__version__ = '6.0' + +import sys, os, struct, getopt + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) try: from calibre.constants import iswindows, isosx @@ -55,6 +81,44 @@ iswindows = sys.platform.startswith('win') isosx = sys.platform.startswith('darwin') +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"adobekey.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + class ADEPTError(Exception): pass @@ -80,13 +144,13 @@ 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', @@ -296,7 +360,7 @@ def CryptUnprotectData(indata, entropy): return CryptUnprotectData CryptUnprotectData = CryptUnprotectData() - def retrieve_keys(): + def adeptkeys(): if AES is None: raise ADEPTError("PyCrypto or OpenSSL must be installed") root = GetSystemDirectory().split('\\')[0] + '\\' @@ -308,9 +372,9 @@ def retrieve_keys(): cuser = winreg.HKEY_CURRENT_USER try: regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH) + device = winreg.QueryValueEx(regkey, 'key')[0] except WindowsError: raise ADEPTError("Adobe Digital Editions not activated") - device = winreg.QueryValueEx(regkey, 'key')[0] keykey = CryptUnprotectData(device, entropy) userkey = None keys = [] @@ -339,11 +403,13 @@ def retrieve_keys(): aes = AES(keykey) userkey = aes.decrypt(userkey) userkey = userkey[26:-ord(userkey[-1])] + #print "found key:",userkey.encode('hex') keys.append(userkey) if len(keys) == 0: raise ADEPTError('Could not locate privateLicenseKey') + print u"Found {0:d} keys".format(len(keys)) return keys - + elif isosx: import xml.etree.ElementTree as etree @@ -353,6 +419,9 @@ def retrieve_keys(): 'enc': 'http://www.w3.org/2001/04/xmlenc#'} def findActivationDat(): + import warnings + warnings.filterwarnings('ignore', category=FutureWarning) + home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"' cmdline = cmdline.encode(sys.getfilesystemencoding()) @@ -360,6 +429,7 @@ def findActivationDat(): out1, out2 = p2.communicate() reslst = out1.split('\n') cnt = len(reslst) + ActDatPath = "activation.dat" for j in xrange(cnt): resline = reslst[j] pp = resline.find('activation.dat') @@ -370,10 +440,10 @@ def findActivationDat(): return ActDatPath return None - def retrieve_keys(): + def adeptkeys(): actpath = findActivationDat() if actpath is None: - raise ADEPTError("Could not locate ADE activation") + raise ADEPTError("Could not find ADE activation.dat file.") tree = etree.parse(actpath) adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey')) @@ -383,75 +453,151 @@ def retrieve_keys(): return [userkey] else: - def retrieve_keys(keypath): + def adeptkeys(): raise ADEPTError("This script only supports Windows and Mac OS X.") return [] - -def retrieve_key(keypath): - keys = retrieve_keys() - with open(keypath, 'wb') as f: - f.write(keys[0]) - return True - -def extractKeyfile(keypath): - try: - success = retrieve_key(keypath) - except ADEPTError, e: - print "Key generation Error: " + str(e) - return 1 - except Exception, e: - print "General Error: " + str(e) - return 1 - if not success: - return 1 - return 0 +# interface for Python DeDRM +def getkey(outpath): + keys = adeptkeys() + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'wb') as keyfileout: + keyfileout.write(keys[0]) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + print u"Saved a key to {0}".format(outfile) + return True + return False + +def usage(progname): + print u"Finds, decrypts and saves the default Adobe Adept encryption key(s)." + print u"Keys are saved to the current directory, or a specified output directory." + print u"If a file name is passed instead of a directory, only the first key is saved, in that file." + print u"Usage:" + print u" {0:s} [-h] []".format(progname) + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__) -def cli_main(argv=sys.argv): - keypath = argv[1] - return extractKeyfile(keypath) + try: + opts, args = getopt.getopt(argv[1:], "h") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + + for o, a in opts: + if o == "-h": + usage(progname) + sys.exit(0) + + if len(args) > 1: + usage(progname) + sys.exit(2) + + if len(args) == 1: + # save to the specified file or directory + outpath = args[0] + if not os.path.isabs(outpath): + outpath = os.path.abspath(outpath) + else: + # save to the same directory as the script + outpath = os.path.dirname(argv[0]) + + # make sure the outpath is the + outpath = os.path.realpath(os.path.normpath(outpath)) + + keys = adeptkeys() + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'wb') as keyfileout: + keyfileout.write(keys[0]) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + print u"Saved a key to {0}".format(outfile) + else: + print u"Could not retrieve Adobe Adept key." + return 0 -def main(argv=sys.argv): - import Tkinter - import Tkconstants - import tkMessageBox - import traceback +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() class ExceptionDialog(Tkinter.Frame): def __init__(self, root, text): Tkinter.Frame.__init__(self, root, border=5) - label = Tkinter.Label(self, text="Unexpected error:", + label = Tkinter.Label(self, text=u"Unexpected error:", anchor=Tkconstants.W, justify=Tkconstants.LEFT) label.pack(fill=Tkconstants.X, expand=0) self.text = Tkinter.Text(self) self.text.pack(fill=Tkconstants.BOTH, expand=1) - + self.text.insert(Tkconstants.END, text) + argv=unicode_argv() root = Tkinter.Tk() root.withdraw() - progname = os.path.basename(argv[0]) - keypath = os.path.abspath("adeptkey.der") + progpath, progname = os.path.split(argv[0]) success = False try: - success = retrieve_key(keypath) + keys = adeptkeys() + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(progpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + success = True + tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) except ADEPTError, e: - tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) + tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) except Exception: root.wm_state('normal') - root.title('ADEPT Key') + root.title(progname) text = traceback.format_exc() ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) root.mainloop() if not success: return 1 - tkMessageBox.showinfo( - "ADEPT Key", "Key successfully retrieved to %s" % (keypath)) return 0 if __name__ == '__main__': if len(sys.argv) > 1: sys.exit(cli_main()) - sys.exit(main()) + sys.exit(gui_main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/aescbc.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/aescbc.py similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/aescbc.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/aescbc.py diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto.dll b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.dll similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto.dll rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.dll diff --git a/Other_Tools/KindleBooks/lib/alfcrypto.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py similarity index 91% rename from Other_Tools/KindleBooks/lib/alfcrypto.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py index e25a0c8..036ba10 100644 --- a/Other_Tools/KindleBooks/lib/alfcrypto.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py @@ -1,11 +1,18 @@ -#! /usr/bin/env python +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# crypto library mainly by some_updates + +# pbkdf2.py pbkdf2 code taken from pbkdf2.py +# pbkdf2.py Copyright © 2004 Matt Johnston +# pbkdf2.py Copyright © 2009 Daniel Holth +# pbkdf2.py This code may be freely used and modified for any purpose. import sys, os import hmac from struct import pack import hashlib - # interface to needed routines libalfcrypto def _load_libalfcrypto(): import ctypes @@ -26,11 +33,15 @@ def _load_libalfcrypto(): name_of_lib = 'libalfcrypto32.so' else: name_of_lib = 'libalfcrypto64.so' - - libalfcrypto = sys.path[0] + os.sep + name_of_lib + # hard code to local location for libalfcrypto + libalfcrypto = os.path.join(sys.path[0],name_of_lib) if not os.path.isfile(libalfcrypto): - raise Exception('libalfcrypto not found') + libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib) + if not os.path.isfile(libalfcrypto): + libalfcrypto = os.path.join('.',name_of_lib) + if not os.path.isfile(libalfcrypto): + raise Exception('libalfcrypto not found at %s' % libalfcrypto) libalfcrypto = CDLL(libalfcrypto) @@ -55,7 +66,7 @@ def F(restype, name, argtypes): # # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); # - # + # # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, # const unsigned long length, const AES_KEY *key, # unsigned char *ivec, const int enc); @@ -147,7 +158,7 @@ def decrypt(self, data, ctx=None): topazCryptoDecrypt(ctx, data, out, len(data)) return out.raw - print "Using Library AlfCrypto DLL/DYLIB/SO" + print u"Using Library AlfCrypto DLL/DYLIB/SO" return (AES_CBC, Pukall_Cipher, Topaz_Cipher) @@ -164,8 +175,7 @@ def PC1(self, key, src, decryption=True): sum2 = 0; keyXorVal = 0; if len(key)!=16: - print "Bad key length!" - return None + raise Exception('Pukall_Cipher: Bad key length.') wkey = [] for i in xrange(8): wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) @@ -234,6 +244,7 @@ def decrypt(self, data): cleartext = self.aes.decrypt(iv + data) return cleartext + print u"Using Library AlfCrypto Python" return (AES_CBC, Pukall_Cipher, Topaz_Cipher) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto64.dll b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto64.dll similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto64.dll rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto64.dll diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto_src.zip b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto_src.zip similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/alfcrypto_src.zip rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto_src.zip diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/argv_utils.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/argv_utils.py new file mode 100644 index 0000000..85ffaa4 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/argv_utils.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys, os +import locale +import codecs + +# get sys.argv arguments and encode them into utf-8 +def unicode_argv(): + if sys.platform.startswith('win'): + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"DeDRM.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + + +def add_cp65001_codec(): + try: + codecs.lookup('cp65001') + except LookupError: + codecs.register( + lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None) + return + + +def set_utf8_default_encoding(): + if sys.getdefaultencoding() == 'utf-8': + return + + # Regenerate setdefaultencoding. + reload(sys) + sys.setdefaultencoding('utf-8') + + for attr in dir(locale): + if attr[0:3] != 'LC_': + continue + aref = getattr(locale, attr) + try: + locale.setlocale(aref, '') + except locale.Error: + continue + try: + lang = locale.getlocale(aref)[0] + except (TypeError, ValueError): + continue + if lang: + try: + locale.setlocale(aref, (lang, 'UTF-8')) + except locale.Error: + os.environ[attr] = lang + '.UTF-8' + try: + locale.setlocale(locale.LC_ALL, '') + except locale.Error: + pass + return + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/askfolder_ed.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/askfolder_ed.py new file mode 100644 index 0000000..d26df30 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/askfolder_ed.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +# to work around tk_chooseDirectory not properly returning unicode paths on Windows +# need to use a dialog that can be hacked up to actually return full unicode paths +# originally based on AskFolder from EasyDialogs for Windows but modified to fix it +# to actually use unicode for path + +# The original license for EasyDialogs is as follows +# +# Copyright (c) 2003-2005 Jimmy Retzlaff +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +""" +AskFolder(...) -- Ask the user to select a folder Windows specific +""" + +import os + +import ctypes +from ctypes import POINTER, byref, cdll, c_int, windll +from ctypes.wintypes import LPCWSTR, LPWSTR +import ctypes.wintypes as wintypes + + +__all__ = ['AskFolder'] + +# Load required Windows DLLs +ole32 = ctypes.windll.ole32 +shell32 = ctypes.windll.shell32 +user32 = ctypes.windll.user32 + + +# Windows Constants +BFFM_INITIALIZED = 1 +BFFM_SETOKTEXT = 1129 +BFFM_SETSELECTIONA = 1126 +BFFM_SETSELECTIONW = 1127 +BIF_EDITBOX = 16 +BS_DEFPUSHBUTTON = 1 +CB_ADDSTRING = 323 +CB_GETCURSEL = 327 +CB_SETCURSEL = 334 +CDM_SETCONTROLTEXT = 1128 +EM_GETLINECOUNT = 186 +EM_GETMARGINS = 212 +EM_POSFROMCHAR = 214 +EM_SETSEL = 177 +GWL_STYLE = -16 +IDC_STATIC = -1 +IDCANCEL = 2 +IDNO = 7 +IDOK = 1 +IDYES = 6 +MAX_PATH = 260 +OFN_ALLOWMULTISELECT = 512 +OFN_ENABLEHOOK = 32 +OFN_ENABLESIZING = 8388608 +OFN_ENABLETEMPLATEHANDLE = 128 +OFN_EXPLORER = 524288 +OFN_OVERWRITEPROMPT = 2 +OPENFILENAME_SIZE_VERSION_400 = 76 +PBM_GETPOS = 1032 +PBM_SETMARQUEE = 1034 +PBM_SETPOS = 1026 +PBM_SETRANGE = 1025 +PBM_SETRANGE32 = 1030 +PBS_MARQUEE = 8 +PM_REMOVE = 1 +SW_HIDE = 0 +SW_SHOW = 5 +SW_SHOWNORMAL = 1 +SWP_NOACTIVATE = 16 +SWP_NOMOVE = 2 +SWP_NOSIZE = 1 +SWP_NOZORDER = 4 +VER_PLATFORM_WIN32_NT = 2 +WM_COMMAND = 273 +WM_GETTEXT = 13 +WM_GETTEXTLENGTH = 14 +WM_INITDIALOG = 272 +WM_NOTIFY = 78 + +# Windows function prototypes +BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.LPARAM, wintypes.LPARAM) + +# Windows types +LPCTSTR = ctypes.c_char_p +LPTSTR = ctypes.c_char_p +LPVOID = ctypes.c_voidp +TCHAR = ctypes.c_char + +class BROWSEINFO(ctypes.Structure): + _fields_ = [ + ("hwndOwner", wintypes.HWND), + ("pidlRoot", LPVOID), + ("pszDisplayName", LPTSTR), + ("lpszTitle", LPCTSTR), + ("ulFlags", ctypes.c_uint), + ("lpfn", BrowseCallbackProc), + ("lParam", wintypes.LPARAM), + ("iImage", ctypes.c_int) + ] + + +# Utilities +def CenterWindow(hwnd): + desktopRect = GetWindowRect(user32.GetDesktopWindow()) + myRect = GetWindowRect(hwnd) + x = width(desktopRect) // 2 - width(myRect) // 2 + y = height(desktopRect) // 2 - height(myRect) // 2 + user32.SetWindowPos(hwnd, 0, + desktopRect.left + x, + desktopRect.top + y, + 0, 0, + SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER + ) + + +def GetWindowRect(hwnd): + rect = wintypes.RECT() + user32.GetWindowRect(hwnd, ctypes.byref(rect)) + return rect + +def width(rect): + return rect.right-rect.left + +def height(rect): + return rect.bottom-rect.top + + +def AskFolder( + message=None, + version=None, + defaultLocation=None, + location=None, + windowTitle=None, + actionButtonLabel=None, + cancelButtonLabel=None, + multiple=None): + """Display a dialog asking the user for select a folder. + modified to use unicode strings as much as possible + returns unicode path + """ + + def BrowseCallback(hwnd, uMsg, lParam, lpData): + if uMsg == BFFM_INITIALIZED: + if actionButtonLabel: + label = unicode(actionButtonLabel, errors='replace') + user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label) + if cancelButtonLabel: + label = unicode(cancelButtonLabel, errors='replace') + cancelButton = user32.GetDlgItem(hwnd, IDCANCEL) + if cancelButton: + user32.SetWindowTextW(cancelButton, label) + if windowTitle: + title = unicode(windowTitle, erros='replace') + user32.SetWindowTextW(hwnd, title) + if defaultLocation: + user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\')) + if location: + x, y = location + desktopRect = wintypes.RECT() + user32.GetWindowRect(0, ctypes.byref(desktopRect)) + user32.SetWindowPos(hwnd, 0, + desktopRect.left + x, + desktopRect.top + y, 0, 0, + SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER) + else: + CenterWindow(hwnd) + return 0 + + # This next line is needed to prevent gc of the callback + callback = BrowseCallbackProc(BrowseCallback) + + browseInfo = BROWSEINFO() + browseInfo.pszDisplayName = ctypes.c_char_p('\0' * (MAX_PATH+1)) + browseInfo.lpszTitle = message + browseInfo.lpfn = callback + + pidl = shell32.SHBrowseForFolder(ctypes.byref(browseInfo)) + if not pidl: + result = None + else: + path = LPCWSTR(u" " * (MAX_PATH+1)) + shell32.SHGetPathFromIDListW(pidl, path) + ole32.CoTaskMemFree(pidl) + result = path.value + return result + + + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py new file mode 100644 index 0000000..f159a9f --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py @@ -0,0 +1,907 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +__license__ = 'GPL v3' + +# Standard Python modules. +import os, traceback + +# PyQT4 modules (part of calibre). +from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, + QGroupBox, QPushButton, QListWidget, QListWidgetItem, + QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl, QString) +from PyQt4 import QtGui + +from zipfile import ZipFile + +# calibre modules and constants. +from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url, + choose_dir, choose_files) +from calibre.utils.config import dynamic, config_dir, JSONConfig +from calibre.constants import iswindows, isosx + +# modules from this plugin's zipfile. +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION +from calibre_plugins.dedrm.__init__ import RESOURCE_NAME as help_file_name +from calibre_plugins.dedrm.utilities import uStrCmp + +import calibre_plugins.dedrm.prefs as prefs + +class ConfigWidget(QWidget): + def __init__(self, plugin_path, alfdir): + QWidget.__init__(self) + + self.plugin_path = plugin_path + self.alfdir = alfdir + + # get the prefs + self.dedrmprefs = prefs.DeDRM_Prefs() + + # make a local copy + self.tempdedrmprefs = {} + self.tempdedrmprefs['bandnkeys'] = self.dedrmprefs['bandnkeys'].copy() + self.tempdedrmprefs['adeptkeys'] = self.dedrmprefs['adeptkeys'].copy() + self.tempdedrmprefs['ereaderkeys'] = self.dedrmprefs['ereaderkeys'].copy() + self.tempdedrmprefs['kindlekeys'] = self.dedrmprefs['kindlekeys'].copy() + self.tempdedrmprefs['pids'] = list(self.dedrmprefs['pids']) + self.tempdedrmprefs['serials'] = list(self.dedrmprefs['serials']) + self.tempdedrmprefs['adobewineprefix'] = self.dedrmprefs['adobewineprefix'] + self.tempdedrmprefs['kindlewineprefix'] = self.dedrmprefs['kindlewineprefix'] + + # Start Qt Gui dialog layout + layout = QVBoxLayout(self) + self.setLayout(layout) + + help_layout = QHBoxLayout() + layout.addLayout(help_layout) + # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked. + help_label = QLabel('Plugin Help', self) + help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) + help_label.setAlignment(Qt.AlignRight) + help_label.linkActivated.connect(self.help_link_activated) + help_layout.addWidget(help_label) + + keys_group_box = QGroupBox(_('Configuration:'), self) + layout.addWidget(keys_group_box) + keys_group_box_layout = QHBoxLayout() + keys_group_box.setLayout(keys_group_box_layout) + + + button_layout = QVBoxLayout() + keys_group_box_layout.addLayout(button_layout) + self.bandn_button = QtGui.QPushButton(self) + self.bandn_button.setToolTip(_(u"Click to manage keys for Barnes and Noble ebooks")) + self.bandn_button.setText(u"Barnes and Noble ebooks") + self.bandn_button.clicked.connect(self.bandn_keys) + self.kindle_serial_button = QtGui.QPushButton(self) + self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks")) + self.kindle_serial_button.setText(u"eInk Kindle ebooks") + self.kindle_serial_button.clicked.connect(self.kindle_serials) + self.kindle_key_button = QtGui.QPushButton(self) + self.kindle_key_button.setToolTip(_(u"Click to manage keys for Kindle for Mac/PC ebooks")) + self.kindle_key_button.setText(u"Kindle for Mac/PC ebooks") + self.kindle_key_button.clicked.connect(self.kindle_keys) + self.adept_button = QtGui.QPushButton(self) + self.adept_button.setToolTip(_(u"Click to manage keys for Adobe Digital Editions ebooks")) + self.adept_button.setText(u"Adobe Digital Editions ebooks") + self.adept_button.clicked.connect(self.adept_keys) + self.mobi_button = QtGui.QPushButton(self) + self.mobi_button.setToolTip(_(u"Click to manage PIDs for Mobipocket ebooks")) + self.mobi_button.setText(u"Mobipocket ebooks") + self.mobi_button.clicked.connect(self.mobi_keys) + self.ereader_button = QtGui.QPushButton(self) + self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks")) + self.ereader_button.setText(u"eReader ebooks") + self.ereader_button.clicked.connect(self.ereader_keys) + button_layout.addWidget(self.kindle_serial_button) + button_layout.addWidget(self.bandn_button) + button_layout.addWidget(self.mobi_button) + button_layout.addWidget(self.ereader_button) + button_layout.addWidget(self.adept_button) + button_layout.addWidget(self.kindle_key_button) + + self.resize(self.sizeHint()) + + def kindle_serials(self): + d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog) + d.exec_() + + def kindle_keys(self): + if isosx or iswindows: + d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i') + else: + # linux + d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix']) + d.exec_() + self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix() + + def adept_keys(self): + if isosx or iswindows: + d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der') + else: + # linux + d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix']) + d.exec_() + self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix() + + def mobi_keys(self): + d = ManageKeysDialog(self,u"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog) + d.exec_() + + def bandn_keys(self): + d = ManageKeysDialog(self,u"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64') + d.exec_() + + def ereader_keys(self): + d = ManageKeysDialog(self,u"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63') + d.exec_() + + def help_link_activated(self, url): + def get_help_file_resource(): + # Copy the HTML helpfile to the plugin directory each time the + # link is clicked in case the helpfile is updated in newer plugins. + file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name) + with open(file_path,'w') as f: + f.write(self.load_resource(help_file_name)) + return file_path + url = 'file:///' + get_help_file_resource() + open_url(QUrl(url)) + + def save_settings(self): + self.dedrmprefs.set('bandnkeys', self.tempdedrmprefs['bandnkeys']) + self.dedrmprefs.set('adeptkeys', self.tempdedrmprefs['adeptkeys']) + self.dedrmprefs.set('ereaderkeys', self.tempdedrmprefs['ereaderkeys']) + self.dedrmprefs.set('kindlekeys', self.tempdedrmprefs['kindlekeys']) + self.dedrmprefs.set('pids', self.tempdedrmprefs['pids']) + self.dedrmprefs.set('serials', self.tempdedrmprefs['serials']) + self.dedrmprefs.set('adobewineprefix', self.tempdedrmprefs['adobewineprefix']) + self.dedrmprefs.set('kindlewineprefix', self.tempdedrmprefs['kindlewineprefix']) + self.dedrmprefs.set('configured', True) + self.dedrmprefs.writeprefs() + + def load_resource(self, name): + with ZipFile(self.plugin_path, 'r') as zf: + if name in zf.namelist(): + return zf.read(name) + return "" + + + +class ManageKeysDialog(QDialog): + def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u"", wineprefix = None): + QDialog.__init__(self,parent) + self.parent = parent + self.key_type_name = key_type_name + self.plugin_keys = plugin_keys + self.create_key = create_key + self.keyfile_ext = keyfile_ext + self.import_key = (keyfile_ext != u"") + self.binary_file = (keyfile_ext == u".der") + self.json_file = (keyfile_ext == u".k4i") + self.wineprefix = wineprefix + + self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name)) + + # Start Qt Gui dialog layout + layout = QVBoxLayout(self) + self.setLayout(layout) + + help_layout = QHBoxLayout() + layout.addLayout(help_layout) + # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked. + help_label = QLabel('Help', self) + help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) + help_label.setAlignment(Qt.AlignRight) + help_label.linkActivated.connect(self.help_link_activated) + help_layout.addWidget(help_label) + + keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self) + layout.addWidget(keys_group_box) + keys_group_box_layout = QHBoxLayout() + keys_group_box.setLayout(keys_group_box_layout) + + self.listy = QListWidget(self) + self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name)) + self.listy.setSelectionMode(QAbstractItemView.SingleSelection) + self.populate_list() + keys_group_box_layout.addWidget(self.listy) + + button_layout = QVBoxLayout() + keys_group_box_layout.addLayout(button_layout) + self._add_key_button = QtGui.QToolButton(self) + self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name)) + self._add_key_button.setIcon(QIcon(I('plus.png'))) + self._add_key_button.clicked.connect(self.add_key) + button_layout.addWidget(self._add_key_button) + + self._delete_key_button = QtGui.QToolButton(self) + self._delete_key_button.setToolTip(_(u"Delete highlighted key")) + self._delete_key_button.setIcon(QIcon(I('list_remove.png'))) + self._delete_key_button.clicked.connect(self.delete_key) + button_layout.addWidget(self._delete_key_button) + + if type(self.plugin_keys) == dict and self.import_key: + self._rename_key_button = QtGui.QToolButton(self) + self._rename_key_button.setToolTip(_(u"Rename highlighted key")) + self._rename_key_button.setIcon(QIcon(I('edit-select-all.png'))) + self._rename_key_button.clicked.connect(self.rename_key) + button_layout.addWidget(self._rename_key_button) + + self.export_key_button = QtGui.QToolButton(self) + self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext)) + self.export_key_button.setIcon(QIcon(I('save.png'))) + self.export_key_button.clicked.connect(self.export_key) + button_layout.addWidget(self.export_key_button) + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + button_layout.addItem(spacerItem) + + if self.wineprefix is not None: + layout.addSpacing(5) + wineprefix_layout = QHBoxLayout() + layout.addLayout(wineprefix_layout) + wineprefix_layout.setAlignment(Qt.AlignCenter) + self.wp_label = QLabel(u"WINEPREFIX:") + wineprefix_layout.addWidget(self.wp_label) + self.wp_lineedit = QLineEdit(self) + wineprefix_layout.addWidget(self.wp_lineedit) + self.wp_label.setBuddy(self.wp_lineedit) + self.wp_lineedit.setText(self.wineprefix) + + layout.addSpacing(5) + migrate_layout = QHBoxLayout() + layout.addLayout(migrate_layout) + if self.import_key: + migrate_layout.setAlignment(Qt.AlignJustify) + self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self) + self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext)) + self.migrate_btn.clicked.connect(self.migrate_wrapper) + migrate_layout.addWidget(self.migrate_btn) + migrate_layout.addStretch() + self.button_box = QDialogButtonBox(QDialogButtonBox.Close) + self.button_box.rejected.connect(self.close) + migrate_layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + def getwineprefix(self): + if self.wineprefix is not None: + return unicode(self.wp_lineedit.text().toUtf8(), 'utf8').strip() + return u"" + + def populate_list(self): + if type(self.plugin_keys) == dict: + for key in self.plugin_keys.keys(): + self.listy.addItem(QListWidgetItem(key)) + else: + for key in self.plugin_keys: + self.listy.addItem(QListWidgetItem(key)) + + def add_key(self): + d = self.create_key(self) + d.exec_() + + if d.result() != d.Accepted: + # New key generation cancelled. + return + new_key_value = d.key_value + if type(self.plugin_keys) == dict: + if new_key_value in self.plugin_keys.values(): + old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] + info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), + u"The new {1} is the same as the existing {1} named {0} and has not been added.".format(old_key_name,self.key_type_name), show=True) + return + self.plugin_keys[d.key_name] = new_key_value + else: + if new_key_value in self.plugin_keys: + info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), + u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True) + return + + self.plugin_keys.append(d.key_value) + self.listy.clear() + self.populate_list() + + def rename_key(self): + if not self.listy.currentItem(): + errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name) + r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + return + + d = RenameKeyDialog(self) + d.exec_() + + if d.result() != d.Accepted: + # rename cancelled or moot. + return + keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8') + if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named {0} to {1}?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): + return + self.plugin_keys[d.key_name] = self.plugin_keys[keyname] + del self.plugin_keys[keyname] + + self.listy.clear() + self.populate_list() + + def delete_key(self): + if not self.listy.currentItem(): + return + keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8') + if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} {0}?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): + return + if type(self.plugin_keys) == dict: + del self.plugin_keys[keyname] + else: + self.plugin_keys.remove(keyname) + + self.listy.clear() + self.populate_list() + + def help_link_activated(self, url): + def get_help_file_resource(): + # Copy the HTML helpfile to the plugin directory each time the + # link is clicked in case the helpfile is updated in newer plugins. + help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name) + file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name) + with open(file_path,'w') as f: + f.write(self.parent.load_resource(help_file_name)) + return file_path + url = 'file:///' + get_help_file_resource() + open_url(QUrl(url)) + + def migrate_files(self): + dynamic[PLUGIN_NAME + u"config_dir"] = config_dir + files = choose_files(self, PLUGIN_NAME + u"config_dir", + u"Select {0} files to import".format(self.key_type_name), [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])], False) + counter = 0 + skipped = 0 + if files: + for filename in files: + fpath = os.path.join(config_dir, filename) + filename = os.path.basename(filename) + new_key_name = os.path.splitext(os.path.basename(filename))[0] + with open(fpath,'rb') as keyfile: + new_key_value = keyfile.read() + if self.binary_file: + new_key_value = new_key_value.encode('hex') + elif self.json_file: + new_key_value = json.loads(new_key_value) + match = False + for key in self.plugin_keys.keys(): + if uStrCmp(new_key_name, key, True): + skipped += 1 + msg = u"A key with the name {0} already exists!\nSkipping key file {1}.\nRename the existing key and import again".format(new_key_name,filename) + inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(msg), show_copy_button=False, show=True) + match = True + break + if not match: + if new_key_value in self.plugin_keys.values(): + old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] + skipped += 1 + info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + u"The key in file {0} is the same as the existing key {1} and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True) + else: + counter += 1 + self.plugin_keys[new_key_name] = new_key_value + + msg = u"" + if counter+skipped > 1: + if counter > 0: + msg += u"Imported {0:d} key {1}. ".format(counter, u"file" if counter == 1 else u"files") + if skipped > 0: + msg += u"Skipped {0:d} key {1}.".format(skipped, u"file" if counter == 1 else u"files") + inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(msg), show_copy_button=False, show=True) + return counter > 0 + + def migrate_wrapper(self): + if self.migrate_files(): + self.listy.clear() + self.populate_list() + + def export_key(self): + if not self.listy.currentItem(): + errmsg = u"No keyfile selected to export. Highlight a keyfile first." + r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + return + filter = QString(u"{0} Files (*.{1})".format(self.key_type_name, self.keyfile_ext)) + keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8') + if dynamic.get(PLUGIN_NAME + 'save_dir'): + defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), u"{0}.{1}".format(keyname , self.keyfile_ext)) + else: + defaultname = os.path.join(os.path.expanduser('~'), u"{0}.{1}".format(keyname , self.keyfile_ext)) + filename = unicode(QtGui.QFileDialog.getSaveFileName(self, u"Save {0} File as...".format(self.key_type_name), defaultname, + u"{0} Files (*.{1})".format(self.key_type_name,self.keyfile_ext), filter)) + if filename: + dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0] + with file(filename, 'w') as fname: + if self.binary_file: + fname.write(self.plugin_keys[keyname].decode('hex')) + elif self.json_file: + fname.write(json.dumps(self.plugin_keys[keyname])) + else: + fname.write(self.plugin_keys[keyname]) + + + + +class RenameKeyDialog(QDialog): + def __init__(self, parent=None,): + print repr(self), repr(parent) + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox('', self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + data_group_box_layout.addWidget(QLabel('New Key Name:', self)) + self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self) + self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name)) + data_group_box_layout.addWidget(self.key_ledit) + + layout.addSpacing(20) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + def accept(self): + if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace(): + errmsg = u"Key name field cannot be empty!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + if len(self.key_ledit.text()) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()): + # Same exact name ... do nothing. + return QDialog.reject(self) + for k in self.parent.plugin_keys.keys(): + if (uStrCmp(self.key_ledit.text(), k, True) and + not uStrCmp(k, self.parent.listy.currentItem().text(), True)): + errmsg = u"The key name {0} is already being used.".format(self.key_ledit.text()) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + QDialog.accept(self) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + + + + + + + +class AddBandNKeyDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(_(u"

Enter an identifying name for this new key.

" + + u"

It should be something that will help you remember " + + u"what personal information was used to create it.")) + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + name_group = QHBoxLayout() + data_group_box_layout.addLayout(name_group) + name_group.addWidget(QLabel(u"Your Name:", self)) + self.name_ledit = QLineEdit(u"", self) + self.name_ledit.setToolTip(_(u"

Enter your name as it appears in your B&N " + + u"account or on your credit card.

" + + u"

It will only be used to generate this " + + u"one-time key and won\'t be stored anywhere " + + u"in calibre or on your computer.

" + + u"

(ex: Jonathan Smith)")) + name_group.addWidget(self.name_ledit) + name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) + name_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(name_disclaimer_label) + + ccn_group = QHBoxLayout() + data_group_box_layout.addLayout(ccn_group) + ccn_group.addWidget(QLabel(u"Credit Card#:", self)) + self.cc_ledit = QLineEdit(u"", self) + self.cc_ledit.setToolTip(_(u"

Enter the full credit card number on record " + + u"in your B&N account.

" + + u"

No spaces or dashes... just the numbers. " + + u"This number will only be used to generate this " + + u"one-time key and won\'t be stored anywhere in " + + u"calibre or on your computer.")) + ccn_group.addWidget(self.cc_ledit) + ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) + ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(ccn_disclaimer_label) + layout.addSpacing(10) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key + return generate_bandn_key(self.user_name,self.cc_number) + + @property + def user_name(self): + return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','') + + @property + def cc_number(self): + return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','') + + + def accept(self): + if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if not self.cc_number.isdigit(): + errmsg = u"Numbers only in the credit card number field!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + +class AddEReaderDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + name_group = QHBoxLayout() + data_group_box_layout.addLayout(name_group) + name_group.addWidget(QLabel(u"Your Name:", self)) + self.name_ledit = QLineEdit(u"", self) + self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)") + name_group.addWidget(self.name_ledit) + name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) + name_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(name_disclaimer_label) + + ccn_group = QHBoxLayout() + data_group_box_layout.addLayout(ccn_group) + ccn_group.addWidget(QLabel(u"Credit Card#:", self)) + self.cc_ledit = QLineEdit(u"", self) + self.cc_ledit.setToolTip(u"

Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.") + ccn_group.addWidget(self.cc_ledit) + ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) + ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(ccn_disclaimer_label) + layout.addSpacing(10) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key + return generate_ereader_key(self.user_name,self.cc_number).encode('hex') + + @property + def user_name(self): + return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','') + + @property + def cc_number(self): + return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','') + + + def accept(self): + if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if not self.cc_number.isdigit(): + errmsg = u"Numbers only in the credit card number field!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddAdeptDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.adobekey import adeptkeys + + defaultkeys = adeptkeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.path.join(parent.parent.alfdir,u"adobekey.py") + defaultkeys = WineGetKeys(scriptpath, u".der",parent.getwineprefix()) + + self.default_key = defaultkeys[0] + except: + traceback.print_exc() + self.default_key = u"" + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + if len(self.default_key)>0: + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit(u"default_key", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for the current default Adobe Digital Editions key.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + self.button_box.accepted.connect(self.accept) + else: + default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self) + default_key_error.setAlignment(Qt.AlignHCenter) + layout.addWidget(default_key_error) + # if no default, bot buttons do the same + self.button_box.accepted.connect(self.reject) + + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return self.default_key.encode('hex') + + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddKindleDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + try: + if iswindows or isosx: + from calibre_plugins.dedrm.kindlekey import kindlekeys + + defaultkeys = kindlekeys() + else: # linux + from wineutils import WineGetKeys + + scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py") + defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix()) + + self.default_key = defaultkeys[0] + except: + traceback.print_exc() + self.default_key = u"" + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + if len(self.default_key)>0: + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit(u"default_key", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for the current default Kindle for Mac/PC key.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + self.button_box.accepted.connect(self.accept) + else: + default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self) + default_key_error.setAlignment(Qt.AlignHCenter) + layout.addWidget(default_key_error) + # if no default, bot buttons do the same + self.button_box.accepted.connect(self.reject) + + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return self.default_key + + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddSerialDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) != 16: + errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name)) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddPIDDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"PID:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog." + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) != 8 and len(self.key_name) != 10: + errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name)) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/convert2xml.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py old mode 100755 new mode 100644 similarity index 92% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/convert2xml.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py index c412d7b..101c45a --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/convert2xml.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py @@ -230,6 +230,7 @@ def __init__(self, filename, dict, debug, flat_xml): 'empty' : (1, 'snippets', 1, 0), 'page' : (1, 'snippets', 1, 0), + 'page.class' : (1, 'scalar_text', 0, 0), 'page.pageid' : (1, 'scalar_text', 0, 0), 'page.pagelabel' : (1, 'scalar_text', 0, 0), 'page.type' : (1, 'scalar_text', 0, 0), @@ -238,11 +239,13 @@ def __init__(self, filename, dict, debug, flat_xml): 'page.startID' : (1, 'scalar_number', 0, 0), 'group' : (1, 'snippets', 1, 0), + 'group.class' : (1, 'scalar_text', 0, 0), 'group.type' : (1, 'scalar_text', 0, 0), 'group._tag' : (1, 'scalar_text', 0, 0), 'group.orientation': (1, 'scalar_text', 0, 0), 'region' : (1, 'snippets', 1, 0), + 'region.class' : (1, 'scalar_text', 0, 0), 'region.type' : (1, 'scalar_text', 0, 0), 'region.x' : (1, 'scalar_number', 0, 0), 'region.y' : (1, 'scalar_number', 0, 0), @@ -252,13 +255,16 @@ def __init__(self, filename, dict, debug, flat_xml): 'empty_text_region' : (1, 'snippets', 1, 0), - 'img' : (1, 'snippets', 1, 0), - 'img.x' : (1, 'scalar_number', 0, 0), - 'img.y' : (1, 'scalar_number', 0, 0), - 'img.h' : (1, 'scalar_number', 0, 0), - 'img.w' : (1, 'scalar_number', 0, 0), - 'img.src' : (1, 'scalar_number', 0, 0), - 'img.color_src' : (1, 'scalar_number', 0, 0), + 'img' : (1, 'snippets', 1, 0), + 'img.x' : (1, 'scalar_number', 0, 0), + 'img.y' : (1, 'scalar_number', 0, 0), + 'img.h' : (1, 'scalar_number', 0, 0), + 'img.w' : (1, 'scalar_number', 0, 0), + 'img.src' : (1, 'scalar_number', 0, 0), + 'img.color_src' : (1, 'scalar_number', 0, 0), + 'img.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'img.gridEndCenter' : (1, 'scalar_number', 0, 0), + 'img.image_type' : (1, 'scalar_number', 0, 0), 'paragraph' : (1, 'snippets', 1, 0), 'paragraph.class' : (1, 'scalar_text', 0, 0), @@ -267,15 +273,20 @@ def __init__(self, filename, dict, debug, flat_xml): 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), 'paragraph.gridSize' : (1, 'scalar_number', 0, 0), 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), 'word_semantic' : (1, 'snippets', 1, 1), 'word_semantic.type' : (1, 'scalar_text', 0, 0), + 'word_semantic.class' : (1, 'scalar_text', 0, 0), 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0), 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0), 'word' : (1, 'snippets', 1, 0), 'word.type' : (1, 'scalar_text', 0, 0), @@ -284,6 +295,7 @@ def __init__(self, filename, dict, debug, flat_xml): 'word.lastGlyph' : (1, 'scalar_number', 0, 0), '_span' : (1, 'snippets', 1, 0), + '_span.class' : (1, 'scalar_text', 0, 0), '_span.firstWord' : (1, 'scalar_number', 0, 0), '_span.lastWord' : (1, 'scalar_number', 0, 0), '_span.gridSize' : (1, 'scalar_number', 0, 0), @@ -302,6 +314,7 @@ def __init__(self, filename, dict, debug, flat_xml): 'span.gridEndCenter' : (1, 'scalar_number', 0, 0), 'extratokens' : (1, 'snippets', 1, 0), + 'extratokens.class' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), @@ -347,16 +360,18 @@ def __init__(self, filename, dict, debug, flat_xml): 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0), 'version.toc' : (1, 'scalar_text', 0, 0), - 'stylesheet' : (1, 'snippets', 1, 0), - 'style' : (1, 'snippets', 1, 0), - 'style._tag' : (1, 'scalar_text', 0, 0), - 'style.type' : (1, 'scalar_text', 0, 0), - 'style._parent_type' : (1, 'scalar_text', 0, 0), - 'style.class' : (1, 'scalar_text', 0, 0), - 'style._after_class' : (1, 'scalar_text', 0, 0), - 'rule' : (1, 'snippets', 1, 0), - 'rule.attr' : (1, 'scalar_text', 0, 0), - 'rule.value' : (1, 'scalar_text', 0, 0), + 'stylesheet' : (1, 'snippets', 1, 0), + 'style' : (1, 'snippets', 1, 0), + 'style._tag' : (1, 'scalar_text', 0, 0), + 'style.type' : (1, 'scalar_text', 0, 0), + 'style._after_type' : (1, 'scalar_text', 0, 0), + 'style._parent_type' : (1, 'scalar_text', 0, 0), + 'style._after_parent_type' : (1, 'scalar_text', 0, 0), + 'style.class' : (1, 'scalar_text', 0, 0), + 'style._after_class' : (1, 'scalar_text', 0, 0), + 'rule' : (1, 'snippets', 1, 0), + 'rule.attr' : (1, 'scalar_text', 0, 0), + 'rule.value' : (1, 'scalar_text', 0, 0), 'original' : (0, 'number', 1, 1), 'original.pnum' : (1, 'number', 0, 0), diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/dialogs.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/dialogs.py new file mode 100644 index 0000000..f734a27 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/dialogs.py @@ -0,0 +1,719 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +from __future__ import with_statement +__license__ = 'GPL v3' + +# Standard Python modules. +import os, sys, re, hashlib +import json + +from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget, QListWidgetItem, QAbstractItemView, QLineEdit, QPushButton, QIcon, QGroupBox, QDialog, QDialogButtonBox, QUrl, QString) +from PyQt4 import QtGui + +# calibre modules and constants. +from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url, + choose_dir, choose_files) +from calibre.utils.config import dynamic, config_dir, JSONConfig + +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION +from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString) +from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key +from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key +from calibre_plugins.dedrm.adobekey import adeptkeys as retrieve_adept_keys +from calibre_plugins.dedrm.kindlekey import kindlekeys as retrieve_kindle_keys + +class ManageKeysDialog(QDialog): + def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u""): + QDialog.__init__(self,parent) + self.parent = parent + self.key_type_name = key_type_name + self.plugin_keys = plugin_keys + self.create_key = create_key + self.keyfile_ext = keyfile_ext + self.import_key = (keyfile_ext != u"") + self.binary_file = (key_type_name == u"Adobe Digital Editions Key") + self.json_file = (key_type_name == u"Kindle for Mac and PC Key") + + self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name)) + + # Start Qt Gui dialog layout + layout = QVBoxLayout(self) + self.setLayout(layout) + + help_layout = QHBoxLayout() + layout.addLayout(help_layout) + # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked. + help_label = QLabel('Help', self) + help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) + help_label.setAlignment(Qt.AlignRight) + help_label.linkActivated.connect(self.help_link_activated) + help_layout.addWidget(help_label) + + keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self) + layout.addWidget(keys_group_box) + keys_group_box_layout = QHBoxLayout() + keys_group_box.setLayout(keys_group_box_layout) + + self.listy = QListWidget(self) + self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name)) + self.listy.setSelectionMode(QAbstractItemView.SingleSelection) + self.populate_list() + keys_group_box_layout.addWidget(self.listy) + + button_layout = QVBoxLayout() + keys_group_box_layout.addLayout(button_layout) + self._add_key_button = QtGui.QToolButton(self) + self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name)) + self._add_key_button.setIcon(QIcon(I('plus.png'))) + self._add_key_button.clicked.connect(self.add_key) + button_layout.addWidget(self._add_key_button) + + self._delete_key_button = QtGui.QToolButton(self) + self._delete_key_button.setToolTip(_(u"Delete highlighted key")) + self._delete_key_button.setIcon(QIcon(I('list_remove.png'))) + self._delete_key_button.clicked.connect(self.delete_key) + button_layout.addWidget(self._delete_key_button) + + if type(self.plugin_keys) == dict: + self._rename_key_button = QtGui.QToolButton(self) + self._rename_key_button.setToolTip(_(u"Rename highlighted key")) + self._rename_key_button.setIcon(QIcon(I('edit-select-all.png'))) + self._rename_key_button.clicked.connect(self.rename_key) + button_layout.addWidget(self._rename_key_button) + + self.export_key_button = QtGui.QToolButton(self) + self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext)) + self.export_key_button.setIcon(QIcon(I('save.png'))) + self.export_key_button.clicked.connect(self.export_key) + button_layout.addWidget(self.export_key_button) + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + button_layout.addItem(spacerItem) + + layout.addSpacing(5) + migrate_layout = QHBoxLayout() + layout.addLayout(migrate_layout) + if self.import_key: + migrate_layout.setAlignment(Qt.AlignJustify) + self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self) + self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext)) + self.migrate_btn.clicked.connect(self.migrate_wrapper) + migrate_layout.addWidget(self.migrate_btn) + migrate_layout.addStretch() + self.button_box = QDialogButtonBox(QDialogButtonBox.Close) + self.button_box.rejected.connect(self.close) + migrate_layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + def populate_list(self): + if type(self.plugin_keys) == dict: + for key in self.plugin_keys.keys(): + self.listy.addItem(QListWidgetItem(key)) + else: + for key in self.plugin_keys: + self.listy.addItem(QListWidgetItem(key)) + + def add_key(self): + d = self.create_key(self) + d.exec_() + + if d.result() != d.Accepted: + # New key generation cancelled. + return + new_key_value = d.key_value + if type(self.plugin_keys) == dict: + if new_key_value in self.plugin_keys.values(): + old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] + info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), + u"The new {1} is the same as the existing {1} named {0} and has not been added.".format(old_key_name,self.key_type_name), show=True) + return + self.plugin_keys[d.key_name] = new_key_value + else: + if new_key_value in self.plugin_keys: + info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), + u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True) + return + + self.plugin_keys.append(d.key_value) + self.listy.clear() + self.populate_list() + + def rename_key(self): + if not self.listy.currentItem(): + errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name) + r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + return + + d = RenameKeyDialog(self) + d.exec_() + + if d.result() != d.Accepted: + # rename cancelled or moot. + return + keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8') + if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named {0} to {1}?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): + return + self.plugin_keys[d.key_name] = self.plugin_keys[keyname] + del self.plugin_keys[keyname] + + self.listy.clear() + self.populate_list() + + def delete_key(self): + if not self.listy.currentItem(): + return + keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8') + if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} {0}?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): + return + if type(self.plugin_keys) == dict: + del self.plugin_keys[keyname] + else: + self.plugin_keys.remove(keyname) + + self.listy.clear() + self.populate_list() + + def help_link_activated(self, url): + def get_help_file_resource(): + # Copy the HTML helpfile to the plugin directory each time the + # link is clicked in case the helpfile is updated in newer plugins. + help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name) + file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name) + with open(file_path,'w') as f: + f.write(self.parent.load_resource(help_file_name)) + return file_path + url = 'file:///' + get_help_file_resource() + open_url(QUrl(url)) + + def migrate_files(self): + dynamic[PLUGIN_NAME + u"config_dir"] = config_dir + files = choose_files(self, PLUGIN_NAME + u"config_dir", + u"Select {0} files to import".format(self.key_type_name), [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])], False) + counter = 0 + skipped = 0 + if files: + for filename in files: + fpath = os.path.join(config_dir, filename) + filename = os.path.basename(filename) + new_key_name = os.path.splitext(os.path.basename(filename))[0] + with open(fpath,'rb') as keyfile: + new_key_value = keyfile.read() + if self.binary_file: + new_key_value = new_key_value.encode('hex') + elif self.json_file: + new_key_value = json.loads(new_key_value) + match = False + for key in self.plugin_keys.keys(): + if uStrCmp(new_key_name, key, True): + skipped += 1 + msg = u"A key with the name {0} already exists!\nSkipping key file {1}.\nRename the existing key and import again".format(new_key_name,filename) + inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(msg), show_copy_button=False, show=True) + match = True + break + if not match: + if new_key_value in self.plugin_keys.values(): + old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] + skipped += 1 + info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + u"The key in file {0} is the same as the existing key {1} and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True) + else: + counter += 1 + self.plugin_keys[new_key_name] = new_key_value + + msg = u"" + if counter+skipped > 1: + if counter > 0: + msg += u"Imported {0:d} key {1}. ".format(counter, u"file" if counter == 1 else u"files") + if skipped > 0: + msg += u"Skipped {0:d} key {1}.".format(skipped, u"file" if counter == 1 else u"files") + inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(msg), show_copy_button=False, show=True) + return counter > 0 + + def migrate_wrapper(self): + if self.migrate_files(): + self.listy.clear() + self.populate_list() + + def export_key(self): + if not self.listy.currentItem(): + errmsg = u"No keyfile selected to export. Highlight a keyfile first." + r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + return + filter = QString(u"{0} Files (*.{1})".format(self.key_type_name, self.keyfile_ext)) + keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8') + if dynamic.get(PLUGIN_NAME + 'save_dir'): + defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), u"{0}.{1}".format(keyname , self.keyfile_ext)) + else: + defaultname = os.path.join(os.path.expanduser('~'), u"{0}.{1}".format(keyname , self.keyfile_ext)) + filename = unicode(QtGui.QFileDialog.getSaveFileName(self, u"Save {0} File as...".format(self.key_type_name), defaultname, + u"{0} Files (*.{1})".format(self.key_type_name,self.keyfile_ext), filter)) + if filename: + dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0] + with file(filename, 'w') as fname: + if self.binary_file: + fname.write(self.plugin_keys[keyname].decode('hex')) + elif self.json_file: + fname.write(json.dumps(self.plugin_keys[keyname])) + else: + fname.write(self.plugin_keys[keyname]) + + + + +class RenameKeyDialog(QDialog): + def __init__(self, parent=None,): + print repr(self), repr(parent) + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox('', self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + data_group_box_layout.addWidget(QLabel('New Key Name:', self)) + self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self) + self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name)) + data_group_box_layout.addWidget(self.key_ledit) + + layout.addSpacing(20) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + def accept(self): + if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace(): + errmsg = u"Key name field cannot be empty!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + if len(self.key_ledit.text()) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()): + # Same exact name ... do nothing. + return QDialog.reject(self) + for k in self.parent.plugin_keys.keys(): + if (uStrCmp(self.key_ledit.text(), k, True) and + not uStrCmp(k, self.parent.listy.currentItem().text(), True)): + errmsg = u"The key name {0} is already being used.".format(self.key_ledit.text()) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), + _(errmsg), show=True, show_copy_button=False) + QDialog.accept(self) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + + + + + + + +class AddBandNKeyDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(_(u"

Enter an identifying name for this new key.

" + + u"

It should be something that will help you remember " + + u"what personal information was used to create it.")) + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + name_group = QHBoxLayout() + data_group_box_layout.addLayout(name_group) + name_group.addWidget(QLabel(u"Your Name:", self)) + self.name_ledit = QLineEdit(u"", self) + self.name_ledit.setToolTip(_(u"

Enter your name as it appears in your B&N " + + u"account or on your credit card.

" + + u"

It will only be used to generate this " + + u"one-time key and won\'t be stored anywhere " + + u"in calibre or on your computer.

" + + u"

(ex: Jonathan Smith)")) + name_group.addWidget(self.name_ledit) + name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) + name_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(name_disclaimer_label) + + ccn_group = QHBoxLayout() + data_group_box_layout.addLayout(ccn_group) + ccn_group.addWidget(QLabel(u"Credit Card#:", self)) + self.cc_ledit = QLineEdit(u"", self) + self.cc_ledit.setToolTip(_(u"

Enter the full credit card number on record " + + u"in your B&N account.

" + + u"

No spaces or dashes... just the numbers. " + + u"This number will only be used to generate this " + + u"one-time key and won\'t be stored anywhere in " + + u"calibre or on your computer.")) + ccn_group.addWidget(self.cc_ledit) + ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) + ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(ccn_disclaimer_label) + layout.addSpacing(10) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return generate_bandn_key(self.user_name,self.cc_number) + + @property + def user_name(self): + return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','') + + @property + def cc_number(self): + return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','') + + + def accept(self): + if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if not self.cc_number.isdigit(): + errmsg = u"Numbers only in the credit card number field!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + +class AddEReaderDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + name_group = QHBoxLayout() + data_group_box_layout.addLayout(name_group) + name_group.addWidget(QLabel(u"Your Name:", self)) + self.name_ledit = QLineEdit(u"", self) + self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)") + name_group.addWidget(self.name_ledit) + name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) + name_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(name_disclaimer_label) + + ccn_group = QHBoxLayout() + data_group_box_layout.addLayout(ccn_group) + ccn_group.addWidget(QLabel(u"Credit Card#:", self)) + self.cc_ledit = QLineEdit(u"", self) + self.cc_ledit.setToolTip(u"

Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.") + ccn_group.addWidget(self.cc_ledit) + ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) + ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(ccn_disclaimer_label) + layout.addSpacing(10) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return generate_ereader_key(self.user_name,self.cc_number).encode('hex') + + @property + def user_name(self): + return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','') + + @property + def cc_number(self): + return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','') + + + def accept(self): + if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if not self.cc_number.isdigit(): + errmsg = u"Numbers only in the credit card number field!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddAdeptDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + try: + self.default_key = retrieve_adept_keys()[0] + except: + self.default_key = u"" + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + if len(self.default_key)>0: + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for the current default Adobe Digital Editions key.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + self.button_box.accepted.connect(self.accept) + else: + default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self) + default_key_error.setAlignment(Qt.AlignHCenter) + layout.addWidget(default_key_error) + # if no default, bot buttons do the same + self.button_box.accepted.connect(self.reject) + + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return self.default_key.encode('hex') + + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddKindleDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + try: + self.default_key = retrieve_kindle_keys()[0] + except: + self.default_key = u"" + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + if len(self.default_key)>0: + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"Unique Key Name:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"

Enter an identifying name for the current default Kindle for Mac/PC key.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + self.button_box.accepted.connect(self.accept) + else: + default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self) + default_key_error.setAlignment(Qt.AlignHCenter) + layout.addWidget(default_key_error) + # if no default, bot buttons do the same + self.button_box.accepted.connect(self.reject) + + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return self.default_key + + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"All fields are required!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) < 4: + errmsg = u"Key name must be at least 4 characters long!" + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddSerialDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) != 16: + errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name)) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + +class AddPIDDialog(QDialog): + def __init__(self, parent=None,): + QDialog.__init__(self, parent) + self.parent = parent + self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION)) + layout = QVBoxLayout(self) + self.setLayout(layout) + + data_group_box = QGroupBox(u"", self) + layout.addWidget(data_group_box) + data_group_box_layout = QVBoxLayout() + data_group_box.setLayout(data_group_box_layout) + + key_group = QHBoxLayout() + data_group_box_layout.addLayout(key_group) + key_group.addWidget(QLabel(u"PID:", self)) + self.key_ledit = QLineEdit("", self) + self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") + key_group.addWidget(self.key_ledit) + key_label = QLabel(_(''), self) + key_label.setAlignment(Qt.AlignHCenter) + data_group_box_layout.addWidget(key_label) + + self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.resize(self.sizeHint()) + + @property + def key_name(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + @property + def key_value(self): + return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() + + def accept(self): + if len(self.key_name) == 0 or self.key_name.isspace(): + errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog." + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + if len(self.key_name) != 8 and len(self.key_name) != 10: + errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name)) + return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) + QDialog.accept(self) + + diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/encodebase64.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/encodebase64.py new file mode 100644 index 0000000..6bb8c37 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/encodebase64.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# base64.py, version 1.0 +# Copyright © 2010 Apprentice Alf + +# Released under the terms of the GNU General Public Licence, version 3 or +# later. + +# Revision history: +# 1 - Initial release. To allow Applescript to do base64 encoding + +""" +Provide base64 encoding. +""" + +from __future__ import with_statement + +__license__ = 'GPL v3' + +import sys +import os +import base64 + +def usage(progname): + print "Applies base64 encoding to the supplied file, sending to standard output" + print "Usage:" + print " %s " % progname + +def cli_main(argv=sys.argv): + progname = os.path.basename(argv[0]) + + if len(argv)<2: + usage(progname) + sys.exit(2) + + keypath = argv[1] + with open(keypath, 'rb') as f: + keyder = f.read() + print keyder.encode('base64') + return 0 + + +if __name__ == '__main__': + sys.exit(cli_main()) diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/epubtest.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/epubtest.py new file mode 100644 index 0000000..11f1427 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/epubtest.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# This is a python script. You need a Python interpreter to run it. +# For example, ActiveState Python, which exists for windows. +# +# Changelog drmcheck +# 1.00 - Initial version, with code from various other scripts +# 1.01 - Moved authorship announcement to usage section. +# +# Changelog epubtest +# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf +# 1.01 - Added routine for use by Windows DeDRM +# +# Written in 2011 by Paul Durrant +# Released with unlicense. See http://unlicense.org/ +# +############################################################################# +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +############################################################################# +# +# It's still polite to give attribution if you do reuse this code. +# + +from __future__ import with_statement + +__version__ = '1.01' + +import sys, struct, os +import zlib +import zipfile +import xml.etree.ElementTree as etree + +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"epubtest.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +_FILENAME_LEN_OFFSET = 26 +_EXTRA_LEN_OFFSET = 28 +_FILENAME_OFFSET = 30 +_MAX_SIZE = 64 * 1024 + + +def uncompress(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(file, zi): + # get file name length and exta data length to find start of file data + local_header_offset = zi.header_offset + + file.seek(local_header_offset + _FILENAME_LEN_OFFSET) + leninfo = file.read(2) + local_name_length, = struct.unpack(' 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] Des = None -if sys.platform.startswith('win'): +if iswindows: # first try with pycrypto if inCalibre: - from calibre_plugins.erdrpdb2pml import pycrypto_des + from calibre_plugins.dedrm import pycrypto_des else: import pycrypto_des Des = pycrypto_des.load_pycrypto() if Des == None: # they try with openssl if inCalibre: - from calibre_plugins.erdrpdb2pml import openssl_des + from calibre_plugins.dedrm import openssl_des else: import openssl_des Des = openssl_des.load_libcrypto() else: # first try with openssl if inCalibre: - from calibre_plugins.erdrpdb2pml import openssl_des + from calibre_plugins.dedrm import openssl_des else: import openssl_des Des = openssl_des.load_libcrypto() if Des == None: # then try with pycrypto if inCalibre: - from calibre_plugins.erdrpdb2pml import pycrypto_des + from calibre_plugins.dedrm import pycrypto_des else: import pycrypto_des Des = pycrypto_des.load_pycrypto() @@ -116,7 +170,7 @@ def __getattr__(self, attr): # of DES and try to speed it up with Psycho if Des == None: if inCalibre: - from calibre_plugins.erdrpdb2pml import python_des + from calibre_plugins.dedrm import python_des else: import python_des Des = python_des.Des @@ -168,17 +222,30 @@ def loadSection(self, section): off = self.sections[section][0] return self.contents[off:end_off] -def sanitizeFileName(s): - r = '' - for c in s: - if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-": - r += c - return r +# cleanup unicode filenames +# borrowed from calibre from calibre/src/calibre/__init__.py +# added in removal of control (<32) chars +# and removal of . at start and end +# and with some (heavily edited) code from Paul Durrant's kindlenamer.py +def sanitizeFileName(name): + # substitute filename unfriendly characters + name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'") + # delete control characters + name = u"".join(char for char in name if ord(char)>=32) + # white space to single space, delete leading and trailing while space + name = re.sub(ur"\s", u" ", name).strip() + # remove leading dots + while len(name)>0 and name[0] == u".": + name = name[1:] + # remove trailing dots (Windows doesn't like them) + if name.endswith(u'.'): + name = name[:-1] + return name def fixKey(key): def fixByte(b): return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) - return "".join([chr(fixByte(ord(a))) for a in key]) + return "".join([chr(fixByte(ord(a))) for a in key]) def deXOR(text, sp, table): r='' @@ -191,7 +258,7 @@ def deXOR(text, sp, table): return r class EreaderProcessor(object): - def __init__(self, sect, username, creditcard): + def __init__(self, sect, user_key): self.section_reader = sect.loadSection data = self.section_reader(0) version, = struct.unpack('>H', data[0:2]) @@ -212,18 +279,10 @@ def unshuff(data, shuf): for i in xrange(len(data)): j = (j + shuf) % len(data) r[j] = data[i] - assert len("".join(r)) == len(data) + assert len("".join(r)) == len(data) return "".join(r) r = unshuff(input[0:-8], cookie_shuf) - def fixUsername(s): - r = '' - for c in s.lower(): - if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'): - r += c - return r - - user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff) drm_sub_version = struct.unpack('>H', r[0:2])[0] self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1 self.num_image_pages = struct.unpack('>H', r[26:26+2])[0] @@ -302,7 +361,7 @@ def getImage(self, i): sect = self.section_reader(self.first_image_page + i) name = sect[4:4+32].strip('\0') data = sect[62:] - return sanitizeFileName(name), data + return sanitizeFileName(unicode(name,'windows-1252')), data # def getChapterNamePMLOffsetData(self): @@ -399,60 +458,53 @@ def getText(self): return r def cleanPML(pml): - # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) + # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) pml2 = pml for k in xrange(128,256): badChar = chr(k) pml2 = pml2.replace(badChar, '\\a%03d' % k) return pml2 -def convertEreaderToPml(infile, name, cc, outdir): - if not os.path.exists(outdir): - os.makedirs(outdir) +def decryptBook(infile, outpath, make_pmlz, user_key): bookname = os.path.splitext(os.path.basename(infile))[0] - print " Decoding File" - sect = Sectionizer(infile, 'PNRdPPrs') - er = EreaderProcessor(sect, name, cc) - - if er.getNumImages() > 0: - print " Extracting images" - imagedir = bookname + '_img/' - 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" - file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) - - # bkinfo = er.getBookInfo() - # if bkinfo != '': - # print " Extracting book meta information" - # file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo) - - - -def decryptBook(infile, outdir, name, cc, make_pmlz): - if make_pmlz : - # ignore specified outdir, use tempdir instead + if make_pmlz: + # outpath is actually pmlz name + pmlzname = outpath outdir = tempfile.mkdtemp() + imagedirpath = os.path.join(outdir,u"images") + else: + pmlzname = None + outdir = outpath + imagedirpath = os.path.join(outdir,bookname + u"_img") + try: - print "Processing..." - convertEreaderToPml(infile, name, cc, outdir) - if make_pmlz : + if not os.path.exists(outdir): + os.makedirs(outdir) + print u"Decoding File" + sect = Sectionizer(infile, 'PNRdPPrs') + er = EreaderProcessor(sect, user_key) + + if er.getNumImages() > 0: + print u"Extracting images" + 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 u"Extracting pml" + pml_string = er.getText() + pmlfilename = bookname + ".pml" + file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) + if pmlzname is not None: import zipfile import shutil - print " Creating PMLZ file" - zipname = infile[:-4] + '.pmlz' - myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False) + print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname)) + myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False) list = os.listdir(outdir) - for file in list: - localname = file - filePath = os.path.join(outdir,file) + for filename in list: + localname = filename + filePath = os.path.join(outdir,filename) if os.path.isfile(filePath): myZipFile.write(filePath, localname) elif os.path.isdir(filePath): @@ -466,36 +518,48 @@ def decryptBook(infile, outdir, name, cc, make_pmlz): myZipFile.close() # remove temporary directory shutil.rmtree(outdir, True) - print 'output is %s' % zipname + print u"Output is {0}".format(pmlzname) else : - print 'output in %s' % outdir + print u"Output is in {0}".format(outdir) print "done" except ValueError, e: - print "Error: %s" % e + print u"Error: {0}".format(e) + traceback.print_exc() return 1 return 0 def usage(): - print "Converts DRMed eReader books to PML Source" - print "Usage:" - print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number " - print " " - print "Options: " - print " -h prints this message" - print " --make-pmlz create PMLZ instead of using output directory" - print " " - print "Note:" - print " if ommitted, outdir defaults based on 'infile.pdb'" - print " It's enough to enter the last 8 digits of the credit card number" + print u"Converts DRMed eReader books to PML Source" + print u"Usage:" + print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number" + print u" " + print u"Options: " + print u" -h prints this message" + print u" -p create PMLZ instead of source folder" + print u" --make-pmlz create PMLZ instead of source folder" + print u" " + print u"Note:" + print u" if outpath is ommitted, creates source in 'infile_Source' folder" + print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'" + print u" if source folder created, images are in infile_img folder" + print u" if pmlz file created, images are in images folder" + print u" It's enough to enter the last 8 digits of the credit card number" return +def getuser_key(name,cc): + newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9') + cc = cc.replace(" ","") + return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff) -def main(argv=None): +def cli_main(): + print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__) + + argv=unicode_argv() try: - opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"]) + opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"]) except getopt.GetoptError, err: - print str(err) + print err.args[0] usage() return 1 make_pmlz = False @@ -503,24 +567,31 @@ def main(argv=None): if o == "-h": usage() return 0 + elif o == "-p": + make_pmlz = True elif o == "--make-pmlz": make_pmlz = True - print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__ - if len(args)!=3 and len(args)!=4: usage() return 1 if len(args)==3: - infile, name, cc = args[0], args[1], args[2] - outdir = infile[:-4] + '_Source' + infile, name, cc = args + if make_pmlz: + outpath = os.path.splitext(infile)[0] + u".pmlz" + else: + outpath = os.path.splitext(infile)[0] + u"_Source" elif len(args)==4: - infile, outdir, name, cc = args[0], args[1], args[2], args[3] + infile, outpath, name, cc = args + + print getuser_key(name,cc).encode('hex') - return decryptBook(infile, outdir, name, cc, make_pmlz) + return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc)) if __name__ == "__main__": - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) + diff --git a/Other_Tools/KindleBooks/lib/flatxml2html.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/flatxml2html.py similarity index 98% rename from Other_Tools/KindleBooks/lib/flatxml2html.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/flatxml2html.py index e5647f4..4d83368 100644 --- a/Other_Tools/KindleBooks/lib/flatxml2html.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/flatxml2html.py @@ -387,10 +387,14 @@ def getParaDescription(self, start, end, regtype): ws_last = int(argres) elif name.endswith('word.class'): - (cname, space) = argres.split('-',1) - if space == '' : space = '0' - if (cname == 'spaceafter') and (int(space) > 0) : - word_class = 'sa' + # we only handle spaceafter word class + try: + (cname, space) = argres.split('-',1) + if space == '' : space = '0' + if (cname == 'spaceafter') and (int(space) > 0) : + word_class = 'sa' + except: + pass elif name.endswith('word.img.src'): result.append(('img' + word_class, int(argres))) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/flatxml2svg.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/flatxml2svg.py similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/flatxml2svg.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/flatxml2svg.py diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/genbook.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/genbook.py similarity index 98% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/genbook.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/genbook.py index 9733887..3ed925d 100644 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/genbook.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/genbook.py @@ -29,10 +29,10 @@ class TpzDRMError(Exception): inCalibre = False if inCalibre : - from calibre_plugins.k4mobidedrm import convert2xml - from calibre_plugins.k4mobidedrm import flatxml2html - from calibre_plugins.k4mobidedrm import flatxml2svg - from calibre_plugins.k4mobidedrm import stylexml2css + from calibre_plugins.dedrm import convert2xml + from calibre_plugins.dedrm import flatxml2html + from calibre_plugins.dedrm import flatxml2svg + from calibre_plugins.dedrm import stylexml2css else : import convert2xml import flatxml2html @@ -117,7 +117,7 @@ def lookup(self,val): self.pos = val return self.stable[self.pos] else: - print "Error - %d outside of string table limits" % val + print "Error: %d outside of string table limits" % val raise TpzDRMError('outside or string table limits') # sys.exit(-1) def getSize(self): diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py new file mode 100644 index 0000000..ac73d1e --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# ignobleepub.pyw, version 3.8 +# Copyright © 2009-2010 by i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make sure to +# install the version for Python 2.6). Save this script file as +# ineptepub.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ineptepub.pyw. You can run this +# program from the command line (pythonw ineptepub.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. + +# Revision history: +# 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 +# 3.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml +# 3.3 - On Windows try PyCrypto first, OpenSSL next +# 3.4 - Modify interface to allow use with import +# 3.5 - Fix for potential problem with PyCrypto +# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code +# 3.7 - Tweaked to match ineptepub more closely +# 3.8 - Fixed to retain zip file metadata (e.g. file modification date) +# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 4.0 - Work if TkInter is missing + +""" +Decrypt Barnes & Noble encrypted ePub books. +""" + +__license__ = 'GPL v3' +__version__ = "4.0" + +import sys +import os +import traceback +import zlib +import zipfile +from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED +from contextlib import closing +import xml.etree.ElementTree as etree + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + return [u"ineptepub.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + + +class IGNOBLEError(Exception): + pass + +def _load_crypto_libcrypto(): + from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, cast + from ctypes.util import find_library + + if iswindows: + libcrypto = find_library('libeay32') + else: + libcrypto = find_library('crypto') + + if libcrypto is None: + raise IGNOBLEError('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 IGNOBLEError('AES improper key used') + return + key = self._key = AES_KEY() + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) + if rv < 0: + raise IGNOBLEError('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 IGNOBLEError('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, '\x00'*16) + + def decrypt(self, data): + return self._aes.decrypt(data) + + return AES + +def _load_crypto(): + AES = None + cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) + if sys.platform.startswith('win'): + cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) + for loader in cryptolist: + try: + AES = loader() + break + except (ImportError, IGNOBLEError): + pass + return AES + +AES = _load_crypto() + +META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +class Decryptor(object): + def __init__(self, bookkey, encryption): + enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) + self._aes = AES(bookkey) + encryption = etree.fromstring(encryption) + self._encrypted = encrypted = set() + expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), + enc('CipherReference')) + for elem in encryption.findall(expr): + path = elem.get('URI', None) + if path is not None: + path = path.encode('utf-8') + encrypted.add(path) + + def decompress(self, bytes): + dc = zlib.decompressobj(-15) + bytes = dc.decompress(bytes) + ex = dc.decompress('Z') + dc.flush() + if ex: + bytes = bytes + ex + return bytes + + def decrypt(self, path, data): + if path in self._encrypted: + data = self._aes.decrypt(data)[16:] + data = data[:-ord(data[-1])] + data = self.decompress(data) + return data + +# check file to make check whether it's probably an Adobe Adept encrypted ePub +def ignobleBook(inpath): + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + return False + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) == 64: + return True + except: + # if we couldn't check, assume it is + return True + return False + +def decryptBook(keyb64, inpath, outpath): + if AES is None: + raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.") + key = keyb64.decode('base64')[:16] + aes = AES(key) + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + print u"{0:s} is DRM-free.".format(os.path.basename(inpath)) + return 1 + for name in META_NAMES: + namelist.remove(name) + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) != 64: + print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)) + return 1 + bookkey = aes.decrypt(bookkey.decode('base64')) + bookkey = bookkey[:-ord(bookkey[-1])] + encryption = inf.read('META-INF/encryption.xml') + decryptor = Decryptor(bookkey[-16:], encryption) + kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) + with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: + zi = ZipInfo('mimetype') + zi.compress_type=ZIP_STORED + try: + # if the mimetype is present, get its info, including time-stamp + oldzi = inf.getinfo('mimetype') + # copy across fields to be preserved + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, inf.read('mimetype')) + for path in namelist: + data = inf.read(path) + zi = ZipInfo(path) + zi.compress_type=ZIP_DEFLATED + try: + # get the file info, including time-stamp + oldzi = inf.getinfo(path) + # copy across useful fields + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, decryptor.decrypt(path, data)) + except: + print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()) + return 2 + return 0 + + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + if len(argv) != 4: + print u"usage: {0} ".format(progname) + return 1 + keypath, inpath, outpath = argv[1:] + userkey = open(keypath,'rb').read() + result = decryptBook(userkey, inpath, outpath) + if result == 0: + print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) + return result + +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text=u"Select files for decryption") + 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=u"Key file").grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists(u"bnepubkey.b64"): + self.keypath.insert(0, u"bnepubkey.b64") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text=u"Input file").grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text=u"Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text=u"Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title=u"Select Barnes & Noble \'.b64\' key file", + defaultextension=u".b64", + filetypes=[('base64-encoded files', '.b64'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title=u"Select B&N-encrypted ePub file to decrypt", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title=u"Select unencrypted ePub file to produce", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = u"Specified key file does not exist" + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = u"Specified input file does not exist" + return + if not outpath: + self.status['text'] = u"Output file not specified" + return + if inpath == outpath: + self.status['text'] = u"Must have different input and output files" + return + userkey = open(keypath,'rb').read() + self.status['text'] = u"Decrypting..." + try: + decrypt_status = decryptBook(userkey, inpath, outpath) + except Exception, e: + self.status['text'] = u"Error: {0}".format(e.args[0]) + return + if decrypt_status == 0: + self.status['text'] = u"File successfully decrypted" + else: + self.status['text'] = u"The was an error decrypting the file." + + root = Tkinter.Tk() + root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__)) + root.resizable(True, False) + root.minsize(300, 0) + DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) + root.mainloop() + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ignoblekeygen.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py old mode 100755 new mode 100644 similarity index 53% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ignoblekeygen.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py index e2c50e2..5118c87 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ignoblekeygen.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py @@ -1,13 +1,27 @@ -#! /usr/bin/python +#!/usr/bin/env python +# -*- coding: utf-8 -*- from __future__ import with_statement -# ignoblekeygen.pyw, version 2.4 +# ignoblekeygen.pyw, version 2.5 +# Copyright © 2009-2010 i♥cabbages -# To run this program install Python 2.6 from -# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignoblekeygen.pyw and double-click on it to run it. +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python. +# We recommend ActiveState Python 2.7.X for Windows (x86) from +# http://www.activestate.com/activepython/downloads. +# You must also install PyCrypto from +# http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make certain to install the version for Python 2.7). +# Then save this script file as ignoblekeygen.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this +# program from the command line (python ignoblekeygen.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. # Revision history: # 1 - Initial release @@ -16,36 +30,97 @@ # 2.2 - On Windows try PyCrypto first and then OpenSSL next # 2.3 - Modify interface to allow use of import # 2.4 - Improvements to UI and now works in plugins +# 2.5 - Additional improvement for unicode and plugin support +# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 2.7 - Work if TkInter is missing """ Generate Barnes & Noble EPUB user key from name and credit card number. """ __license__ = 'GPL v3' +__version__ = "2.7" import sys import os import hashlib +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"ignoblekeygen.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] -# use openssl's libcrypt if it exists in place of pycrypto -# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages class IGNOBLEError(Exception): pass - def _load_crypto_libcrypto(): from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ Structure, c_ulong, create_string_buffer, cast from ctypes.util import find_library - if sys.platform.startswith('win'): + if iswindows: libcrypto = find_library('libeay32') else: libcrypto = find_library('crypto') + if libcrypto is None: - print 'libcrypto not found' raise IGNOBLEError('libcrypto not found') libcrypto = CDLL(libcrypto) @@ -70,6 +145,7 @@ def F(restype, name, argtypes): 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, iv): self._blocksize = len(userkey) @@ -88,7 +164,6 @@ def encrypt(self, data): return AES - def _load_crypto_pycrypto(): from Crypto.Cipher import AES as _AES @@ -120,25 +195,31 @@ def normalize_name(name): return ''.join(x for x in name.lower() if x != ' ') -def generate_keyfile(name, ccn, outpath): +def generate_key(name, ccn): # remove spaces and case from name and CC numbers. + if type(name)==unicode: + name = name.encode('utf-8') + if type(ccn)==unicode: + ccn = ccn.encode('utf-8') + name = normalize_name(name) + '\x00' ccn = normalize_name(ccn) + '\x00' - + name_sha = hashlib.sha1(name).digest()[:16] ccn_sha = hashlib.sha1(ccn).digest()[:16] both_sha = hashlib.sha1(name + ccn).digest() aes = AES(ccn_sha, name_sha) crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c)) userkey = hashlib.sha1(crypt).digest() - with open(outpath, 'wb') as f: - f.write(userkey.encode('base64')) - return userkey + return userkey.encode('base64') -def cli_main(argv=sys.argv): +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() progname = os.path.basename(argv[0]) if AES is None: print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ @@ -146,54 +227,58 @@ def cli_main(argv=sys.argv): (progname,) return 1 if len(argv) != 4: - print "usage: %s NAME CC# OUTFILE" % (progname,) + print u"usage: {0} ".format(progname) return 1 - name, ccn, outpath = argv[1:] - generate_keyfile(name, ccn, outpath) + name, ccn, keypath = argv[1:] + userkey = generate_key(name, ccn) + open(keypath,'wb').write(userkey) return 0 def gui_main(): - import Tkinter - import Tkconstants - import tkFileDialog - import tkMessageBox + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() class DecryptionDialog(Tkinter.Frame): def __init__(self, root): Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Enter parameters') + self.status = Tkinter.Label(self, text=u"Enter parameters") 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='Account Name').grid(row=0) + Tkinter.Label(body, text=u"Account Name").grid(row=0) self.name = Tkinter.Entry(body, width=40) self.name.grid(row=0, column=1, sticky=sticky) - Tkinter.Label(body, text='CC#').grid(row=1) + Tkinter.Label(body, text=u"CC#").grid(row=1) self.ccn = Tkinter.Entry(body, width=40) self.ccn.grid(row=1, column=1, sticky=sticky) - Tkinter.Label(body, text='Output file').grid(row=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) self.keypath = Tkinter.Entry(body, width=40) self.keypath.grid(row=2, column=1, sticky=sticky) - self.keypath.insert(2, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) + self.keypath.insert(2, u"bnepubkey.b64") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button.grid(row=2, column=2) buttons = Tkinter.Frame(self) buttons.pack() botton = Tkinter.Button( - buttons, text="Generate", width=10, command=self.generate) + buttons, text=u"Generate", width=10, command=self.generate) botton.pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) + buttons, text=u"Quit", width=10, command=self.quit) button.pack(side=Tkconstants.RIGHT) - + def get_keypath(self): keypath = tkFileDialog.asksaveasfilename( - parent=None, title='Select B&N EPUB key file to produce', - defaultextension='.b64', + parent=None, title=u"Select B&N ePub key file to produce", + defaultextension=u".b64", filetypes=[('base64-encoded files', '.b64'), ('All Files', '.*')]) if keypath: @@ -201,27 +286,28 @@ def get_keypath(self): self.keypath.delete(0, Tkconstants.END) self.keypath.insert(0, keypath) return - + def generate(self): name = self.name.get() ccn = self.ccn.get() keypath = self.keypath.get() if not name: - self.status['text'] = 'Name not specified' + self.status['text'] = u"Name not specified" return if not ccn: - self.status['text'] = 'Credit card number not specified' + self.status['text'] = u"Credit card number not specified" return if not keypath: - self.status['text'] = 'Output keyfile path not specified' + self.status['text'] = u"Output keyfile path not specified" return - self.status['text'] = 'Generating...' + self.status['text'] = u"Generating..." try: - generate_keyfile(name, ccn, keypath) + userkey = generate_key(name, ccn) except Exception, e: - self.status['text'] = 'Error: ' + str(e) + self.status['text'] = u"Error: (0}".format(e.args[0]) return - self.status['text'] = 'Keyfile successfully generated' + open(keypath,'wb').write(userkey) + self.status['text'] = u"Keyfile successfully generated" root = Tkinter.Tk() if AES is None: @@ -231,7 +317,7 @@ def generate(self): "This script requires OpenSSL or PyCrypto, which must be installed " "separately. Read the top-of-script comment for details.") return 1 - root.title('Ignoble EPUB Keyfile Generator') + root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__)) root.resizable(True, False) root.minsize(300, 0) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py new file mode 100644 index 0000000..225ffa7 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py @@ -0,0 +1,594 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# ineptepub.pyw, version 5.9 +# Copyright © 2009-2010 by i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make sure to +# install the version for Python 2.6). Save this script file as +# ineptepub.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ineptepub.pyw. You can run this +# program from the command line (pythonw ineptepub.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. + +# Revision history: +# 1 - Initial release +# 2 - Rename to INEPT, fix exit code +# 5 - Version bump to avoid (?) confusion; +# 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 +# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml +# 5.5 - On Windows try PyCrypto first, OpenSSL next +# 5.6 - Modify interface to allow use with import +# 5.7 - Fix for potential problem with PyCrypto +# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code +# 5.9 - Fixed to retain zip file metadata (e.g. file modification date) +# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 6.1 - Work if TkInter is missing + +""" +Decrypt Adobe Digital Editions encrypted ePub books. +""" + +__license__ = 'GPL v3' +__version__ = "6.1" + +import sys +import os +import traceback +import zlib +import zipfile +from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED +from contextlib import closing +import xml.etree.ElementTree as etree + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + return [u"ineptepub.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + + +class ADEPTError(Exception): + pass + +def _load_crypto_libcrypto(): + from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, cast + from ctypes.util import find_library + + if iswindows: + libcrypto = find_library('libeay32') + else: + libcrypto = find_library('crypto') + + if libcrypto is None: + raise ADEPTError('libcrypto not found') + libcrypto = CDLL(libcrypto) + + RSA_NO_PADDING = 3 + AES_MAXNR = 14 + + c_char_pp = POINTER(c_char_p) + c_int_p = POINTER(c_int) + + class RSA(Structure): + pass + RSA_p = POINTER(RSA) + + 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 + + d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', + [RSA_p, c_char_pp, c_long]) + RSA_size = F(c_int, 'RSA_size', [RSA_p]) + RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', + [c_int, c_char_p, c_char_p, RSA_p, c_int]) + RSA_free = F(None, 'RSA_free', [RSA_p]) + 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 RSA(object): + def __init__(self, der): + buf = create_string_buffer(der) + pp = c_char_pp(cast(buf, c_char_p)) + rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) + if rsa is None: + raise ADEPTError('Error parsing ADEPT user key DER') + + def decrypt(self, from_): + rsa = self._rsa + to = create_string_buffer(RSA_size(rsa)) + dlen = RSA_private_decrypt(len(from_), from_, to, rsa, + RSA_NO_PADDING) + if dlen < 0: + raise ADEPTError('RSA decryption failed') + return to[:dlen] + + def __del__(self): + if self._rsa is not None: + RSA_free(self._rsa) + self._rsa = None + + 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: + 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, RSA) + +def _load_crypto_pycrypto(): + from Crypto.Cipher import AES as _AES + from Crypto.PublicKey import RSA as _RSA + + # ASN.1 parsing code from tlslite + class ASN1Error(Exception): + pass + + class ASN1Parser(object): + class Parser(object): + def __init__(self, bytes): + self.bytes = bytes + self.index = 0 + + def get(self, length): + if self.index + length > len(self.bytes): + raise ASN1Error("Error decoding ASN.1") + x = 0 + for count in range(length): + x <<= 8 + x |= self.bytes[self.index] + self.index += 1 + return x + + def getFixBytes(self, lengthBytes): + bytes = self.bytes[self.index : self.index+lengthBytes] + self.index += lengthBytes + return bytes + + def getVarBytes(self, lengthLength): + lengthBytes = self.get(lengthLength) + return self.getFixBytes(lengthBytes) + + def getFixList(self, length, lengthList): + l = [0] * lengthList + for x in range(lengthList): + l[x] = self.get(length) + return l + + def getVarList(self, length, lengthLength): + lengthList = self.get(lengthLength) + if lengthList % length != 0: + raise ASN1Error("Error decoding ASN.1") + lengthList = int(lengthList/length) + l = [0] * lengthList + for x in range(lengthList): + l[x] = self.get(length) + return l + + def startLengthCheck(self, lengthLength): + self.lengthCheck = self.get(lengthLength) + self.indexCheck = self.index + + def setLengthCheck(self, length): + self.lengthCheck = length + self.indexCheck = self.index + + def stopLengthCheck(self): + if (self.index - self.indexCheck) != self.lengthCheck: + raise ASN1Error("Error decoding ASN.1") + + def atLengthCheck(self): + if (self.index - self.indexCheck) < self.lengthCheck: + return False + elif (self.index - self.indexCheck) == self.lengthCheck: + return True + else: + raise ASN1Error("Error decoding ASN.1") + + def __init__(self, bytes): + p = self.Parser(bytes) + p.get(1) + self.length = self._getASN1Length(p) + self.value = p.getFixBytes(self.length) + + def getChild(self, which): + p = self.Parser(self.value) + for x in range(which+1): + markIndex = p.index + p.get(1) + length = self._getASN1Length(p) + p.getFixBytes(length) + return ASN1Parser(p.bytes[markIndex:p.index]) + + def _getASN1Length(self, p): + firstLength = p.get(1) + if firstLength<=127: + return firstLength + else: + lengthLength = firstLength & 0x7F + return p.get(lengthLength) + + class AES(object): + def __init__(self, key): + self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16) + + def decrypt(self, data): + return self._aes.decrypt(data) + + class RSA(object): + def __init__(self, der): + key = ASN1Parser([ord(x) for x in der]) + key = [key.getChild(x).value for x in xrange(1, 4)] + key = [self.bytesToNumber(v) for v in key] + self._rsa = _RSA.construct(key) + + def bytesToNumber(self, bytes): + total = 0L + for byte in bytes: + total = (total << 8) + byte + return total + + def decrypt(self, data): + return self._rsa.decrypt(data) + + return (AES, RSA) + +def _load_crypto(): + AES = RSA = None + cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) + if sys.platform.startswith('win'): + cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) + for loader in cryptolist: + try: + AES, RSA = loader() + break + except (ImportError, ADEPTError): + pass + return (AES, RSA) + +AES, RSA = _load_crypto() + +META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') +NSMAP = {'adept': 'http://ns.adobe.com/adept', + 'enc': 'http://www.w3.org/2001/04/xmlenc#'} + +class Decryptor(object): + def __init__(self, bookkey, encryption): + enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) + self._aes = AES(bookkey) + encryption = etree.fromstring(encryption) + self._encrypted = encrypted = set() + expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), + enc('CipherReference')) + for elem in encryption.findall(expr): + path = elem.get('URI', None) + if path is not None: + path = path.encode('utf-8') + encrypted.add(path) + + def decompress(self, bytes): + dc = zlib.decompressobj(-15) + bytes = dc.decompress(bytes) + ex = dc.decompress('Z') + dc.flush() + if ex: + bytes = bytes + ex + return bytes + + def decrypt(self, path, data): + if path in self._encrypted: + data = self._aes.decrypt(data)[16:] + data = data[:-ord(data[-1])] + data = self.decompress(data) + return data + +# check file to make check whether it's probably an Adobe Adept encrypted ePub +def adeptBook(inpath): + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + return False + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) == 172: + return True + except: + # if we couldn't check, assume it is + return True + return False + +def decryptBook(userkey, inpath, outpath): + if AES is None: + raise ADEPTError(u"PyCrypto or OpenSSL must be installed.") + rsa = RSA(userkey) + with closing(ZipFile(open(inpath, 'rb'))) as inf: + namelist = set(inf.namelist()) + if 'META-INF/rights.xml' not in namelist or \ + 'META-INF/encryption.xml' not in namelist: + print u"{0:s} is DRM-free.".format(os.path.basename(inpath)) + return 1 + for name in META_NAMES: + namelist.remove(name) + try: + rights = etree.fromstring(inf.read('META-INF/rights.xml')) + adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) + expr = './/%s' % (adept('encryptedKey'),) + bookkey = ''.join(rights.findtext(expr)) + if len(bookkey) != 172: + print u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)) + return 1 + bookkey = rsa.decrypt(bookkey.decode('base64')) + # Padded as per RSAES-PKCS1-v1_5 + if bookkey[-17] != '\x00': + print u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)) + return 2 + encryption = inf.read('META-INF/encryption.xml') + decryptor = Decryptor(bookkey[-16:], encryption) + kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) + with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: + zi = ZipInfo('mimetype') + zi.compress_type=ZIP_STORED + try: + # if the mimetype is present, get its info, including time-stamp + oldzi = inf.getinfo('mimetype') + # copy across fields to be preserved + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, inf.read('mimetype')) + for path in namelist: + data = inf.read(path) + zi = ZipInfo(path) + zi.compress_type=ZIP_DEFLATED + try: + # get the file info, including time-stamp + oldzi = inf.getinfo(path) + # copy across useful fields + zi.date_time = oldzi.date_time + zi.comment = oldzi.comment + zi.extra = oldzi.extra + zi.internal_attr = oldzi.internal_attr + # external attributes are dependent on the create system, so copy both. + zi.external_attr = oldzi.external_attr + zi.create_system = oldzi.create_system + except: + pass + outf.writestr(zi, decryptor.decrypt(path, data)) + except: + print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()) + return 2 + return 0 + + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + if len(argv) != 4: + print u"usage: {0} ".format(progname) + return 1 + keypath, inpath, outpath = argv[1:] + userkey = open(keypath,'rb').read() + result = decryptBook(userkey, inpath, outpath) + if result == 0: + print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) + return result + +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text=u"Select files for decryption") + 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=u"Key file").grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists(u"adeptkey.der"): + self.keypath.insert(0, u"adeptkey.der") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text=u"Input file").grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text=u"Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text=u"Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title=u"Select Adobe Adept \'.der\' key file", + defaultextension=u".der", + filetypes=[('Adobe Adept DER-encoded files', '.der'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title=u"Select unencrypted ePub file to produce", + defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = u"Specified key file does not exist" + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = u"Specified input file does not exist" + return + if not outpath: + self.status['text'] = u"Output file not specified" + return + if inpath == outpath: + self.status['text'] = u"Must have different input and output files" + return + userkey = open(keypath,'rb').read() + self.status['text'] = u"Decrypting..." + try: + decrypt_status = decryptBook(userkey, inpath, outpath) + except Exception, e: + self.status['text'] = u"Error: {0}".format(e.args[0]) + return + if decrypt_status == 0: + self.status['text'] = u"File successfully decrypted" + else: + self.status['text'] = u"The was an error decrypting the file." + + root = Tkinter.Tk() + root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__)) + root.resizable(True, False) + root.minsize(300, 0) + DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) + root.mainloop() + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptpdf.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py similarity index 88% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptpdf.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py index 20721d1..797db60 100644 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/ineptpdf.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py @@ -1,13 +1,25 @@ -#! /usr/bin/env python -# ineptpdf.pyw, version 7.11 +#! /usr/bin/python +# -*- coding: utf-8 -*- from __future__ import with_statement -# To run this program install Python 2.6 from http://www.python.org/download/ -# and OpenSSL (already installed on Mac OS X and Linux) OR -# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ineptpdf.pyw and double-click on it to run it. +# ineptpdf.pyw, version 7.11 +# Copyright © 2009-2010 by i♥cabbages + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python 2.6 +# from and PyCrypto from +# (make sure to +# install the version for Python 2.6). Save this script file as +# ineptpdf.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this +# program from the command line (pythonw ineptpdf.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. # Revision history: # 1 - Initial release @@ -36,12 +48,17 @@ # 7.9 - Bug fix for some session key errors when len(bookkey) > length required # 7.10 - Various tweaks to fix minor problems. # 7.11 - More tweaks to fix minor problems. +# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code +# 7.13 - Fixed erroneous mentions of ineptepub +# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 8.0 - Work if TkInter is missing """ Decrypts Adobe ADEPT-encrypted PDF files. """ __license__ = 'GPL v3' +__version__ = "8.0" import sys import os @@ -51,10 +68,63 @@ import hashlib from itertools import chain, islice import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + return [u"ineptpdf.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + class ADEPTError(Exception): pass @@ -1520,9 +1590,7 @@ def initialize_standard(self, password, docid, param): def initialize_ebx(self, password, docid, param): self.is_printable = self.is_modifiable = self.is_extractable = True - with open(password, 'rb') as f: - keyder = f.read() - rsa = RSA(keyder) + rsa = RSA(password) length = int_value(param.get('Length', 0)) / 8 rights = str_value(param.get('ADEPT_LICENSE')).decode('base64') rights = zlib.decompress(rights, -15) @@ -1907,14 +1975,14 @@ def do_keyword(self, pos, token): ### My own code, for which there is none else to blame class PDFSerializer(object): - def __init__(self, inf, keypath): + def __init__(self, inf, userkey): global GEN_XREF_STM, gen_xref_stm gen_xref_stm = GEN_XREF_STM > 1 self.version = inf.read(8) inf.seek(0) self.doc = doc = PDFDocument() parser = PDFParser(doc, inf) - doc.initialize(keypath) + doc.initialize(userkey) self.objids = objids = set() for xref in reversed(doc.xrefs): trailer = xref.trailer @@ -2097,142 +2165,150 @@ def serialize_indirect(self, objid, obj): self.write('endobj\n') -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - ltext='Select file for decryption\n' - self.status = Tkinter.Label(self, text=ltext) - self.status.pack(fill=Tkconstants.X, expand=1) - body = Tkinter.Frame(self) - body.pack(fill=Tkconstants.X, expand=1) - sticky = Tkconstants.E + Tkconstants.W - body.grid_columnconfigure(1, weight=2) - Tkinter.Label(body, text='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('adeptkey.der'): - self.keypath.insert(0, 'adeptkey.der') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - - - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT key file', - defaultextension='.der', filetypes=[('DER-encoded files', '.der'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(os.path.realpath(keypath)) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT encrypted PDF file to decrypt', - defaultextension='.pdf', filetypes=[('PDF files', '.pdf'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(os.path.realpath(inpath)) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted PDF file to produce', - defaultextension='.pdf', filetypes=[('PDF files', '.pdf'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(os.path.realpath(outpath)) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - # keyfile doesn't exist - self.status['text'] = 'Specified Adept key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - # patch for non-ascii characters - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Processing ...' - try: - cli_main(argv) - except Exception, a: - self.status['text'] = 'Error: ' + str(a) - return - self.status['text'] = 'File successfully decrypted.\n'+\ - 'Close this window or decrypt another pdf file.' - return -def decryptBook(keypath, inpath, outpath): +def decryptBook(userkey, inpath, outpath): + if RSA is None: + raise ADEPTError(u"PyCrypto or OpenSSL must be installed.") with open(inpath, 'rb') as inf: try: - serializer = PDFSerializer(inf, keypath) + serializer = PDFSerializer(inf, userkey) except: - print "Error serializing pdf. Probably wrong key." - return 1 + print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath)) + return 2 # hope this will fix the 'bad file descriptor' problem with open(outpath, 'wb') as outf: - # help construct to make sure the method runs to the end + # help construct to make sure the method runs to the end try: serializer.dump(outf) - except: - print "error writing pdf." - return 1 + except Exception, e: + print u"error writing pdf: {0}".format(e.args[0]) + return 2 return 0 -def cli_main(argv=sys.argv): +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() progname = os.path.basename(argv[0]) - if RSA is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) + print u"usage: {0} ".format(progname) return 1 keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) + userkey = open(keypath,'rb').read() + result = decryptBook(userkey, inpath, outpath) + if result == 0: + print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) + return result def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class DecryptionDialog(Tkinter.Frame): + def __init__(self, root): + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text=u"Select files for decryption") + 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=u"Key file").grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) + self.keypath.grid(row=0, column=1, sticky=sticky) + if os.path.exists(u"adeptkey.der"): + self.keypath.insert(0, u"adeptkey.der") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) + button.grid(row=0, column=2) + Tkinter.Label(body, text=u"Input file").grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) + self.inpath.grid(row=1, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_inpath) + button.grid(row=1, column=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) + self.outpath.grid(row=2, column=1, sticky=sticky) + button = Tkinter.Button(body, text=u"...", command=self.get_outpath) + button.grid(row=2, column=2) + buttons = Tkinter.Frame(self) + buttons.pack() + botton = Tkinter.Button( + buttons, text=u"Decrypt", width=10, command=self.decrypt) + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( + buttons, text=u"Quit", width=10, command=self.quit) + button.pack(side=Tkconstants.RIGHT) + + def get_keypath(self): + keypath = tkFileDialog.askopenfilename( + parent=None, title=u"Select Adobe Adept \'.der\' key file", + defaultextension=u".der", + filetypes=[('Adobe Adept DER-encoded files', '.der'), + ('All Files', '.*')]) + if keypath: + keypath = os.path.normpath(keypath) + self.keypath.delete(0, Tkconstants.END) + self.keypath.insert(0, keypath) + return + + def get_inpath(self): + inpath = tkFileDialog.askopenfilename( + parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt", + defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) + if inpath: + inpath = os.path.normpath(inpath) + self.inpath.delete(0, Tkconstants.END) + self.inpath.insert(0, inpath) + return + + def get_outpath(self): + outpath = tkFileDialog.asksaveasfilename( + parent=None, title=u"Select unencrypted PDF file to produce", + defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + return + + def decrypt(self): + keypath = self.keypath.get() + inpath = self.inpath.get() + outpath = self.outpath.get() + if not keypath or not os.path.exists(keypath): + self.status['text'] = u"Specified key file does not exist" + return + if not inpath or not os.path.exists(inpath): + self.status['text'] = u"Specified input file does not exist" + return + if not outpath: + self.status['text'] = u"Output file not specified" + return + if inpath == outpath: + self.status['text'] = u"Must have different input and output files" + return + userkey = open(keypath,'rb').read() + self.status['text'] = u"Decrypting..." + try: + decrypt_status = decryptBook(userkey, inpath, outpath) + except Exception, e: + self.status['text'] = u"Error; {0}".format(e.args[0]) + return + if decrypt_status == 0: + self.status['text'] = u"File successfully decrypted" + else: + self.status['text'] = u"The was an error decrypting the file." + + root = Tkinter.Tk() if RSA is None: root.withdraw() @@ -2241,7 +2317,7 @@ def gui_main(): "This script requires OpenSSL or PyCrypto, which must be installed " "separately. Read the top-of-script comment for details.") return 1 - root.title('INEPT PDF Decrypter') + root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__)) root.resizable(True, False) root.minsize(370, 0) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py new file mode 100644 index 0000000..0e426a1 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# ignobleepub.pyw, version 3.6 +# Copyright © 2009-2012 by DiapDealer et al. + +# engine to remove drm from Kindle for Mac and Kindle for PC books +# for personal use for archiving and converting your ebooks + +# PLEASE DO NOT PIRATE EBOOKS! + +# We want all authors and publishers, and eBook stores to live +# long and prosperous lives but at the same time we just want to +# be able to read OUR books on whatever device we want and to keep +# readable for a long, long time + +# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, +# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates +# and many many others +# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump +# from which this script borrows most unashamedly. + + +# Changelog +# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code +# 1.1 - Adds support for additional kindle.info files +# 1.2 - Better error handling for older Mobipocket +# 1.3 - Don't try to decrypt Topaz books +# 1.7 - Add support for Topaz books and Kindle serial numbers. Split code. +# 1.9 - Tidy up after Topaz, minor exception changes +# 2.1 - Topaz fix and filename sanitizing +# 2.2 - Topaz Fix and minor Mac code fix +# 2.3 - More Topaz fixes +# 2.4 - K4PC/Mac key generation fix +# 2.6 - Better handling of non-K4PC/Mac ebooks +# 2.7 - Better trailing bytes handling in mobidedrm +# 2.8 - Moved parsing of kindle.info files to mac & pc util files. +# 3.1 - Updated for new calibre interface. Now __init__ in plugin. +# 3.5 - Now support Kindle for PC/Mac 1.6 +# 3.6 - Even better trailing bytes handling in mobidedrm +# 3.7 - Add support for Amazon Print Replica ebooks. +# 3.8 - Improved Topaz support +# 4.1 - Improved Topaz support and faster decryption with alfcrypto +# 4.2 - Added support for Amazon's KF8 format ebooks +# 4.4 - Linux calls to Wine added, and improved configuration dialog +# 4.5 - Linux works again without Wine. Some Mac key file search changes +# 4.6 - First attempt to handle unicode properly +# 4.7 - Added timing reports, and changed search for Mac key files +# 4.8 - Much better unicode handling, matching the updated inept and ignoble scripts +# - Moved back into plugin, __init__ in plugin now only contains plugin code. +# 4.9 - Missed some invalid characters in cleanup_name +# 5.0 - Extraction of info from Kindle for PC/Mac moved into kindlekey.py +# - tweaked GetDecryptedBook interface to leave passed parameters unchanged +# 5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 5.2 - Fixed error in command line processing of unicode arguments + +__version__ = '5.2' + + +import sys, os, re +import csv +import getopt +import re +import traceback +import time +import htmlentitydefs +import json + +class DrmException(Exception): + pass + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + from calibre_plugins.dedrm import mobidedrm + from calibre_plugins.dedrm import topazextract + from calibre_plugins.dedrm import kgenpids +else: + import mobidedrm + import topazextract + import kgenpids + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +# cleanup unicode filenames +# borrowed from calibre from calibre/src/calibre/__init__.py +# added in removal of control (<32) chars +# and removal of . at start and end +# and with some (heavily edited) code from Paul Durrant's kindlenamer.py +def cleanup_name(name): + # substitute filename unfriendly characters + name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"") + # delete control characters + name = u"".join(char for char in name if ord(char)>=32) + # white space to single space, delete leading and trailing while space + name = re.sub(ur"\s", u" ", name).strip() + # remove leading dots + while len(name)>0 and name[0] == u".": + name = name[1:] + # remove trailing dots (Windows doesn't like them) + if name.endswith(u'.'): + name = name[:-1] + return name + +# must be passed unicode +def unescape(text): + def fixup(m): + text = m.group(0) + if text[:2] == u"&#": + # character reference + try: + if text[:3] == u"&#x": + return unichr(int(text[3:-1], 16)) + else: + return unichr(int(text[2:-1])) + except ValueError: + pass + else: + # named entity + try: + text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) + except KeyError: + pass + return text # leave as is + return re.sub(u"&#?\w+;", fixup, text) + +def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()): + # handle the obvious cases at the beginning + if not os.path.isfile(infile): + raise DRMException (u"Input file does not exist.") + + mobi = True + magic3 = open(infile,'rb').read(3) + if magic3 == 'TPZ': + mobi = False + + if mobi: + mb = mobidedrm.MobiBook(infile) + else: + mb = topazextract.TopazBook(infile) + + bookname = unescape(mb.getBookTitle()) + print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()) + + # copy list of pids + totalpids = list(pids) + # extend PID list with book-specific PIDs + md1, md2 = mb.getPIDMetaInfo() + totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases)) + print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids)) + + try: + mb.processBook(totalpids) + except: + mb.cleanup + raise + + print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime) + return mb + + +# kDatabaseFiles is a list of files created by kindlekey +def decryptBook(infile, outdir, kDatabaseFiles, serials, pids): + starttime = time.time() + kDatabases = [] + for dbfile in kDatabaseFiles: + kindleDatabase = {} + try: + with open(dbfile, 'r') as keyfilein: + kindleDatabase = json.loads(keyfilein.read()) + kDatabases.append([dbfile,kindleDatabase]) + except Exception, e: + print u"Error getting database from file {0:s}: {1:s}".format(dbfile,e) + traceback.print_exc() + + + + try: + book = GetDecryptedBook(infile, kDatabases, serials, pids, starttime) + except Exception, e: + print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime) + traceback.print_exc() + return 1 + + # if we're saving to the same folder as the original, use file name_ + # if to a different folder, use book name + if os.path.normcase(os.path.normpath(outdir)) == os.path.normcase(os.path.normpath(os.path.dirname(infile))): + outfilename = os.path.splitext(os.path.basename(infile))[0] + else: + outfilename = cleanup_name(book.getBookTitle()) + + # avoid excessively long file names + if len(outfilename)>150: + outfilename = outfilename[:150] + + outfilename = outfilename+u"_nodrm" + outfile = os.path.join(outdir, outfilename + book.getBookExtension()) + + book.getFile(outfile) + print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename) + + if book.getBookType()==u"Topaz": + zipname = os.path.join(outdir, outfilename + u"_SVG.zip") + book.getSVGZip(zipname) + print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename) + + # remove internal temporary directory of Topaz pieces + book.cleanup() + return 0 + + +def usage(progname): + print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks" + print u"Usage:" + print u" {0} [-k ] [-p ] [-s ] ".format(progname) + +# +# Main +# +def cli_main(): + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__) + + try: + opts, args = getopt.getopt(argv[1:], "k:p:s:") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + if len(args)<2: + usage(progname) + sys.exit(2) + + infile = args[0] + outdir = args[1] + kDatabaseFiles = [] + serials = [] + pids = [] + + for o, a in opts: + if o == "-k": + if a == None : + raise DrmException("Invalid parameter for -k") + kDatabaseFiles.append(a) + if o == "-p": + if a == None : + raise DrmException("Invalid parameter for -p") + pids = a.split(',') + if o == "-s": + if a == None : + raise DrmException("Invalid parameter for -s") + serials = a.split(',') + + # try with built in Kindle Info files if not on Linux + k4 = not sys.platform.startswith('linux') + + return decryptBook(infile, outdir, kDatabaseFiles, serials, pids) + + +if __name__ == '__main__': + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kgenpids.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py similarity index 67% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kgenpids.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py index b0fbaa4..dd88797 100644 --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kgenpids.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- from __future__ import with_statement import sys @@ -7,6 +8,7 @@ import zlib import re from struct import pack, unpack, unpack_from +import traceback class DrmException(Exception): pass @@ -15,28 +17,10 @@ class DrmException(Exception): global charMap3 global charMap4 -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False -if inCalibre: - if sys.platform.startswith('win'): - from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - if sys.platform.startswith('darwin'): - from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString -else: - if sys.platform.startswith('win'): - from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - if sys.platform.startswith('darwin'): - from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString - - -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" +charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' +charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' # crypto digestroutines import hashlib @@ -54,7 +38,7 @@ def SHA1(message): # Encode the bytes in data with the characters in map def encode(data, map): - result = "" + result = '' for char in data: value = ord(char) Q = (value ^ 0x80) // len(map) @@ -69,14 +53,14 @@ def encodeHash(data,map): # Decode the string in data with the characters in map. Returns the decoded bytes def decode(data,map): - result = "" + result = '' for i in range (0,len(data)-1,2): high = map.find(data[i]) low = map.find(data[i+1]) if (high == -1) or (low == -1) : break value = (((high * len(map)) ^ 0x80) & 0xFF) + low - result += pack("B",value) + result += pack('B',value) return result # @@ -98,7 +82,7 @@ def getSixBitsFromBitField(bitField,offset): # 8 bits to six bits encoding from hash to generate PID string def encodePID(hash): global charMap3 - PID = "" + PID = '' for position in range (0,8): PID += charMap3[getSixBitsFromBitField(hash,position)] return PID @@ -129,7 +113,7 @@ def generatePidSeed(table,dsn) : def generateDevicePID(table,dsn,nbRoll): global charMap4 seed = generatePidSeed(table,dsn) - pidAscii = "" + 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): @@ -176,53 +160,56 @@ def pidFromSerial(s, l): # Parse the EXTH header records and use the Kindle serial number to calculate the book pid. -def getKindlePid(pidlst, rec209, token, serialnum): +def getKindlePids(rec209, token, serialnum): + pids=[] + + if isinstance(serialnum,unicode): + serialnum = serialnum.encode('ascii') + # Compute book PID pidHash = SHA1(serialnum+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) # compute fixed pid for old pre 2.5 firmware update pid as well - bookPID = pidFromSerial(serialnum, 7) + "*" - bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + kindlePID = pidFromSerial(serialnum, 7) + "*" + kindlePID = checksumPid(kindlePID) + pids.append(kindlePID) - return pidlst + return pids # parse the Kindleinfo file to calculate the book pid. -keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] +keynames = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber'] -def getK4Pids(pidlst, rec209, token, kInfoFile): +def getK4Pids(rec209, token, kindleDatabase): global charMap1 - kindleDatabase = None - try: - kindleDatabase = getDBfromFile(kInfoFile) - except Exception, message: - print(message) - kindleDatabase = None - pass - - if kindleDatabase == None : - return pidlst + pids = [] try: # Get the Mazama Random number - MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] + MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex').encode('ascii') # Get the kindle account token - kindleAccountToken = kindleDatabase["kindle.account.tokens"] + kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex').encode('ascii') + + # Get the IDString used to decode the Kindle Info file + IDString = (kindleDatabase[1])['IDString'].decode('hex').encode('ascii') + + # Get the UserName stored when the Kindle Info file was decoded + UserName = (kindleDatabase[1])['UserName'].decode('hex').encode('ascii') + except KeyError: - print "Keys not found in " + kInfoFile - return pidlst + print u"Keys not found in the database {0}.".format(kindleDatabase[0]) + return pids # Get the ID string used - encodedIDString = encodeHash(GetIDString(),charMap1) + encodedIDString = encodeHash(IDString,charMap1) # Get the current user name - encodedUsername = encodeHash(GetUserName(),charMap1) + encodedUsername = encodeHash(UserName,charMap1) # concat, hash and encode to calculate the DSN DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1) @@ -231,7 +218,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): table = generatePidEncryptionTable() devicePID = generateDevicePID(table,DSN,4) devicePID = checksumPid(devicePID) - pidlst.append(devicePID) + pids.append(devicePID) # Compute book PIDs @@ -239,36 +226,42 @@ def getK4Pids(pidlst, rec209, token, kInfoFile): pidHash = SHA1(DSN+kindleAccountToken+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) # variant 1 pidHash = SHA1(kindleAccountToken+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) # variant 2 pidHash = SHA1(DSN+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) - pidlst.append(bookPID) + pids.append(bookPID) - return pidlst + return pids -def getPidList(md1, md2, k4 = True, serials=[], kInfoFiles=[]): +def getPidList(md1, md2, serials=[], kDatabases=[]): pidlst = [] - if kInfoFiles is None: - kInfoFiles = [] - if k4: - kInfoFiles.extend(getKindleInfoFiles()) - for infoFile in kInfoFiles: + + if kDatabases is None: + kDatabases = [] + if serials is None: + serials = [] + + for kDatabase in kDatabases: try: - pidlst = getK4Pids(pidlst, md1, md2, infoFile) - except Exception, message: - print("Error getting PIDs from " + infoFile + ": " + message) + pidlst.extend(getK4Pids(md1, md2, kDatabase)) + except Exception, e: + print u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]) + traceback.print_exc() + for serialnum in serials: try: - pidlst = getKindlePid(pidlst, md1, md2, serialnum) - except Exception, message: - print("Error getting PIDs from " + serialnum + ": " + message) + pidlst.extend(getKindlePids(md1, md2, serialnum)) + except Exception, e: + print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]) + traceback.print_exc() + return pidlst diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlekey.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlekey.py new file mode 100644 index 0000000..f58e973 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlekey.py @@ -0,0 +1,1918 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# kindlekey.py +# Copyright © 2010-2013 by some_updates and Apprentice Alf +# +# Currently requires alfcrypto.py which requires the alfcrypto library + +# Revision history: +# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc. +# 1.1 - Added Tkinter to match adobekey.py +# 1.2 - Fixed testing of successful retrieval on Mac +# 1.3 - Added getkey interface for Windows DeDRM application +# Simplified some of the Kindle for Mac code. +# 1.4 - Remove dependency on alfcrypto +# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 1.6 - Fixed a problem getting the disk serial numbers +# 1.7 - Work if TkInter is missing +# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names + + +""" +Retrieve Kindle for PC/Mac user key. +""" + +__license__ = 'GPL v3' +__version__ = '1.8' + +import sys, os, re +from struct import pack, unpack, unpack_from +import json +import getopt + +# Routines common to Mac and PC + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"kindlekey.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +class DrmException(Exception): + pass + +# crypto digestroutines +import hashlib + +def MD5(message): + ctx = hashlib.md5() + ctx.update(message) + return ctx.digest() + +def SHA1(message): + ctx = hashlib.sha1() + ctx.update(message) + return ctx.digest() + +def SHA256(message): + ctx = hashlib.sha256() + ctx.update(message) + return ctx.digest() + +# For K4M/PC 1.6.X and later +# generate table of prime number less than or equal to int n +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # no more data expected from caller + finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) + if len(finalBytes) > 0: + ctBlock = self.encryptBlock(finalBytes) + self.encryptBlockCount += 1 + cipherText += ctBlock + self.resetEncrypt() + return cipherText + + def decrypt(self, cipherText, more = None): + """ Decrypt a string and return a string """ + self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt + + numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) + if more == None: # no more calls to decrypt, should have all the data + if numExtraBytes != 0: + raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' + + # hold back some bytes in case last decrypt has zero len + if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : + numBlocks -= 1 + numExtraBytes = self.blockSize + + plainText = '' + for i in range(numBlocks): + bStart = i*self.blockSize + ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) + self.decryptBlockCount += 1 + plainText += ptBlock + + if numExtraBytes > 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # last decrypt remove padding + plainText = self.padding.removePad(plainText, self.blockSize) + self.resetDecrypt() + return plainText + + + class Pad: + def __init__(self): + pass # eventually could put in calculation of min and max size extension + + class padWithPadLen(Pad): + """ Pad a binary string with the length of the padding """ + + def addPad(self, extraBytes, blockSize): + """ Add padding to a binary string to make it an even multiple + of the block size """ + blocks, numExtraBytes = divmod(len(extraBytes), blockSize) + padLength = blockSize - numExtraBytes + return extraBytes + padLength*chr(padLength) + + def removePad(self, paddedBinaryString, blockSize): + """ Remove padding from a binary string """ + if not(0 6 and i%Nk == 4 : + temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) + w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) + return w + + Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! + 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, + 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) + + #------------------------------------- + def AddRoundKey(algInstance, keyBlock): + """ XOR the algorithm state with a block of key material """ + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] ^= keyBlock[column][row] + #------------------------------------- + + def SubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = Sbox[algInstance.state[column][row]] + + def InvSubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] + + Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, + 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, + 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, + 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, + 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, + 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, + 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, + 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, + 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, + 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, + 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, + 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, + 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, + 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, + 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, + 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, + 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) + + InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) + + #------------------------------------- + """ For each block size (Nb), the ShiftRow operation shifts row i + by the amount Ci. Note that row 0 is not shifted. + Nb C1 C2 C3 + ------------------- """ + shiftOffset = { 4 : ( 0, 1, 2, 3), + 5 : ( 0, 1, 2, 3), + 6 : ( 0, 1, 2, 3), + 7 : ( 0, 1, 2, 4), + 8 : ( 0, 1, 3, 4) } + def ShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] + def InvShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] + #------------------------------------- + def MixColumns(a): + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) + Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + + def InvMixColumns(a): + """ Mix the four bytes of every column in a linear way + This is the opposite operation of Mixcolumn """ + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) + Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) + Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) + Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + + #------------------------------------- + def mul(a, b): + """ Multiply two elements of GF(2^m) + needed for MixColumn and InvMixColumn """ + if (a !=0 and b!=0): + return Alogtable[(Logtable[a] + Logtable[b])%255] + else: + return 0 + + Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, + 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, + 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, + 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, + 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, + 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, + 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, + 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, + 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, + 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, + 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, + 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) + + Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, + 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, + 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, + 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, + 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, + 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, + 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, + 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, + 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, + 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) + + + + + """ + AES Encryption Algorithm + The AES algorithm is just Rijndael algorithm restricted to the default + blockSize of 128 bits. + """ + + class AES(Rijndael): + """ The AES algorithm is the Rijndael block cipher restricted to block + sizes of 128 bits and key sizes of 128, 192 or 256 bits + """ + def __init__(self, key = None, padding = padWithPadLen(), keySize=16): + """ Initialize AES, keySize is in bytes """ + if not (keySize == 16 or keySize == 24 or keySize == 32) : + raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' + + Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) + + self.name = 'AES' + + + """ + CBC mode of encryption for block ciphers. + This algorithm mode wraps any BlockCipher to make a + Cipher Block Chaining mode. + """ + from random import Random # should change to crypto.random!!! + + + class CBC(BlockCipher): + """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode + algorithms. The initialization (IV) is automatic if set to None. Padding + is also automatic based on the Pad class used to initialize the algorithm + """ + def __init__(self, blockCipherInstance, padding = padWithPadLen()): + """ CBC algorithms are created by initializing with a BlockCipher instance """ + self.baseCipher = blockCipherInstance + self.name = self.baseCipher.name + '_CBC' + self.blockSize = self.baseCipher.blockSize + self.keySize = self.baseCipher.keySize + self.padding = padding + self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! + self.r = Random() # for IV generation, currently uses + # mediocre standard distro version <---------------- + import time + newSeed = time.ctime()+str(self.r) # seed with instance location + self.r.seed(newSeed) # to make unique + self.reset() + + def setKey(self, key): + self.baseCipher.setKey(key) + + # Overload to reset both CBC state and the wrapped baseCipher + def resetEncrypt(self): + BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) + self.baseCipher.resetEncrypt() # reset base cipher encrypt state + + def resetDecrypt(self): + BlockCipher.resetDecrypt(self) # reset CBC state (super class) + self.baseCipher.resetDecrypt() # reset base cipher decrypt state + + def encrypt(self, plainText, iv=None, more=None): + """ CBC encryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.encryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to encrypt' + + return BlockCipher.encrypt(self,plainText, more=more) + + def decrypt(self, cipherText, iv=None, more=None): + """ CBC decryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.decryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to decrypt' + + return BlockCipher.decrypt(self, cipherText, more=more) + + def encryptBlock(self, plainTextBlock): + """ CBC block encryption, IV is set with 'encrypt' """ + auto_IV = '' + if self.encryptBlockCount == 0: + if self.iv == None: + # generate IV and use + self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) + self.prior_encr_CT_block = self.iv + auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic + else: # application provided IV + assert(len(self.iv) == self.blockSize ),'IV must be same length as block' + self.prior_encr_CT_block = self.iv + """ encrypt the prior CT XORed with the PT """ + ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) + self.prior_encr_CT_block = ct + return auto_IV+ct + + def decryptBlock(self, encryptedBlock): + """ Decrypt a single block """ + + if self.decryptBlockCount == 0: # first call, process IV + if self.iv == None: # auto decrypt IV? + self.prior_CT_block = encryptedBlock + return '' + else: + assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" + self.prior_CT_block = self.iv + + dct = self.baseCipher.decryptBlock(encryptedBlock) + """ XOR the prior decrypted CT with the prior CT """ + dct_XOR_priorCT = xor( self.prior_CT_block, dct ) + + self.prior_CT_block = encryptedBlock + + return dct_XOR_priorCT + + + """ + AES_CBC Encryption Algorithm + """ + + class aescbc_AES_CBC(CBC): + """ AES encryption in CBC feedback mode """ + def __init__(self, key=None, padding=padWithPadLen(), keySize=16): + CBC.__init__( self, AES(key, noPadding(), keySize), padding) + self.name = 'AES_CBC' + + class AES_CBC(object): + def __init__(self): + self._key = None + self._iv = None + self.aes = None + + def set_decrypt_key(self, userkey, iv): + self._key = userkey + self._iv = iv + self.aes = aescbc_AES_CBC(userkey, noPadding(), len(userkey)) + + def decrypt(self, data): + iv = self._iv + cleartext = self.aes.decrypt(iv + data) + return cleartext + + import hmac + + class KeyIVGen(object): + # this only exists in openssl so we will use pure python implementation instead + # 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]) + def pbkdf2(self, passwd, salt, iter, keylen): + + def xorstr( a, b ): + if len(a) != len(b): + raise Exception("xorstr(): lengths differ") + return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) + + def prf( h, data ): + hm = h.copy() + hm.update( data ) + return hm.digest() + + def pbkdf2_F( h, salt, itercount, blocknum ): + U = prf( h, salt + pack('>i',blocknum ) ) + T = U + for i in range(2, itercount+1): + U = prf( h, U ) + T = xorstr( T, U ) + return T + + sha = hashlib.sha1 + digest_size = sha().digest_size + # l - number of output blocks to produce + l = keylen / digest_size + if keylen % digest_size != 0: + l += 1 + h = hmac.new( passwd, None, sha ) + T = "" + for i in range(1, l+1): + T += pbkdf2_F( h, salt, iter, i ) + return T[0: keylen] + + def UnprotectHeaderData(encryptedData): + passwdData = 'header_key_data' + salt = 'HEADER.2011' + iter = 0x80 + keylen = 0x100 + key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen) + key = key_iv[0:32] + iv = key_iv[32:48] + aes=AES_CBC() + aes.set_decrypt_key(key, iv) + cleartext = aes.decrypt(encryptedData) + return cleartext + + # Various character maps used to decrypt kindle info values. + # Probably supposed to act as obfuscation + charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" + charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" + # New maps in K4PC 1.9.0 + testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" + testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG" + testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD" + + # interface with Windows OS Routines + 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 GetIDString(): + vsn = GetVolumeSerialNumber() + #print('Using Volume Serial Number for ID: '+vsn) + return vsn + + def getLastError(): + GetLastError = kernel32.GetLastError + GetLastError.argtypes = None + GetLastError.restype = c_uint + def getLastError(): + return GetLastError() + return getLastError + getLastError = getLastError() + + def GetUserName(): + GetUserNameW = advapi32.GetUserNameW + GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] + GetUserNameW.restype = c_uint + def GetUserName(): + buffer = create_unicode_buffer(2) + size = c_uint(len(buffer)) + while not GetUserNameW(buffer, byref(size)): + errcd = getLastError() + if errcd == 234: + # bad wine implementation up through wine 1.3.21 + return "AlternateUserName" + 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, flags): + 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, flags, byref(outdata)): + # raise DrmException("Failed to Unprotect Data") + return 'failed' + return string_at(outdata.pbData, outdata.cbData) + return CryptUnprotectData + CryptUnprotectData = CryptUnprotectData() + + + # Locate all of the kindle-info style files and return as list + def getKindleInfoFiles(): + kInfoFiles = [] + # some 64 bit machines do not have the proper registry key for some reason + # or the pythonn interface to the 32 vs 64 bit registry is broken + path = "" + if 'LOCALAPPDATA' in os.environ.keys(): + path = os.environ['LOCALAPPDATA'] + else: + # User Shell Folders show take precedent over Shell Folders if present + try: + regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\") + path = winreg.QueryValueEx(regkey, 'Local AppData')[0] + if not os.path.isdir(path): + path = "" + try: + regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") + path = winreg.QueryValueEx(regkey, 'Local AppData')[0] + if not os.path.isdir(path): + path = "" + except RegError: + pass + except RegError: + pass + + found = False + if path == "": + print ('Could not find the folder in which to look for kinfoFiles.') + else: + print('searching for kinfoFiles in ' + path) + + # look for (K4PC 1.9.0 and later) .kinf2011 file + kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file + kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.6-1.8 kinf file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for (K4PC 1.5.0 and later) rainier.2.1.1.kinf file + kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.5 kinf file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for original (earlier than K4PC 1.5.0) kindle-info files + kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC kindle.info file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + if not found: + print('No K4PC kindle.info/kinf/kinf2011 files have been found.') + return kInfoFiles + + + # determine type of kindle info provided and return a + # database of keynames and values + def getDBfromFile(kInfoFile): + names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF'] + DB = {} + with open(kInfoFile, 'rb') as infoReader: + hdr = infoReader.read(1) + data = infoReader.read() + + if data.find('{') != -1 : + # older style kindle-info file + items = data.split('{') + for item in items: + if item != '': + keyhash, rawdata = item.split(':') + keyname = "unknown" + for name in names: + if encodeHash(name,charMap2) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + encryptedValue = decode(rawdata,charMap2) + DB[keyname] = CryptUnprotectData(encryptedValue, "", 0) + elif hdr == '/': + # else rainier-2-1-1 .kinf file + # the .kinf file uses "/" to separate it into records + # so remove the trailing "/" to make it easy to use split + data = data[:-1] + items = data.split('/') + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the raw keyhash string is used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = "unknown" + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents)-largest prime number <= int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using Map5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + else: + # else newest .kinf2011 style .kinf file + # the .kinf file uses "/" to separate it into records + # so remove the trailing "/" to make it easy to use split + # need to put back the first char read because it it part + # of the added entropy blob + data = hdr + data[:-1] + items = data.split('/') + + # starts with and encoded and encrypted header blob + headerblob = items.pop(0) + encryptedValue = decode(headerblob, testMap1) + cleartext = UnprotectHeaderData(encryptedValue) + # now extract the pieces that form the added entropy + pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) + for m in re.finditer(pattern, cleartext): + added_entropy = m.group(2) + m.group(4) + + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the sha1 of raw keyhash string is used to create entropy along + # with the added entropy provided above from the headerblob + entropy = SHA1(keyhash) + added_entropy + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + # key names now use the new testMap8 encoding + keyname = "unknown" + for name in names: + if encodeHash(name,testMap8) == keyhash: + keyname = name + break + + # the testMap8 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using testMap8 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the testMap8 encoded contents seems to be: + # len(contents)-largest prime number <= int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by testMap8 + # by moving noffset chars from the start of the + # string to the end of the string + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using new testMap8 to get the original CryptProtect Data + encryptedValue = decode(encdata,testMap8) + cleartext = CryptUnprotectData(encryptedValue, entropy, 1) + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1")) + # store values used in decryption + DB['IDString'] = GetIDString() + DB['UserName'] = GetUserName() + else: + DB = {} + return DB +elif isosx: + import copy + import subprocess + + # interface to needed routines in openssl's libcrypto + def _load_crypto_libcrypto(): + from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, addressof, string_at, cast + from ctypes.util import find_library + + libcrypto = find_library('crypto') + if libcrypto is None: + raise DrmException(u"libcrypto not found") + libcrypto = CDLL(libcrypto) + + # From OpenSSL's crypto aes header + # + # AES_ENCRYPT 1 + # AES_DECRYPT 0 + # AES_MAXNR 14 (in bytes) + # AES_BLOCK_SIZE 16 (in bytes) + # + # struct aes_key_st { + # unsigned long rd_key[4 *(AES_MAXNR + 1)]; + # int rounds; + # }; + # typedef struct aes_key_st AES_KEY; + # + # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); + # + # note: the ivec string, and output buffer are both mutable + # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc); + + 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]) + + # From OpenSSL's Crypto evp/p5_crpt2.c + # + # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, + # const unsigned char *salt, int saltlen, int iter, + # int keylen, unsigned char *out); + + PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', + [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) + + class LibCrypto(object): + def __init__(self): + self._blocksize = 0 + self._keyctx = None + self._iv = 0 + + def set_decrypt_key(self, userkey, iv): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise DrmException(u"AES improper key used") + return + keyctx = self._keyctx = AES_KEY() + self._iv = iv + self._userkey = userkey + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) + if rv < 0: + raise DrmException(u"Failed to initialize AES key") + + def decrypt(self, data): + out = create_string_buffer(len(data)) + mutable_iv = create_string_buffer(self._iv, len(self._iv)) + keyctx = self._keyctx + rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) + if rv == 0: + raise DrmException(u"AES decryption failed") + return out.raw + + def keyivgen(self, passwd, salt, iter, keylen): + saltlen = len(salt) + passlen = len(passwd) + out = create_string_buffer(keylen) + rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) + return out.raw + return LibCrypto + + def _load_crypto(): + LibCrypto = None + try: + LibCrypto = _load_crypto_libcrypto() + except (ImportError, DrmException): + pass + return LibCrypto + + LibCrypto = _load_crypto() + + # Various character maps used to decrypt books. Probably supposed to act as obfuscation + charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' + charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM' + + # For kinf approach of K4Mac 1.6.X or later + # On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE' + # For Mac they seem to re-use charMap2 here + charMap5 = charMap2 + + # new in K4M 1.9.X + testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD' + + # uses a sub process to get the Hard Drive Serial Number using ioreg + # returns serial numbers of all internal hard drive drives + def GetVolumesSerialNumbers(): + sernums = [] + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + sernums.append(sernum.strip()) + cmdline = '/usr/sbin/ioreg -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('\"Serial Number\" = \"') + if pp >= 0: + sernum = resline[pp+19:-1] + sernums.append(sernum.strip()) + return sernums + + def GetUserHomeAppSupKindleDirParitionName(): + home = os.getenv('HOME') + dpath = home + '/Library' + cmdline = '/sbin/mount' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + disk = '' + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + if resline.startswith('/dev'): + (devpart, mpath) = resline.split(' on ') + dpart = devpart[5:] + pp = mpath.find('(') + if pp >= 0: + mpath = mpath[:pp-1] + if dpath.startswith(mpath): + disk = dpart + return disk + + # uses a sub process to get the UUID of the specified disk partition using ioreg + def GetDiskPartitionUUIDs(diskpart): + uuids = [] + uuidnum = os.getenv('MYUUIDNUMBER') + if uuidnum != None: + uuids.append(strip(uuidnum)) + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + bsdname = None + uuidnum = None + foundIt = False + nest = 0 + uuidnest = -1 + partnest = -2 + for j in xrange(cnt): + resline = reslst[j] + if resline.find('{') >= 0: + nest += 1 + if resline.find('}') >= 0: + nest -= 1 + pp = resline.find('\"UUID\" = \"') + if pp >= 0: + uuidnum = resline[pp+10:-1] + uuidnum = uuidnum.strip() + uuidnest = nest + if partnest == uuidnest and uuidnest > 0: + foundIt = True + break + bb = resline.find('\"BSD Name\" = \"') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == diskpart): + partnest = nest + else : + partnest = -2 + if partnest == uuidnest and partnest > 0: + foundIt = True + break + if nest == 0: + partnest = -2 + uuidnest = -1 + uuidnum = None + bsdname = None + if foundIt: + uuids.append(uuidnum) + return uuids + + def GetMACAddressesMunged(): + macnums = [] + macnum = os.getenv('MYMACNUM') + if macnum != None: + macnums.append(macnum) + cmdline = '/sbin/ifconfig en0' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + macnum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('ether ') + if pp >= 0: + macnum = resline[pp+6:-1] + macnum = macnum.strip() + # print 'original mac', macnum + # now munge it up the way Kindle app does + # by xoring it with 0xa5 and swapping elements 3 and 4 + maclst = macnum.split(':') + n = len(maclst) + if n != 6: + fountIt = False + break + for i in range(6): + maclst[i] = int('0x' + maclst[i], 0) + mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + mlst[5] = maclst[5] ^ 0xa5 + mlst[4] = maclst[3] ^ 0xa5 + mlst[3] = maclst[4] ^ 0xa5 + mlst[2] = maclst[2] ^ 0xa5 + mlst[1] = maclst[1] ^ 0xa5 + mlst[0] = maclst[0] ^ 0xa5 + macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5]) + foundIt = True + break + if foundIt: + macnums.append(macnum) + return macnums + + + # uses unix env to get username instead of using sysctlbyname + def GetUserName(): + username = os.getenv('USER') + return username + + def GetIDStrings(): + # Return all possible ID Strings + strings = [] + strings.extend(GetMACAddressesMunged()) + strings.extend(GetVolumesSerialNumbers()) + diskpart = GetUserHomeAppSupKindleDirParitionName() + strings.extend(GetDiskPartitionUUIDs(diskpart)) + strings.append('9999999999') + #print strings + return strings + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used by Kindle for Mac versions < 1.6.0 + class CryptUnprotectData(object): + def __init__(self, IDString): + sp = IDString + '!@#' + GetUserName() + passwdData = encode(SHA256(sp),charMap1) + salt = '16743' + self.crp = LibCrypto() + iter = 0x3e8 + keylen = 0x80 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext,charMap1) + return cleartext + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used for Kindle for Mac Versions >= 1.6.0 + class CryptUnprotectDataV2(object): + def __init__(self, IDString): + sp = GetUserName() + ':&%:' + IDString + passwdData = encode(SHA256(sp),charMap5) + # salt generation as per the code + salt = 0x0512981d * 2 * 1 * 1 + salt = str(salt) + GetUserName() + salt = encode(salt,charMap5) + self.crp = LibCrypto() + iter = 0x800 + keylen = 0x400 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext, charMap5) + return cleartext + + + # unprotect the new header blob in .kinf2011 + # used in Kindle for Mac Version >= 1.9.0 + def UnprotectHeaderData(encryptedData): + passwdData = 'header_key_data' + salt = 'HEADER.2011' + iter = 0x80 + keylen = 0x100 + crp = LibCrypto() + key_iv = crp.keyivgen(passwdData, salt, iter, keylen) + key = key_iv[0:32] + iv = key_iv[32:48] + crp.set_decrypt_key(key,iv) + cleartext = crp.decrypt(encryptedData) + return cleartext + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used for Kindle for Mac Versions >= 1.9.0 + class CryptUnprotectDataV3(object): + def __init__(self, entropy, IDString): + sp = GetUserName() + '+@#$%+' + IDString + passwdData = encode(SHA256(sp),charMap2) + salt = entropy + self.crp = LibCrypto() + iter = 0x800 + keylen = 0x400 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext, charMap2) + return cleartext + + + # Locate the .kindle-info files + def getKindleInfoFiles(): + # file searches can take a long time on some systems, so just look in known specific places. + kInfoFiles=[] + found = False + home = os.getenv('HOME') + # check for .kinf2011 file in new location (App Store Kindle for Mac) + testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kinf2011 file: ' + testpath) + found = True + # check for .kinf2011 files from 1.10 + testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kinf2011 file: ' + testpath) + found = True + # check for .rainier-2.1.1-kinf files from 1.6 + testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac rainier file: ' + testpath) + found = True + # check for .kindle-info files from 1.4 + testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + # check for .kindle-info file from 1.2.2 + testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + # check for .kindle-info file from 1.0 beta 1 (27214) + testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + if not found: + print('No k4Mac kindle-info/rainier/kinf2011 files have been found.') + return kInfoFiles + + # determine type of kindle info provided and return a + # database of keynames and values + def getDBfromFile(kInfoFile): + names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF'] + with open(kInfoFile, 'rb') as infoReader: + filehdr = infoReader.read(1) + filedata = infoReader.read() + + IDStrings = GetIDStrings() + for IDString in IDStrings: + DB = {} + #print "trying IDString:",IDString + try: + hdr = filehdr + data = filedata + if data.find('[') != -1 : + # older style kindle-info file + cud = CryptUnprotectData(IDString) + items = data.split('[') + for item in items: + if item != '': + keyhash, rawdata = item.split(':') + keyname = 'unknown' + for name in names: + if encodeHash(name,charMap2) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + encryptedValue = decode(rawdata,charMap2) + cleartext = cud.decrypt(encryptedValue) + if len(cleartext) > 0: + DB[keyname] = cleartext + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + elif hdr == '/': + # else newer style .kinf file used by K4Mac >= 1.6.0 + # the .kinf file uses '/' to separate it into records + # so remove the trailing '/' to make it easy to use split + data = data[:-1] + items = data.split('/') + cud = CryptUnprotectDataV2(IDString) + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + keyname = 'unknown' + + # the raw keyhash string is also used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + # 'entropy' not used for K4Mac only K4PC + # entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = 'unknown' + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split 'about' 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = ''.join(edlst) + contlen = len(encdata) + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using charMap5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + cleartext = cud.decrypt(encryptedValue) + if len(cleartext) > 0: + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + else: + # the latest .kinf2011 version for K4M 1.9.1 + # put back the hdr char, it is needed + data = hdr + data + data = data[:-1] + items = data.split('/') + + # the headerblob is the encrypted information needed to build the entropy string + headerblob = items.pop(0) + encryptedValue = decode(headerblob, charMap1) + cleartext = UnprotectHeaderData(encryptedValue) + + # now extract the pieces in the same way + # this version is different from K4PC it scales the build number by multipying by 735 + pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) + for m in re.finditer(pattern, cleartext): + entropy = str(int(m.group(2)) * 0x2df) + m.group(4) + + cud = CryptUnprotectDataV3(entropy,IDString) + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + keyname = 'unknown' + + # unlike K4PC the keyhash is not used in generating entropy + # entropy = SHA1(keyhash) + added_entropy + # entropy = added_entropy + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = 'unknown' + for name in names: + if encodeHash(name,testMap8) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + + # the testMap8 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using testMap8 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the testMap8 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split 'about' 2/3rds of the way through) + + # move first offsets chars to end to align for decode by testMap8 + encdata = ''.join(edlst) + contlen = len(encdata) + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using testMap8 to get the CryptProtect Data + encryptedValue = decode(encdata,testMap8) + cleartext = cud.decrypt(encryptedValue) + # print keyname + # print cleartext + if len(cleartext) > 0: + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + except: + pass + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + # store values used in decryption + print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName()) + DB['IDString'] = IDString + DB['UserName'] = GetUserName() + else: + print u"Couldn't decrypt file." + DB = {} + return DB +else: + def getDBfromFile(kInfoFile): + raise DrmException(u"This script only runs under Windows or Mac OS X.") + return {} + +def kindlekeys(files = []): + keys = [] + if files == []: + files = getKindleInfoFiles() + for file in files: + key = getDBfromFile(file) + if key: + # convert all values to hex, just in case. + for keyname in key: + key[keyname]=key[keyname].encode('hex') + keys.append(key) + return keys + +# interface for Python DeDRM +# returns single key or multiple keys, depending on path or file passed in +def getkey(outpath, files=[]): + keys = kindlekeys(files) + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(keys[0])) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(key)) + print u"Saved a key to {0}".format(outfile) + return True + return False + +def usage(progname): + print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys." + print u"Keys are saved to the current directory, or a specified output directory." + print u"If a file name is passed instead of a directory, only the first key is saved, in that file." + print u"Usage:" + print u" {0:s} [-h] [-k ] []".format(progname) + + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"{0} v{1}\nCopyright © 2010-2013 some_updates and Apprentice Alf".format(progname,__version__) + + try: + opts, args = getopt.getopt(argv[1:], "hk:") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + + files = [] + for o, a in opts: + if o == "-h": + usage(progname) + sys.exit(0) + if o == "-k": + files = [a] + + if len(args) > 1: + usage(progname) + sys.exit(2) + + if len(args) == 1: + # save to the specified file or directory + outpath = args[0] + if not os.path.isabs(outpath): + outpath = os.path.abspath(outpath) + else: + # save to the same directory as the script + outpath = os.path.dirname(argv[0]) + + # make sure the outpath is the + outpath = os.path.realpath(os.path.normpath(outpath)) + + if not getkey(outpath, files): + print u"Could not retrieve Kindle for Mac/PC key." + return 0 + + +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class ExceptionDialog(Tkinter.Frame): + def __init__(self, root, text): + Tkinter.Frame.__init__(self, root, border=5) + label = Tkinter.Label(self, text=u"Unexpected error:", + anchor=Tkconstants.W, justify=Tkconstants.LEFT) + label.pack(fill=Tkconstants.X, expand=0) + self.text = Tkinter.Text(self) + self.text.pack(fill=Tkconstants.BOTH, expand=1) + + self.text.insert(Tkconstants.END, text) + + + argv=unicode_argv() + root = Tkinter.Tk() + root.withdraw() + progpath, progname = os.path.split(argv[0]) + success = False + try: + keys = kindlekeys() + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(progpath,u"kindlekey{0:d}.k4i".format(keycount)) + if not os.path.exists(outfile): + break + + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(key)) + success = True + tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) + except DrmException, e: + tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) + except Exception: + root.wm_state('normal') + root.title(progname) + text = traceback.format_exc() + ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) + root.mainloop() + if not success: + return 1 + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlepid.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlepid.py new file mode 100644 index 0000000..8bbcf69 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlepid.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Mobipocket PID calculator v0.4 for Amazon Kindle. +# Copyright (c) 2007, 2009 Igor Skochinsky +# History: +# 0.1 Initial release +# 0.2 Added support for generating PID for iPhone (thanks to mbp) +# 0.3 changed to autoflush stdout, fixed return code usage +# 0.3 updated for unicode +# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs. +# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility + +import sys +import binascii + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"kindlepid.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +if sys.hexversion >= 0x3000000: + print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.' + sys.exit(2) + +letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' + +def crc32(s): + return (~binascii.crc32(s,-1))&0xFFFFFFFF + +def checksumPid(s): + crc = crc32(s) + 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 pidFromSerial(s, l): + crc = crc32(s) + + arr1 = [0]*l + for i in xrange(len(s)): + arr1[i%l] ^= ord(s[i]) + + crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] + for i in xrange(l): + arr1[i] ^= crc_bytes[i&3] + + pid = '' + for i in xrange(l): + b = arr1[i] & 0xff + pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] + + return pid + +def cli_main(): + print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky" + argv=unicode_argv() + if len(argv)==2: + serial = argv[1] + else: + print u"Usage: kindlepid.py /" + return 1 + if len(serial)==16: + if serial.startswith("B") or serial.startswith("9"): + print u"Kindle serial number detected" + else: + print u"Warning: unrecognized serial number. Please recheck input." + return 1 + pid = pidFromSerial(serial.encode("utf-8"),7)+'*' + print u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)) + return 0 + elif len(serial)==40: + print u"iPhone serial number (UDID) detected" + pid = pidFromSerial(serial.encode("utf-8"),8) + print u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)) + return 0 + print u"Warning: unrecognized serial number. Please recheck input." + return 1 + + +if __name__ == "__main__": + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/libalfcrypto.dylib b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/libalfcrypto.dylib old mode 100755 new mode 100644 similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/libalfcrypto.dylib rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/libalfcrypto.dylib diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/libalfcrypto32.so b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/libalfcrypto32.so old mode 100755 new mode 100644 similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/libalfcrypto32.so rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/libalfcrypto32.so diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/libalfcrypto64.so b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/libalfcrypto64.so old mode 100755 new mode 100644 similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/libalfcrypto64.so rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/libalfcrypto64.so diff --git a/Other_Tools/KindleBooks/lib/mobidedrm.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py similarity index 66% rename from Other_Tools/KindleBooks/lib/mobidedrm.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py index cd993e1..7b69edc 100644 --- a/Other_Tools/KindleBooks/lib/mobidedrm.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py @@ -1,5 +1,11 @@ -#!/usr/bin/python +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# mobidedrm.py, version 0.38 +# Copyright © 2008 The Dark Reverser # +# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf + # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. # @@ -59,26 +65,81 @@ # 0.35 - add interface to get mobi_version # 0.36 - fixed problem with TEXtREAd and getBookTitle interface # 0.37 - Fixed double announcement for stand-alone operation +# 0.38 - Unicode used wherever possible, cope with absent alfcrypto +# 0.39 - Fixed problem with TEXtREAd and getBookType interface +# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 0.41 - Fixed potential unicode problem in command line calls -__version__ = '0.37' +__version__ = u"0.41" import sys - -class Unbuffered: +import os +import struct +import binascii +try: + from alfcrypto import Pukall_Cipher +except: + print u"AlfCrypto not found. Using python PC1 implementation." + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: def __init__(self, stream): self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) -import os -import struct -import binascii -from alfcrypto import Pukall_Cipher +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = 'utf-8' + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + class DrmException(Exception): pass @@ -90,40 +151,45 @@ class DrmException(Exception): # Implementation of Pukall Cipher 1 def PC1(key, src, decryption=True): - return Pukall_Cipher().PC1(key,src,decryption) -# 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 + # if we can get it from alfcrypto, use that + try: + return Pukall_Cipher().PC1(key,src,decryption) + except NameError: + pass + + # use slow python version, since Pukall_Cipher didn't load + sum1 = 0; + sum2 = 0; + keyXorVal = 0; + if len(key)!=16: + DrmException (u"PC1: Bad key length") + 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" + letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' crc = (~binascii.crc32(s,-1))&0xFFFFFFFF crc = crc ^ (crc >> 16) res = s @@ -171,17 +237,24 @@ def loadSection(self, section): off = self.sections[section][0] return self.data_file[off:endoff] - def __init__(self, infile, announce = True): - if announce: - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) + def cleanup(self): + # to match function in Topaz book + pass + + def __init__(self, infile): + print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__) + + try: + from alfcrypto import Pukall_Cipher + except: + print u"AlfCrypto not found. Using python PC1 implementation." # initial sanity check on file self.data_file = file(infile, 'rb').read() self.mobi_data = '' self.header = self.data_file[0:78] if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") + raise DrmException(u"Invalid file format") self.magic = self.header[0x3C:0x3C+8] self.crypto_type = -1 @@ -198,35 +271,37 @@ def __init__(self, infile, announce = True): self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2]) + # det default values before PalmDoc test + self.print_replica = False + self.extra_data_flags = 0 + self.meta_array = {} + self.mobi_length = 0 + self.mobi_codepage = 1252 + self.mobi_version = -1 + if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_codepage = 1252 - self.mobi_version = -1 - self.meta_array = {} + print u"PalmDoc format book detected." return + self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20]) self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 + print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length) if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags + print u"Extra Data Flags: {0:d}".format(self.extra_data_flags) if (self.compression != 17480): # multibyte utf8 data is included in the encryption for PalmDoc compression # so clear that byte so that we leave it to be decrypted. self.extra_data_flags &= 0xFFFE # if exth region exists parse it for metadata array - self.meta_array = {} try: exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' + exth = '' if exth_flag & 0x40: exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): + if (len(exth) >= 12) and (exth[:4] == 'EXTH'): nitems, = struct.unpack('>I', exth[8:12]) pos = 12 for i in xrange(nitems): @@ -236,16 +311,14 @@ def __init__(self, infile, announce = True): # reset the text to speech flag and clipping limit, if present if type == 401 and size == 9: # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) + self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8) elif type == 404 and size == 9: # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) + self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8) # print type, size, content, content.encode('hex') pos += size except: - self.meta_array = {} pass - self.print_replica = False def getBookTitle(self): codec_map = { @@ -265,8 +338,8 @@ def getBookTitle(self): codec = codec_map[self.mobi_codepage] if title == '': title = self.header[:32] - title = title.split("\0")[0] - return unicode(title, codec).encode('utf-8') + title = title.split('\0')[0] + return unicode(title, codec) def getPIDMetaInfo(self): rec209 = '' @@ -297,7 +370,7 @@ def patchSection(self, section, new, in_off = 0): def parseDRM(self, data, count, pidlist): found_key = None - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" + keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96' for pid in pidlist: bigpid = pid.ljust(16,'\0') temp_key = PC1(keyvec1, bigpid, False) @@ -315,7 +388,7 @@ def parseDRM(self, data, count, pidlist): break if not found_key: # Then try the default encoding that doesn't require a PID - pid = "00000000" + pid = '00000000' temp_key = keyvec1 temp_key_sum = sum(map(ord,temp_key)) & 0xff for i in xrange(count): @@ -328,82 +401,92 @@ def parseDRM(self, data, count, pidlist): break return [found_key,pid] - def getMobiFile(self, outpath): + def getFile(self, outpath): file(outpath,'wb').write(self.mobi_data) - def getMobiVersion(self): - return self.mobi_version - - def getPrintReplica(self): - return self.print_replica + def getBookType(self): + if self.print_replica: + return u"Print Replica" + if self.mobi_version >= 8: + return u"Kindle Format 8" + if self.mobi_version >= 0: + return u"Mobipocket {0:d}".format(self.mobi_version) + return u"PalmDoc" + + def getBookExtension(self): + if self.print_replica: + return u".azw4" + if self.mobi_version >= 8: + return u".azw3" + return u".mobi" def processBook(self, pidlist): crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) - print 'Crypto Type is: ', crypto_type + print u"Crypto Type is: {0:d}".format(crypto_type) self.crypto_type = crypto_type if crypto_type == 0: - print "This book is not encrypted." + print u"This book is not encrypted." # we must still check for Print Replica self.print_replica = (self.loadSection(1)[0:4] == '%MOP') self.mobi_data = self.data_file return if crypto_type != 2 and crypto_type != 1: - raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) + raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type)) if 406 in self.meta_array: data406 = self.meta_array[406] val406, = struct.unpack('>Q',data406) if val406 != 0: - raise DrmException("Cannot decode library or rented ebooks.") + raise DrmException(u"Cannot decode library or rented ebooks.") goodpids = [] for pid in pidlist: if len(pid)==10: if checksumPid(pid[0:-2]) != pid: - print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) + print u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])) goodpids.append(pid[0:-2]) elif len(pid)==8: goodpids.append(pid) if self.crypto_type == 1: - t1_keyvec = "QDCVEPMU675RUBSZ" + t1_keyvec = 'QDCVEPMU675RUBSZ' if self.magic == 'TEXtREAd': bookkey_data = self.sect[0x0E:0x0E+16] elif self.mobi_version < 0: bookkey_data = self.sect[0x90:0x90+16] else: bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] - pid = "00000000" + pid = '00000000' found_key = PC1(t1_keyvec, bookkey_data) else : # calculate the keys drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) if drm_count == 0: - raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") + raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.") found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) if not found_key: - raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.") + raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids))) # kill the drm keys - self.patchSection(0, "\0" * drm_size, drm_ptr) + self.patchSection(0, '\0' * drm_size, drm_ptr) # kill the drm pointers - self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) + self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8) - if pid=="00000000": - print "File has default encryption, no specific PID." + if pid=='00000000': + print u"File has default encryption, no specific key needed." else: - print "File is encoded with PID "+checksumPid(pid)+"." + print u"File is encoded with PID {0}.".format(checksumPid(pid)) # clear the crypto type self.patchSection(0, "\0" * 2, 0xC) # decrypt sections - print "Decrypting. Please wait . . .", + print u"Decrypting. Please wait . . .", mobidataList = [] mobidataList.append(self.data_file[:self.sections[1][0]]) for i in xrange(1, self.records+1): data = self.loadSection(i) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: - print ".", + print u".", # print "record %d, extra_size %d" %(i,extra_size) decoded_data = PC1(found_key, data[0:len(data) - extra_size]) if i==1: @@ -414,31 +497,25 @@ def processBook(self, pidlist): if self.num_sections > self.records+1: mobidataList.append(self.data_file[self.sections[self.records+1][0]:]) self.mobi_data = "".join(mobidataList) - print "done" + print u"done" return -def getUnencryptedBook(infile,pid,announce=True): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile,announce) - book.processBook([pid]) - return book.mobi_data - -def getUnencryptedBookWithList(infile,pidlist,announce=True): +def getUnencryptedBook(infile,pidlist): if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile, announce) + raise DrmException(u"Input File Not Found.") + book = MobiBook(infile) book.processBook(pidlist) return book.mobi_data -def main(argv=sys.argv): - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) +def cli_main(): + argv=unicode_argv() + progname = os.path.basename(argv[0]) if len(argv)<3 or len(argv)>4: - print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks" - print "Usage:" - print " %s []" % sys.argv[0] + print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__) + print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks" + print u"Usage:" + print u" {0} []".format(progname) return 1 else: infile = argv[1] @@ -446,15 +523,17 @@ def main(argv=sys.argv): if len(argv) is 4: pidlist = argv[3].split(',') else: - pidlist = {} + pidlist = [] try: - stripped_file = getUnencryptedBookWithList(infile, pidlist, False) + stripped_file = getUnencryptedBook(infile, pidlist) file(outfile, 'wb').write(stripped_file) except DrmException, e: - print "Error: %s" % e + print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0]) return 1 return 0 -if __name__ == "__main__": - sys.exit(main()) +if __name__ == '__main__': + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/Other_Tools/eReader_PDB_Tools/lib/openssl_des.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/openssl_des.py similarity index 98% rename from Other_Tools/eReader_PDB_Tools/lib/openssl_des.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/openssl_des.py index a4a40ca..9a84e58 100644 --- a/Other_Tools/eReader_PDB_Tools/lib/openssl_des.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/openssl_des.py @@ -65,7 +65,7 @@ def F(restype, name, argtypes): class DES(object): def __init__(self, key): if len(key) != 8 : - raise Error('DES improper key used') + raise Exception('DES improper key used') return self.key = key self.keyschedule = DES_KEY_SCHEDULE() diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py new file mode 100755 index 0000000..2c8c665 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +from __future__ import with_statement +__license__ = 'GPL v3' + +# Standard Python modules. +import os, sys, re, hashlib +import json +import traceback + +from calibre.utils.config import dynamic, config_dir, JSONConfig +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION +from calibre.constants import iswindows, isosx + +class DeDRM_Prefs(): + def __init__(self): + JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json') + self.dedrmprefs = JSONConfig(JSON_PATH) + + self.dedrmprefs.defaults['configured'] = False + self.dedrmprefs.defaults['bandnkeys'] = {} + self.dedrmprefs.defaults['adeptkeys'] = {} + self.dedrmprefs.defaults['ereaderkeys'] = {} + self.dedrmprefs.defaults['kindlekeys'] = {} + self.dedrmprefs.defaults['pids'] = [] + self.dedrmprefs.defaults['serials'] = [] + self.dedrmprefs.defaults['adobewineprefix'] = "" + self.dedrmprefs.defaults['kindlewineprefix'] = "" + + # initialise + # we must actually set the prefs that are dictionaries and lists + # to empty dictionaries and lists, otherwise we are unable to add to them + # as then it just adds to the (memory only) dedrmprefs.defaults versions! + if self.dedrmprefs['bandnkeys'] == {}: + self.dedrmprefs['bandnkeys'] = {} + if self.dedrmprefs['adeptkeys'] == {}: + self.dedrmprefs['adeptkeys'] = {} + if self.dedrmprefs['ereaderkeys'] == {}: + self.dedrmprefs['ereaderkeys'] = {} + if self.dedrmprefs['kindlekeys'] == {}: + self.dedrmprefs['kindlekeys'] = {} + if self.dedrmprefs['pids'] == []: + self.dedrmprefs['pids'] = [] + if self.dedrmprefs['serials'] == []: + self.dedrmprefs['serials'] = [] + + def __getitem__(self,kind = None): + if kind is not None: + return self.dedrmprefs[kind] + return self.dedrmprefs + + def set(self, kind, value): + self.dedrmprefs[kind] = value + + def writeprefs(self,value = True): + self.dedrmprefs['configured'] = value + + def addnamedvaluetoprefs(self, prefkind, keyname, keyvalue): + try: + if keyvalue not in self.dedrmprefs[prefkind].values(): + # ensure that the keyname is unique + # by adding a number (starting with 2) to the name if it is not + namecount = 1 + newname = keyname + while newname in self.dedrmprefs[prefkind]: + namecount += 1 + newname = "{0:s}_{1:d}".format(keyname,namecount) + # add to the preferences + self.dedrmprefs[prefkind][newname] = keyvalue + return (True, newname) + except: + traceback.print_exc() + pass + return (False, keyname) + + def addvaluetoprefs(self, prefkind, prefsvalue): + # ensure the keyvalue isn't already in the preferences + try: + if prefsvalue not in self.dedrmprefs[prefkind]: + self.dedrmprefs[prefkind].append(prefsvalue) + return True + except: + traceback.print_exc() + return False + + +def convertprefs(always = False): + + def parseIgnobleString(keystuff): + from calibre_plugins.dedrm.ignoblekeygen import generate_key + userkeys = [] + ar = keystuff.split(':') + for keystring in ar: + try: + name, ccn = keystring.split(',') + # Generate Barnes & Noble EPUB user key from name and credit card number. + keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:]) + keyvalue = generate_key(name, ccn) + userkeys.append([keyname,keyvalue]) + except Exception, e: + traceback.print_exc() + print e.args[0] + pass + return userkeys + + def parseeReaderString(keystuff): + from calibre_plugins.dedrm.erdr2pml import getuser_key + userkeys = [] + ar = keystuff.split(':') + for keystring in ar: + try: + name, cc = keystring.split(',') + # Generate eReader user key from name and credit card number. + keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:]) + keyvalue = getuser_key(name,cc).encode('hex') + userkeysappend([keyname,keyvalue]) + except Exception, e: + traceback.print_exc() + print e.args[0] + pass + return userkeys + + def parseKindleString(keystuff): + pids = [] + serials = [] + ar = keystuff.split(',') + for keystring in ar: + keystring = str(keystring).strip().replace(" ","") + if len(keystring) == 10 or len(keystring) == 8 and keystring not in pids: + pids.append(keystring) + elif len(keystring) == 16 and keystring[0] == 'B' and keystring not in serials: + serials.append(keystring) + return (pids,serials) + + def getConfigFiles(extension, encoding = None): + # get any files with extension 'extension' in the config dir + userkeys = [] + files = [f for f in os.listdir(config_dir) if f.endswith(extension)] + for filename in files: + try: + fpath = os.path.join(config_dir, filename) + key = os.path.splitext(filename)[0] + value = open(fpath, 'rb').read() + if encoding is not None: + value = value.encode(encoding) + userkeys.append([key,value]) + except: + traceback.print_exc() + pass + return userkeys + + dedrmprefs = DeDRM_Prefs() + + if (not always) and dedrmprefs['configured']: + # We've already converted old preferences, + # and we're not being forced to do it again, so just return + return + + + print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION) + + IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM" + EREADERPLUGINNAME = "eReader PDB 2 PML" + OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM" + + # get prefs from older tools + kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM")) + ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm")) + + # Handle the old ignoble plugin's customization string by converting the + # old string to stored keys... get that personal data out of plain sight. + from calibre.customize.ui import config + sc = config['plugin_customization'] + val = sc.pop(IGNOBLEPLUGINNAME, None) + if val is not None: + print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) + priorkeycount = len(dedrmprefs['bandnkeys']) + userkeys = parseIgnobleString(str(val)) + for keypair in userkeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) + addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount + print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # Handle the old eReader plugin's customization string by converting the + # old string to stored keys... get that personal data out of plain sight. + val = sc.pop(EREADERPLUGINNAME, None) + if val is not None: + print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) + priorkeycount = len(dedrmprefs['ereaderkeys']) + userkeys = parseeReaderString(str(val)) + for keypair in userkeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value) + addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount + print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get old Kindle plugin configuration string + val = sc.pop(OLDKINDLEPLUGINNAME, None) + if val is not None: + print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) + priorpidcount = len(dedrmprefs['pids']) + priorserialcount = len(dedrmprefs['serials']) + pids, serials = parseKindleString(val) + for pid in pids: + dedrmprefs.addvaluetoprefs('pids',pid) + for serial in serials: + dedrmprefs.addvaluetoprefs('serials',serial) + addedpidcount = len(dedrmprefs['pids']) - priorpidcount + addedserialcount = len(dedrmprefs['serials']) - priorserialcount + print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # copy the customisations back into calibre preferences, as we've now removed the nasty plaintext + config['plugin_customization'] = sc + + # get any .b64 files in the config dir + priorkeycount = len(dedrmprefs['bandnkeys']) + bandnfilekeys = getConfigFiles('.b64') + for keypair in bandnfilekeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) + addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount + if addedkeycount > 0: + print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, ignoblecount, u"key file" if ignoblecount==1 else u"key files") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get any .der files in the config dir + priorkeycount = len(dedrmprefs['adeptkeys']) + adeptfilekeys = getConfigFiles('.der','hex') + ineptcount = addConfigFiles('.der', 'adeptkeys') + for keypair in adeptfilekeys: + name = keypair[0] + value = keypair[1] + dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value) + addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount + if addedkeycount > 0: + print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, ineptcount, u"keyfile" if ineptcount==1 else u"keyfiles") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get ignoble json prefs + if 'keys' in ignobleprefs: + priorkeycount = len(dedrmprefs['bandnkeys']) + for name in ignobleprefs['keys']: + value = ignobleprefs['keys'][name] + dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) + addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount + # no need to delete old prefs, since they contain no recoverable private data + if addedkeycount > 0: + print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") + # Make the json write all the prefs to disk + dedrmprefs.writeprefs(False) + + # get kindle json prefs + priorpidcount = len(dedrmprefs['pids']) + priorserialcount = len(dedrmprefs['serials']) + if 'pids' in kindleprefs: + pids, serials = parseKindleString(kindleprefs['pids']) + for pid in pids: + dedrmprefs.addvaluetoprefs('pids',pid) + if 'serials' in kindleprefs: + pids, serials = parseKindleString(kindleprefs['serials']) + for serial in serials: + dedrmprefs.addvaluetoprefs('serials',serial) + addedpidcount = len(dedrmprefs['pids']) - priorpidcount + if addedpidcount > 0: + print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs") + addedserialcount = len(dedrmprefs['serials']) - priorserialcount + if addedserialcount > 0: + print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers") + try: + if kindleprefs['wineprefix'] != "": + dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix']) + dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix']) + print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix']) + except: + traceback.print_exc() + + + # Make the json write all the prefs to disk + dedrmprefs.writeprefs() + print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION) diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/pycrypto_des.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/pycrypto_des.py similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/pycrypto_des.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/pycrypto_des.py diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/python_des.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/python_des.py similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/python_des.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/python_des.py diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py new file mode 100644 index 0000000..3be643f --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import sys +import os +import re +import ineptepub +import ignobleepub +import epubtest +import zipfix +import ineptpdf +import erdr2pml +import k4mobidedrm +import traceback + +def decryptepub(infile, outdir, rscpath): + errlog = '' + + # first fix the epub to make sure we do not get errors + name, ext = os.path.splitext(os.path.basename(infile)) + bpath = os.path.dirname(infile) + zippath = os.path.join(bpath,name + '_temp.zip') + rv = zipfix.repairBook(infile, zippath) + if rv != 0: + print "Error while trying to fix epub" + return rv + + # determine a good name for the output file + outfile = os.path.join(outdir, name + '_nodrm.epub') + + rv = 1 + # first try with the Adobe adept epub + if ineptepub.adeptBook(zippath): + # try with any keyfiles (*.der) in the rscpath + files = os.listdir(rscpath) + filefilter = re.compile("\.der$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + keypath = os.path.join(rscpath, filename) + userkey = open(keypath,'rb').read() + try: + rv = ineptepub.decryptBook(userkey, zippath, outfile) + if rv == 0: + print "Decrypted Adobe ePub with key file {0}".format(filename) + break + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + # now try with ignoble epub + elif ignobleepub.ignobleBook(zippath): + # try with any keyfiles (*.b64) in the rscpath + files = os.listdir(rscpath) + filefilter = re.compile("\.b64$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + keypath = os.path.join(rscpath, filename) + userkey = open(keypath,'r').read() + #print userkey + try: + rv = ignobleepub.decryptBook(userkey, zippath, outfile) + if rv == 0: + print "Decrypted B&N ePub with key file {0}".format(filename) + break + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + else: + encryption = epubtest.encryption(zippath) + if encryption == "Unencrypted": + print "{0} is not DRMed.".format(name) + rv = 0 + else: + print "{0} has an unknown encryption.".format(name) + + os.remove(zippath) + if rv != 0: + print errlog + return rv + + +def decryptpdf(infile, outdir, rscpath): + errlog = '' + rv = 1 + + # determine a good name for the output file + name, ext = os.path.splitext(os.path.basename(infile)) + outfile = os.path.join(outdir, name + '_nodrm.pdf') + + # try with any keyfiles (*.der) in the rscpath + files = os.listdir(rscpath) + filefilter = re.compile("\.der$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + keypath = os.path.join(rscpath, filename) + userkey = open(keypath,'rb').read() + try: + rv = ineptpdf.decryptBook(userkey, infile, outfile) + if rv == 0: + break + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + + if rv != 0: + print errlog + return rv + + +def decryptpdb(infile, outdir, rscpath): + outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz" + outpath = os.path.join(outdir, outname) + rv = 1 + socialpath = os.path.join(rscpath,'sdrmlist.txt') + if os.path.exists(socialpath): + keydata = file(socialpath,'r').read() + keydata = keydata.rstrip(os.linesep) + ar = keydata.split(',') + for i in ar: + try: + name, cc8 = i.split(':') + except ValueError: + print ' Error parsing user supplied social drm data.' + return 1 + try: + rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8)) + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + + if rv == 0: + break + return rv + + +def decryptk4mobi(infile, outdir, rscpath): + rv = 1 + pidnums = [] + pidspath = os.path.join(rscpath,'pidlist.txt') + if os.path.exists(pidspath): + pidstr = file(pidspath,'r').read() + pidstr = pidstr.rstrip(os.linesep) + pidstr = pidstr.strip() + if pidstr != '': + pidnums = pidstr.split(',') + serialnums = [] + serialnumspath = os.path.join(rscpath,'seriallist.txt') + if os.path.exists(serialnumspath): + serialstr = file(serialnumspath,'r').read() + serialstr = serialstr.rstrip(os.linesep) + serialstr = serialstr.strip() + if serialstr != '': + serialnums = serialstr.split(',') + kDatabaseFiles = [] + files = os.listdir(rscpath) + filefilter = re.compile("\.k4i$", re.IGNORECASE) + files = filter(filefilter.search, files) + if files: + for filename in files: + dpath = os.path.join(rscpath,filename) + kDatabaseFiles.append(dpath) + try: + rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, serialnums, pidnums) + except Exception, e: + errlog += traceback.format_exc() + errlog += str(e) + rv = 1 + + return rv diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/scrolltextwidget.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scrolltextwidget.py old mode 100755 new mode 100644 similarity index 100% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/scrolltextwidget.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scrolltextwidget.py diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/simpleprefs.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/simpleprefs.py similarity index 100% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/simpleprefs.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/simpleprefs.py diff --git a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/stylexml2css.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/stylexml2css.py similarity index 95% rename from DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/stylexml2css.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/stylexml2css.py index 2347f6a..c111850 100644 --- a/DeDRM_Applications/Windows/DeDRM_5.4.1/DeDRM_lib/lib/stylexml2css.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/stylexml2css.py @@ -10,6 +10,7 @@ from struct import pack from struct import unpack +debug = False class DocParser(object): def __init__(self, flatxml, fontsize, ph, pw): @@ -113,7 +114,9 @@ def process(self): # process each style converting what you can + if debug: print ' ', 'Processing styles.' for j in xrange(stylecnt): + if debug: print ' ', 'Processing style %d' %(j) start = styleList[j] end = styleList[j+1] @@ -132,6 +135,8 @@ def process(self): else : sclass = '' + if debug: print 'sclass', sclass + # check for any "after class" specifiers (pos, aftclass) = self.findinDoc('style._after_class',start,end) if aftclass != None: @@ -140,6 +145,8 @@ def process(self): else : aftclass = '' + if debug: print 'aftclass', aftclass + cssargs = {} while True : @@ -147,6 +154,9 @@ def process(self): (pos1, attr) = self.findinDoc('style.rule.attr', start, end) (pos2, val) = self.findinDoc('style.rule.value', start, end) + if debug: print 'attr', attr + if debug: print 'val', val + if attr == None : break if (attr == 'display') or (attr == 'pos') or (attr == 'align'): @@ -164,7 +174,7 @@ def process(self): scale = self.pw elif attr == 'line-space': scale = self.fontsize * 2.0 - + if val == "": val = 0 @@ -179,6 +189,7 @@ def process(self): if aftclass != "" : keep = False if keep : + if debug: print 'keeping style' # make sure line-space does not go below 100% or above 300% since # it can be wacky in some styles if 'line-space' in cssargs: @@ -256,7 +267,9 @@ def convert2CSS(flatxml, fontsize, ph, pw): # create a document parser dp = DocParser(flatxml, fontsize, ph, pw) + if debug: print ' ', 'Created DocParser.' csspage = dp.process() + if debug: print ' ', 'Processed DocParser.' return csspage diff --git a/Other_Tools/KindleBooks/lib/topazextract.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py old mode 100755 new mode 100644 similarity index 56% rename from Other_Tools/KindleBooks/lib/topazextract.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py index bf2ad47..97f6583 --- a/Other_Tools/KindleBooks/lib/topazextract.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py @@ -1,43 +1,97 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -class Unbuffered: +# topazextract.py +# Mostly written by some_updates based on code from many others + +# Changelog +# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 5.0 - Fixed potential unicode problem with command line interface + +__version__ = '5.0' + +import sys +import os, csv, getopt +import zlib, zipfile, tempfile, shutil +import traceback +from struct import pack +from struct import unpack +from alfcrypto import Topaz_Cipher + +class SafeUnbuffered: def __init__(self, stream): self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) -import sys +iswindows = sys.platform.startswith('win') +isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. + + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"mobidedrm.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = 'utf-8' + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +#global switch +debug = False if 'calibre' in sys.modules: inCalibre = True + from calibre_plugins.dedrm import kgenpids else: inCalibre = False + import kgenpids -buildXML = False - -import os, csv, getopt -import zlib, zipfile, tempfile, shutil -from struct import pack -from struct import unpack -from alfcrypto import Topaz_Cipher -class TpzDRMError(Exception): +class DrmException(Exception): pass -# local support routines -if inCalibre: - from calibre_plugins.k4mobidedrm import kgenpids -else: - import kgenpids - # recursive zip creation support routine def zipUpDir(myzip, tdir, localname): currentdir = tdir - if localname != "": + if localname != u"": currentdir = os.path.join(currentdir,localname) list = os.listdir(currentdir) for file in list: @@ -73,7 +127,7 @@ def bookReadEncodedNumber(fo): # Get a length prefixed string from file def bookReadString(fo): stringLength = bookReadEncodedNumber(fo) - return unpack(str(stringLength)+"s",fo.read(stringLength))[0] + return unpack(str(stringLength)+'s',fo.read(stringLength))[0] # # crypto routines @@ -112,13 +166,13 @@ def decryptRecord(data,PID): # Try to decrypt a dkey record (contains the bookPID) def decryptDkeyRecord(data,PID): record = decryptRecord(data,PID) - fields = unpack("3sB8sB8s3s",record) - if fields[0] != "PID" or fields[5] != "pid" : - raise TpzDRMError("Didn't find PID magic numbers in record") + fields = unpack('3sB8sB8s3s',record) + if fields[0] != 'PID' or fields[5] != 'pid' : + raise DrmException(u"Didn't find PID magic numbers in record") elif fields[1] != 8 or fields[3] != 8 : - raise TpzDRMError("Record didn't contain correct length fields") + raise DrmException(u"Record didn't contain correct length fields") elif fields[2] != PID : - raise TpzDRMError("Record didn't contain PID") + raise DrmException(u"Record didn't contain PID") return fields[4] # Decrypt all dkey records (contain the book PID) @@ -131,11 +185,11 @@ def decryptDkeyRecords(data,PID): try: key = decryptDkeyRecord(data[1:length+1],PID) records.append(key) - except TpzDRMError: + except DrmException: pass data = data[1+length:] if len(records) == 0: - raise TpzDRMError("BookKey Not Found") + raise DrmException(u"BookKey Not Found") return records @@ -148,9 +202,9 @@ def __init__(self, filename): self.bookHeaderRecords = {} self.bookMetadata = {} self.bookKey = None - magic = unpack("4s",self.fo.read(4))[0] + magic = unpack('4s',self.fo.read(4))[0] if magic != 'TPZ0': - raise TpzDRMError("Parse Error : Invalid Header, not a Topaz file") + raise DrmException(u"Parse Error : Invalid Header, not a Topaz file") self.parseTopazHeaders() self.parseMetadata() @@ -159,6 +213,7 @@ def bookReadHeaderRecordData(): # Read and return the data of one header record at the current book file position # [[offset,decompressedLength,compressedLength],...] nbValues = bookReadEncodedNumber(self.fo) + if debug: print "%d records in header " % nbValues, values = [] for i in range (0,nbValues): values.append([bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo)]) @@ -167,33 +222,34 @@ def parseTopazHeaderRecord(): # Read and parse one header record at the current book file position and return the associated data # [[offset,decompressedLength,compressedLength],...] if ord(self.fo.read(1)) != 0x63: - raise TpzDRMError("Parse Error : Invalid Header") + raise DrmException(u"Parse Error : Invalid Header") tag = bookReadString(self.fo) record = bookReadHeaderRecordData() return [tag,record] nbRecords = bookReadEncodedNumber(self.fo) + if debug: print "Headers: %d" % nbRecords for i in range (0,nbRecords): result = parseTopazHeaderRecord() - # print result[0], result[1] + if debug: print result[0], ": ", result[1] self.bookHeaderRecords[result[0]] = result[1] if ord(self.fo.read(1)) != 0x64 : - raise TpzDRMError("Parse Error : Invalid Header") + raise DrmException(u"Parse Error : Invalid Header") self.bookPayloadOffset = self.fo.tell() def parseMetadata(self): # Parse the metadata record from the book payload and return a list of [key,values] - self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords["metadata"][0][0]) + self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0]) tag = bookReadString(self.fo) - if tag != "metadata" : - raise TpzDRMError("Parse Error : Record Names Don't Match") + if tag != 'metadata' : + raise DrmException(u"Parse Error : Record Names Don't Match") flags = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1)) - # print nbRecords + if debug: print "Metadata Records: %d" % nbRecords for i in range (0,nbRecords) : keyval = bookReadString(self.fo) content = bookReadString(self.fo) - # print keyval - # print content + if debug: print keyval + if debug: print content self.bookMetadata[keyval] = content return self.bookMetadata @@ -210,7 +266,7 @@ def getBookTitle(self): title = '' if 'Title' in self.bookMetadata: title = self.bookMetadata['Title'] - return title + return title.decode('utf-8') def setBookKey(self, key): self.bookKey = key @@ -223,13 +279,13 @@ def getBookPayloadRecord(self, name, index): try: recordOffset = self.bookHeaderRecords[name][index][0] except: - raise TpzDRMError("Parse Error : Invalid Record, record not found") + raise DrmException("Parse Error : Invalid Record, record not found") self.fo.seek(self.bookPayloadOffset + recordOffset) tag = bookReadString(self.fo) if tag != name : - raise TpzDRMError("Parse Error : Invalid Record, record name doesn't match") + raise DrmException("Parse Error : Invalid Record, record name doesn't match") recordIndex = bookReadEncodedNumber(self.fo) if recordIndex < 0 : @@ -237,7 +293,7 @@ def getBookPayloadRecord(self, name, index): recordIndex = -recordIndex -1 if recordIndex != index : - raise TpzDRMError("Parse Error : Invalid Record, index doesn't match") + raise DrmException("Parse Error : Invalid Record, index doesn't match") if (self.bookHeaderRecords[name][index][2] > 0): compressed = True @@ -250,7 +306,7 @@ def getBookPayloadRecord(self, name, index): ctx = topazCryptoInit(self.bookKey) record = topazCryptoDecrypt(record,ctx) else : - raise TpzDRMError("Error: Attempt to decrypt without bookKey") + raise DrmException("Error: Attempt to decrypt without bookKey") if compressed: record = zlib.decompress(record) @@ -262,20 +318,20 @@ def processBook(self, pidlst): fixedimage=True try: keydata = self.getBookPayloadRecord('dkey', 0) - except TpzDRMError, e: - print "no dkey record found, book may not be encrypted" - print "attempting to extrct files without a book key" + except DrmException, e: + print u"no dkey record found, book may not be encrypted" + print u"attempting to extrct files without a book key" self.createBookDirectory() self.extractFiles() - print "Successfully Extracted Topaz contents" + print u"Successfully Extracted Topaz contents" if inCalibre: - from calibre_plugins.k4mobidedrm import genbook + from calibre_plugins.dedrm import genbook else: import genbook rv = genbook.generateBook(self.outdir, raw, fixedimage) if rv == 0: - print "\nBook Successfully generated" + print u"Book Successfully generated." return rv # try each pid to decode the file @@ -283,33 +339,33 @@ def processBook(self, pidlst): for pid in pidlst: # use 8 digit pids here pid = pid[0:8] - print "\nTrying: ", pid + print u"Trying: {0}".format(pid) bookKeys = [] data = keydata try: bookKeys+=decryptDkeyRecords(data,pid) - except TpzDRMError, e: + except DrmException, e: pass else: bookKey = bookKeys[0] - print "Book Key Found!" + print u"Book Key Found! ({0})".format(bookKey.encode('hex')) break if not bookKey: - raise TpzDRMError("Topaz Book. No key found in " + str(len(pidlst)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.") + raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(pidlst))) self.setBookKey(bookKey) self.createBookDirectory() self.extractFiles() - print "Successfully Extracted Topaz contents" + print u"Successfully Extracted Topaz contents" if inCalibre: - from calibre_plugins.k4mobidedrm import genbook + from calibre_plugins.dedrm import genbook else: import genbook rv = genbook.generateBook(self.outdir, raw, fixedimage) if rv == 0: - print "\nBook Successfully generated" + print u"Book Successfully generated" return rv def createBookDirectory(self): @@ -317,16 +373,16 @@ def createBookDirectory(self): # create output directory structure if not os.path.exists(outdir): os.makedirs(outdir) - destdir = os.path.join(outdir,'img') + destdir = os.path.join(outdir,u"img") if not os.path.exists(destdir): os.makedirs(destdir) - destdir = os.path.join(outdir,'color_img') + destdir = os.path.join(outdir,u"color_img") if not os.path.exists(destdir): os.makedirs(destdir) - destdir = os.path.join(outdir,'page') + destdir = os.path.join(outdir,u"page") if not os.path.exists(destdir): os.makedirs(destdir) - destdir = os.path.join(outdir,'glyphs') + destdir = os.path.join(outdir,u"glyphs") if not os.path.exists(destdir): os.makedirs(destdir) @@ -334,149 +390,149 @@ def extractFiles(self): outdir = self.outdir for headerRecord in self.bookHeaderRecords: name = headerRecord - if name != "dkey" : - ext = '.dat' - if name == 'img' : ext = '.jpg' - if name == 'color' : ext = '.jpg' - print "\nProcessing Section: %s " % name + if name != 'dkey': + ext = u".dat" + if name == 'img': ext = u".jpg" + if name == 'color' : ext = u".jpg" + print u"Processing Section: {0}\n. . .".format(name), for index in range (0,len(self.bookHeaderRecords[name])) : - fnum = "%04d" % index - fname = name + fnum + ext + fname = u"{0}{1:04d}{2}".format(name,index,ext) destdir = outdir if name == 'img': - destdir = os.path.join(outdir,'img') + destdir = os.path.join(outdir,u"img") if name == 'color': - destdir = os.path.join(outdir,'color_img') + destdir = os.path.join(outdir,u"color_img") if name == 'page': - destdir = os.path.join(outdir,'page') + destdir = os.path.join(outdir,u"page") if name == 'glyphs': - destdir = os.path.join(outdir,'glyphs') + destdir = os.path.join(outdir,u"glyphs") outputFile = os.path.join(destdir,fname) - print ".", + print u".", record = self.getBookPayloadRecord(name,index) if record != '': file(outputFile, 'wb').write(record) - print " " + print u" " - def getHTMLZip(self, zipname): + def getFile(self, zipname): htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html') - htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf') - if os.path.isfile(os.path.join(self.outdir,'cover.jpg')): - htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg') - htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css') - zipUpDir(htmlzip, self.outdir, 'img') + htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html") + htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf") + if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")): + htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg") + htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css") + zipUpDir(htmlzip, self.outdir, u"img") htmlzip.close() + def getBookType(self): + return u"Topaz" + + def getBookExtension(self): + return u".htmlz" + def getSVGZip(self, zipname): svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml') - zipUpDir(svgzip, self.outdir, 'svg') - zipUpDir(svgzip, self.outdir, 'img') + svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml") + zipUpDir(svgzip, self.outdir, u"svg") + zipUpDir(svgzip, self.outdir, u"img") svgzip.close() - def getXMLZip(self, zipname): - xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) - targetdir = os.path.join(self.outdir,'xml') - zipUpDir(xmlzip, targetdir, '') - zipUpDir(xmlzip, self.outdir, 'img') - xmlzip.close() - def cleanup(self): if os.path.isdir(self.outdir): shutil.rmtree(self.outdir, True) def usage(progname): - print "Removes DRM protection from Topaz ebooks and extract the contents" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - + print u"Removes DRM protection from Topaz ebooks and extracts the contents" + print u"Usage:" + print u" {0} [-k ] [-p ] [-s ] ".format(progname) # Main -def main(argv=sys.argv): - global buildXML +def cli_main(): + argv=unicode_argv() progname = os.path.basename(argv[0]) - k4 = False - pids = [] - serials = [] - kInfoFiles = [] + print u"TopazExtract v{0}.".format(__version__) try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") + opts, args = getopt.getopt(argv[1:], "k:p:s:x") except getopt.GetoptError, err: - print str(err) + print u"Error in options or arguments: {0}".format(err.args[0]) usage(progname) return 1 if len(args)<2: usage(progname) return 1 + infile = args[0] + outdir = args[1] + if not os.path.isfile(infile): + print u"Input File {0} Does Not Exist.".format(infile) + return 1 + + if not os.path.exists(outdir): + print u"Output Directory {0} Does Not Exist.".format(outdir) + return 1 + + kDatabaseFiles = [] + serials = [] + pids = [] + for o, a in opts: - if o == "-k": + if o == '-k': if a == None : - print "Invalid parameter for -k" - return 1 - kInfoFiles.append(a) - if o == "-p": + raise DrmException("Invalid parameter for -k") + kDatabaseFiles.append(a) + if o == '-p': if a == None : - print "Invalid parameter for -p" - return 1 + raise DrmException("Invalid parameter for -p") pids = a.split(',') - if o == "-s": + if o == '-s': if a == None : - print "Invalid parameter for -s" - return 1 - serials = a.split(',') - k4 = True - - infile = args[0] - outdir = args[1] - - if not os.path.isfile(infile): - print "Input File Does Not Exist" - return 1 + raise DrmException("Invalid parameter for -s") + serials = [serial.replace(" ","") for serial in a.split(',')] bookname = os.path.splitext(os.path.basename(infile))[0] tb = TopazBook(infile) title = tb.getBookTitle() - print "Processing Book: ", title - keysRecord, keysRecordRecord = tb.getPIDMetaInfo() - pids.extend(kgenpids.getPidList(keysRecord, keysRecordRecord, k4, serials, kInfoFiles)) + print u"Processing Book: {0}".format(title) + md1, md2 = tb.getPIDMetaInfo() + pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles)) try: - print "Decrypting Book" + print u"Decrypting Book" tb.processBook(pids) - print " Creating HTML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz') - tb.getHTMLZip(zipname) + print u" Creating HTML ZIP Archive" + zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz") + tb.getFile(zipname) - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, bookname + '_SVG' + '.zip') + print u" Creating SVG ZIP Archive" + zipname = os.path.join(outdir, bookname + u"_SVG.zip") tb.getSVGZip(zipname) - if buildXML: - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, bookname + '_XML' + '.zip') - tb.getXMLZip(zipname) - # removing internal temporary directory of pieces tb.cleanup() - except TpzDRMError, e: - print str(e) - # tb.cleanup() + except DrmException, e: + print u"Decryption failed\n{0}".format(traceback.format_exc()) + + try: + tb.cleanup() + except: + pass return 1 except Exception, e: - print str(e) - # tb.cleanup + print u"Decryption failed\m{0}".format(traceback.format_exc()) + try: + tb.cleanup() + except: + pass return 1 return 0 if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + sys.exit(cli_main()) diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/utilities.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/utilities.py new file mode 100644 index 0000000..4ebb301 --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/utilities.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +__license__ = 'GPL v3' + +DETAILED_MESSAGE = \ +'You have personal information stored in this plugin\'s customization '+ \ +'string from a previous version of this plugin.\n\n'+ \ +'This new version of the plugin can convert that info '+ \ +'into key data that the new plugin can then use (which doesn\'t '+ \ +'require personal information to be stored/displayed in an insecure '+ \ +'manner like the old plugin did).\n\nIf you choose NOT to migrate this data at this time '+ \ +'you will be prompted to save that personal data to a file elsewhere; and you\'ll have '+ \ +'to manually re-configure this plugin with your information.\n\nEither way... ' + \ +'this new version of the plugin will not be responsible for storing that personal '+ \ +'info in plain sight any longer.' + +def uStrCmp (s1, s2, caseless=False): + import unicodedata as ud + str1 = s1 if isinstance(s1, unicode) else unicode(s1) + str2 = s2 if isinstance(s2, unicode) else unicode(s2) + if caseless: + return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower()) + else: + return ud.normalize('NFC', str1) == ud.normalize('NFC', str2) + +def parseCustString(keystuff): + userkeys = [] + ar = keystuff.split(':') + for i in ar: + try: + name, ccn = i.split(',') + # Generate Barnes & Noble EPUB user key from name and credit card number. + userkeys.append(generate_key(name, ccn)) + except: + pass + return userkeys diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/wineutils.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/wineutils.py new file mode 100755 index 0000000..f8d5f7a --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/wineutils.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +__license__ = 'GPL v3' + +# Standard Python modules. +import os, sys, re, hashlib +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION + +def WineGetKeys(scriptpath, extension, wineprefix=""): + import subprocess + from subprocess import Popen, PIPE, STDOUT + + import subasyncio + from subasyncio import Process + + if extension == u".k4i": + import json + + basepath, script = os.path.split(scriptpath) + print u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script) + + outdirpath = os.path.join(basepath, u"winekeysdir") + if not os.path.exists(outdirpath): + os.mkdir(outdirpath) + + if wineprefix != "" and os.path.exists(wineprefix): + cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix) + else: + cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath) + print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline) + + try: + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) + result = p2.wait("wait") + except Exception, e: + print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]) + return [] + + winekeys = [] + # get any files with extension in the output dir + files = [f for f in os.listdir(outdirpath) if f.endswith(extension)] + for filename in files: + try: + fpath = os.path.join(outdirpath, filename) + with open(fpath, 'rb') as keyfile: + if extension == u".k4i": + new_key_value = json.loads(keyfile.read()) + else: + new_key_value = keyfile.read() + winekeys.append(new_key_value) + except: + print u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename) + traceback.print_exc() + os.remove(fpath) + print u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files") + return winekeys diff --git a/Other_Tools/ePub_Fixer/lib/zipfilerugged.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfilerugged.py similarity index 99% rename from Other_Tools/ePub_Fixer/lib/zipfilerugged.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfilerugged.py index adf3c53..4a55a69 100644 --- a/Other_Tools/ePub_Fixer/lib/zipfilerugged.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfilerugged.py @@ -354,7 +354,7 @@ def _encodeFilenameFlags(self): def _decodeFilename(self): if self.flag_bits & 0x800: try: - print "decoding filename",self.filename + #print "decoding filename",self.filename return self.filename.decode('utf-8') except: return self.filename diff --git a/Other_Tools/ePub_Fixer/lib/zipfix.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py old mode 100755 new mode 100644 similarity index 69% rename from Other_Tools/ePub_Fixer/lib/zipfix.py rename to DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py index c7921f2..8ddfae3 --- a/Other_Tools/ePub_Fixer/lib/zipfix.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py @@ -1,4 +1,22 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + +# zipfix.py, version 1.1 +# Copyright © 2010-2013 by some_updates, DiapDealer and Apprentice Alf + +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Revision history: +# 1.0 - Initial release +# 1.1 - Updated to handle zip file metadata correctly + +""" +Re-write zip (or ePub) fixing problems with file names (and mimetype entry). +""" + +__license__ = 'GPL v3' +__version__ = "1.1" import sys import zlib @@ -27,14 +45,10 @@ def __init__(self, zinput, zoutput): self.ztype = 'zip' if zinput.lower().find('.epub') >= 0 : self.ztype = 'epub' - print "opening input" self.inzip = zipfilerugged.ZipFile(zinput,'r') - print "opening outout" self.outzip = zipfilerugged.ZipFile(zoutput,'w') - print "opening input as raw file" # open the input zip for reading only as a raw file self.bzf = file(zinput,'rb') - print "finished initialising" def getlocalname(self, zi): local_header_offset = zi.header_offset @@ -99,25 +113,41 @@ def fix(self): # if epub write mimetype file first, with no compression if self.ztype == 'epub': - nzinfo = ZipInfo('mimetype', compress_type=zipfilerugged.ZIP_STORED) - self.outzip.writestr(nzinfo, _MIMETYPE) + # first get a ZipInfo with current time and no compression + mimeinfo = ZipInfo('mimetype',compress_type=zipfilerugged.ZIP_STORED) + mimeinfo.internal_attr = 1 # text file + try: + # if the mimetype is present, get its info, including time-stamp + oldmimeinfo = self.inzip.getinfo('mimetype') + # copy across useful fields + mimeinfo.date_time = oldmimeinfo.date_time + mimeinfo.comment = oldmimeinfo.comment + mimeinfo.extra = oldmimeinfo.extra + mimeinfo.internal_attr = oldmimeinfo.internal_attr + mimeinfo.external_attr = oldmimeinfo.external_attr + mimeinfo.create_system = oldmimeinfo.create_system + except: + pass + self.outzip.writestr(mimeinfo, _MIMETYPE) # write the rest of the files for zinfo in self.inzip.infolist(): - if zinfo.filename != "mimetype" or self.ztype == '.zip': + if zinfo.filename != "mimetype" or self.ztype != 'epub': data = None - nzinfo = zinfo try: data = self.inzip.read(zinfo.filename) except zipfilerugged.BadZipfile or zipfilerugged.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 + zinfo.filename = local_name + + # create new ZipInfo with only the useful attributes from the old info + nzinfo = ZipInfo(zinfo.filename, zinfo.date_time, compress_type=zinfo.compress_type) + nzinfo.comment=zinfo.comment + nzinfo.extra=zinfo.extra + nzinfo.internal_attr=zinfo.internal_attr + nzinfo.external_attr=zinfo.external_attr + nzinfo.create_system=zinfo.create_system self.outzip.writestr(nzinfo,data) self.bzf.close() diff --git a/DeDRM_Windows_Application/DeDRM_App_ReadMe.txt b/DeDRM_Windows_Application/DeDRM_App_ReadMe.txt new file mode 100644 index 0000000..cecf8ab --- /dev/null +++ b/DeDRM_Windows_Application/DeDRM_App_ReadMe.txt @@ -0,0 +1,66 @@ +DeDRM_App - DeDRM_App.pyw and DeDRM_Drop_Target.bat +=========================================================== + +DeDRM_App.pyw is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target.bat to have the DRM removed. It repackages all the "tools" python software in one easy to use program that remembers preferences and settings. + +It will work without manual configuration for Kindle for PC ebooks and Adobe Digital Edition epub and pdf ebooks, when Kindle for PC and/or Adobe Digital Editions are installed on the same computer. + +To remove the DRM from eInk Kindle ebooks, Barnes and Noble epubs, Mobipocket ebooks and Fictionwise eReader ebooks requires the user to double-click the DeDRM_Drop_Target.bat file and set some additional Preferences including: + +eInk Kindle: 16 digit Serial Number +Barnes & Noble: key file (bnepubkey.b64) generate using ignoblekeygen.pyw +eReader Social DRM: Name:Last 8 digits of CC number +MobiPocket: 10 digit PID + +Once these preferences have been set, the user can simply drag and drop ebooks onto the DeDRM_Drop_Target to remove the DRM. Note that after setting preferences it is necessary to click on "Set Prefs" button and then quit the application for the change in preferences to fully take effect. + +This program requires that a 32 bit version of Python 2.x (tested with Python 2.5 through Python 2.7) and PyCrypto be installed on your computer before it will work. See below for where to get theese programs for Windows. + + +Installation +------------ +0. If you don't already have a correct version of Python and PyCrypto installed, follow the "Installing Python on Windows" and "Installing PyCrypto on Windows" sections below before continuing. + +1. Drag the DeDRM_App folder from tools_v6.0.0/DeDRM_Application_Windows to your "My Documents" folder. + +2. Open the DeDRM_App folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop. + +3. To set the preferences simply double-click on the short-cut you've just created. + + +Credits +------- +The mobidedrm and erdr2pml scripts were created by The Dark Reverser +The ignobleepub, ignoblekeygen, ineptepub and adobe key scripts were created by i♥cabbages +The k4mobidedrm script and supporting scripts were written by some_updates with help from DiapDealer and Apprentice Alf, based on code by Bart Simpson (aka Skindle), CMBDTC and clarknova +The alfcrypto library was created by some_updates +The ePub encryption detection script was adapted by Apprentice Alf from a script by Paul Durrant +The DeDRM all-in-one AppleScript was created by Apprentice Alf +The DeDRM all-in-one python script was created by some_updates and Apprentice Alf + + +Installing Python on Windows +---------------------------- +I strongly recommend fully installing ActiveState’s Active Python, free Community Edition for Windows (x86) 32 bits. This is a free, full version of the Python. It comes with some important additional modules that are not included in the bare-bones version from www.python.org unless you choose to install everything. + +1. Download ActivePython 2.7.X for Windows (x86) (or later 2.7 version for Windows (x86) ) from http://www.activestate.com/activepython/downloads. Do not download the ActivePython 2.7.X for Windows (64-bit, x64) verson, even if you are running 64-bit Windows. + +2. When it has finished downloading, run the installer. Accept the default options. + + +Installing PyCrypto on Windows +------------------------------ +PyCrypto is a set of encryption/decryption routines that work with Python. The sources are freely available, and compiled versions are available from several sources. You must install a version that is for 32-bit Windows and Python 2.7. I recommend the installer linked from Michael Foord’s blog. + +1. Download PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto + +2. When it has finished downloading, unzip it. This will produce a file “pycrypto-2.1.0.win32-py2.7.exe”. + +3. Double-click “pycrypto-2.1.0.win32-py2.7.exe” to run it. Accept the default options. + + + + +Linux Users +=========== +The DeDRM_app.pyw script, although not the bat shortcut, should work under Linux. Drag & drop functionality is not available. diff --git a/DeDRM_calibre_plugin/DeDRM_plugin.zip b/DeDRM_calibre_plugin/DeDRM_plugin.zip new file mode 100644 index 0000000..0420378 Binary files /dev/null and b/DeDRM_calibre_plugin/DeDRM_plugin.zip differ diff --git a/DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt b/DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt new file mode 100644 index 0000000..c58d3dc --- /dev/null +++ b/DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt @@ -0,0 +1,95 @@ +DeDRM_plugin.zip +================ + +This calibre plugin replaces all previous DRM removal plugins. When you install this plugin, the older separate plugins should be removed. + +This plugin will remove the DRM from Amazon Kindle ebooks (Mobi, KF8, Topaz and Print Replica), Mobipocket, Adobe Digital Edition ePubs (including Sony and Kobo ePubs), Barnes and Noble ePubs, Adobe Digital Edition PDFs, and Fictionwise eReader ebooks. + + +Installation +------------ +Do NOT select "Get plugins to enhance calibre" as this is reserved for 'official' calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (DeDRM_plugin.zip) and click the "Add" button. Click "Yes" in the the "Are you sure?" dialog. Click OK in the "Success" dialog. + + +Customization +------------- +The keys for ebooks downloaded using Kindle for Mac/PC and Adobe Digital Editions are automatically generated and saved when needed. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. (On Linux, Kindle for PC and Adobe Digital Editions along with Python and PyCrypto need to be installed under Wine for this to work, see the Linux section at the end.) + +If you have books from other sources (e.g. from an eInk Kindle), highlight the plugin (DeDRM under the "File type plugins" category) and click the "Customize Plugin" button. + +The buttons in the configuration dialog will open individual configuration dialogs that will allow you to enter the needed information, depending on the type and source of your DRMed eBooks. Additional help on the information required is available in each of the the dialogs. + +If you have used previous versions of the various DeDRM plugins on this machine, you may find that some of the configuration dialogs already contain the information you entered through those previous plugins. + +When you have finished entering your configuration information, you must click the OK button to save it. If you click the Cancel button, all your changes in all the configuration dialogs will be lost. + + +Troubleshooting +--------------- +If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by deleting the DRMed ebook from calibre and then trying to add the ebook to calibre in debug mode with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. + +On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.) + +On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run). +On Macintosh, open the Terminal application (in your Utilities folder). +On Linux open a command window. Hopefully all Linux users know how to do this. + +You should now have a text-based command-line window open. + +Type in "calibre-debug -g" (without the ") and press the return/enter key. Calibre will launch and run as normal, but with debugging information output to the terminal window. + +Import the drmed eBook into calibre in any of the the normal ways. (I usually drag&drop onto the calibre window.) + +More debug information will be written to the terminal window. + +Copy the output from the terminal window. +On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. +On Macintosh and Linux, just use the normal text select and copy commands. + +Paste the information into a comment at my blog, http://apprenticealf.wordpress.com/ describing your problem. + + +Credits +------- +The mobidedrm and erdr2pml scripts were created by The Dark Reverser +The ignobleepub, ignoblekeygen, ineptepub and adobe key scripts were created by i♥cabbages +The k4mobidedrm script and supporting scripts were written by some_updates with help from DiapDealer and Apprentice Alf, based on code by Bart Simpson (aka Skindle), CMBDTC and clarknova +The alfcrypto library was created by some_updates +The ePub encryption detection script was adapted by Apprentice Alf from a script by Paul Durrant +The DeDRM all-in-one AppleScript was created by Apprentice Alf +The DeDRM all-in-one python script was created by some_updates and Apprentice Alf + + + + +Linux Systems Only +================== + +Instructions for installing Wine, Kindle for PC, Adobe Digital Editions, Python and PyCrypto +-------------------------------------------------------------------------------------------- + +These instructions have been tested with Wine 1.4 on Ubuntu. + + 1. First download the software you're going to to have to install. + a. Kindle for PC from http://www.amazon.co.uk/gp/kindle/pc/ + b. Adobe Digital Editions 1.7.x from http://helpx.adobe.com/digital-editions/kb/cant-install-digital-editions.html + (Adobe Digital Editions 2.x doesn't work with Wine.) + c. ActivePython 2.7.X for Windows (x86) from http://www.activestate.com/activepython/downloads + d. PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto + (PyCrypto downloads as a zip file. You will need to unzip it.) + 2. Install Wine for 32-bit x86. (e.g. on Ubuntu, Open the Ubuntu Software Center, search for Wine, and install "Wine Windows Program Loader".) + 3. Run "Configure Wine", which will set up the default 'wineprefix' + 4. Run winetricks, select the default wineprefix and install component vcrun2008 + 5. Run the mis-named "Uninstall Wine Software", which also allows installation of software. + 6. Install Kindle for PC. Accept all defaults and register with your Amazon Account. + 7. Install Adobe Digital Editions. Accept all defaults and register with your Adobe ID. + 8. Install ActiveState Python 2.7.x. Accept all defaults. + 9. Install PyCrypto 2.1. Accept all defaults. + + +Instructions for getting Kindle for PC and Adobe Digital Editions default decryption keys +----------------------------------------------------------------------------------------- + +If everything has been installed in wine as above, the keys will be retrieve automatically. + +If you have a more complex wine installation, you may enter the appropriate WINEPREFIX in the configuration dialogs for Kindle for PC and Adobe Digital Editions. You can also test that you have entered the WINEPREFIX correctly by trying to add the default keys to the preferences by clicking on the green plus button in the configuration dialogs. diff --git a/Other_Tools/Additional_Tools/DumpMobiHeader_v010.py b/Other_Tools/Additional_Tools/DumpMobiHeader_v010.py deleted file mode 100644 index 6726048..0000000 --- a/Other_Tools/Additional_Tools/DumpMobiHeader_v010.py +++ /dev/null @@ -1,528 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -KF8_BOUNDARY = "BOUNDARY" -""" The section data that divides KF8 mobi ebooks. """ - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -sys.stdout=Unbuffered(sys.stdout) - -import os, getopt, struct -import imghdr - -def sortedHeaderKeys(mheader): - hdrkeys = sorted(mheader.keys(), key=lambda akey: mheader[akey][0]) - return hdrkeys - -class dumpHeaderException(Exception): - pass - -class PalmDB: - # important palmdb header offsets - unique_id_seed = 68 - number_of_pdb_records = 76 - first_pdb_record = 78 - - def __init__(self, palmdata): - self.data = palmdata - self.nsec, = struct.unpack_from('>H',self.data,PalmDB.number_of_pdb_records) - - def getsecaddr(self,secno): - secstart, = struct.unpack_from('>L', self.data, PalmDB.first_pdb_record+secno*8) - if secno == self.nsec-1: - secend = len(self.data) - else: - secend, = struct.unpack_from('>L',self.data,PalmDB.first_pdb_record+(secno+1)*8) - return secstart,secend - - def readsection(self,secno): - if secno < self.nsec: - secstart, secend = self.getsecaddr(secno) - return self.data[secstart:secend] - return '' - - def getnumsections(self): - return self.nsec - - - -class HdrParser: - # all values are packed in big endian format - mobi6_header = { - 'compression_type' : (0x00, '>H', 2), - 'fill0' : (0x02, '>H', 2), - 'text_length' : (0x04, '>L', 4), - 'text_records' : (0x08, '>H', 2), - 'max_section_size' : (0x0a, '>H', 2), - 'crypto_type' : (0x0c, '>H', 2), - 'fill1' : (0x0e, '>H', 2), - 'magic' : (0x10, '4s', 4), - 'header_length' : (0x14, '>L', 4), - 'type' : (0x18, '>L', 4), - 'codepage' : (0x1c, '>L', 4), - 'unique_id' : (0x20, '>L', 4), - 'version' : (0x24, '>L', 4), - 'metaorthindex' : (0x28, '>L', 4), - 'metainflindex' : (0x2c, '>L', 4), - 'index_names' : (0x30, '>L', 4), - 'index_keys' : (0x34, '>L', 4), - 'extra_index0' : (0x38, '>L', 4), - 'extra_index1' : (0x3c, '>L', 4), - 'extra_index2' : (0x40, '>L', 4), - 'extra_index3' : (0x44, '>L', 4), - 'extra_index4' : (0x48, '>L', 4), - 'extra_index5' : (0x4c, '>L', 4), - 'first_nontext' : (0x50, '>L', 4), - 'title_offset' : (0x54, '>L', 4), - 'title_length' : (0x58, '>L', 4), - 'language_code' : (0x5c, '>L', 4), - 'dict_in_lang' : (0x60, '>L', 4), - 'dict_out_lang' : (0x64, '>L', 4), - 'min_version' : (0x68, '>L', 4), - 'first_resc_offset' : (0x6c, '>L', 4), - 'huff_offset' : (0x70, '>L', 4), - 'huff_num' : (0x74, '>L', 4), - 'huff_tbl_offset' : (0x78, '>L', 4), - 'huff_tbl_len' : (0x7c, '>L', 4), - 'exth_flags' : (0x80, '>L', 4), - 'fill3_a' : (0x84, '>L', 4), - 'fill3_b' : (0x88, '>L', 4), - 'fill3_c' : (0x8c, '>L', 4), - 'fill3_d' : (0x90, '>L', 4), - 'fill3_e' : (0x94, '>L', 4), - 'fill3_f' : (0x98, '>L', 4), - 'fill3_g' : (0x9c, '>L', 4), - 'fill3_h' : (0xa0, '>L', 4), - 'unknown0' : (0xa4, '>L', 4), - 'drm_offset' : (0xa8, '>L', 4), - 'drm_count' : (0xac, '>L', 4), - 'drm_size' : (0xb0, '>L', 4), - 'drm_flags' : (0xb4, '>L', 4), - 'fill4_a' : (0xb8, '>L', 4), - 'fill4_b' : (0xbc, '>L', 4), - 'first_content' : (0xc0, '>H', 2), - 'last_content' : (0xc2, '>H', 2), - 'unknown0' : (0xc4, '>L', 4), - 'fcis_offset' : (0xc8, '>L', 4), - 'fcis_count' : (0xcc, '>L', 4), - 'flis_offset' : (0xd0, '>L', 4), - 'flis_count' : (0xd4, '>L', 4), - 'unknown1' : (0xd8, '>L', 4), - 'unknown2' : (0xdc, '>L', 4), - 'srcs_offset' : (0xe0, '>L', 4), - 'srcs_count' : (0xe4, '>L', 4), - 'unknown3' : (0xe8, '>L', 4), - 'unknown4' : (0xec, '>L', 4), - 'fill5' : (0xf0, '>H', 2), - 'traildata_flags' : (0xf2, '>H', 2), - 'ncx_index' : (0xf4, '>L', 4), - 'unknown5' : (0xf8, '>L', 4), - 'unknown6' : (0xfc, '>L', 4), - 'datp_offset' : (0x100, '>L', 4), - 'unknown7' : (0x104, '>L', 4), - } - - mobi8_header = { - 'compression_type' : (0x00, '>H', 2), - 'fill0' : (0x02, '>H', 2), - 'text_length' : (0x04, '>L', 4), - 'text_records' : (0x08, '>H', 2), - 'max_section_size' : (0x0a, '>H', 2), - 'crypto_type' : (0x0c, '>H', 2), - 'fill1' : (0x0e, '>H', 2), - 'magic' : (0x10, '4s', 4), - 'header_length' : (0x14, '>L', 4), - 'type' : (0x18, '>L', 4), - 'codepage' : (0x1c, '>L', 4), - 'unique_id' : (0x20, '>L', 4), - 'version' : (0x24, '>L', 4), - 'metaorthindex' : (0x28, '>L', 4), - 'metainflindex' : (0x2c, '>L', 4), - 'index_names' : (0x30, '>L', 4), - 'index_keys' : (0x34, '>L', 4), - 'extra_index0' : (0x38, '>L', 4), - 'extra_index1' : (0x3c, '>L', 4), - 'extra_index2' : (0x40, '>L', 4), - 'extra_index3' : (0x44, '>L', 4), - 'extra_index4' : (0x48, '>L', 4), - 'extra_index5' : (0x4c, '>L', 4), - 'first_nontext' : (0x50, '>L', 4), - 'title_offset' : (0x54, '>L', 4), - 'title_length' : (0x58, '>L', 4), - 'language_code' : (0x5c, '>L', 4), - 'dict_in_lang' : (0x60, '>L', 4), - 'dict_out_lang' : (0x64, '>L', 4), - 'min_version' : (0x68, '>L', 4), - 'first_resc_offset' : (0x6c, '>L', 4), - 'huff_offset' : (0x70, '>L', 4), - 'huff_num' : (0x74, '>L', 4), - 'huff_tbl_offset' : (0x78, '>L', 4), - 'huff_tbl_len' : (0x7c, '>L', 4), - 'exth_flags' : (0x80, '>L', 4), - 'fill3_a' : (0x84, '>L', 4), - 'fill3_b' : (0x88, '>L', 4), - 'fill3_c' : (0x8c, '>L', 4), - 'fill3_d' : (0x90, '>L', 4), - 'fill3_e' : (0x94, '>L', 4), - 'fill3_f' : (0x98, '>L', 4), - 'fill3_g' : (0x9c, '>L', 4), - 'fill3_h' : (0xa0, '>L', 4), - 'unknown0' : (0xa4, '>L', 4), - 'drm_offset' : (0xa8, '>L', 4), - 'drm_count' : (0xac, '>L', 4), - 'drm_size' : (0xb0, '>L', 4), - 'drm_flags' : (0xb4, '>L', 4), - 'fill4_a' : (0xb8, '>L', 4), - 'fill4_b' : (0xbc, '>L', 4), - 'fdst_offset' : (0xc0, '>L', 4), - 'fdst_flow_count' : (0xc4, '>L', 4), - 'fcis_offset' : (0xc8, '>L', 4), - 'fcis_count' : (0xcc, '>L', 4), - 'flis_offset' : (0xd0, '>L', 4), - 'flis_count' : (0xd4, '>L', 4), - 'unknown1' : (0xd8, '>L', 4), - 'unknown2' : (0xdc, '>L', 4), - 'srcs_offset' : (0xe0, '>L', 4), - 'srcs_count' : (0xe4, '>L', 4), - 'unknown3' : (0xe8, '>L', 4), - 'unknown4' : (0xec, '>L', 4), - 'fill5' : (0xf0, '>H', 2), - 'traildata_flags' : (0xf2, '>H', 2), - 'ncx_index' : (0xf4, '>L', 4), - 'fragment_index' : (0xf8, '>L', 4), - 'skeleton_index' : (0xfc, '>L', 4), - 'datp_offset' : (0x100, '>L', 4), - 'guide_index' : (0x104, '>L', 4), - } - - mobi6_header_sorted_keys = sortedHeaderKeys(mobi6_header) - mobi8_header_sorted_keys = sortedHeaderKeys(mobi8_header) - - def __init__(self, header, start): - # first 16 bytes are not part of the official mobiheader - # but we will treat it as such - # so section 0 is 16 (decimal) + self.length in total == 0x108 bytes for Mobi 8 headers - self.header = header - self.start = start - self.version, = struct.unpack_from('>L', self.header, 0x24) - self.length, = struct.unpack_from('>L',self.header, 0x14) - print "Header Version is: 0x%0x" % self.version - print "Header start position is: 0x%0x" % self.start - print "Header Length is: 0x%0x" % self.length - # if self.length != 0xf8: - # print "Error: Unexpected Header Length: 0x%0x" % self.length - self.hdr = {} - self.extra = self.header[self.length+16:] - # set it up for the proper header version - if self.version < 8: - self.mobi_header_sorted_keys = HdrParser.mobi6_header_sorted_keys - self.mobi_header = HdrParser.mobi6_header - else: - self.mobi_header_sorted_keys = HdrParser.mobi8_header_sorted_keys - self.mobi_header = HdrParser.mobi8_header - - # parse the header information - for key in self.mobi_header_sorted_keys: - (pos, format, tot_len) = self.mobi_header[key] - if pos < (self.length + 16): - val, = struct.unpack_from(format, self.header, pos) - self.hdr[key] = val - self.exth = '' - if self.hdr['exth_flags'] & 0x40: - exth_offset = self.length + 16 - self.exth = self.header[exth_offset:] - self.extra = self.header[self.length+ 16: exth_offset] - - def dumpHeaderInfo(self): - for key in self.mobi_header_sorted_keys: - (pos, format, tot_len) = self.mobi_header[key] - if pos < (self.length + 16): - if key != 'magic': - fmt_string = " Field: %20s Offset: 0x%03x Width: %d Value: 0x%0" + str(tot_len) + "x" - else: - fmt_string = " Field: %20s Offset: 0x%03x Width: %d Value: %s" - print fmt_string % (key, pos, tot_len, self.hdr[key]) - print "Extra Region Length: 0x%0x" % len(self.extra) - print "EXTH Region Length: 0x%0x" % len(self.exth) - print "EXTH MetaData" - self.dump_exth() - return - - - def dump_exth(self): - # determine text encoding - codepage = self.hdr['codepage'] - codec = 'windows-1252' - codec_map = { - 1252 : 'windows-1252', - 65001: 'utf-8', - } - if codepage in codec_map.keys(): - codec = codec_map[codepage] - if self.exth == '': - return - extheader = self.exth - id_map_strings = { - 1 : 'Drm Server Id', - 2 : 'Drm Commerce Id', - 3 : 'Drm Ebookbase Book Id', - 100 : 'Creator', - 101 : 'Publisher', - 102 : 'Imprint', - 103 : 'Description', - 104 : 'ISBN', - 105 : 'Subject', - 106 : 'Published', - 107 : 'Review', - 108 : 'Contributor', - 109 : 'Rights', - 110 : 'SubjectCode', - 111 : 'Type', - 112 : 'Source', - 113 : 'ASIN', - 114 : 'versionNumber', - 117 : 'Adult', - 118 : 'Price', - 119 : 'Currency', - 122 : 'fixed-layout', - 123 : 'book-type', - 124 : 'orientation-lock', - 126 : 'original-resolution', - 127 : 'zero-gutter', - 128 : 'zero-margin', - 129 : 'K8(129)_Masthead/Cover_Image', - 132 : 'RegionMagnification', - 200 : 'DictShortName', - 208 : 'Watermark', - 501 : 'CDE_Type', - 502 : 'last_update_time', - 503 : 'Updated_Title', - 504 : 'ASIN_(504)', - 524 : 'Language_(524)', - 525 : 'TextDirection', - 528 : 'Unknown_Logical_Value_(528)', - 535 : 'Kindlegen_BuildRev_Number', - - } - id_map_values = { - 115 : 'sample', - 116 : 'StartOffset', - 121 : 'K8(121)_Boundary_Section', - 125 : 'K8(125)_Count_of_Resources_Fonts_Images', - 131 : 'K8(131)_Unidentified_Count', - 201 : 'CoverOffset', - 202 : 'ThumbOffset', - 203 : 'Fake Cover', - 204 : 'Creator Software', - 205 : 'Creator Major Version', - 206 : 'Creator Minor Version', - 207 : 'Creator Build Number', - 401 : 'Clipping Limit', - 402 : 'Publisher Limit', - 404 : 'Text to Speech Disabled', - } - id_map_hexstrings = { - 209 : 'Tamper Proof Keys (hex)', - 300 : 'Font Signature (hex)', - } - _length, num_items = struct.unpack('>LL', extheader[4:12]) - extheader = extheader[12:] - pos = 0 - for _ in range(num_items): - id, size = struct.unpack('>LL', extheader[pos:pos+8]) - content = extheader[pos + 8: pos + size] - if id in id_map_strings.keys(): - name = id_map_strings[id] - print '\n Key: "%s"\n Value: "%s"' % (name, unicode(content, codec).encode("utf-8")) - elif id in id_map_values.keys(): - name = id_map_values[id] - if size == 9: - value, = struct.unpack('B',content) - print '\n Key: "%s"\n Value: 0x%01x' % (name, value) - elif size == 10: - value, = struct.unpack('>H',content) - print '\n Key: "%s"\n Value: 0x%02x' % (name, value) - elif size == 12: - value, = struct.unpack('>L',content) - print '\n Key: "%s"\n Value: 0x%04x' % (name, value) - else: - print "\nError: Value for %s has unexpected size of %s" % (name, size) - elif id in id_map_hexstrings.keys(): - name = id_map_hexstrings[id] - print '\n Key: "%s"\n Value: 0x%s' % (name, content.encode('hex')) - else: - print "\nWarning: Unknown metadata with id %s found" % id - name = str(id) + ' (hex)' - print ' Key: "%s"\n Value: 0x%s' % (name, content.encode('hex')) - pos += size - return - - -def usage(progname): - print "" - print "Description:" - print " Dump all mobi headers in the mobi ebook file as generated by the latest kindlegen" - print " " - print "Usage:" - print " %s -h infile.mobi" % progname - print " " - print "Options:" - print " -h print this help message" - - -def main(argv=sys.argv): - print "DumpMobiHeader v010" - progname = os.path.basename(argv[0]) - try: - opts, args = getopt.getopt(sys.argv[1:], "h") - except getopt.GetoptError, err: - print str(err) - usage(progname) - sys.exit(2) - - if len(args) != 1: - usage(progname) - sys.exit(2) - - for o, a in opts: - if o == "-h": - usage(progname) - sys.exit(0) - - infile = args[0] - infileext = os.path.splitext(infile)[1].upper() - print infile, infileext - if infileext not in ['.MOBI', '.PRC', '.AZW', '.AZW3','.AZW4']: - print "Error: first parameter must be a Kindle/Mobipocket ebook." - return 1 - - try: - # make sure it is really a mobi ebook - mobidata = file(infile, 'rb').read() - palmheader = mobidata[0:78] - ident = palmheader[0x3C:0x3C+8] - if ident != 'BOOKMOBI': - raise dumpHeaderException('invalid file format') - - headers = {} - - pp = PalmDB(mobidata) - header = pp.readsection(0) - - print "\n\nFirst Header Dump from Section %d" % 0 - hp = HdrParser(header, 0) - hp.dumpHeaderInfo() - headers[0] = hp - - - # next determine if this is a combo (dual) KF8 mobi file - # we could examine the metadata for exth_121 in the old mobi header - # but it is just as quick to scan the palmdb for the boundary section - n = pp.getnumsections() - for i in xrange(n): - before, after = pp.getsecaddr(i) - if (after - before) == 8: - data = pp.readsection(i) - if data == KF8_BOUNDARY: - header = pp.readsection(i+1) - print "\n\nMobi Ebook uses the new dual mobi/KF8 file format" - print "\nSecond Header Dump from Section %d" % (i+1) - hp = HdrParser(header, i+1) - hp.dumpHeaderInfo() - headers[i+1] = hp - break - - # now dump a basic sector map of the palmdb - n = pp.getnumsections() - dtmap = { - "FLIS": "FLIS", - "FCIS": "FCIS", - "FDST": "FDST", - "DATP": "DATP", - "BOUN": "BOUNDARY", - "FONT": "FONT", - "RESC": "RESC", - chr(0xe9) + chr(0x8e) + "\r\n" : "EOF_RECORD", - } - indmap = { - "INDX" : "INDX", - "IDXT" : "IDXT" - } - boundary = -1 - tr = -1 - off = -1 - hp = None - secmap = {} - print "\nMap of Palm DB Sections" - print " Dec - Hex : Description" - print " ---- - ---- -----------" - for i in xrange(n): - before, after = pp.getsecaddr(i) - data = pp.readsection(i) - dt = data[0:4] - desc = '' - imgtype = imghdr.what(None, data) - if i in headers.keys(): - hp = headers[i] - off = i - version = hp.hdr['version'] - desc = "HEADER %d" % version - # update known section map - tr = hp.hdr['text_records'] - for j in xrange(tr): - secmap[j + off + 1] = "Text Record %d" % j - ncx_index = hp.hdr.get('ncx_index', 0xffffffff) - if ncx_index != 0xffffffff: - secmap[ncx_index + off] = "NCX Index 0" - secmap[ncx_index + off + 1] = "NCX Index 1" - secmap[ncx_index + off + 2] = "NCX Index CNX" - skel_index = hp.hdr.get('skeleton_index', 0xffffffff) - if skel_index != 0xffffffff: - secmap[skel_index + off] = "Skeleton Index 0" - secmap[skel_index + off + 1] = "Skeleton Index_Index 1" - frag_index = hp.hdr.get('fragment_index', 0xffffffff) - if frag_index != 0xffffffff: - secmap[frag_index + off] = "Fragment Index 0" - secmap[frag_index + off + 1] = "Fragment Index 1" - secmap[frag_index + off + 2] = "Fragment Index CNX" - guide_index = hp.hdr.get('guide_index', 0xffffffff) - if guide_index != 0xffffffff: - secmap[guide_index + off] = "Guide Index 0" - secmap[guide_index + off + 1] = "Guide Index 1" - secmap[guide_index + off + 2] = "Guide Index CNX" - srcs_offset = hp.hdr.get('srcs_offset', 0xffffffff) - if srcs_offset != 0xffffffff: - srcs_count = hp.hdr['srcs_count'] - for j in xrange(srcs_count): - secmap[j + srcs_offset + off] = 'Source Archive %d' % j - elif i in secmap.keys(): - desc = secmap[i] - elif dt in dtmap.keys(): - desc = dtmap[dt] - elif dt in indmap.keys(): - desc = "Index" - elif imgtype is not None: - desc = "Image " + imgtype - else: - desc = dt.encode('hex') - print " %04d - %04x: %s" % (i, i, desc) - - except Exception, e: - print "Error: %s" % e - return 1 - - return 0 - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/FindTopazEbooks.pyw b/Other_Tools/Additional_Tools/FindTopazEbooks.pyw deleted file mode 100755 index e39025b..0000000 --- a/Other_Tools/Additional_Tools/FindTopazEbooks.pyw +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env python - -# This is a simple tool to identify all Amazon Topaz ebooks in a specific directory. -# There always seems to be confusion since Topaz books downloaded to K4PC/Mac can have -# almost any extension (.azw, .azw1, .prc, tpz). While the .azw1 and .tpz extensions -# are fairly easy to indentify, the others are not (without opening the files in an editor). - -# To run the tool with the GUI frontend, just double-click on the 'FindTopazFiles.pyw' file -# and select the folder where all of the ebooks in question are located. Then click 'Search'. -# The program will list the file names of the ebooks that are indentified as being Topaz. -# You can then isolate those books and use the Topaz tools to decrypt and convert them. - -# You can also run the script from a command line... supplying the folder to search -# as a parameter: python FindTopazEbooks.pyw "C:\My Folder" (change appropriately for -# your particular O.S.) - -# ** NOTE: This program does NOT decrypt or modify Topaz files in any way. It simply identifies them. - -# PLEASE DO NOT PIRATE EBOOKS! - -# We want all authors and publishers, and eBook stores to live -# long and prosperous lives but at the same time we just want to -# be able to read OUR books on whatever device we want and to keep -# readable for a long, long time - -# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, -# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates -# and many many others - -# Revision history: -# 1 - Initial release. - -from __future__ import with_statement - -__license__ = 'GPL v3' - -import sys -import os -os.environ['PYTHONIOENCODING'] = "utf-8" -import re -import shutil -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - - -class ScrolledText(Tkinter.Text): - def __init__(self, master=None, **kw): - self.frame = Tkinter.Frame(master) - self.vbar = Tkinter.Scrollbar(self.frame) - self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) - kw.update({'yscrollcommand': self.vbar.set}) - Tkinter.Text.__init__(self, self.frame, **kw) - self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) - self.vbar['command'] = self.yview - # Copy geometry methods of self.frame without overriding Text - # methods = hack! - text_meths = vars(Tkinter.Text).keys() - methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() - methods = set(methods).difference(text_meths) - for m in methods: - if m[0] != '_' and m != 'config' and m != 'configure': - setattr(self, m, getattr(self.frame, m)) - - def __str__(self): - return str(self.frame) - - -def cli_main(argv=sys.argv, obj=None): - progname = os.path.basename(argv[0]) - if len(argv) != 2: - print "usage: %s DIRECTORY" % (progname,) - return 1 - - if obj == None: - print "\nTopaz search results:\n" - else: - obj.stext.insert(Tkconstants.END,"Topaz search results:\n\n") - - inpath = argv[1] - files = os.listdir(inpath) - filefilter = re.compile("(\.azw$)|(\.azw1$)|(\.prc$)|(\.tpz$)", re.IGNORECASE) - files = filter(filefilter.search, files) - - if files: - topazcount = 0 - totalcount = 0 - for filename in files: - with open(os.path.join(inpath, filename), 'rb') as f: - try: - if f.read().startswith('TPZ'): - f.close() - basename, extension = os.path.splitext(filename) - if obj == None: - print " %s is a Topaz formatted ebook." % filename - """ - if extension == '.azw' or extension == '.prc': - print " renaming to %s" % (basename + '.tpz') - shutil.move(os.path.join(inpath, filename), - os.path.join(inpath, basename + '.tpz')) - """ - else: - msg1 = " %s is a Topaz formatted ebook.\n" % filename - obj.stext.insert(Tkconstants.END,msg1) - """ - if extension == '.azw' or extension == '.prc': - msg2 = " renaming to %s\n" % (basename + '.tpz') - obj.stext.insert(Tkconstants.END,msg2) - shutil.move(os.path.join(inpath, filename), - os.path.join(inpath, basename + '.tpz')) - """ - topazcount += 1 - except: - if obj == None: - print " Error reading %s." % filename - else: - msg = " Error reading or %s.\n" % filename - obj.stext.insert(Tkconstants.END,msg) - pass - totalcount += 1 - if topazcount == 0: - if obj == None: - print "\nNo Topaz books found in %s." % inpath - else: - msg = "\nNo Topaz books found in %s.\n\n" % inpath - obj.stext.insert(Tkconstants.END,msg) - else: - if obj == None: - print "\n%i Topaz books found in %s\n%i total books checked.\n" % (topazcount, inpath, totalcount) - else: - msg = "\n%i Topaz books found in %s\n%i total books checked.\n\n" %(topazcount, inpath, totalcount) - obj.stext.insert(Tkconstants.END,msg) - else: - if obj == None: - print "No typical Topaz file extensions found in %s.\n" % inpath - else: - msg = "No typical Topaz file extensions found in %s.\n\n" % inpath - obj.stext.insert(Tkconstants.END,msg) - - return 0 - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - ltext='Search a directory for Topaz eBooks\n' - self.status = Tkinter.Label(self, text=ltext) - self.status.pack(fill=Tkconstants.X, expand=1) - body = Tkinter.Frame(self) - body.pack(fill=Tkconstants.X, expand=1) - sticky = Tkconstants.E + Tkconstants.W - body.grid_columnconfigure(1, weight=2) - Tkinter.Label(body, text='Directory to Search').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - msg1 = 'Topaz search results \n\n' - self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, - height=15, width=60, wrap=Tkconstants.WORD) - self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky) - #self.stext.insert(Tkconstants.END,msg1) - buttons = Tkinter.Frame(self) - buttons.pack() - - - self.botton = Tkinter.Button( - buttons, text="Search", width=10, command=self.search) - self.botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - self.button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - self.button.pack(side=Tkconstants.RIGHT) - - def get_inpath(self): - cwd = os.getcwdu() - cwd = cwd.encode('utf-8') - inpath = tkFileDialog.askdirectory( - parent=None, title='Directory to search', - initialdir=cwd, initialfile=None) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - - def search(self): - inpath = self.inpath.get() - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified directory does not exist' - return - argv = [sys.argv[0], inpath] - self.status['text'] = 'Searching...' - self.botton.configure(state='disabled') - cli_main(argv, self) - self.status['text'] = 'Search a directory for Topaz files' - self.botton.configure(state='normal') - - return - - -def gui_main(): - root = Tkinter.Tk() - root.title('Topaz eBook Finder') - root.resizable(True, False) - root.minsize(370, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/Other_Tools/Additional_Tools/KindleExplode.py b/Other_Tools/Additional_Tools/KindleExplode.py deleted file mode 100644 index 6726048..0000000 --- a/Other_Tools/Additional_Tools/KindleExplode.py +++ /dev/null @@ -1,528 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -KF8_BOUNDARY = "BOUNDARY" -""" The section data that divides KF8 mobi ebooks. """ - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -sys.stdout=Unbuffered(sys.stdout) - -import os, getopt, struct -import imghdr - -def sortedHeaderKeys(mheader): - hdrkeys = sorted(mheader.keys(), key=lambda akey: mheader[akey][0]) - return hdrkeys - -class dumpHeaderException(Exception): - pass - -class PalmDB: - # important palmdb header offsets - unique_id_seed = 68 - number_of_pdb_records = 76 - first_pdb_record = 78 - - def __init__(self, palmdata): - self.data = palmdata - self.nsec, = struct.unpack_from('>H',self.data,PalmDB.number_of_pdb_records) - - def getsecaddr(self,secno): - secstart, = struct.unpack_from('>L', self.data, PalmDB.first_pdb_record+secno*8) - if secno == self.nsec-1: - secend = len(self.data) - else: - secend, = struct.unpack_from('>L',self.data,PalmDB.first_pdb_record+(secno+1)*8) - return secstart,secend - - def readsection(self,secno): - if secno < self.nsec: - secstart, secend = self.getsecaddr(secno) - return self.data[secstart:secend] - return '' - - def getnumsections(self): - return self.nsec - - - -class HdrParser: - # all values are packed in big endian format - mobi6_header = { - 'compression_type' : (0x00, '>H', 2), - 'fill0' : (0x02, '>H', 2), - 'text_length' : (0x04, '>L', 4), - 'text_records' : (0x08, '>H', 2), - 'max_section_size' : (0x0a, '>H', 2), - 'crypto_type' : (0x0c, '>H', 2), - 'fill1' : (0x0e, '>H', 2), - 'magic' : (0x10, '4s', 4), - 'header_length' : (0x14, '>L', 4), - 'type' : (0x18, '>L', 4), - 'codepage' : (0x1c, '>L', 4), - 'unique_id' : (0x20, '>L', 4), - 'version' : (0x24, '>L', 4), - 'metaorthindex' : (0x28, '>L', 4), - 'metainflindex' : (0x2c, '>L', 4), - 'index_names' : (0x30, '>L', 4), - 'index_keys' : (0x34, '>L', 4), - 'extra_index0' : (0x38, '>L', 4), - 'extra_index1' : (0x3c, '>L', 4), - 'extra_index2' : (0x40, '>L', 4), - 'extra_index3' : (0x44, '>L', 4), - 'extra_index4' : (0x48, '>L', 4), - 'extra_index5' : (0x4c, '>L', 4), - 'first_nontext' : (0x50, '>L', 4), - 'title_offset' : (0x54, '>L', 4), - 'title_length' : (0x58, '>L', 4), - 'language_code' : (0x5c, '>L', 4), - 'dict_in_lang' : (0x60, '>L', 4), - 'dict_out_lang' : (0x64, '>L', 4), - 'min_version' : (0x68, '>L', 4), - 'first_resc_offset' : (0x6c, '>L', 4), - 'huff_offset' : (0x70, '>L', 4), - 'huff_num' : (0x74, '>L', 4), - 'huff_tbl_offset' : (0x78, '>L', 4), - 'huff_tbl_len' : (0x7c, '>L', 4), - 'exth_flags' : (0x80, '>L', 4), - 'fill3_a' : (0x84, '>L', 4), - 'fill3_b' : (0x88, '>L', 4), - 'fill3_c' : (0x8c, '>L', 4), - 'fill3_d' : (0x90, '>L', 4), - 'fill3_e' : (0x94, '>L', 4), - 'fill3_f' : (0x98, '>L', 4), - 'fill3_g' : (0x9c, '>L', 4), - 'fill3_h' : (0xa0, '>L', 4), - 'unknown0' : (0xa4, '>L', 4), - 'drm_offset' : (0xa8, '>L', 4), - 'drm_count' : (0xac, '>L', 4), - 'drm_size' : (0xb0, '>L', 4), - 'drm_flags' : (0xb4, '>L', 4), - 'fill4_a' : (0xb8, '>L', 4), - 'fill4_b' : (0xbc, '>L', 4), - 'first_content' : (0xc0, '>H', 2), - 'last_content' : (0xc2, '>H', 2), - 'unknown0' : (0xc4, '>L', 4), - 'fcis_offset' : (0xc8, '>L', 4), - 'fcis_count' : (0xcc, '>L', 4), - 'flis_offset' : (0xd0, '>L', 4), - 'flis_count' : (0xd4, '>L', 4), - 'unknown1' : (0xd8, '>L', 4), - 'unknown2' : (0xdc, '>L', 4), - 'srcs_offset' : (0xe0, '>L', 4), - 'srcs_count' : (0xe4, '>L', 4), - 'unknown3' : (0xe8, '>L', 4), - 'unknown4' : (0xec, '>L', 4), - 'fill5' : (0xf0, '>H', 2), - 'traildata_flags' : (0xf2, '>H', 2), - 'ncx_index' : (0xf4, '>L', 4), - 'unknown5' : (0xf8, '>L', 4), - 'unknown6' : (0xfc, '>L', 4), - 'datp_offset' : (0x100, '>L', 4), - 'unknown7' : (0x104, '>L', 4), - } - - mobi8_header = { - 'compression_type' : (0x00, '>H', 2), - 'fill0' : (0x02, '>H', 2), - 'text_length' : (0x04, '>L', 4), - 'text_records' : (0x08, '>H', 2), - 'max_section_size' : (0x0a, '>H', 2), - 'crypto_type' : (0x0c, '>H', 2), - 'fill1' : (0x0e, '>H', 2), - 'magic' : (0x10, '4s', 4), - 'header_length' : (0x14, '>L', 4), - 'type' : (0x18, '>L', 4), - 'codepage' : (0x1c, '>L', 4), - 'unique_id' : (0x20, '>L', 4), - 'version' : (0x24, '>L', 4), - 'metaorthindex' : (0x28, '>L', 4), - 'metainflindex' : (0x2c, '>L', 4), - 'index_names' : (0x30, '>L', 4), - 'index_keys' : (0x34, '>L', 4), - 'extra_index0' : (0x38, '>L', 4), - 'extra_index1' : (0x3c, '>L', 4), - 'extra_index2' : (0x40, '>L', 4), - 'extra_index3' : (0x44, '>L', 4), - 'extra_index4' : (0x48, '>L', 4), - 'extra_index5' : (0x4c, '>L', 4), - 'first_nontext' : (0x50, '>L', 4), - 'title_offset' : (0x54, '>L', 4), - 'title_length' : (0x58, '>L', 4), - 'language_code' : (0x5c, '>L', 4), - 'dict_in_lang' : (0x60, '>L', 4), - 'dict_out_lang' : (0x64, '>L', 4), - 'min_version' : (0x68, '>L', 4), - 'first_resc_offset' : (0x6c, '>L', 4), - 'huff_offset' : (0x70, '>L', 4), - 'huff_num' : (0x74, '>L', 4), - 'huff_tbl_offset' : (0x78, '>L', 4), - 'huff_tbl_len' : (0x7c, '>L', 4), - 'exth_flags' : (0x80, '>L', 4), - 'fill3_a' : (0x84, '>L', 4), - 'fill3_b' : (0x88, '>L', 4), - 'fill3_c' : (0x8c, '>L', 4), - 'fill3_d' : (0x90, '>L', 4), - 'fill3_e' : (0x94, '>L', 4), - 'fill3_f' : (0x98, '>L', 4), - 'fill3_g' : (0x9c, '>L', 4), - 'fill3_h' : (0xa0, '>L', 4), - 'unknown0' : (0xa4, '>L', 4), - 'drm_offset' : (0xa8, '>L', 4), - 'drm_count' : (0xac, '>L', 4), - 'drm_size' : (0xb0, '>L', 4), - 'drm_flags' : (0xb4, '>L', 4), - 'fill4_a' : (0xb8, '>L', 4), - 'fill4_b' : (0xbc, '>L', 4), - 'fdst_offset' : (0xc0, '>L', 4), - 'fdst_flow_count' : (0xc4, '>L', 4), - 'fcis_offset' : (0xc8, '>L', 4), - 'fcis_count' : (0xcc, '>L', 4), - 'flis_offset' : (0xd0, '>L', 4), - 'flis_count' : (0xd4, '>L', 4), - 'unknown1' : (0xd8, '>L', 4), - 'unknown2' : (0xdc, '>L', 4), - 'srcs_offset' : (0xe0, '>L', 4), - 'srcs_count' : (0xe4, '>L', 4), - 'unknown3' : (0xe8, '>L', 4), - 'unknown4' : (0xec, '>L', 4), - 'fill5' : (0xf0, '>H', 2), - 'traildata_flags' : (0xf2, '>H', 2), - 'ncx_index' : (0xf4, '>L', 4), - 'fragment_index' : (0xf8, '>L', 4), - 'skeleton_index' : (0xfc, '>L', 4), - 'datp_offset' : (0x100, '>L', 4), - 'guide_index' : (0x104, '>L', 4), - } - - mobi6_header_sorted_keys = sortedHeaderKeys(mobi6_header) - mobi8_header_sorted_keys = sortedHeaderKeys(mobi8_header) - - def __init__(self, header, start): - # first 16 bytes are not part of the official mobiheader - # but we will treat it as such - # so section 0 is 16 (decimal) + self.length in total == 0x108 bytes for Mobi 8 headers - self.header = header - self.start = start - self.version, = struct.unpack_from('>L', self.header, 0x24) - self.length, = struct.unpack_from('>L',self.header, 0x14) - print "Header Version is: 0x%0x" % self.version - print "Header start position is: 0x%0x" % self.start - print "Header Length is: 0x%0x" % self.length - # if self.length != 0xf8: - # print "Error: Unexpected Header Length: 0x%0x" % self.length - self.hdr = {} - self.extra = self.header[self.length+16:] - # set it up for the proper header version - if self.version < 8: - self.mobi_header_sorted_keys = HdrParser.mobi6_header_sorted_keys - self.mobi_header = HdrParser.mobi6_header - else: - self.mobi_header_sorted_keys = HdrParser.mobi8_header_sorted_keys - self.mobi_header = HdrParser.mobi8_header - - # parse the header information - for key in self.mobi_header_sorted_keys: - (pos, format, tot_len) = self.mobi_header[key] - if pos < (self.length + 16): - val, = struct.unpack_from(format, self.header, pos) - self.hdr[key] = val - self.exth = '' - if self.hdr['exth_flags'] & 0x40: - exth_offset = self.length + 16 - self.exth = self.header[exth_offset:] - self.extra = self.header[self.length+ 16: exth_offset] - - def dumpHeaderInfo(self): - for key in self.mobi_header_sorted_keys: - (pos, format, tot_len) = self.mobi_header[key] - if pos < (self.length + 16): - if key != 'magic': - fmt_string = " Field: %20s Offset: 0x%03x Width: %d Value: 0x%0" + str(tot_len) + "x" - else: - fmt_string = " Field: %20s Offset: 0x%03x Width: %d Value: %s" - print fmt_string % (key, pos, tot_len, self.hdr[key]) - print "Extra Region Length: 0x%0x" % len(self.extra) - print "EXTH Region Length: 0x%0x" % len(self.exth) - print "EXTH MetaData" - self.dump_exth() - return - - - def dump_exth(self): - # determine text encoding - codepage = self.hdr['codepage'] - codec = 'windows-1252' - codec_map = { - 1252 : 'windows-1252', - 65001: 'utf-8', - } - if codepage in codec_map.keys(): - codec = codec_map[codepage] - if self.exth == '': - return - extheader = self.exth - id_map_strings = { - 1 : 'Drm Server Id', - 2 : 'Drm Commerce Id', - 3 : 'Drm Ebookbase Book Id', - 100 : 'Creator', - 101 : 'Publisher', - 102 : 'Imprint', - 103 : 'Description', - 104 : 'ISBN', - 105 : 'Subject', - 106 : 'Published', - 107 : 'Review', - 108 : 'Contributor', - 109 : 'Rights', - 110 : 'SubjectCode', - 111 : 'Type', - 112 : 'Source', - 113 : 'ASIN', - 114 : 'versionNumber', - 117 : 'Adult', - 118 : 'Price', - 119 : 'Currency', - 122 : 'fixed-layout', - 123 : 'book-type', - 124 : 'orientation-lock', - 126 : 'original-resolution', - 127 : 'zero-gutter', - 128 : 'zero-margin', - 129 : 'K8(129)_Masthead/Cover_Image', - 132 : 'RegionMagnification', - 200 : 'DictShortName', - 208 : 'Watermark', - 501 : 'CDE_Type', - 502 : 'last_update_time', - 503 : 'Updated_Title', - 504 : 'ASIN_(504)', - 524 : 'Language_(524)', - 525 : 'TextDirection', - 528 : 'Unknown_Logical_Value_(528)', - 535 : 'Kindlegen_BuildRev_Number', - - } - id_map_values = { - 115 : 'sample', - 116 : 'StartOffset', - 121 : 'K8(121)_Boundary_Section', - 125 : 'K8(125)_Count_of_Resources_Fonts_Images', - 131 : 'K8(131)_Unidentified_Count', - 201 : 'CoverOffset', - 202 : 'ThumbOffset', - 203 : 'Fake Cover', - 204 : 'Creator Software', - 205 : 'Creator Major Version', - 206 : 'Creator Minor Version', - 207 : 'Creator Build Number', - 401 : 'Clipping Limit', - 402 : 'Publisher Limit', - 404 : 'Text to Speech Disabled', - } - id_map_hexstrings = { - 209 : 'Tamper Proof Keys (hex)', - 300 : 'Font Signature (hex)', - } - _length, num_items = struct.unpack('>LL', extheader[4:12]) - extheader = extheader[12:] - pos = 0 - for _ in range(num_items): - id, size = struct.unpack('>LL', extheader[pos:pos+8]) - content = extheader[pos + 8: pos + size] - if id in id_map_strings.keys(): - name = id_map_strings[id] - print '\n Key: "%s"\n Value: "%s"' % (name, unicode(content, codec).encode("utf-8")) - elif id in id_map_values.keys(): - name = id_map_values[id] - if size == 9: - value, = struct.unpack('B',content) - print '\n Key: "%s"\n Value: 0x%01x' % (name, value) - elif size == 10: - value, = struct.unpack('>H',content) - print '\n Key: "%s"\n Value: 0x%02x' % (name, value) - elif size == 12: - value, = struct.unpack('>L',content) - print '\n Key: "%s"\n Value: 0x%04x' % (name, value) - else: - print "\nError: Value for %s has unexpected size of %s" % (name, size) - elif id in id_map_hexstrings.keys(): - name = id_map_hexstrings[id] - print '\n Key: "%s"\n Value: 0x%s' % (name, content.encode('hex')) - else: - print "\nWarning: Unknown metadata with id %s found" % id - name = str(id) + ' (hex)' - print ' Key: "%s"\n Value: 0x%s' % (name, content.encode('hex')) - pos += size - return - - -def usage(progname): - print "" - print "Description:" - print " Dump all mobi headers in the mobi ebook file as generated by the latest kindlegen" - print " " - print "Usage:" - print " %s -h infile.mobi" % progname - print " " - print "Options:" - print " -h print this help message" - - -def main(argv=sys.argv): - print "DumpMobiHeader v010" - progname = os.path.basename(argv[0]) - try: - opts, args = getopt.getopt(sys.argv[1:], "h") - except getopt.GetoptError, err: - print str(err) - usage(progname) - sys.exit(2) - - if len(args) != 1: - usage(progname) - sys.exit(2) - - for o, a in opts: - if o == "-h": - usage(progname) - sys.exit(0) - - infile = args[0] - infileext = os.path.splitext(infile)[1].upper() - print infile, infileext - if infileext not in ['.MOBI', '.PRC', '.AZW', '.AZW3','.AZW4']: - print "Error: first parameter must be a Kindle/Mobipocket ebook." - return 1 - - try: - # make sure it is really a mobi ebook - mobidata = file(infile, 'rb').read() - palmheader = mobidata[0:78] - ident = palmheader[0x3C:0x3C+8] - if ident != 'BOOKMOBI': - raise dumpHeaderException('invalid file format') - - headers = {} - - pp = PalmDB(mobidata) - header = pp.readsection(0) - - print "\n\nFirst Header Dump from Section %d" % 0 - hp = HdrParser(header, 0) - hp.dumpHeaderInfo() - headers[0] = hp - - - # next determine if this is a combo (dual) KF8 mobi file - # we could examine the metadata for exth_121 in the old mobi header - # but it is just as quick to scan the palmdb for the boundary section - n = pp.getnumsections() - for i in xrange(n): - before, after = pp.getsecaddr(i) - if (after - before) == 8: - data = pp.readsection(i) - if data == KF8_BOUNDARY: - header = pp.readsection(i+1) - print "\n\nMobi Ebook uses the new dual mobi/KF8 file format" - print "\nSecond Header Dump from Section %d" % (i+1) - hp = HdrParser(header, i+1) - hp.dumpHeaderInfo() - headers[i+1] = hp - break - - # now dump a basic sector map of the palmdb - n = pp.getnumsections() - dtmap = { - "FLIS": "FLIS", - "FCIS": "FCIS", - "FDST": "FDST", - "DATP": "DATP", - "BOUN": "BOUNDARY", - "FONT": "FONT", - "RESC": "RESC", - chr(0xe9) + chr(0x8e) + "\r\n" : "EOF_RECORD", - } - indmap = { - "INDX" : "INDX", - "IDXT" : "IDXT" - } - boundary = -1 - tr = -1 - off = -1 - hp = None - secmap = {} - print "\nMap of Palm DB Sections" - print " Dec - Hex : Description" - print " ---- - ---- -----------" - for i in xrange(n): - before, after = pp.getsecaddr(i) - data = pp.readsection(i) - dt = data[0:4] - desc = '' - imgtype = imghdr.what(None, data) - if i in headers.keys(): - hp = headers[i] - off = i - version = hp.hdr['version'] - desc = "HEADER %d" % version - # update known section map - tr = hp.hdr['text_records'] - for j in xrange(tr): - secmap[j + off + 1] = "Text Record %d" % j - ncx_index = hp.hdr.get('ncx_index', 0xffffffff) - if ncx_index != 0xffffffff: - secmap[ncx_index + off] = "NCX Index 0" - secmap[ncx_index + off + 1] = "NCX Index 1" - secmap[ncx_index + off + 2] = "NCX Index CNX" - skel_index = hp.hdr.get('skeleton_index', 0xffffffff) - if skel_index != 0xffffffff: - secmap[skel_index + off] = "Skeleton Index 0" - secmap[skel_index + off + 1] = "Skeleton Index_Index 1" - frag_index = hp.hdr.get('fragment_index', 0xffffffff) - if frag_index != 0xffffffff: - secmap[frag_index + off] = "Fragment Index 0" - secmap[frag_index + off + 1] = "Fragment Index 1" - secmap[frag_index + off + 2] = "Fragment Index CNX" - guide_index = hp.hdr.get('guide_index', 0xffffffff) - if guide_index != 0xffffffff: - secmap[guide_index + off] = "Guide Index 0" - secmap[guide_index + off + 1] = "Guide Index 1" - secmap[guide_index + off + 2] = "Guide Index CNX" - srcs_offset = hp.hdr.get('srcs_offset', 0xffffffff) - if srcs_offset != 0xffffffff: - srcs_count = hp.hdr['srcs_count'] - for j in xrange(srcs_count): - secmap[j + srcs_offset + off] = 'Source Archive %d' % j - elif i in secmap.keys(): - desc = secmap[i] - elif dt in dtmap.keys(): - desc = dtmap[dt] - elif dt in indmap.keys(): - desc = "Index" - elif imgtype is not None: - desc = "Image " + imgtype - else: - desc = dt.encode('hex') - print " %04d - %04x: %s" % (i, i, desc) - - except Exception, e: - print "Error: %s" % e - return 1 - - return 0 - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/KindlePID.pyw b/Other_Tools/Additional_Tools/KindlePID.pyw deleted file mode 100755 index ae3fb8a..0000000 --- a/Other_Tools/Additional_Tools/KindlePID.pyw +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import sys -sys.path.append('lib') -import os, os.path, urllib -os.environ['PYTHONIOENCODING'] = "utf-8" -import subprocess -from subprocess import Popen, PIPE, STDOUT -import subasyncio -from subasyncio import Process -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox -from scrolltextwidget import ScrolledText - -class MainDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.root = root - self.interval = 2000 - self.p2 = None - self.status = Tkinter.Label(self, text='Find your Kindle PID') - 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='Kindle Serial # or iPhone UDID').grid(row=1, sticky=Tkconstants.E) - self.serialnum = Tkinter.StringVar() - self.serialinfo = Tkinter.Entry(body, width=45, textvariable=self.serialnum) - self.serialinfo.grid(row=1, 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) - self.stext.grid(row=3, column=0, columnspan=2,sticky=sticky) - self.stext.insert(Tkconstants.END,msg1) - - buttons = Tkinter.Frame(self) - buttons.pack() - self.sbotton = Tkinter.Button( - buttons, text="Start", width=10, command=self.convertit) - self.sbotton.pack(side=Tkconstants.LEFT) - - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - self.qbutton = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quitting) - self.qbutton.pack(side=Tkconstants.RIGHT) - - # read from subprocess pipe without blocking - # invoked every interval via the widget "after" - # option being used, so need to reset it for the next time - def processPipe(self): - poll = self.p2.wait('nowait') - if poll != None: - text = self.p2.readerr() - text += self.p2.read() - msg = text + '\n\n' + 'Kindle PID Successfully Determined\n' - if poll != 0: - msg = text + '\n\n' + 'Error: Kindle PID Failed\n' - self.showCmdOutput(msg) - self.p2 = None - self.sbotton.configure(state='normal') - return - text = self.p2.readerr() - text += self.p2.read() - self.showCmdOutput(text) - # make sure we get invoked again by event loop after interval - self.stext.after(self.interval,self.processPipe) - return - - # post output from subprocess in scrolled text widget - def showCmdOutput(self, msg): - if msg and msg !='': - if sys.platform.startswith('win'): - msg = msg.replace('\r\n','\n') - self.stext.insert(Tkconstants.END,msg) - self.stext.yview_pickplace(Tkconstants.END) - return - - # run as a subprocess via pipes and collect stdout - def pidrdr(self, serial): - # os.putenv('PYTHONUNBUFFERED', '1') - pengine = sys.executable - if pengine is None or pengine == '': - pengine = "python" - pengine = os.path.normpath(pengine) - cmdline = pengine + ' ./lib/kindlepid.py "' + serial + '"' - 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\kindlepid.py "' + serial + '"' - # else : - # cmdline = 'lib\kindlepid.py "' + serial + '"' - cmdline = pengine + ' lib\\kindlepid.py "' + serial + '"' - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) - return p2 - - def quitting(self): - # kill any still running subprocess - if self.p2 != None: - if (self.p2.wait('nowait') == None): - self.p2.terminate() - self.root.destroy() - - # actually ready to run the subprocess and get its output - def convertit(self): - # now disable the button to prevent multiple launches - self.sbotton.configure(state='disabled') - serial = self.serialinfo.get() - if not serial or serial == '': - self.status['text'] = 'No Kindle Serial Number or iPhone UDID specified' - self.sbotton.configure(state='normal') - return - - log = 'Command = "python kindlepid.py"\n' - log += 'Serial = "' + serial + '"\n' - log += '\n\n' - log += 'Please Wait ...\n\n' - self.stext.insert(Tkconstants.END,log) - self.p2 = self.pidrdr(serial) - - # python does not seem to allow you to create - # your own eventloop which every other gui does - strange - # so need to use the widget "after" command to force - # event loop to run non-gui events every interval - self.stext.after(self.interval,self.processPipe) - return - - -def main(argv=None): - root = Tkinter.Tk() - root.title('Kindle and iPhone PID Calculator') - root.resizable(True, False) - root.minsize(300, 0) - MainDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/MobiML2HTML.zip b/Other_Tools/Additional_Tools/MobiML2HTML.zip deleted file mode 100644 index f197538..0000000 Binary files a/Other_Tools/Additional_Tools/MobiML2HTML.zip and /dev/null differ diff --git a/Other_Tools/Additional_Tools/Mobi_Unpack_v056.zip b/Other_Tools/Additional_Tools/Mobi_Unpack_v056.zip deleted file mode 100644 index 23800b3..0000000 Binary files a/Other_Tools/Additional_Tools/Mobi_Unpack_v056.zip and /dev/null differ diff --git a/Other_Tools/Additional_Tools/kindlestrip_v134.py b/Other_Tools/Additional_Tools/kindlestrip_v134.py deleted file mode 100755 index caec0dc..0000000 --- a/Other_Tools/Additional_Tools/kindlestrip_v134.py +++ /dev/null @@ -1,232 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# This script strips the penultimate record from a Mobipocket file. -# This is useful because the current KindleGen add a compressed copy -# of the source files used in this record, making the ebook produced -# about twice as big as it needs to be. -# -# -# This is free and unencumbered software released into the public domain. -# -# Anyone is free to copy, modify, publish, use, compile, sell, or -# distribute this software, either in source code form or as a compiled -# binary, for any purpose, commercial or non-commercial, and by any -# means. -# -# In jurisdictions that recognize copyright laws, the author or authors -# of this software dedicate any and all copyright interest in the -# software to the public domain. We make this dedication for the benefit -# of the public at large and to the detriment of our heirs and -# successors. We intend this dedication to be an overt act of -# relinquishment in perpetuity of all present and future rights to this -# software under copyright law. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -# -# For more information, please refer to -# -# Written by Paul Durrant, 2010-2011, paul@durrant.co.uk, pdurrant on mobileread.com -# With enhancements by Kevin Hendricks, KevinH on mobileread.com -# -# Changelog -# 1.00 - Initial version -# 1.10 - Added an option to output the stripped data -# 1.20 - Added check for source files section (thanks Piquan) -# 1.30 - Added prelim Support for K8 style mobis -# 1.31 - removed the SRCS section but kept a 0 size entry for it -# 1.32 - removes the SRCS section and its entry, now updates metadata 121 if needed -# 1.33 - now uses and modifies mobiheader SRCS and CNT -# 1.34 - added credit for Kevin Hendricks - -__version__ = '1.34' - -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 StripException(Exception): - pass - - -class SectionStripper: - 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 strip(self, off, len): - self.data_file = self.data_file[:off] + self.data_file[off+len:] - - 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 updateEXTH121(self, srcs_secnum, srcs_cnt, mobiheader): - mobi_length, = struct.unpack('>L',mobiheader[0x14:0x18]) - exth_flag, = struct.unpack('>L', mobiheader[0x80:0x84]) - exth = 'NONE' - try: - if exth_flag & 0x40: - exth = mobiheader[16 + mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - # print type, size - if type == 121: - boundaryptr, =struct.unpack('>L',exth[pos+8: pos + size]) - if srcs_secnum <= boundaryptr: - boundaryptr -= srcs_cnt - prefix = mobiheader[0:16 + mobi_length + pos + 8] - suffix = mobiheader[16 + mobi_length + pos + 8 + 4:] - nval = struct.pack('>L',boundaryptr) - mobiheader = prefix + nval + suffix - pos += size - except: - pass - return mobiheader - - def __init__(self, datain): - if datain[0x3C:0x3C+8] != 'BOOKMOBI': - raise StripException("invalid file format") - self.num_sections, = struct.unpack('>H', datain[76:78]) - - # get mobiheader and check SRCS section number and count - offset0, flgval0 = struct.unpack_from('>2L', datain, 78) - offset1, flgval1 = struct.unpack_from('>2L', datain, 86) - mobiheader = datain[offset0:offset1] - srcs_secnum, srcs_cnt = struct.unpack_from('>2L', mobiheader, 0xe0) - if srcs_secnum == 0xffffffff or srcs_cnt == 0: - raise StripException("File doesn't contain the sources section.") - - print "Found SRCS section number %d, and count %d" % (srcs_secnum, srcs_cnt) - # find its offset and length - next = srcs_secnum + srcs_cnt - srcs_offset, flgval = struct.unpack_from('>2L', datain, 78+(srcs_secnum*8)) - next_offset, flgval = struct.unpack_from('>2L', datain, 78+(next*8)) - srcs_length = next_offset - srcs_offset - if datain[srcs_offset:srcs_offset+4] != 'SRCS': - raise StripException("SRCS section num does not point to SRCS.") - print " beginning at offset %0x and ending at offset %0x" % (srcs_offset, srcs_length) - - # it appears bytes 68-71 always contain (2*num_sections) + 1 - # this is not documented anyplace at all but it appears to be some sort of next - # available unique_id used to identify specific sections in the palm db - self.data_file = datain[:68] + struct.pack('>L',((self.num_sections-srcs_cnt)*2+1)) - self.data_file += datain[72:76] - - # write out the number of sections reduced by srtcs_cnt - self.data_file = self.data_file + struct.pack('>H',self.num_sections-srcs_cnt) - - # we are going to remove srcs_cnt SRCS sections so the offset of every entry in the table - # up to the srcs secnum must begin 8 bytes earlier per section removed (each table entry is 8 ) - delta = -8 * srcs_cnt - for i in xrange(srcs_secnum): - offset, flgval = struct.unpack_from('>2L', datain, 78+(i*8)) - offset += delta - self.data_file += struct.pack('>L',offset) + struct.pack('>L',flgval) - - # for every record after the srcs_cnt SRCS records we must start it - # earlier by 8*srcs_cnt + the length of the srcs sections themselves) - delta = delta - srcs_length - for i in xrange(srcs_secnum+srcs_cnt,self.num_sections): - offset, flgval = struct.unpack_from('>2L', datain, 78+(i*8)) - offset += delta - flgval = 2 * (i - srcs_cnt) - self.data_file += struct.pack('>L',offset) + struct.pack('>L',flgval) - - # now pad it out to begin right at the first offset - # typically this is 2 bytes of nulls - first_offset, flgval = struct.unpack_from('>2L', self.data_file, 78) - self.data_file += '\0' * (first_offset - len(self.data_file)) - - # now finally add on every thing up to the original src_offset - self.data_file += datain[first_offset + 8: srcs_offset] - - # and everything afterwards - self.data_file += datain[srcs_offset+srcs_length:] - - #store away the SRCS section in case the user wants it output - self.stripped_data_header = datain[srcs_offset:srcs_offset+16] - self.stripped_data = datain[srcs_offset+16:srcs_offset+srcs_length] - - # update the number of sections count - self.num_section = self.num_sections - srcs_cnt - - # update the srcs_secnum and srcs_cnt in the mobiheader - offset0, flgval0 = struct.unpack_from('>2L', self.data_file, 78) - offset1, flgval1 = struct.unpack_from('>2L', self.data_file, 86) - mobiheader = self.data_file[offset0:offset1] - mobiheader = mobiheader[:0xe0]+ struct.pack('>L', 0xffffffff) + struct.pack('>L', 0) + mobiheader[0xe8:] - - # if K8 mobi, handle metadata 121 in old mobiheader - mobiheader = self.updateEXTH121(srcs_secnum, srcs_cnt, mobiheader) - self.data_file = self.data_file[0:offset0] + mobiheader + self.data_file[offset1:] - print "done" - - def getResult(self): - return self.data_file - - def getStrippedData(self): - return self.stripped_data - - def getHeader(self): - return self.stripped_data_header - -if __name__ == "__main__": - sys.stdout=Unbuffered(sys.stdout) - print ('KindleStrip v%(__version__)s. ' - 'Written 2010-2012 by Paul Durrant and Kevin Hendricks.' % globals()) - if len(sys.argv)<3 or len(sys.argv)>4: - print "Strips the penultimate record from Mobipocket ebooks" - print "For ebooks generated using KindleGen 1.1 and later that add the source" - print "Usage:" - print " %s " % sys.argv[0] - print " is optional." - sys.exit(1) - else: - infile = sys.argv[1] - outfile = sys.argv[2] - data_file = file(infile, 'rb').read() - try: - strippedFile = SectionStripper(data_file) - file(outfile, 'wb').write(strippedFile.getResult()) - print "Header Bytes: " + binascii.b2a_hex(strippedFile.getHeader()) - if len(sys.argv)==4: - file(sys.argv[3], 'wb').write(strippedFile.getStrippedData()) - except StripException, e: - print "Error: %s" % e - sys.exit(1) - sys.exit(0) diff --git a/Other_Tools/Additional_Tools/lib/kindlepid.py b/Other_Tools/Additional_Tools/lib/kindlepid.py deleted file mode 100644 index 62fdc67..0000000 --- a/Other_Tools/Additional_Tools/lib/kindlepid.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/python -# Mobipocket PID calculator v0.2 for Amazon Kindle. -# Copyright (c) 2007, 2009 Igor Skochinsky -# History: -# 0.1 Initial release -# 0.2 Added support for generating PID for iPhone (thanks to mbp) -# 0.3 changed to autoflush stdout, fixed return code usage -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 binascii - -if sys.hexversion >= 0x3000000: - print "This script is incompatible with Python 3.x. Please install Python 2.6.x from python.org" - sys.exit(2) - -letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - -def crc32(s): - return (~binascii.crc32(s,-1))&0xFFFFFFFF - -def checksumPid(s): - crc = crc32(s) - 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 pidFromSerial(s, l): - crc = crc32(s) - - arr1 = [0]*l - for i in xrange(len(s)): - arr1[i%l] ^= ord(s[i]) - - crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] - for i in xrange(l): - arr1[i] ^= crc_bytes[i&3] - - pid = "" - for i in xrange(l): - b = arr1[i] & 0xff - pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] - - return pid - -def main(argv=sys.argv): - print "Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky" - if len(sys.argv)==2: - serial = sys.argv[1] - else: - print "Usage: kindlepid.py /" - return 1 - if len(serial)==16: - if serial.startswith("B"): - print "Kindle serial number detected" - else: - print "Warning: unrecognized serial number. Please recheck input." - return 1 - pid = pidFromSerial(serial,7)+"*" - print "Mobipocked PID for Kindle serial# "+serial+" is "+checksumPid(pid) - return 0 - elif len(serial)==40: - print "iPhone serial number (UDID) detected" - pid = pidFromSerial(serial,8) - print "Mobipocked PID for iPhone serial# "+serial+" is "+checksumPid(pid) - return 0 - else: - print "Warning: unrecognized serial number. Please recheck input." - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/lib/mobihuff.py b/Other_Tools/Additional_Tools/lib/mobihuff.py deleted file mode 100644 index 844b8a4..0000000 --- a/Other_Tools/Additional_Tools/lib/mobihuff.py +++ /dev/null @@ -1,189 +0,0 @@ -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Big Thanks to Igor SKOCHINSKY for providing me with all his information -# and source code relating to the inner workings of this compression scheme. -# Without it, I wouldn't be able to solve this as easily. -# -# Changelog -# 0.01 - Initial version -# 0.02 - Fix issue with size computing -# 0.03 - Fix issue with some files -# 0.04 - make stdout self flushing and fix return values - -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 struct - -class BitReader: - def __init__(self, data): - self.data, self.pos, self.nbits = data + "\x00\x00\x00\x00", 0, len(data) * 8 - def peek(self, n): - r, g = 0, 0 - while g < n: - r, g = (r << 8) | ord(self.data[(self.pos+g)>>3]), g + 8 - ((self.pos+g) & 7) - return (r >> (g - n)) & ((1 << n) - 1) - def eat(self, n): - self.pos += n - return self.pos <= self.nbits - def left(self): - return self.nbits - self.pos - -class HuffReader: - def __init__(self, huffs): - self.huffs = huffs - h = huffs[0] - if huffs[0][0:4] != 'HUFF' or huffs[0][4:8] != '\x00\x00\x00\x18': - raise ValueError('invalid huff1 header') - if huffs[1][0:4] != 'CDIC' or huffs[1][4:8] != '\x00\x00\x00\x10': - raise ValueError('invalid huff2 header') - self.entry_bits, = struct.unpack('>L', huffs[1][12:16]) - off1,off2 = struct.unpack('>LL', huffs[0][16:24]) - self.dict1 = struct.unpack('<256L', huffs[0][off1:off1+256*4]) - self.dict2 = struct.unpack('<64L', huffs[0][off2:off2+64*4]) - self.dicts = huffs[1:] - self.r = '' - - def _unpack(self, bits, depth = 0): - if depth > 32: - raise ValueError('corrupt file') - while bits.left(): - dw = bits.peek(32) - v = self.dict1[dw >> 24] - codelen = v & 0x1F - assert codelen != 0 - code = dw >> (32 - codelen) - r = (v >> 8) - if not (v & 0x80): - while code < self.dict2[(codelen-1)*2]: - codelen += 1 - code = dw >> (32 - codelen) - r = self.dict2[(codelen-1)*2+1] - r -= code - assert codelen != 0 - if not bits.eat(codelen): - return - dicno = r >> self.entry_bits - off1 = 16 + (r - (dicno << self.entry_bits)) * 2 - dic = self.dicts[dicno] - off2 = 16 + ord(dic[off1]) * 256 + ord(dic[off1+1]) - blen = ord(dic[off2]) * 256 + ord(dic[off2+1]) - slice = dic[off2+2:off2+2+(blen&0x7fff)] - if blen & 0x8000: - self.r += slice - else: - self._unpack(BitReader(slice), depth + 1) - - def unpack(self, data): - self.r = '' - self._unpack(BitReader(data)) - return self.r - -class Sectionizer: - def __init__(self, filename, ident): - self.contents = file(filename, 'rb').read() - self.header = self.contents[0:72] - self.num_sections, = struct.unpack('>H', self.contents[76:78]) - if self.header[0x3C:0x3C+8] != ident: - raise ValueError('Invalid file format') - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - def loadSection(self, section): - if section + 1 == self.num_sections: - end_off = len(self.contents) - else: - end_off = self.sections[section + 1][0] - off = self.sections[section][0] - return self.contents[off:end_off] - - -def getSizeOfTrailingDataEntry(ptr, size): - bitpos, result = 0, 0 - 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 - -def getSizeOfTrailingDataEntries(ptr, size, flags): - num = 0 - flags >>= 1 - while flags: - if flags & 1: - num += getSizeOfTrailingDataEntry(ptr, size - num) - flags >>= 1 - return num - -def unpackBook(input_file): - sect = Sectionizer(input_file, 'BOOKMOBI') - - header = sect.loadSection(0) - - crypto_type, = struct.unpack('>H', header[0xC:0xC+2]) - if crypto_type != 0: - raise ValueError('The book is encrypted. Run mobidedrm first') - - if header[0:2] != 'DH': - raise ValueError('invalid compression type') - - extra_flags, = struct.unpack('>L', header[0xF0:0xF4]) - records, = struct.unpack('>H', header[0x8:0x8+2]) - - huffoff,huffnum = struct.unpack('>LL', header[0x70:0x78]) - huffs = [sect.loadSection(i) for i in xrange(huffoff, huffoff+huffnum)] - huff = HuffReader(huffs) - - def decompressSection(nr): - data = sect.loadSection(nr) - trail_size = getSizeOfTrailingDataEntries(data, len(data), extra_flags) - return huff.unpack(data[0:len(data)-trail_size]) - - r = '' - for i in xrange(1, records+1): - r += decompressSection(i) - return r - -def main(argv=sys.argv): - print "MobiHuff v0.03" - print " Copyright (c) 2008 The Dark Reverser " - if len(sys.argv)!=3: - print "" - print "Description:" - print " Unpacks the new mobipocket huffdic compression." - print " This program works with unencrypted files only." - print "Usage:" - print " mobihuff.py infile.mobi outfile.html" - return 1 - else: - infile = sys.argv[1] - outfile = sys.argv[2] - try: - print "Decompressing...", - result = unpackBook(infile) - file(outfile, 'wb').write(result) - print "done" - except ValueError, e: - print - print "Error: %s" % e - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/lib/mobiml2html.py b/Other_Tools/Additional_Tools/lib/mobiml2html.py deleted file mode 100644 index ef3e13f..0000000 --- a/Other_Tools/Additional_Tools/lib/mobiml2html.py +++ /dev/null @@ -1,532 +0,0 @@ -#! /usr/bin/python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -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) - - -# Modified version of Calibre Mobi/reader.py -# to remove all code to unpack the mobi file -# and to remove dependencies on lxml, BeautifulSoup , etc - -# Original author is '2008, Kovid Goyal ' -# license is GPL v3 based on the original license - -# this program works in concert with mobiunpack.py - -''' -Convert from Mobi ML to XHTML -''' - -import os -import re -import struct - -class MobiMLConverter(object): - - PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE) - IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex') - - def __init__(self, filename): - self.base_css_rules = 'blockquote { margin: 0em 0em 0em 1.25em; text-align: justify }\n' - self.base_css_rules += 'p { margin: 0em; text-align: justify }\n' - self.base_css_rules += '.bold { font-weight: bold }\n' - self.base_css_rules += '.italic { font-style: italic }\n' - self.base_css_rules += '.mbp_pagebreak { page-break-after: always; margin: 0; display: block }\n' - self.tag_css_rules = {} - self.tag_css_rule_cnt = 0 - self.filename = filename - self.wipml = open(self.filename, 'rb').read() - self.orig_codec = None - self.pos = 0 - opfname = self.filename.rsplit('.',1)[0] + '.opf' - self.opf = open(opfname, 'rb').read() - self.opos = 0 - self.meta = '' - self.cssname = os.path.join(os.path.dirname(self.filename),'styles.css') - - - # now parse the opf to extract meta information - def getMetaData(self): - opftags = { - 'dc:title' : 'Title', - 'dc:creator' : 'Author', - 'dc:publisher' : 'Publisher', - 'dc:rights' : 'Rights', - 'dc:date' : 'Published', - 'dc:language' : 'Language', - 'dc:description' : 'Description', - 'dc:subject' : 'Subject', - } - idschemes = { - 'uid' : 'UniqueID', - 'ISBN': 'ISBN', - } - metadata = {} - getfield = False - content = None - while True: - r = self.parseopf() - if not r: - break - text, tag = r - if text: - if getfield: - content = text - if tag: - ttype, tname, tattr = self.parsetag(tag) - if tattr == None: tattr = {} - if tname in opftags: - if ttype == 'begin': - getfield = True - else: - name = opftags[tname] - metadata[name] = content - content = None - getfield = False - elif tname == 'dc:identifier': - if ttype == 'begin': - getfield = True - else: - scheme = tattr.get('id','uid') - name = idschemes.get(scheme,'Identifier') - metadata[name] = content - content = None - getfield = False - elif tname == 'output': - if ttype == 'begin' or ttype == 'single': - codec = tattr.get('encoding','Windows-1252') - self.orig_codec = codec - name = 'Codec' - metadata[name] = 'utf-8' - - elif tname == 'meta': - if ttype == 'begin' or ttype == 'single': - name = tattr.get('name', '') - content = tattr.get('content', '') - metadata[name] = content - - - # store the metadata as html tags - # Handle Codec and Title and then all of the remainder - self.meta += '' + metadata.get('Title','Untitled') + '\n' - self.meta += '\n' - for key in metadata.keys(): - tag = '\n' - self.meta += tag - if self.orig_codec != 'utf-8': - meta = self.meta - meta = meta.decode(self.orig_codec) - meta = meta.encode('utf-8') - self.meta = meta - - - def cleanup_html(self): - if self.orig_codec != 'utf-8': - wipml = self.wipml - wipml = wipml.decode(self.orig_codec) - wipml = wipml.encode('utf-8') - self.wipml = wipml - self.wipml = re.sub(r'

', '', self.wipml) - self.wipml = self.wipml.replace('\r\n', '\n') - self.wipml = self.wipml.replace('> <', '>\n<') - self.wipml = self.wipml.replace(']*>', '', self.wipml) - self.wipml = self.wipml.replace('

','
') - - - def replace_page_breaks(self): - self.wipml = self.PAGE_BREAK_PAT.sub( - '
\n', - self.wipml) - - # parse leading text of ml and tag - def parseml(self): - p = self.pos - if p >= len(self.wipml): - return None - if self.wipml[p] != '<': - res = self.wipml.find('<',p) - if res == -1 : - res = len(self.wipml) - self.pos = res - return self.wipml[p:res], None - tb = p - te = self.wipml.find('>',p+1) - ntb = self.wipml.find('<',p+1) - if ntb != -1 and ntb < te: - self.pos = ntb - return self.wipml[p:ntb], None - self.pos = te + 1 - return None, self.wipml[p:te+1] - - - # parse leading text of opf and tag - def parseopf(self): - p = self.opos - if p >= len(self.opf): - return None - if self.opf[p] != '<': - res = self.opf.find('<',p) - if res == -1 : - res = len(self.opf) - self.opos = res - return self.opf[p:res], None - tb = p - te = self.opf.find('>',p+1) - ntb = self.opf.find('<',p+1) - if ntb != -1 and ntb < te: - self.opos = ntb - return self.opf[p:ntb], None - self.opos = te + 1 - return None, self.opf[p:te+1] - - - - # parses string version of tag to identify its name, - # its type 'begin', 'end' or 'single', - # plus build a hashtable of its atributes - # code is written to handle the possiblity of very poor formating - def parsetag(self, s): - p = 1 - # get the tag name - tname = None - ttype = None - tattr = None - while s[p:p+1] == ' ' : p += 1 - if s[p:p+1] == '/': - ttype = 'end' - p += 1 - while s[p:p+1] == ' ' : p += 1 - b = p - while s[p:p+1] not in ('>', '/', ' ', '"') : p += 1 - tname=s[b:p].lower() - if not ttype: - - # parse any attributes - tattr = {} - while s.find('=',p) != -1 : - while s[p:p+1] == ' ' : p += 1 - b = p - while s[p:p+1] != '=' : p += 1 - aname = s[b:p].lower() - aname = aname.rstrip(' ') - p += 1 - while s[p:p+1] == ' ' : p += 1 - if s[p:p+1] == '"' : - p = p + 1 - b = p - while s[p:p+1] != '"': p += 1 - val = s[b:p] - p += 1 - else : - b = p - while s[p:p+1] not in ('>', '/', ' ') : p += 1 - val = s[b:p] - tattr[aname] = val - - if tattr and len(tattr)== 0: tattr = None - - # label beginning and single tags - if not ttype: - ttype = 'begin' - if s.find('/',p) >= 0: - ttype = 'single' - - return ttype, tname, tattr - - - # main routine to convert from mobi markup language to html - def processml(self): - - # first get the metadata from the opf file - metadata = self.getMetaData() - - # are these really needed - html_done = False - head_done = False - body_done = False - - skip = False - - htmlstr = '' - self.replace_page_breaks() - self.cleanup_html() - - # now parse the cleaned up ml into standard xhtml - while True: - - r = self.parseml() - if not r: - break - - text, tag = r - - if text: - if not skip: - htmlstr += text - - if tag: - ttype, tname, tattr = self.parsetag(tag) - - if tname in ('guide', 'ncx', 'reference', 'svg:svg','svg:image'): - if ttype == 'begin': - skip = True - else: - skip = False - else: - taginfo = (ttype, tname, tattr) - htmlstr += self.processtag(taginfo) - - # handle potential issue of multiple html, head, and body setions - if tname == 'html' and ttype == 'begin' and not html_done: - htmlstr += '\n' - html_done = True - - if tname == 'head' and ttype == 'begin' and not head_done: - htmlstr += '\n' - # also add in metadata and style link tags - htmlstr += self.meta - htmlstr += '\n' - head_done = True - - if tname == 'body' and ttype == 'begin' and not body_done: - htmlstr += '\n' - body_done = True - - - # handle issue of possiby missing html, head, and body tags - # I have not seen this but the original did something like this so ... - - if not body_done: - htmlstr = '\n' + htmlstr + '\n' - if not head_done: - headstr = '\n' - headstr += self.meta - headstr += '\n' - headstr += '\n' - htmlstr = headstr + htmlstr - if not html_done: - htmlstr = '\n' + htmlstr + '\n' - - # finally add DOCTYPE info - htmlstr = '\n' + htmlstr - - # save style sheet - with open(self.cssname, 'wb') as s: - s.write(self.base_css_rules) - for cls, rule in self.tag_css_rules.items(): - s.write('.%s { %s }\n' % (cls, rule)) - s.close() - return htmlstr - - - def ensure_unit(self, raw, unit='px'): - if re.search(r'\d+$', raw) is not None: - raw += unit - return raw - - - # flatten possibly modified tag back to string - def taginfo_tostring(self, taginfo): - (ttype, tname, tattr) = taginfo - res = '<' - if ttype == 'end': - res += '/' + tname + '>' - return res - res += tname - if tattr: - for key in tattr.keys(): - res += ' ' - res += key + '="' - res += tattr[key] + '"' - res == ' ' - if ttype == 'single': - res += ' />' - else : - res += '>' - return res - - - # routines to convert from mobi ml tags atributes to xhtml attributes and styles - def processtag(self, taginfo): - - # Converting mobi font sizes to numerics - size_map = { - 'xx-small': '0.5', - 'x-small': '1', - 'small': '2', - 'medium': '3', - 'large': '4', - 'x-large': '5', - 'xx-large': '6', - } - - - # current tag to work on - (ttype, tname, tattr) = taginfo - if not tattr: - tattr = {} - - styles = [] - - # have not seen an example of this yet so keep it here to be safe - # until this is better understood - if tname in ('country-region', 'place', 'placetype', 'placename', - 'state', 'city', 'street', 'address', 'content'): - tname = 'div' if tname == 'content' else 'span' - for key in tattr.keys(): - tattr.pop(key) - - - # handle general case of style, height, width, bgcolor in any tag - if 'style' in tattr.keys(): - style = tattr.pop('style').strip() - if style: - styles.append(style) - - if 'height' in tattr.keys(): - height = tattr.pop('height').strip() - if height and '<' not in height and '>' not in height and re.search(r'\d+', height): - if tname in ('table', 'td', 'tr'): - pass - elif tname == 'img': - tattr['height'] = height - else: - styles.append('margin-top: %s' % self.ensure_unit(height)) - - if 'width' in tattr.keys(): - width = tattr.pop('width').strip() - if width and re.search(r'\d+', width): - if tname in ('table', 'td', 'tr'): - pass - elif tname == 'img': - tattr['width'] = width - else: - styles.append('text-indent: %s' % self.ensure_unit(width)) - if width.startswith('-'): - styles.append('margin-left: %s' % self.ensure_unit(width[1:])) - - if 'align' in tattr.keys(): - align = tattr.pop('align').strip() - # print align - if align: - if tname in ('table', 'td', 'tr'): - pass - else: - styles.append('text-align: %s' % align) - - if 'bgcolor' in tattr.keys(): - # no proprietary html allowed - if tname == 'div': - del tattr['bgcolor'] - - # now handle tag specific changes - - # should not need to remap this tag in mobi markup - # if tname == 'i': - # tname = 'span' - # tattr['class'] = 'italic' - - # should not need to remap this tag in mobi markup - # elif tname == 'b': - # tname = 'span' - # tattr['class'] = 'bold' - - # should not need to remap this tag in mobi markup - # elif tname == 'pre': - - - elif tname == 'font': - sz = ' ' - if 'size' in tattr.keys(): - sz = tattr['size'].lower() - try: - float(sz) - except ValueError: - if sz in size_map.keys(): - tattr['size'] = size_map[sz] - - elif tname == 'img': - for attr in ('width', 'height'): - if attr in tattr: - val = tattr[attr] - if val.lower().endswith('em'): - try: - nval = float(val[:-2]) - nval *= 16 * (168.451/72) # Assume this was set using the Kindle profile - tattr[attr] = "%dpx"%int(nval) - except: - del tattr[attr] - elif val.lower().endswith('%'): - del tattr[attr] - - # convert the anchor tags - if 'filepos-id' in tattr: - tattr['id'] = tattr.pop('filepos-id') - if 'name' in tattr and tattr['name'] != tattr['id']: - tattr['name'] = tattr['id'] - - if 'filepos' in tattr: - filepos = tattr.pop('filepos') - try: - tattr['href'] = "#filepos%d" % int(filepos) - except ValueError: - pass - - if styles: - ncls = None - rule = '; '.join(styles) - for sel, srule in self.tag_css_rules.items(): - if srule == rule: - ncls = sel - break - if ncls is None: - self.tag_css_rule_cnt += 1 - ncls = 'rule_%d' % self.tag_css_rule_cnt - self.tag_css_rules[ncls] = rule - cls = tattr.get('class', '') - cls = cls + (' ' if cls else '') + ncls - tattr['class'] = cls - - # convert updated tag back to string representation - if len(tattr) == 0: tattr = None - taginfo = (ttype, tname, tattr) - return self.taginfo_tostring(taginfo) - - - -def main(argv=sys.argv): - if len(argv) != 2: - return 1 - else: - infile = argv[1] - - try: - print 'Converting Mobi Markup Language to XHTML' - mlc = MobiMLConverter(infile) - print 'Processing ...' - htmlstr = mlc.processml() - outname = infile.rsplit('.',1)[0] + '_converted.html' - file(outname, 'wb').write(htmlstr) - print 'Completed' - print 'XHTML version of book can be found at: ' + outname - - except ValueError, e: - print "Error: %s" % e - return 1 - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/lib/prc.py b/Other_Tools/Additional_Tools/lib/prc.py deleted file mode 100644 index 8aa678d..0000000 --- a/Other_Tools/Additional_Tools/lib/prc.py +++ /dev/null @@ -1,529 +0,0 @@ -# -# $Id: prc.py,v 1.3 2001/12/27 08:48:02 rob Exp $ -# -# Copyright 1998-2001 Rob Tillotson -# All Rights Reserved -# -# Permission to use, copy, modify, and distribute this software and -# its documentation for any purpose and without fee or royalty is -# hereby granted, provided that the above copyright notice appear in -# all copies and that both the copyright notice and this permission -# notice appear in supporting documentation or portions thereof, -# including modifications, that you you make. -# -# THE AUTHOR ROB TILLOTSON DISCLAIMS ALL WARRANTIES WITH REGARD TO -# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER -# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE! -# -"""PRC/PDB file I/O in pure Python. - - This module serves two purposes: one, it allows access to Palm OS(tm) - database files on the desktop in pure Python without requiring - pilot-link (hence, it may be useful for import/export utilities), - and two, it caches the contents of the file in memory so it can - be freely modified using an identical API to databases over a - DLP connection. -""" - -__version__ = '$Id: prc.py,v 1.3 2001/12/27 08:48:02 rob Exp $' - -__copyright__ = 'Copyright 1998-2001 Rob Tillotson ' - - -# temporary hack until we get gettext support again -def _(s): return s - -# -# DBInfo structure: -# -# int more -# unsigned int flags -# unsigned int miscflags -# unsigned long type -# unsigned long creator -# unsigned int version -# unsigned long modnum -# time_t createDate, modifydate, backupdate -# unsigned int index -# char name[34] -# -# -# DB Header: -# 32 name -# 2 flags -# 2 version -# 4 creation time -# 4 modification time -# 4 backup time -# 4 modification number -# 4 appinfo offset -# 4 sortinfo offset -# 4 type -# 4 creator -# 4 unique id seed (garbage?) -# 4 next record list id (normally 0) -# 2 num of records for this header -# (maybe 2 more bytes) -# -# Resource entry header: (if low bit of attr = 1) -# 4 type -# 2 id -# 4 offset -# -# record entry header: (if low bit of attr = 0) -# 4 offset -# 1 attributes -# 3 unique id -# -# then 2 bytes of 0 -# -# then appinfo then sortinfo -# - -import sys, os, stat, struct - -PI_HDR_SIZE = 78 -PI_RESOURCE_ENT_SIZE = 10 -PI_RECORD_ENT_SIZE = 8 - -PILOT_TIME_DELTA = 2082844800L - -flagResource = 0x0001 -flagReadOnly = 0x0002 -flagAppInfoDirty = 0x0004 -flagBackup = 0x0008 -flagOpen = 0x8000 -# 2.x -flagNewer = 0x0010 -flagReset = 0x0020 -# -flagExcludeFromSync = 0x0080 - -attrDeleted = 0x80 -attrDirty = 0x40 -attrBusy = 0x20 -attrSecret = 0x10 -attrArchived = 0x08 - -default_info = { - 'name': '', - 'type': 'DATA', - 'creator': ' ', - 'createDate': 0, - 'modifyDate': 0, - 'backupDate': 0, - 'modnum': 0, - 'version': 0, - 'flagReset': 0, - 'flagResource': 0, - 'flagNewer': 0, - 'flagExcludeFromSync': 0, - 'flagAppInfoDirty': 0, - 'flagReadOnly': 0, - 'flagBackup': 0, - 'flagOpen': 0, - 'more': 0, - 'index': 0 - } - -def null_terminated(s): - for x in range(0, len(s)): - if s[x] == '\000': return s[:x] - return s - -def trim_null(s): - return string.split(s, '\0')[0] - -def pad_null(s, l): - if len(s) > l - 1: - s = s[:l-1] - s = s + '\0' - if len(s) < l: s = s + '\0' * (l - len(s)) - return s - -# -# new stuff - -# Record object to be put in tree... -class PRecord: - def __init__(self, attr=0, id=0, category=0, raw=''): - self.raw = raw - self.id = id - self.attr = attr - self.category = category - - # comparison and hashing are done by ID; - # thus, the id value *may not be changed* once - # the object is created. - def __cmp__(self, obj): - if type(obj) == type(0): - return cmp(self.id, obj) - else: - return cmp(self.id, obj.id) - - def __hash__(self): - return self.id - -class PResource: - def __init__(self, typ=' ', id=0, raw=''): - self.raw = raw - self.id = id - self.type = typ - - def __cmp__(self, obj): - if type(obj) == type(()): - return cmp( (self.type, self.id), obj) - else: - return cmp( (self.type, self.id), (obj.type, obj.id) ) - - def __hash__(self): - return hash((self.type, self.id)) - - -class PCache: - def __init__(self): - self.data = [] - self.appblock = '' - self.sortblock = '' - self.dirty = 0 - self.next = 0 - self.info = {} - self.info.update(default_info) - # if allow_zero_ids is 1, then this prc behaves appropriately - # for a desktop database. That is, it never attempts to assign - # an ID, and lets new records be inserted with an ID of zero. - self.allow_zero_ids = 0 - - # pi-file API - def getRecords(self): return len(self.data) - def getAppBlock(self): return self.appblock and self.appblock or None - def setAppBlock(self, raw): - self.dirty = 1 - self.appblock = raw - def getSortBlock(self): return self.sortblock and self.sortblock or None - def setSortBlock(self, raw): - self.dirty = 1 - self.appblock = raw - def checkID(self, id): return id in self.data - def getRecord(self, i): - try: r = self.data[i] - except: return None - return r.raw, i, r.id, r.attr, r.category - def getRecordByID(self, id): - try: - i = self.data.index(id) - r = self.data[i] - except: return None - return r.raw, i, r.id, r.attr, r.category - def getResource(self, i): - try: r = self.data[i] - except: return None - return r.raw, r.type, r.id - def getDBInfo(self): return self.info - def setDBInfo(self, info): - self.dirty = 1 - self.info = {} - self.info.update(info) - - def updateDBInfo(self, info): - self.dirty = 1 - self.info.update(info) - - def setRecord(self, attr, id, cat, data): - if not self.allow_zero_ids and not id: - if not len(self.data): id = 1 - else: - xid = self.data[0].id + 1 - while xid in self.data: xid = xid + 1 - id = xid - - r = PRecord(attr, id, cat, data) - if id and id in self.data: - self.data.remove(id) - self.data.append(r) - self.dirty = 1 - return id - - def setRecordIdx(self, i, data): - self.data[i].raw = data - self.dirty = 1 - - def setResource(self, typ, id, data): - if (typ, id) in self.data: - self.data.remove((typ,id)) - r = PResource(typ, id, data) - self.data.append(r) - self.dirty = 1 - return id - - def getNextRecord(self, cat): - while self.next < len(self.data): - r = self.data[self.next] - i = self.next - self.next = self.next + 1 - if r.category == cat: - return r.raw, i, r.id, r.attr, r.category - return '' - - def getNextModRecord(self, cat=-1): - while self.next < len(self.data): - r = self.data[self.next] - i = self.next - self.next = self.next + 1 - if (r.attr & attrModified) and (cat < 0 or r.category == cat): - return r.raw, i, r.id, r.attr, r.category - - def getResourceByID(self, type, id): - try: r = self.data[self.data.index((type,id))] - except: return None - return r.raw, r.type, r.id - - def deleteRecord(self, id): - if not id in self.data: return None - self.data.remove(id) - self.dirty = 1 - - def deleteRecords(self): - self.data = [] - self.dirty = 1 - - def deleteResource(self, type, id): - if not (type,id) in self.data: return None - self.data.remove((type,id)) - self.dirty = 1 - - def deleteResources(self): - self.data = [] - self.dirty = 1 - - def getRecordIDs(self, sort=0): - m = map(lambda x: x.id, self.data) - if sort: m.sort() - return m - - def moveCategory(self, frm, to): - for r in self.data: - if r.category == frm: - r.category = to - self.dirty = 1 - - def deleteCategory(self, cat): - raise RuntimeError, _("unimplemented") - - def purge(self): - ndata = [] - # change to filter later - for r in self.data: - if (r.attr & attrDeleted): - continue - ndata.append(r) - self.data = ndata - self.dirty = 1 - - def resetNext(self): - self.next = 0 - - def resetFlags(self): - # special behavior for resources - if not self.info.get('flagResource',0): - # use map() - for r in self.data: - r.attr = r.attr & ~attrDirty - self.dirty = 1 - -import pprint -class File(PCache): - def __init__(self, name=None, read=1, write=0, info={}): - PCache.__init__(self) - self.filename = name - self.info.update(info) - self.writeback = write - self.isopen = 0 - - if read: - self.load(name) - self.isopen = 1 - - def close(self): - if self.writeback and self.dirty: - self.save(self.filename) - self.isopen = 0 - - def __del__(self): - if self.isopen: self.close() - - def load(self, f): - if type(f) == type(''): f = open(f, 'rb') - - data = f.read() - self.unpack(data) - - def unpack(self, data): - if len(data) < PI_HDR_SIZE: raise IOError, _("file too short") - (name, flags, ver, ctime, mtime, btime, mnum, appinfo, sortinfo, - typ, creator, uid, nextrec, numrec) \ - = struct.unpack('>32shhLLLlll4s4sllh', data[:PI_HDR_SIZE]) - - if nextrec or appinfo < 0 or sortinfo < 0 or numrec < 0: - raise IOError, _("invalid database header") - - self.info = { - 'name': null_terminated(name), - 'type': typ, - 'creator': creator, - 'createDate': ctime - PILOT_TIME_DELTA, - 'modifyDate': mtime - PILOT_TIME_DELTA, - 'backupDate': btime - PILOT_TIME_DELTA, - 'modnum': mnum, - 'version': ver, - 'flagReset': flags & flagReset, - 'flagResource': flags & flagResource, - 'flagNewer': flags & flagNewer, - 'flagExcludeFromSync': flags & flagExcludeFromSync, - 'flagAppInfoDirty': flags & flagAppInfoDirty, - 'flagReadOnly': flags & flagReadOnly, - 'flagBackup': flags & flagBackup, - 'flagOpen': flags & flagOpen, - 'more': 0, - 'index': 0 - } - - rsrc = flags & flagResource - if rsrc: s = PI_RESOURCE_ENT_SIZE - else: s = PI_RECORD_ENT_SIZE - - entries = [] - - pos = PI_HDR_SIZE - for x in range(0,numrec): - hstr = data[pos:pos+s] - pos = pos + s - if not hstr or len(hstr) < s: - raise IOError, _("bad database header") - - if rsrc: - (typ, id, offset) = struct.unpack('>4shl', hstr) - entries.append((offset, typ, id)) - else: - (offset, auid) = struct.unpack('>ll', hstr) - attr = (auid & 0xff000000) >> 24 - uid = auid & 0x00ffffff - entries.append((offset, attr, uid)) - - offset = len(data) - entries.reverse() - for of, q, id in entries: - size = offset - of - if size < 0: raise IOError, _("bad pdb/prc record entry (size < 0)") - d = data[of:offset] - offset = of - if len(d) != size: raise IOError, _("failed to read record") - if rsrc: - r = PResource(q, id, d) - self.data.append(r) - else: - r = PRecord(q & 0xf0, id, q & 0x0f, d) - self.data.append(r) - self.data.reverse() - - if sortinfo: - sortinfo_size = offset - sortinfo - offset = sortinfo - else: - sortinfo_size = 0 - - if appinfo: - appinfo_size = offset - appinfo - offset = appinfo - else: - appinfo_size = 0 - - if appinfo_size < 0 or sortinfo_size < 0: - raise IOError, _("bad database header (appinfo or sortinfo size < 0)") - - if appinfo_size: - self.appblock = data[appinfo:appinfo+appinfo_size] - if len(self.appblock) != appinfo_size: - raise IOError, _("failed to read appinfo block") - - if sortinfo_size: - self.sortblock = data[sortinfo:sortinfo+sortinfo_size] - if len(self.sortblock) != sortinfo_size: - raise IOError, _("failed to read sortinfo block") - - def save(self, f): - """Dump the cache to a file. - """ - if type(f) == type(''): f = open(f, 'wb') - - # first, we need to precalculate the offsets. - if self.info.get('flagResource'): - entries_len = 10 * len(self.data) - else: entries_len = 8 * len(self.data) - - off = PI_HDR_SIZE + entries_len + 2 - if self.appblock: - appinfo_offset = off - off = off + len(self.appblock) - else: - appinfo_offset = 0 - if self.sortblock: - sortinfo_offset = off - off = off + len(self.sortblock) - else: - sortinfo_offset = 0 - - rec_offsets = [] - for x in self.data: - rec_offsets.append(off) - off = off + len(x.raw) - - info = self.info - flg = 0 - if info.get('flagResource',0): flg = flg | flagResource - if info.get('flagReadOnly',0): flg = flg | flagReadOnly - if info.get('flagAppInfoDirty',0): flg = flg | flagAppInfoDirty - if info.get('flagBackup',0): flg = flg | flagBackup - if info.get('flagOpen',0): flg = flg | flagOpen - if info.get('flagNewer',0): flg = flg | flagNewer - if info.get('flagReset',0): flg = flg | flagReset - # excludefromsync doesn't actually get stored? - hdr = struct.pack('>32shhLLLlll4s4sllh', - pad_null(info.get('name',''), 32), - flg, - info.get('version',0), - info.get('createDate',0L)+PILOT_TIME_DELTA, - info.get('modifyDate',0L)+PILOT_TIME_DELTA, - info.get('backupDate',0L)+PILOT_TIME_DELTA, - info.get('modnum',0), - appinfo_offset, # appinfo - sortinfo_offset, # sortinfo - info.get('type',' '), - info.get('creator',' '), - 0, # uid??? - 0, # nextrec??? - len(self.data)) - - f.write(hdr) - - entries = [] - record_data = [] - rsrc = self.info.get('flagResource') - for x, off in map(None, self.data, rec_offsets): - if rsrc: - record_data.append(x.raw) - entries.append(struct.pack('>4shl', x.type, x.id, off)) - else: - record_data.append(x.raw) - a = ((x.attr | x.category) << 24) | x.id - entries.append(struct.pack('>ll', off, a)) - - for x in entries: f.write(x) - f.write('\0\0') # padding? dunno, it's always there. - if self.appblock: f.write(self.appblock) - if self.sortblock: f.write(self.sortblock) - for x in record_data: f.write(x) diff --git a/Other_Tools/Additional_Tools/lib/scrolltextwidget.py b/Other_Tools/Additional_Tools/lib/scrolltextwidget.py deleted file mode 100644 index 98b4147..0000000 --- a/Other_Tools/Additional_Tools/lib/scrolltextwidget.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import Tkinter -import Tkconstants - -# basic scrolled text widget -class ScrolledText(Tkinter.Text): - def __init__(self, master=None, **kw): - self.frame = Tkinter.Frame(master) - self.vbar = Tkinter.Scrollbar(self.frame) - self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) - kw.update({'yscrollcommand': self.vbar.set}) - Tkinter.Text.__init__(self, self.frame, **kw) - self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) - self.vbar['command'] = self.yview - # Copy geometry methods of self.frame without overriding Text - # methods = hack! - text_meths = vars(Tkinter.Text).keys() - methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() - methods = set(methods).difference(text_meths) - for m in methods: - if m[0] != '_' and m != 'config' and m != 'configure': - setattr(self, m, getattr(self.frame, m)) - - def __str__(self): - return str(self.frame) diff --git a/Other_Tools/Additional_Tools/lib/subasyncio.py b/Other_Tools/Additional_Tools/lib/subasyncio.py deleted file mode 100644 index 21d6d2c..0000000 --- a/Other_Tools/Additional_Tools/lib/subasyncio.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import os, sys -import signal -import threading -import subprocess -from subprocess import Popen, PIPE, STDOUT - -# **heavily** chopped up and modfied version of asyncproc.py -# to make it actually work on Windows as well as Mac/Linux -# For the original see: -# "http://www.lysator.liu.se/~bellman/download/" -# author is "Thomas Bellman " -# available under GPL version 3 or Later - -# create an asynchronous subprocess whose output can be collected in -# a non-blocking manner - -# What a mess! Have to use threads just to get non-blocking io -# in a cross-platform manner - -# luckily all thread use is hidden within this class - -class Process(object): - def __init__(self, *params, **kwparams): - if len(params) <= 3: - kwparams.setdefault('stdin', subprocess.PIPE) - if len(params) <= 4: - kwparams.setdefault('stdout', subprocess.PIPE) - if len(params) <= 5: - kwparams.setdefault('stderr', subprocess.PIPE) - self.__pending_input = [] - self.__collected_outdata = [] - self.__collected_errdata = [] - self.__exitstatus = None - self.__lock = threading.Lock() - self.__inputsem = threading.Semaphore(0) - self.__quit = False - - self.__process = subprocess.Popen(*params, **kwparams) - - if self.__process.stdin: - self.__stdin_thread = threading.Thread( - name="stdin-thread", - target=self.__feeder, args=(self.__pending_input, - self.__process.stdin)) - self.__stdin_thread.setDaemon(True) - self.__stdin_thread.start() - - if self.__process.stdout: - self.__stdout_thread = threading.Thread( - name="stdout-thread", - target=self.__reader, args=(self.__collected_outdata, - self.__process.stdout)) - self.__stdout_thread.setDaemon(True) - self.__stdout_thread.start() - - if self.__process.stderr: - self.__stderr_thread = threading.Thread( - name="stderr-thread", - target=self.__reader, args=(self.__collected_errdata, - self.__process.stderr)) - self.__stderr_thread.setDaemon(True) - self.__stderr_thread.start() - - def pid(self): - return self.__process.pid - - def kill(self, signal): - self.__process.send_signal(signal) - - # check on subprocess (pass in 'nowait') to act like poll - def wait(self, flag): - if flag.lower() == 'nowait': - rc = self.__process.poll() - else: - rc = self.__process.wait() - if rc != None: - if self.__process.stdin: - self.closeinput() - if self.__process.stdout: - self.__stdout_thread.join() - if self.__process.stderr: - self.__stderr_thread.join() - return self.__process.returncode - - def terminate(self): - if self.__process.stdin: - self.closeinput() - self.__process.terminate() - - # thread gets data from subprocess stdout - def __reader(self, collector, source): - while True: - data = os.read(source.fileno(), 65536) - self.__lock.acquire() - collector.append(data) - self.__lock.release() - if data == "": - source.close() - break - return - - # thread feeds data to subprocess stdin - def __feeder(self, pending, drain): - while True: - self.__inputsem.acquire() - self.__lock.acquire() - if not pending and self.__quit: - drain.close() - self.__lock.release() - break - data = pending.pop(0) - self.__lock.release() - drain.write(data) - - # non-blocking read of data from subprocess stdout - def read(self): - self.__lock.acquire() - outdata = "".join(self.__collected_outdata) - del self.__collected_outdata[:] - self.__lock.release() - return outdata - - # non-blocking read of data from subprocess stderr - def readerr(self): - self.__lock.acquire() - errdata = "".join(self.__collected_errdata) - del self.__collected_errdata[:] - self.__lock.release() - return errdata - - # non-blocking write to stdin of subprocess - def write(self, data): - if self.__process.stdin is None: - raise ValueError("Writing to process with stdin not a pipe") - self.__lock.acquire() - self.__pending_input.append(data) - self.__inputsem.release() - self.__lock.release() - - # close stdinput of subprocess - def closeinput(self): - self.__lock.acquire() - self.__quit = True - self.__inputsem.release() - self.__lock.release() - diff --git a/Other_Tools/Additional_Tools/mergeKF8Only.zip b/Other_Tools/Additional_Tools/mergeKF8Only.zip deleted file mode 100644 index fc83aad..0000000 Binary files a/Other_Tools/Additional_Tools/mergeKF8Only.zip and /dev/null differ diff --git a/Other_Tools/Additional_Tools/older_tools/Kindleizer.pyw b/Other_Tools/Additional_Tools/older_tools/Kindleizer.pyw deleted file mode 100755 index a725626..0000000 --- a/Other_Tools/Additional_Tools/older_tools/Kindleizer.pyw +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import sys -sys.path.append('lib') -import os, os.path, urllib -import subprocess -from subprocess import Popen, PIPE, STDOUT -import subasyncio -from subasyncio import Process -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox -from scrolltextwidget import ScrolledText - -class MainDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.root = root - self.interval = 2000 - self.p2 = None - self.status = Tkinter.Label(self, text='Fix Encrypted Mobi eBooks so the Kindle can read them') - 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='Mobi eBook input file').grid(row=0, sticky=Tkconstants.E) - self.mobipath = Tkinter.Entry(body, width=50) - self.mobipath.grid(row=0, column=1, sticky=sticky) - cwd = os.getcwdu() - cwd = cwd.encode('utf-8') - self.mobipath.insert(0, cwd) - button = Tkinter.Button(body, text="...", command=self.get_mobipath) - button.grid(row=0, column=2) - - Tkinter.Label(body, text='10 Character PID').grid(row=1, sticky=Tkconstants.E) - self.pidnum = Tkinter.StringVar() - self.pidinfo = Tkinter.Entry(body, width=12, textvariable=self.pidnum) - self.pidinfo.grid(row=1, 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) - 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) - self.sbotton.pack(side=Tkconstants.LEFT) - - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - self.qbutton = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quitting) - self.qbutton.pack(side=Tkconstants.RIGHT) - - # read from subprocess pipe without blocking - # invoked every interval via the widget "after" - # option being used, so need to reset it for the next time - def processPipe(self): - poll = self.p2.wait('nowait') - if poll != None: - text = self.p2.readerr() - text += self.p2.read() - msg = text + '\n\n' + 'Fix for Kindle successful\n' - if poll != 0: - msg = text + '\n\n' + 'Error: Fix for Kindle Failed\n' - self.showCmdOutput(msg) - self.p2 = None - self.sbotton.configure(state='normal') - return - text = self.p2.readerr() - text += self.p2.read() - self.showCmdOutput(text) - # make sure we get invoked again by event loop after interval - self.stext.after(self.interval,self.processPipe) - return - - # post output from subprocess in scrolled text widget - def showCmdOutput(self, msg): - if msg and msg !='': - msg = msg.encode('utf-8') - if sys.platform.startswith('win'): - msg = msg.replace('\r\n','\n') - self.stext.insert(Tkconstants.END,msg) - self.stext.yview_pickplace(Tkconstants.END) - return - - # run as a subprocess via pipes and collect stdout - def krdr(self, infile, pidnum): - # os.putenv('PYTHONUNBUFFERED', '1') - cmdline = 'python ./lib/kindlefix.py "' + infile + '" "' + pidnum + '"' - 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\kindlefix.py "' + infile + '" "' + pidnum + '"' - else : - cmdline = 'lib\kindlefix.py "' + infile + '" "' + pidnum + '"' - - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) - return p2 - - - def get_mobipath(self): - mobipath = tkFileDialog.askopenfilename( - parent=None, title='Select Mobi eBook File', - defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.mobi'), - ('All Files', '.*')]) - if mobipath: - mobipath = os.path.normpath(mobipath) - self.mobipath.delete(0, Tkconstants.END) - self.mobipath.insert(0, mobipath) - return - - def quitting(self): - # kill any still running subprocess - if self.p2 != None: - if (self.p2.wait('nowait') == None): - self.p2.terminate() - self.root.destroy() - - # actually ready to run the subprocess and get its output - def convertit(self): - # now disable the button to prevent multiple launches - self.sbotton.configure(state='disabled') - mobipath = self.mobipath.get() - pidnum = self.pidinfo.get() - if not mobipath or not os.path.exists(mobipath): - self.status['text'] = 'Specified Mobi eBook file does not exist' - self.sbotton.configure(state='normal') - return - if not pidnum or pidnum == '': - self.status['text'] = 'No PID specified' - self.sbotton.configure(state='normal') - return - - log = 'Command = "python kindlefix.py"\n' - log += 'Mobi Path = "'+ mobipath + '"\n' - log += 'PID = "' + pidnum + '"\n' - log += '\n\n' - log += 'Please Wait ...\n\n' - log = log.encode('utf-8') - self.stext.insert(Tkconstants.END,log) - self.p2 = self.krdr(mobipath, pidnum) - - # python does not seem to allow you to create - # your own eventloop which every other gui does - strange - # so need to use the widget "after" command to force - # event loop to run non-gui events every interval - self.stext.after(self.interval,self.processPipe) - return - - -def main(argv=None): - root = Tkinter.Tk() - root.title('Fix Encrypted Mobi eBooks to work with the Kindle') - root.resizable(True, False) - root.minsize(300, 0) - MainDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/older_tools/PIDCheck.py b/Other_Tools/Additional_Tools/older_tools/PIDCheck.py deleted file mode 100644 index 1960453..0000000 --- a/Other_Tools/Additional_Tools/older_tools/PIDCheck.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 1.00 - Initial version - -__version__ = '1.00' - -import sys -import struct -import binascii - -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 - -if __name__ == "__main__": - if len(sys.argv) != 2: - print "Checks Mobipocket PID checksum" - print "Usage:" - print " %s " % sys.argv[0] - sys.exit(1) - else: - pid = sys.argv[1] - if len(pid) == 8: - pid = checksumPid(pid) - else: - pid = checksumPid(pid[:8]) - print pid - sys.exit(0) \ No newline at end of file diff --git a/Other_Tools/Additional_Tools/older_tools/kindlefix.py b/Other_Tools/Additional_Tools/older_tools/kindlefix.py deleted file mode 100644 index 6a0b57d..0000000 --- a/Other_Tools/Additional_Tools/older_tools/kindlefix.py +++ /dev/null @@ -1,172 +0,0 @@ -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 prc, struct -from binascii import hexlify - -def strByte(s,off=0): - return struct.unpack(">B",s[off])[0]; - -def strSWord(s,off=0): - return struct.unpack(">h",s[off:off+2])[0]; - -def strWord(s,off=0): - return struct.unpack(">H",s[off:off+2])[0]; - -def strDWord(s,off=0): - return struct.unpack(">L",s[off:off+4])[0]; - -def strPutDWord(s,off,i): - return s[:off]+struct.pack(">L",i)+s[off+4:]; - -keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" - -#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 find_key(rec0, pid): - off1 = strDWord(rec0, 0xA8) - if off1==0xFFFFFFFF or off1==0: - print "No DRM" - return None - size1 = strDWord(rec0, 0xB0) - cnt = strDWord(rec0, 0xAC) - flag = strDWord(rec0, 0xB4) - - temp_key = PC1(keyvec1, pid.ljust(16,'\0'), False) - cksum = 0 - #print pid, "->", hexlify(temp_key) - for i in xrange(len(temp_key)): - cksum += ord(temp_key[i]) - cksum &= 0xFF - temp_key = temp_key.ljust(16,'\0') - #print "pid cksum: %02X"%cksum - - #print "Key records: %02X-%02X, count: %d, flag: %02X"%(off1, off1+size1, cnt, flag) - iOff = off1 - drm_key = None - for i in xrange(cnt): - dwCheck = strDWord(rec0, iOff) - dwSize = strDWord(rec0, iOff+4) - dwType = strDWord(rec0, iOff+8) - nCksum = strByte(rec0, iOff+0xC) - #print "Key record %d: check=%08X, size=%d, type=%d, cksum=%02X"%(i, dwCheck, dwSize, dwType, nCksum) - if nCksum==cksum: - drmInfo = PC1(temp_key, rec0[iOff+0x10:iOff+0x30]) - dw0, dw4, dw18, dw1c = struct.unpack(">II16xII", drmInfo) - #print "Decrypted drmInfo:", "%08X, %08X, %s, %08X, %08X"%(dw0, dw4, hexlify(drmInfo[0x8:0x18]), dw18, dw1c) - #print "Decrypted drmInfo:", hexlify(drmInfo) - if dw0==dwCheck: - print "Found the matching record; setting the CustomDRM flag for Kindle" - drmInfo = strPutDWord(drmInfo,4,(dw4|0x800)) - dw0, dw4, dw18, dw1c = struct.unpack(">II16xII", drmInfo) - #print "Updated drmInfo:", "%08X, %08X, %s, %08X, %08X"%(dw0, dw4, hexlify(drmInfo[0x8:0x18]), dw18, dw1c) - return rec0[:iOff+0x10] + PC1(temp_key, drmInfo, False) + rec0[:iOff+0x30] - iOff += dwSize - return None - -def replaceext(filename, newext): - nameparts = filename.split(".") - if len(nameparts)>1: - return (".".join(nameparts[:-1]))+newext - else: - return nameparts[0]+newext - -def main(argv=sys.argv): - print "The Kindleizer v0.2. Copyright (c) 2007 Igor Skochinsky" - if len(sys.argv) != 3: - print "Fixes encrypted Mobipocket books to be readable by Kindle" - print "Usage: kindlefix.py file.mobi PID" - return 1 - fname = sys.argv[1] - pid = sys.argv[2] - if len(pid)==10 and pid[-3]=='*': - pid = pid[:-2] - if len(pid)!=8 or pid[-1]!='*': - print "PID is not valid! (should be in format AAAAAAA*DD)" - return 3 - db = prc.File(fname) - #print dir(db) - if db.getDBInfo()["creator"]!='MOBI': - print "Not a Mobi file!" - return 1 - rec0 = db.getRecord(0)[0] - enc = strSWord(rec0, 0xC) - print "Encryption:", enc - if enc!=2: - print "Unknown encryption type" - return 1 - - if len(rec0)<0x28 or rec0[0x10:0x14] != 'MOBI': - print "bad file format" - return 1 - print "Mobi publication type:", strDWord(rec0, 0x18) - formatVer = strDWord(rec0, 0x24) - print "Mobi format version:", formatVer - last_rec = strWord(rec0, 8) - dwE0 = 0 - if formatVer>=4: - new_rec0 = find_key(rec0, pid) - if new_rec0: - db.setRecordIdx(0,new_rec0) - else: - print "PID doesn't match this file" - return 2 - else: - print "Wrong Mobi format version" - return 1 - - outfname = replaceext(fname, ".azw") - if outfname==fname: - outfname = replaceext(fname, "_fixed.azw") - db.save(outfname) - print "Output written to "+outfname - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/Additional_Tools/older_tools/mobiunpack.py b/Other_Tools/Additional_Tools/older_tools/mobiunpack.py deleted file mode 100644 index b135fe4..0000000 --- a/Other_Tools/Additional_Tools/older_tools/mobiunpack.py +++ /dev/null @@ -1,1720 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -# Changelog -# 0.11 - Version by adamselene -# 0.11pd - Tweaked version by pdurrant -# 0.12 - extracts pictures too, and all into a folder. -# 0.13 - added back in optional output dir for those who don't want it based on infile -# 0.14 - auto flush stdout and wrapped in main, added proper return codes -# 0.15 - added support for metadata -# 0.16 - metadata now starting to be output as an opf file (PD) -# 0.17 - Also created tweaked text as source for Mobipocket Creator -# 0.18 - removed raw mobi file completely but kept _meta.html file for ease of conversion -# 0.19 - added in metadata for ASIN, Updated Title and Rights to the opf -# 0.20 - remove _meta.html since no longer needed -# 0.21 - Fixed some typos in the opf output, and also updated handling -# of test for trailing data/multibyte characters -# 0.22 - Fixed problem with > 9 images -# 0.23 - Now output Start guide item -# 0.24 - Set firstimg value for 'TEXtREAd' -# 0.25 - Now added character set metadata to html file for utf-8 files. -# 0.26 - Dictionary support added. Image handling speed improved. For huge files create temp files to speed up decoding. -# Language decoding fixed. Metadata is now converted to utf-8 when written to opf file. -# 0.27 - Add idx:entry attribute "scriptable" if dictionary contains entry length tags. Don't save non-image sections -# as images. Extract and save source zip file included by kindlegen as kindlegensrc.zip. -# 0.28 - Added back correct image file name extensions, created FastConcat class to simplify and clean up -# 0.29 - Metadata handling reworked, multiple entries of the same type are now supported. Serveral missing types added. -# FastConcat class has been removed as in-memory handling with lists is faster, even for huge files. -# 0.30 - Add support for outputting **all** metadata values - encode content with hex if of unknown type -# 0.31 - Now supports Print Replica ebooks, outputting PDF and mysterious data sections -# 0.32 - Now supports NCX file extraction/building. -# Overhauled the structure of mobiunpack to be more class oriented. - -DEBUG = False -DEBUG_NCX = False -""" Set to True to print debug information. """ - -WRITE_RAW_DATA = False -""" Set to True to create additional files with raw data for debugging/reverse engineering. """ - -EOF_RECORD = chr(0xe9) + chr(0x8e) + "\r\n" -""" The EOF record content. """ - -KINDLEGENSRC_FILENAME = "kindlegensrc.zip" -""" The name for the kindlegen source archive. """ - -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 array, struct, os, re, imghdr - -class unpackException(Exception): - pass - -class fileNames: - def __init__(self, infile, outdir): - self.infile = infile - self.outdir = outdir - if not os.path.exists(outdir): - os.mkdir(outdir) - self.outsrc = os.path.join(outdir, os.path.splitext(os.path.split(infile)[1])[0]) + '.html' - self.outopf = os.path.join(outdir, os.path.splitext(os.path.split(infile)[1])[0]) + '.opf' - self.outncx = os.path.join(outdir, os.path.splitext(os.path.split(infile)[1])[0]) + '.ncx' - self.imgdir = os.path.join(outdir, 'images') - if not os.path.exists(self.imgdir): - os.mkdir(self.imgdir) - self.outsrcbasename = os.path.basename(self.outsrc) - self.outhtmlbasename = unicode(os.path.basename(self.outsrc), sys.getfilesystemencoding()).encode("utf-8") - - def getOutRaw(self, ext): - return os.path.join(self.outdir, os.path.splitext(os.path.split(self.infile)[1])[0]) + ext - -class UncompressedReader: - def unpack(self, data): - return data - -class PalmdocReader: - def unpack(self, i): - o, p = '', 0 - while p < len(i): - c = ord(i[p]) - p += 1 - if (c >= 1 and c <= 8): - o += i[p:p+c] - p += c - elif (c < 128): - o += chr(c); - elif (c >= 192): - o += ' ' + chr(c ^ 128); - else: - if p < len(i): - c = (c << 8) | ord(i[p]) - p += 1 - m = (c >> 3) & 0x07ff - n = (c & 7) + 3 - if (m > n): - o += o[-m:n-m] - else: - for _ in xrange(n): - o += o[-m] - return o - -class HuffcdicReader: - q = struct.Struct('>Q').unpack_from - - def loadHuff(self, huff): - if huff[0:8] != 'HUFF\x00\x00\x00\x18': - raise unpackException('invalid huff header') - off1, off2 = struct.unpack_from('>LL', huff, 8) - - def dict1_unpack(v): - codelen, term, maxcode = v&0x1f, v&0x80, v>>8 - assert codelen != 0 - if codelen <= 8: - assert term - maxcode = ((maxcode + 1) << (32 - codelen)) - 1 - return (codelen, term, maxcode) - self.dict1 = map(dict1_unpack, struct.unpack_from('>256L', huff, off1)) - - dict2 = struct.unpack_from('>64L', huff, off2) - self.mincode, self.maxcode = (), () - for codelen, mincode in enumerate((0,) + dict2[0::2]): - self.mincode += (mincode << (32 - codelen), ) - for codelen, maxcode in enumerate((0,) + dict2[1::2]): - self.maxcode += (((maxcode + 1) << (32 - codelen)) - 1, ) - - self.dictionary = [] - - def loadCdic(self, cdic): - if cdic[0:8] != 'CDIC\x00\x00\x00\x10': - raise unpackException('invalid cdic header') - phrases, bits = struct.unpack_from('>LL', cdic, 8) - n = min(1<H').unpack_from - def getslice(off): - blen, = h(cdic, 16+off) - slice = cdic[18+off:18+off+(blen&0x7fff)] - return (slice, blen&0x8000) - self.dictionary += map(getslice, struct.unpack_from('>%dH' % n, cdic, 16)) - - def unpack(self, data): - q = HuffcdicReader.q - - bitsleft = len(data) * 8 - data += "\x00\x00\x00\x00\x00\x00\x00\x00" - pos = 0 - x, = q(data, pos) - n = 32 - - s = '' - while True: - if n <= 0: - pos += 4 - x, = q(data, pos) - n += 32 - code = (x >> n) & ((1 << 32) - 1) - - codelen, term, maxcode = self.dict1[code >> 24] - if not term: - while code < self.mincode[codelen]: - codelen += 1 - maxcode = self.maxcode[codelen] - - n -= codelen - bitsleft -= codelen - if bitsleft < 0: - break - - r = (maxcode - code) >> (32 - codelen) - slice, flag = self.dictionary[r] - if not flag: - self.dictionary[r] = None - slice = self.unpack(slice) - self.dictionary[r] = (slice, 1) - s += slice - return s - -class Sectionizer: - def __init__(self, filename, perm): - self.f = file(filename, perm) - header = self.f.read(78) - self.ident = header[0x3C:0x3C+8] - self.num_sections, = struct.unpack_from('>H', header, 76) - sections = self.f.read(self.num_sections*8) - self.sections = struct.unpack_from('>%dL' % (self.num_sections*2), sections, 0)[::2] + (0xfffffff, ) - - def loadSection(self, section): - before, after = self.sections[section:section+2] - self.f.seek(before) - return self.f.read(after - before) - -class mobiUnpack: - def __init__(self, files): - self.infile = files.infile - self.outdir = files.outdir - self.sect = Sectionizer(self.infile, 'rb') - if self.sect.ident != 'BOOKMOBI' and sect.ident != 'TEXtREAd': - raise unpackException('invalid file format') - - self.header = self.sect.loadSection(0) - self.records, = struct.unpack_from('>H', self.header, 0x8) - self.length, self.type, self.codepage, self.unique_id, self.version = struct.unpack('>LLLLL', self.header[20:40]) - self.crypto_type, = struct.unpack_from('>H', self.header, 0xC) - self.rawText = self.__getRawtext() - - def processPrintReplica(self): - # read in number of tables, and so calculate the start of the indicies into the data - numTables, = struct.unpack_from('>L', self.rawText, 0x04) - tableIndexOffset = 8 + 4*numTables - # for each table, read in count of sections, assume first section is a PDF - # and output other sections as binary files - paths = [] - for i in xrange(numTables): - sectionCount, = struct.unpack_from('>L', self.rawText, 0x08 + 4*i) - for j in xrange(sectionCount): - sectionOffset, sectionLength, = struct.unpack_from('>LL', self.rawText, tableIndexOffset) - tableIndexOffset += 8 - if j == 0: - entryName = os.path.join(self.outdir, os.path.splitext(os.path.split(self.infile)[1])[0]) + ('.%03d.pdf' % (i+1)) - paths.append(entryName) - else: - entryName = os.path.join(self.outdir, os.path.splitext(os.path.split(self.infile)[1])[0]) + ('.%03d.%03d.data' % ((i+1),j)) - f = open(entryName, 'wb') - f.write(self.rawText[sectionOffset:(sectionOffset+sectionLength)]) - f.close() - self.printReplicaPaths = paths - - def __getSizeOfTrailingDataEntry(self, data): - num = 0 - for v in data[-4:]: - if ord(v) & 0x80: - num = 0 - num = (num << 7) | (ord(v) & 0x7f) - return num - - def Language(self): - langcode = struct.unpack('!L', self.header[0x5c:0x60])[0] - langid = langcode & 0xFF - sublangid = (langcode >> 10) & 0xFF - return [getLanguage(langid, sublangid)] - - def DictInLanguage(self): - langcode = struct.unpack('!L', self.header[0x60:0x64])[0] - langid = langcode & 0xFF - sublangid = (langcode >> 10) & 0xFF - if langid != 0: - return [getLanguage(langid, sublangid)] - return False - - def DictOutLanguage(self): - langcode = struct.unpack('!L', self.header[0x64:0x68])[0] - langid = langcode & 0xFF - sublangid = (langcode >> 10) & 0xFF - if langid != 0: - return [getLanguage(langid, sublangid)] - return False - - def getMetaData(self): - codec=self.codec - extheader=self.header[16 + self.length:] - - id_map_strings = { - 1 : 'Drm Server Id', - 2 : 'Drm Commerce Id', - 3 : 'Drm Ebookbase Book Id', - 100 : 'Creator', - 101 : 'Publisher', - 102 : 'Imprint', - 103 : 'Description', - 104 : 'ISBN', - 105 : 'Subject', - 106 : 'Published', - 107 : 'Review', - 108 : 'Contributor', - 109 : 'Rights', - 110 : 'SubjectCode', - 111 : 'Type', - 112 : 'Source', - 113 : 'ASIN', - 117 : 'Adult', - 118 : 'Price', - 119 : 'Currency', - 200 : 'DictShortName', - 208 : 'Watermark', - 501 : 'CDE Type', - 503 : 'Updated Title', - } - id_map_values = { - 116 : 'StartOffset', - 201 : 'CoverOffset', - 202 : 'ThumbOffset', - 203 : 'Fake Cover', - 204 : 'Creator Software', - 205 : 'Creator Major Version', - 206 : 'Creator Minor Version', - 207 : 'Creator Build Number', - 401 : 'Clipping Limit', - 402 : 'Publisher Limit', - 404 : 'Text to Speech Disabled', - } - id_map_hexstrings = { - 209 : 'Tamper Proof Keys (hex)', - 300 : 'Font Signature (hex)', - } - - metadata = {} - - def addValue(name, value): - if name not in metadata: - metadata[name] = [value] - else: - metadata[name].append(value) - if DEBUG: - print "multiple values: metadata[%s]=%s" % (name, metadata[name]) - - _length, num_items = struct.unpack('>LL', extheader[4:12]) - extheader = extheader[12:] - pos = 0 - for _ in range(num_items): - id, size = struct.unpack('>LL', extheader[pos:pos+8]) - content = extheader[pos + 8: pos + size] - if id in id_map_strings.keys(): - name = id_map_strings[id] - addValue(name, unicode(content, codec).encode("utf-8")) - elif id in id_map_values.keys(): - name = id_map_values[id] - if size == 9: - value, = struct.unpack('B',content) - addValue(name, str(value)) - elif size == 10: - value, = struct.unpack('>H',content) - addValue(name, str(value)) - elif size == 12: - value, = struct.unpack('>L',content) - addValue(name, str(value)) - else: - print "Error: Value for %s has unexpected size of %s" % (name, size) - elif id in id_map_hexstrings.keys(): - name = id_map_hexstrings[id] - addValue(name, content.encode('hex')) - else: - print "Warning: Unknown metadata with id %s found" % id - name = str(id) + ' (hex)' - addValue(name, content.encode('hex')) - pos += size - return metadata - - def __getRawtext(self): - multibyte = 0 - trailers = 0 - if self.sect.ident == 'BOOKMOBI': - mobi_length, = struct.unpack_from('>L', self.header, 0x14) - mobi_version, = struct.unpack_from('>L', self.header, 0x68) - if (mobi_length >= 0xE4) and (mobi_version >= 5): - flags, = struct.unpack_from('>H', self.header, 0xF2) - multibyte = flags & 1 - while flags > 1: - if flags & 2: - trailers += 1 - flags = flags >> 1 - - compression, = struct.unpack_from('>H', self.header, 0x0) - if compression == 0x4448: - print "Huffdic compression" - reader = HuffcdicReader() - huffoff, huffnum = struct.unpack_from('>LL', self.header, 0x70) - reader.loadHuff(self.sect.loadSection(huffoff)) - for i in xrange(1, huffnum): - reader.loadCdic(self.sect.loadSection(huffoff+i)) - unpack = reader.unpack - elif compression == 2: - print "Palmdoc compression" - unpack = PalmdocReader().unpack - elif compression == 1: - print "No compression" - unpack = UncompressedReader().unpack - else: - raise unpackException('invalid compression type: 0x%4x' % compression) - - def trimTrailingDataEntries(data): - for _ in xrange(trailers): - num = self.__getSizeOfTrailingDataEntry(data) - data = data[:-num] - if multibyte: - num = (ord(data[-1]) & 3) + 1 - data = data[:-num] - return data - - # get raw mobi html-like markup languge - print "Unpack raw html" - dataList = [] - for i in xrange(self.records): - data = trimTrailingDataEntries(self.sect.loadSection(1+i)) - dataList.append(unpack(data)) - return "".join(dataList) - - @property - def isPrintReplica(self): - return (self.rawText[0:4] == "%MOP") - - @property - def isEncrypted(self): - if self.crypto_type != 0: - return True - return False - - @property - def codec_map(self): - return { - 1252 : 'windows-1252', - 65001: 'utf-8', - } - - @property - def firstidx(self): - if self.sect.ident != 'TEXtREAd': - idx, = struct.unpack_from('>L', self.header, 0x50) - else: - idx = 0xFFFFFFFF - return idx - - @property - def firstimg(self): - if self.sect.ident != 'TEXtREAd': - img, = struct.unpack_from('>L', self.header, 0x6C) - else: - img = self.records + 1 - return img - - @property - def codec(self): - if self.codepage in self.codec_map.keys(): - return self.codec_map[self.codepage] - else: - return 'windows-1252' - - @property - def title(self): - toff, tlen = struct.unpack('>II', self.header[0x54:0x5c]) - tend = toff + tlen - return self.header[toff:tend] - - @property - def hasExth(self): - exth_flag, = struct.unpack('>L', self.header[0x80:0x84]) - return exth_flag & 0x40 - -class ncxExtract: - def __init__(self, header, sect, records, files): - self.header = header - self.sect = sect - self.records = records - self.isNCX = False - self.files = files - - def parseINDX(self): - files = self.files - indx_data = False - indx_text = False - indx_num = 1 - idnx_codec = '' #not used.. - - # get first INDX section - indx_first, = struct.unpack('>L', self.header[0xf4:0xf8]) - if indx_first == 0xffffffff: - print "No ncx" - return False - - # sanity check of indx_first - if indx_first > (self.sect.num_sections - 2) or indx_first<=self.records: - print "Warning: incorrect index section number:",\ - self.records, '<', indx_first, '<', self.sect.num_sections - return False - - # read INDX0 - data = self.sect.loadSection(indx_first) - if DEBUG_NCX: - outraw = os.path.join(files.outdir, 'indx0.dat') - f = open(outraw, 'wb') - f.write(data) - f.close() - indxHeader = self.parseINDXHeader(data) - if not indxHeader: - return False - - #must be of type 0 - if not indxHeader['type'] == 0: - print "Warning: INDX0 not type 0" - return False - - #NOTE: the number of "DATA" indx is stored in here... - indx_num = indxHeader['count'] - - #TODO: use indxHeader "code" to set encoding... - indx_codec = indxHeader['code'] - - #NOTE: used to figure out the INDX structure - tagx = readTagSection(indxHeader['len'], data) - if DEBUG_NCX: - print "INDX0: ", indx_num, "INDX sections" - print "TAGX: ", tagx - - # read CTOC - if DEBUG_NCX: - print "CTOC" - data = self.sect.loadSection(indx_first + indx_num + 1) - if data[:4] == 'INDX': - print "Warning: CTOC is an INDX" - return False - indx_text = self.readCTOC(data) - - # read all INDXx - indx_data = [] - for n in range(indx_num): - indx_id = n + 1 - if DEBUG_NCX: - print "INDX%d" % indx_id - data = self.sect.loadSection(indx_first + indx_id) - - if DEBUG_NCX: - #dump the whole section, not just the navdata part as before - outraw = os.path.join(files.outdir,'indx%d.dat' % indx_id) - f = open(outraw, 'wb') - f.write(data) - f.close() - - #parse header - indxHeader = self.parseINDXHeader(data) - if not indxHeader: - return False - #must be of type 1 - if not indxHeader['type'] == 1: - print "Warning: INDX%d not type 1" % indx_id - - #parse IDXT (starts @ 'start') - #NOTE: IDXT contains the offset to each entry - idxt = self.parseIDXT(data[indxHeader['start']:]) - if DEBUG_NCX: - print 'IDXT', idxt - - # now process the indx - #(actually starts @ 'len' but we use the IDXT offset data to navigate) - #print "INDX1" - tmp = self.parseINDX1(data, idxt, indx_text, tagx) - if not tmp: - print "Warning: error parsing NCX data in INDX%d" % indx_id - return False - indx_data = indx_data + tmp - - if len(indx_data) < indxHeader['count']: - print "Warning: missing INDX entries %d/%d" %\ - (len(indx_data), indxHeader['count']) - self.indx_data = indx_data - return indx_data - - def parseINDXHeader(self, data): - "read INDX header" - #must be INDX - if not data[:4] == 'INDX': - print "Warning: index section is not INDX" - return False - - words = ( - 'len', 'nul1', 'type', 'gen', 'start', 'count', 'code', - 'lng', 'total', 'ordt', 'ligt', 'nligt', 'nctoc' - ) - - num = len(words) - values = struct.unpack('>%dL' % num, data[4:4*(num+1)]) - - header = {} - for n in range(num): - header[words[n]] = values[n] - - if DEBUG_NCX: - print "parsed INDX header:" - for n in words: - print n, "%X" % header[n], - print - return header - - def readCTOC(self, txtdata): - files = self.files - # read all blocks from CTOC - if DEBUG_NCX: - outraw = os.path.join(files.outdir,'ctoc.dat') - f = open(outraw, 'wb') - f.write(txtdata) - f.close() - - ctoc_data = {} - offset = 0 - while offset next bytes: name - name = txtdata[offset:offset+ilen] - offset += ilen - # print idx_offs, name - ctoc_data[idx_offs] = name - return ctoc_data - - def parseIDXT(self, data, pos_offset=0): - if not data[:4] == 'IDXT': - print "Warning: not IDXT" - return False - pos = [] - offset = 4 - while offsetH', data, offset) - offset += 2 - #note: some files have a trailing 00 00 - if value: - pos.append(value) - return pos - - def parseINDX1(self, data, idxt, indx_txt, tagx): - #read all blocks from INDX1 - tag_fieldname_map = { - 1: 'pos', - 2: 'len', - 3: 'noffs', - 4: 'hlvl', - 5: 'koffs', - 21: 'parent', - 22: 'child1', - 23: 'childn' - } - - indx_data = [] - num = 0 - offset = 0 - max_offset = len(data) - 1 - taglst_cnt, taglst = tagx - if taglst_cnt > 1: - print "Error: multiple tagx taglist entries not handled" - - for offset in idxt: - if offset > max_offset: - print 'Warning: wrong IDXT entries, offset out of range', offset - break - if data[offset] == '\0': - print 'Warning: missing ncx entry @ %X' % offset - break - - tmp = { - 'name': None, - 'type': 0, - 'pos': -1, - 'len': 0, - 'noffs': -1, - 'text' : "Unknown Text", - 'hlvl' : -1, - 'kind' : "Unknown Kind", - 'parent' : -1, - 'child1' : -1, - 'childn' : -1, - 'num' : num - } - - #first byte: name len - ilen, = struct.unpack('B', data[offset]) - offset += 1 - # next bytes: name - name = data[offset:offset+ilen] - offset += ilen - tmp['name'] = name - - #next byte: type: - type, = struct.unpack('B', data[offset]) - offset += 1 - tmp['type'] = type - - # The tagx info and the type byte is used to decipher which fields - # should be read in - for (tag, nvars, mask, stop) in taglst: - if stop: - break - if tag in tag_fieldname_map.keys(): - fieldname = tag_fieldname_map[tag] - if type & mask == mask: - assert(nvars == 1) - pos, fieldvalue = getVariableWidthValue(data, offset) - offset += pos - tmp[fieldname] = fieldvalue - if tag == 3: - tmp['text'] = indx_txt.get(fieldvalue, 'Unknown Text') - if tag == 5: - tmp['kind'] = indx_txt.get(fieldvalue, 'Unknown Kind') - else : - # unknown tag so skip proper number of values if needed and continue - print 'reading indx1 - unknown tag: ', tag, ' skipping it' - # NOTE: skipping should not be needed anymore with IDXT... - if type & mask == mask: - for i in range(nvars): - pos, temp = getVariableWidthValue(data, offset) - offset += pos - - indx_data.append(tmp) - if DEBUG_NCX: - if True: - print "record number is ", num - print "name is ", tmp['name'], "type is %x " % tmp['type'] - print "position is ", tmp['pos']," and length is ", tmp['len'] - print "name offset is ", tmp['noffs']," which is text ", tmp['text'] - print "kind is ", tmp['kind']," and heading level is ", tmp['hlvl'] - print "parent is ", tmp['parent'] - print "first child is ",tmp['child1']," and last child is ", tmp['childn'] - print "\n\n" - else: - fld_dbg = ('type', 'hlvl', 'parent', 'child1', 'childn') - print "\t".join(['%X'%tmp[f] for f in fld_dbg]) - num += 1 - return indx_data - - def buildNCX(self, htmlfile, title, ident): - indx_data = self.indx_data - - ncx_header = \ -''' - - - - - - - - - - %s - - -''' - - ncx_footer = \ -''' - -''' - - ncx_entry = \ -''' - - %s - - ''' - - #recursive part - def recursINDX(max_lvl=0, num=0, lvl=0, start=-1, end=-1): - if start>len(indx_data) or end>len(indx_data): - print "Warning: missing INDX child entries", start, end, len(indx_data) - return '' - if DEBUG_NCX: - print "recursINDX lvl %d from %d to %d" % (lvl, start, end) - xml = '' - if start <= 0: - start = 0 - if end <= 0: - end = len(indx_data) - if lvl > max_lvl: - max_lvl = lvl - indent = ' ' * (2 + lvl) - - for i in range(start, end): - e = indx_data[i] - if not e['hlvl'] == lvl: - continue - #open entry - num += 1 - link = '%s#filepos%d' % (htmlfile, e['pos']) - tagid = 'np_%d' % num - entry = ncx_entry % (tagid, num, e['text'], link) - entry = re.sub(re.compile('^', re.M), indent, entry, 0) - xml += entry + '\n' - #recurs - if e['child1']>=0: - xmlrec, max_lvl, num = recursINDX(max_lvl, num, lvl + 1,\ - e['child1'], e['childn'] + 1) - xml += xmlrec - #close entry - xml += indent + '\n' - return xml, max_lvl, num - - body, max_lvl, num = recursINDX() - header = ncx_header % (ident, max_lvl + 1, title) - ncx = header + body + ncx_footer - if not len(indx_data) == num: - print "Warning: different number of entries in NCX", len(indx_data), num - return ncx - - def writeNCX(self, files, metadata): - # build the xml - self.isNCX = True - print "Write ncx" - xml = self.buildNCX(files.outsrcbasename, metadata['Title'][0], metadata['UniqueID'][0]) - - #write the ncx file ("outncx" is then used when building the opf) - f = open(files.outncx, 'wb') - f.write(xml) - f.close - -class dictSupport: - def __init__(self, header, sect): - self.header = header - self.sect = sect - - def getPositionMap (self): - header = self.header - sect = self.sect - - positionMap = {} - - metaOrthIndex, = struct.unpack_from('>L', header, 0x28) - metaInflIndex, = struct.unpack_from('>L', header, 0x2C) - - decodeInflection = True - if metaOrthIndex != 0xFFFFFFFF: - print "Info: Document contains orthographic index, handle as dictionary" - if metaInflIndex == 0xFFFFFFFF: - decodeInflection = False - else: - metaInflIndexData = sect.loadSection(metaInflIndex) - metaIndexCount, = struct.unpack_from('>L', metaInflIndexData, 0x18) - if metaIndexCount != 1: - print "Error: Dictionary contains multiple inflection index sections, which is not yet supported" - decodeInflection = False - inflIndexData = sect.loadSection(metaInflIndex + 1) - inflNameData = sect.loadSection(metaInflIndex + 1 + metaIndexCount) - tagSectionStart, = struct.unpack_from('>L', metaInflIndexData, 0x04) - inflectionControlByteCount, inflectionTagTable = readTagSection(tagSectionStart, metaInflIndexData) - if DEBUG: - print "inflectionTagTable: %s" % inflectionTagTable - if self.hasTag(inflectionTagTable, 0x07): - print "Error: Dictionary uses obsolete inflection rule scheme which is not yet supported" - decodeInflection = False - - data = sect.loadSection(metaOrthIndex) - tagSectionStart, = struct.unpack_from('>L', data, 0x04) - controlByteCount, tagTable = readTagSection(tagSectionStart, data) - orthIndexCount, = struct.unpack_from('>L', data, 0x18) - if DEBUG: - print "orthTagTable: %s" % tagTable - hasEntryLength = self.hasTag(tagTable, 0x02) - if not hasEntryLength: - print "Info: Index doesn't contain entry length tags" - - print "Read dictionary index data" - for i in range(metaOrthIndex + 1, metaOrthIndex + 1 + orthIndexCount): - data = sect.loadSection(i) - idxtPos, = struct.unpack_from('>L', data, 0x14) - entryCount, = struct.unpack_from('>L', data, 0x18) - idxPositions = [] - for j in range(entryCount): - pos, = struct.unpack_from('>H', data, idxtPos + 4 + (2 * j)) - idxPositions.append(pos) - # The last entry ends before the IDXT tag (but there might be zero fill bytes we need to ignore!) - idxPositions.append(idxtPos) - - for j in range(entryCount): - startPos = idxPositions[j] - endPos = idxPositions[j+1] - textLength = ord(data[startPos]) - text = data[startPos+1:startPos+1+textLength] - tagMap = self.getTagMap(controlByteCount, tagTable, data, startPos+1+textLength, endPos) - if 0x01 in tagMap: - if decodeInflection and 0x2a in tagMap: - inflectionGroups = self.getInflectionGroups(text, inflectionControlByteCount, inflectionTagTable, inflIndexData, inflNameData, tagMap[0x2a]) - else: - inflectionGroups = "" - assert len(tagMap[0x01]) == 1 - entryStartPosition = tagMap[0x01][0] - if hasEntryLength: - # The idx:entry attribute "scriptable" must be present to create entry length tags. - ml = '%s' % (text, inflectionGroups) - if entryStartPosition in positionMap: - positionMap[entryStartPosition] = positionMap[entryStartPosition] + ml - else: - positionMap[entryStartPosition] = ml - assert len(tagMap[0x02]) == 1 - entryEndPosition = entryStartPosition + tagMap[0x02][0] - if entryEndPosition in positionMap: - positionMap[entryEndPosition] = "" + positionMap[entryEndPosition] - else: - positionMap[entryEndPosition] = "" - - else: - indexTags = '\n\n%s\n' % (text, inflectionGroups) - if entryStartPosition in positionMap: - positionMap[entryStartPosition] = positionMap[entryStartPosition] + indexTags - else: - positionMap[entryStartPosition] = indexTags - return positionMap - - def hasTag(self, tagTable, tag): - ''' - Test if tag table contains given tag. - - @param tagTable: The tag table. - @param tag: The tag to search. - @return: True if tag table contains given tag; False otherwise. - ''' - for currentTag, _, _, _ in tagTable: - if currentTag == tag: - return True - return False - - def getInflectionGroups(self, mainEntry, controlByteCount, tagTable, data, inflectionNames, groupList): - ''' - Create string which contains the inflection groups with inflection rules as mobipocket tags. - - @param mainEntry: The word to inflect. - @param controlByteCount: The number of control bytes. - @param tagTable: The tag table. - @param data: The inflection index data. - @param inflectionNames: The inflection rule name data. - @param groupList: The list of inflection groups to process. - @return: String with inflection groups and rules or empty string if required tags are not available. - ''' - result = "" - idxtPos, = struct.unpack_from('>L', data, 0x14) - entryCount, = struct.unpack_from('>L', data, 0x18) - for value in groupList: - offset, = struct.unpack_from('>H', data, idxtPos + 4 + (2 * value)) - if value + 1 < entryCount: - nextOffset, = struct.unpack_from('>H', data, idxtPos + 4 + (2 * (value + 1))) - else: - nextOffset = None - - # First byte seems to be always 0x00 and must be skipped. - assert ord(data[offset]) == 0x00 - tagMap = self.getTagMap(controlByteCount, tagTable, data, offset + 1, nextOffset) - - # Make sure that the required tags are available. - if 0x05 not in tagMap: - print "Error: Required tag 0x05 not found in tagMap" - return "" - if 0x1a not in tagMap: - print "Error: Required tag 0x1a not found in tagMap" - return "" - - result += "" - - for i in range(len(tagMap[0x05])): - # Get name of inflection rule. - value = tagMap[0x05][i] - consumed, textLength = getVariableWidthValue(inflectionNames, value) - inflectionName = inflectionNames[value+consumed:value+consumed+textLength] - - # Get and apply inflection rule. - value = tagMap[0x1a][i] - offset, = struct.unpack_from('>H', data, idxtPos + 4 + (2 * value)) - textLength = ord(data[offset]) - inflection = self.applyInflectionRule(mainEntry, data, offset+1, offset+1+textLength) - if inflection != None: - result += ' ' % (inflectionName, inflection) - - result += "" - return result - - def getTagMap(self, controlByteCount, tagTable, entryData, startPos, endPos): - ''' - Create a map of tags and values from the given byte section. - - @param controlByteCount: The number of control bytes. - @param tagTable: The tag table. - @param entryData: The data to process. - @param startPos: The starting position in entryData. - @param endPos: The end position in entryData or None if it is unknown. - @return: Hashmap of tag and list of values. - ''' - tags = [] - tagHashMap = {} - controlByteIndex = 0 - dataStart = startPos + controlByteCount - - for tag, valuesPerEntry, mask, endFlag in tagTable: - if endFlag == 0x01: - controlByteIndex += 1 - continue - - value = ord(entryData[startPos + controlByteIndex]) & mask - - if value != 0: - if value == mask: - if self.countSetBits(mask) > 1: - # If all bits of masked value are set and the mask has more than one bit, a variable width value - # will follow after the control bytes which defines the length of bytes (NOT the value count!) - # which will contain the corresponding variable width values. - consumed, value = getVariableWidthValue(entryData, dataStart) - dataStart += consumed - tags.append((tag, None, value, valuesPerEntry)) - else: - tags.append((tag, 1, None, valuesPerEntry)) - else: - # Shift bits to get the masked value. - while mask & 0x01 == 0: - mask = mask >> 1 - value = value >> 1 - tags.append((tag, value, None, valuesPerEntry)) - - for tag, valueCount, valueBytes, valuesPerEntry in tags: - values = [] - if valueCount != None: - # Read valueCount * valuesPerEntry variable width values. - for _ in range(valueCount): - for _ in range(valuesPerEntry): - consumed, data = getVariableWidthValue(entryData, dataStart) - dataStart += consumed - values.append(data) - else: - # Convert valueBytes to variable width values. - totalConsumed = 0 - while totalConsumed < valueBytes: - # Does this work for valuesPerEntry != 1? - consumed, data = getVariableWidthValue(entryData, dataStart) - dataStart += consumed - totalConsumed += consumed - values.append(data) - if totalConsumed != valueBytes: - print "Error: Should consume %s bytes, but consumed %s" % (valueBytes, totalConsumed) - tagHashMap[tag] = values - - # Test that all bytes have been processed if endPos is given. - if endPos is not None and dataStart != endPos: - # The last entry might have some zero padding bytes, so complain only if non zero bytes are left. - for char in entryData[dataStart:endPos]: - if char != chr(0x00): - print "Warning: There are unprocessed index bytes left: %s" % toHex(entryData[dataStart:endPos]) - if DEBUG: - print "controlByteCount: %s" % controlByteCount - print "tagTable: %s" % tagTable - print "data: %s" % toHex(entryData[startPos:endPos]) - print "tagHashMap: %s" % tagHashMap - break - - return tagHashMap - - def applyInflectionRule(self, mainEntry, inflectionRuleData, start, end): - ''' - Apply inflection rule. - - @param mainEntry: The word to inflect. - @param inflectionRuleData: The inflection rules. - @param start: The start position of the inflection rule to use. - @param end: The end position of the inflection rule to use. - @return: The string with the inflected word or None if an error occurs. - ''' - mode = -1 - byteArray = array.array("c", mainEntry) - position = len(byteArray) - for charOffset in range(start, end): - char = inflectionRuleData[charOffset] - byte = ord(char) - if byte >= 0x0a and byte <= 0x13: - # Move cursor backwards - offset = byte - 0x0a - if mode not in [0x02, 0x03]: - mode = 0x02 - position = len(byteArray) - position -= offset - elif byte > 0x13: - if mode == -1: - print "Error: Unexpected first byte %i of inflection rule" % byte - return None - elif position == -1: - print "Error: Unexpected first byte %i of inflection rule" % byte - return None - else: - if mode == 0x01: - # Insert at word start - byteArray.insert(position, char) - position += 1 - elif mode == 0x02: - # Insert at word end - byteArray.insert(position, char) - elif mode == 0x03: - # Delete at word end - position -= 1 - deleted = byteArray.pop(position) - if deleted != char: - if DEBUG: - print "0x03: %s %s %s %s" % (mainEntry, toHex(inflectionRuleData[start:end]), char, deleted) - print "Error: Delete operation of inflection rule failed" - return None - elif mode == 0x04: - # Delete at word start - deleted = byteArray.pop(position) - if deleted != char: - if DEBUG: - print "0x03: %s %s %s %s" % (mainEntry, toHex(inflectionRuleData[start:end]), char, deleted) - print "Error: Delete operation of inflection rule failed" - return None - else: - print "Error: Inflection rule mode %x is not implemented" % mode - return None - elif byte == 0x01: - # Insert at word start - if mode not in [0x01, 0x04]: - position = 0 - mode = byte - elif byte == 0x02: - # Insert at word end - if mode not in [0x02, 0x03]: - position = len(byteArray) - mode = byte - elif byte == 0x03: - # Delete at word end - if mode not in [0x02, 0x03]: - position = len(byteArray) - mode = byte - elif byte == 0x04: - # Delete at word start - if mode not in [0x01, 0x04]: - position = 0 - mode = byte - else: - print "Error: Inflection rule mode %x is not implemented" % byte - return None - return byteArray.tostring() - - def countSetBits(self, value, bits = 8): - ''' - Count the set bits in the given value. - - @param value: Integer value. - @param bits: The number of bits of the input value (defaults to 8). - @return: Number of set bits. - ''' - count = 0 - for _ in range(bits): - if value & 0x01 == 0x01: - count += 1 - value = value >> 1 - return count - -class processHTML: - def __init__(self, files, metadata): - self.files = files - self.metadata = metadata - - def processImages(self, firstimg, sect): - outdir = self.files.outdir - imgdir = self.files.imgdir - # write out the images to the folder of images - print "Decode images" - imgnames = [] - for i in xrange(firstimg, sect.num_sections): - # We might write sections which doesn't contain an image (usually the last sections), but they won't be - # referenced as images from the html code, so there is no need to filter them. - data = sect.loadSection(i) - type = data[0:4] - if type in ["FLIS", "FCIS", "FDST", "DATP"]: # FIXME FDST and DATP aren't mentioned in MOBI wiki entry. - # Ignore FLIS, FCIS, FDST and DATP sections. - if DEBUG: - print "Skip section %i as it doesn't contain an image but a %s record." % (i, type) - imgnames.append(None) - continue - elif type == "SRCS": - # The mobi file was created by kindlegen and contains a zip archive with all source files. - # Extract the archive and save it. - print "Info: File contains kindlegen source archive, extracting as %s" % KINDLEGENSRC_FILENAME - f = open(os.path.join(outdir, KINDLEGENSRC_FILENAME), "wb") - f.write(data[16:]) - f.close() - imgnames.append(None) - continue - if data == EOF_RECORD: - if DEBUG: - print "Skip section %i as it doesn't contain an image but the EOF record." % i - # The EOF section should be the last section. - if i + 1 != sect.num_sections: - print "Warning: EOF section is not the last section" - imgnames.append(None) - continue - # Get the proper file extension - imgtype = imghdr.what(None, data) - if imgtype is None: - print "Warning: Section %s contains no image or an unknown image format" % i - imgnames.append(None) - if DEBUG: - print 'First 4 bytes: %s' % toHex(data[0:4]) - imgname = "image%05d.raw" % (1+i-firstimg) - outimg = os.path.join(imgdir, imgname) - f = open(outimg, 'wb') - f.write(data) - f.close() - else: - imgname = "image%05d.%s" % (1+i-firstimg, imgtype) - imgnames.append(imgname) - outimg = os.path.join(imgdir, imgname) - f = open(outimg, 'wb') - f.write(data) - f.close() - self.imgnames = imgnames - return self.imgnames - - def findAnchors(self, rawtext, indx_data, positionMap): - # process the raw text - # find anchors... - print "Find link anchors" - link_pattern = re.compile(r'''<[^<>]+filepos=['"]{0,1}(\d+)[^<>]*>''', re.IGNORECASE) - # TEST NCX: merge in filepos from indx - pos_links = [int(m.group(1)) for m in link_pattern.finditer(rawtext)] - if indx_data: - pos_indx = [e['pos'] for e in indx_data if e['pos']>0] - pos_links = list(set(pos_links + pos_indx)) - - for position in pos_links: - if position in positionMap: - positionMap[position] = positionMap[position] + '' % position - else: - positionMap[position] = '' % position - - # apply dictionary metadata and anchors - print "Insert data into html" - pos = 0 - lastPos = len(rawtext) - dataList = [] - for end in sorted(positionMap.keys()): - if end == 0 or end > lastPos: - continue # something's up - can't put a tag in outside ... - dataList.append(rawtext[pos:end]) - dataList.append(positionMap[end]) - pos = end - dataList.append(rawtext[pos:]) - srctext = "".join(dataList) - rawtext = None - datalist = None - self.srctext = srctext - self.indx_data = indx_data - return srctext - - def insertHREFS(self): - srctext = self.srctext - imgnames = self.imgnames - files = self.files - metadata = self.metadata - - # put in the hrefs - print "Insert hrefs into html" - # Two different regex search and replace routines. - # Best results are with the second so far IMO (DiapDealer). - - #link_pattern = re.compile(r'''''', re.IGNORECASE) - link_pattern = re.compile(r'''''', re.IGNORECASE) - #srctext = link_pattern.sub(r'''''', srctext) - srctext = link_pattern.sub(r'''''', srctext) - - # remove empty anchors - print "Remove empty anchors from html" - srctext = re.sub(r"",r"", srctext) - - # convert image references - print "Insert image references into html" - # split string into image tag pieces and other pieces - image_pattern = re.compile(r'''()''', re.IGNORECASE) - image_index_pattern = re.compile(r'''recindex=['"]{0,1}([0-9]+)['"]{0,1}''', re.IGNORECASE) - srcpieces = re.split(image_pattern, srctext) - srctext = self.srctext = None - - # all odd pieces are image tags (nulls string on even pieces if no space between them in srctext) - for i in range(1, len(srcpieces), 2): - tag = srcpieces[i] - for m in re.finditer(image_index_pattern, tag): - imageNumber = int(m.group(1)) - imageName = imgnames[imageNumber-1] - if imageName is None: - print "Error: Referenced image %s was not recognized as a valid image" % imageNumber - else: - replacement = 'src="images/' + imageName + '"' - tag = re.sub(image_index_pattern, replacement, tag, 1) - srcpieces[i] = tag - srctext = "".join(srcpieces) - - # add in character set meta into the html header if needed - if 'Codec' in metadata: - srctext = srctext[0:12]+''+srctext[12:] - # write out source text - print "Write html" - f = open(files.outsrc, 'wb') - f.write(srctext) - f.close - return srctext - - def processOPF(self, printReplica, isNCX, codec, srctext = False): - files = self.files - metadata = self.metadata - imgnames = self.imgnames - - # write out the metadata as an OEB 1.0 OPF file - print "Write opf" - f = file(files.outopf, 'wb') - META_TAGS = ['Drm Server Id', 'Drm Commerce Id', 'Drm Ebookbase Book Id', 'ASIN', 'ThumbOffset', 'Fake Cover', - 'Creator Software', 'Creator Major Version', 'Creator Minor Version', 'Creator Build Number', - 'Watermark', 'Clipping Limit', 'Publisher Limit', 'Text to Speech Disabled', 'CDE Type', - 'Updated Title', 'Font Signature (hex)', 'Tamper Proof Keys (hex)', ] - def handleTag(data, metadata, key, tag): - ''' - Format metadata values. - - @param data: List of formatted metadata entries. - @param metadata: The metadata dictionary. - @param key: The key of the metadata value to handle. - @param tag: The opf tag the the metadata value. - ''' - if key in metadata: - for value in metadata[key]: - # Strip all tag attributes for the closing tag. - closingTag = tag.split(" ")[0] - data.append('<%s>%s\n' % (tag, value, closingTag)) - del metadata[key] - - data = [] - data.append('\n') - data.append('\n') - data.append('\n') - data.append('\n') - # Handle standard metadata - if 'Title' in metadata: - handleTag(data, metadata, 'Title', 'dc:Title') - else: - data.append('Untitled\n') - handleTag(data, metadata, 'Language', 'dc:Language') - if 'UniqueID' in metadata: - handleTag(data, metadata, 'UniqueID', 'dc:Identifier id="uid"') - else: - data.append('0\n') - handleTag(data, metadata, 'Creator', 'dc:Creator') - handleTag(data, metadata, 'Contributor', 'dc:Contributor') - handleTag(data, metadata, 'Publisher', 'dc:Publisher') - handleTag(data, metadata, 'Source', 'dc:Source') - handleTag(data, metadata, 'Type', 'dc:Type') - handleTag(data, metadata, 'ISBN', 'dc:Identifier scheme="ISBN"') - if 'Subject' in metadata: - if 'SubjectCode' in metadata: - codeList = metadata['SubjectCode'] - del metadata['SubjectCode'] - else: - codeList = None - for i in range(len(metadata['Subject'])): - if codeList and i < len(codeList): - data.append('') - else: - data.append('') - data.append(metadata['Subject'][i]+'\n') - del metadata['Subject'] - handleTag(data, metadata, 'Description', 'dc:Description') - handleTag(data, metadata, 'Published', 'dc:Date') - handleTag(data, metadata, 'Rights', 'dc:Rights') - data.append('\n\n') - handleTag(data, metadata, 'DictInLanguage', 'DictionaryInLanguage') - handleTag(data, metadata, 'DictOutLanguage', 'DictionaryOutLanguage') - if 'Codec' in metadata: - for value in metadata['Codec']: - data.append('\n') - del metadata['Codec'] - if 'CoverOffset' in metadata: - imageNumber = int(metadata['CoverOffset'][0]) - imageName = imgnames[imageNumber] - if imageName is None: - print "Error: Cover image %s was not recognized as a valid image" % imageNumber - else: - data.append('images/'+imageName+'\n') - del metadata['CoverOffset'] - handleTag(data, metadata, 'Review', 'Review') - handleTag(data, metadata, 'Imprint', 'Imprint') - handleTag(data, metadata, 'Adult', 'Adult') - handleTag(data, metadata, 'DictShortName', 'DictionaryVeryShortName') - if 'Price' in metadata and 'Currency' in metadata: - priceList = metadata['Price'] - currencyList = metadata['Currency'] - if len(priceList) != len(currencyList): - print "Error: found %s price entries, but %s currency entries." - else: - for i in range(len(priceList)): - data.append(''+priceList[i]+'\n') - del metadata['Price'] - del metadata['Currency'] - data += '\n' - data.append("\n") - if 'ThumbOffset' in metadata: - imageNumber = int(metadata['ThumbOffset'][0]) - imageName = imgnames[imageNumber] - if imageName is None: - print "Error: Cover Thumbnail image %s was not recognized as a valid image" % imageNumber - else: - data.append('\n') - del metadata['ThumbOffset'] - for metaName in META_TAGS: - if metaName in metadata: - for value in metadata[metaName]: - data.append('\n') - del metadata[metaName] - for key in metadata.keys(): - if key != 'StartOffset': - for value in metadata[key]: - data.append('\n') - del metadata[key] - data.append('\n\n') - data.append('\n') - if isNCX: - outncxbasename = os.path.basename(files.outncx) - data += '\n' - data.append('\n\n\n\n\n\n') - else: - data.append('\n\n\n\n\n\n') - - # get guide items from metadata - metaguidetext = '' - if 'StartOffset' in metadata: - metaguidetext += '\n' - del metadata['StartOffset'] - - guidetext ='' - if not printReplica: - # get guide items from text - guidematch = re.search(r'''(.*)''',srctext,re.IGNORECASE+re.DOTALL) - if guidematch: - replacetext = r'''href="'''+files.outhtmlbasename+r'''#filepos\1"''' - guidetext = re.sub(r'''filepos=['"]{0,1}0*(\d+)['"]{0,1}''', replacetext, guidematch.group(1)) - guidetext += '\n' - guidetext = unicode(guidetext, codec).encode("utf-8") - data.append('\n' + metaguidetext + guidetext + '\n') - data.append('') - - f.write("".join(data)) - f.close() - -def getLanguage(langID, sublangID): - mobilangdict = { - 54 : {0 : 'af'}, # Afrikaans - 28 : {0 : 'sq'}, # Albanian - 1 : {0 : 'ar' , 5 : 'ar-dz' , 15 : 'ar-bh' , 3 : 'ar-eg' , 2 : 'ar-iq', 11 : 'ar-jo' , 13 : 'ar-kw' , 12 : 'ar-lb' , 4: 'ar-ly', 6 : 'ar-ma' , 8 : 'ar-om' , 16 : 'ar-qa' , 1 : 'ar-sa' , 10 : 'ar-sy' , 7 : 'ar-tn' , 14 : 'ar-ae' , 9 : 'ar-ye'}, # Arabic, Arabic (Algeria), Arabic (Bahrain), Arabic (Egypt), Arabic (Iraq), Arabic (Jordan), Arabic (Kuwait), Arabic (Lebanon), Arabic (Libya), Arabic (Morocco), Arabic (Oman), Arabic (Qatar), Arabic (Saudi Arabia), Arabic (Syria), Arabic (Tunisia), Arabic (United Arab Emirates), Arabic (Yemen) - 43 : {0 : 'hy'}, # Armenian - 77 : {0 : 'as'}, # Assamese - 44 : {0 : 'az'}, # "Azeri (IANA: Azerbaijani) - 45 : {0 : 'eu'}, # Basque - 35 : {0 : 'be'}, # Belarusian - 69 : {0 : 'bn'}, # Bengali - 2 : {0 : 'bg'}, # Bulgarian - 3 : {0 : 'ca'}, # Catalan - 4 : {0 : 'zh' , 3 : 'zh-hk' , 2 : 'zh-cn' , 4 : 'zh-sg' , 1 : 'zh-tw'}, # Chinese, Chinese (Hong Kong), Chinese (PRC), Chinese (Singapore), Chinese (Taiwan) - 26 : {0 : 'hr'}, # Croatian - 5 : {0 : 'cs'}, # Czech - 6 : {0 : 'da'}, # Danish - 19 : {1 : 'nl' , 2 : 'nl-be'}, # Dutch / Flemish, Dutch (Belgium) - 9 : {1 : 'en' , 3 : 'en-au' , 40 : 'en-bz' , 4 : 'en-ca' , 6 : 'en-ie' , 8 : 'en-jm' , 5 : 'en-nz' , 13 : 'en-ph' , 7 : 'en-za' , 11 : 'en-tt' , 2 : 'en-gb', 1 : 'en-us' , 12 : 'en-zw'}, # English, English (Australia), English (Belize), English (Canada), English (Ireland), English (Jamaica), English (New Zealand), English (Philippines), English (South Africa), English (Trinidad), English (United Kingdom), English (United States), English (Zimbabwe) - 37 : {0 : 'et'}, # Estonian - 56 : {0 : 'fo'}, # Faroese - 41 : {0 : 'fa'}, # Farsi / Persian - 11 : {0 : 'fi'}, # Finnish - 12 : {1 : 'fr' , 2 : 'fr-be' , 3 : 'fr-ca' , 5 : 'fr-lu' , 6 : 'fr-mc' , 4 : 'fr-ch'}, # French, French (Belgium), French (Canada), French (Luxembourg), French (Monaco), French (Switzerland) - 55 : {0 : 'ka'}, # Georgian - 7 : {1 : 'de' , 3 : 'de-at' , 5 : 'de-li' , 4 : 'de-lu' , 2 : 'de-ch'}, # German, German (Austria), German (Liechtenstein), German (Luxembourg), German (Switzerland) - 8 : {0 : 'el'}, # Greek, Modern (1453-) - 71 : {0 : 'gu'}, # Gujarati - 13 : {0 : 'he'}, # Hebrew (also code 'iw'?) - 57 : {0 : 'hi'}, # Hindi - 14 : {0 : 'hu'}, # Hungarian - 15 : {0 : 'is'}, # Icelandic - 33 : {0 : 'id'}, # Indonesian - 16 : {1 : 'it' , 2 : 'it-ch'}, # Italian, Italian (Switzerland) - 17 : {0 : 'ja'}, # Japanese - 75 : {0 : 'kn'}, # Kannada - 63 : {0 : 'kk'}, # Kazakh - 87 : {0 : 'x-kok'}, # Konkani (real language code is 'kok'?) - 18 : {0 : 'ko'}, # Korean - 38 : {0 : 'lv'}, # Latvian - 39 : {0 : 'lt'}, # Lithuanian - 47 : {0 : 'mk'}, # Macedonian - 62 : {0 : 'ms'}, # Malay - 76 : {0 : 'ml'}, # Malayalam - 58 : {0 : 'mt'}, # Maltese - 78 : {0 : 'mr'}, # Marathi - 97 : {0 : 'ne'}, # Nepali - 20 : {0 : 'no'}, # Norwegian - 72 : {0 : 'or'}, # Oriya - 21 : {0 : 'pl'}, # Polish - 22 : {2 : 'pt' , 1 : 'pt-br'}, # Portuguese, Portuguese (Brazil) - 70 : {0 : 'pa'}, # Punjabi - 23 : {0 : 'rm'}, # "Rhaeto-Romanic" (IANA: Romansh) - 24 : {0 : 'ro'}, # Romanian - 25 : {0 : 'ru'}, # Russian - 59 : {0 : 'sz'}, # "Sami (Lappish)" (not an IANA language code) - # IANA code for "Northern Sami" is 'se' - # 'SZ' is the IANA region code for Swaziland - 79 : {0 : 'sa'}, # Sanskrit - 26 : {3 : 'sr'}, # Serbian - 27 : {0 : 'sk'}, # Slovak - 36 : {0 : 'sl'}, # Slovenian - 46 : {0 : 'sb'}, # "Sorbian" (not an IANA language code) - # 'SB' is IANA region code for 'Solomon Islands' - # Lower Sorbian = 'dsb' - # Upper Sorbian = 'hsb' - # Sorbian Languages = 'wen' - 10 : {0 : 'es' , 4 : 'es' , 44 : 'es-ar' , 64 : 'es-bo' , 52 : 'es-cl' , 36 : 'es-co' , 20 : 'es-cr' , 28 : 'es-do' , 48 : 'es-ec' , 68 : 'es-sv' , 16 : 'es-gt' , 72 : 'es-hn' , 8 : 'es-mx' , 76 : 'es-ni' , 24 : 'es-pa' , 60 : 'es-py' , 40 : 'es-pe' , 80 : 'es-pr' , 56 : 'es-uy' , 32 : 'es-ve'}, # Spanish, Spanish (Mobipocket bug?), Spanish (Argentina), Spanish (Bolivia), Spanish (Chile), Spanish (Colombia), Spanish (Costa Rica), Spanish (Dominican Republic), Spanish (Ecuador), Spanish (El Salvador), Spanish (Guatemala), Spanish (Honduras), Spanish (Mexico), Spanish (Nicaragua), Spanish (Panama), Spanish (Paraguay), Spanish (Peru), Spanish (Puerto Rico), Spanish (Uruguay), Spanish (Venezuela) - 48 : {0 : 'sx'}, # "Sutu" (not an IANA language code) - # "Sutu" is another name for "Southern Sotho"? - # IANA code for "Southern Sotho" is 'st' - 65 : {0 : 'sw'}, # Swahili - 29 : {0 : 'sv' , 1 : 'sv' , 8 : 'sv-fi'}, # Swedish, Swedish (Finland) - 73 : {0 : 'ta'}, # Tamil - 68 : {0 : 'tt'}, # Tatar - 74 : {0 : 'te'}, # Telugu - 30 : {0 : 'th'}, # Thai - 49 : {0 : 'ts'}, # Tsonga - 50 : {0 : 'tn'}, # Tswana - 31 : {0 : 'tr'}, # Turkish - 34 : {0 : 'uk'}, # Ukrainian - 32 : {0 : 'ur'}, # Urdu - 67 : {2 : 'uz'}, # Uzbek - 42 : {0 : 'vi'}, # Vietnamese - 52 : {0 : 'xh'}, # Xhosa - 53 : {0 : 'zu'}, # Zulu - } - return mobilangdict.get(int(langID), {0 : 'en'}).get(int(sublangID), 'en') - -def getVariableWidthValue(data, offset): - ''' - Decode variable width value from given bytes. - - @param data: The bytes to decode. - @param offset: The start offset into data. - @return: Tuple of consumed bytes count and decoded value. - ''' - value = 0 - consumed = 0 - finished = False - while not finished: - v = data[offset + consumed] - consumed += 1 - if ord(v) & 0x80: - finished = True - value = (value << 7) | (ord(v) & 0x7f) - return consumed, value - -def toHex(byteList): - ''' - Convert list of characters into a string of hex values. - - @param byteList: List of characters. - @return: String with the character hex values separated by spaces. - ''' - return " ".join([hex(ord(c))[2:].zfill(2) for c in byteList]) - -def toBin(value, bits = 8): - ''' - Convert integer value to binary string representation. - - @param value: The integer value. - @param bits: The number of bits for the binary string (defaults to 8). - @return: String with the binary representation. - ''' - return "".join(map(lambda y:str((value>>y)&1), range(bits-1, -1, -1))) - -def readTagSection(start, data): - ''' - Read tag section from given data. - - @param start: The start position in the data. - @param data: The data to process. - @return: Tuple of control byte count and list of tag tuples. - ''' - tags = [] - assert data[start:start+4] == "TAGX" - firstEntryOffset, = struct.unpack_from('>L', data, start + 0x04) - controlByteCount, = struct.unpack_from('>L', data, start + 0x08) - - # Skip the first 12 bytes already read above. - for i in range(12, firstEntryOffset, 4): - pos = start + i - tags.append((ord(data[pos]), ord(data[pos+1]), ord(data[pos+2]), ord(data[pos+3]))) - return controlByteCount, tags - - - -def unpackBook(infile, outdir): - files = fileNames(infile, outdir) - - # Instantiate the mobiUnpack class - mu = mobiUnpack(files) - if mu.isEncrypted: - raise unpackException('file is encrypted') - header = mu.header - sect = mu.sect - records = mu.records - - if WRITE_RAW_DATA: - #write out raw header - f = open(files.getOutRaw('.rawhdr'), 'wb') - f.write(header) - f.close() - - # if exth region exists then parse it for the metadata - metadata = {} - if mu.hasExth: - metadata = mu.getMetaData() - metadata['Language'] = mu.Language() - if mu.DictInLanguage(): - metadata['DictInLanguage'] = mu.DictInLanguage() - if mu.DictOutLanguage(): - metadata['DictOutLanguage'] = mu.DictOutLanguage() - metadata['Title'] = [unicode(mu.title, mu.codec).encode("utf-8")] - metadata['Codec'] = [mu.codec] - metadata['UniqueID'] = [str(mu.unique_id)] - - # Extract raw text - rawtext = mu.rawText - - # Instantiate printReplica class - printReplica = mu.isPrintReplica - if printReplica: - print "Print Replica ebook detected" - - # Instantiate nxcExtract class and parse the INDX. - ncx = ncxExtract(header, sect, records, files) - indx_data = ncx.parseINDX() - - # Build the ncx file if ncx data exists. - if indx_data: - ncx.writeNCX(files, metadata) - - # write out raw text - if WRITE_RAW_DATA: - if printReplica: - outraw = files.getOutRaw('.rawpr') - else: - outraw = files.getOutRaw('.rawml') - f = open(outraw, 'wb') - f.write(rawtext) - f.close() - - #write out raw index sections - if WRITE_RAW_DATA: - if mu.firstidx != 0xffffffff: - for i in xrange(mu.firstidx, mu.firstimg): - data = sect.loadSection(i) - outraw = files.getOutRaw( ('.%03x.rawidx' % i)) - f = open(outraw, 'wb') - f.write(data) - f.close() - - # Get the position map from the dictSupport class. - positionMap = dictSupport(header, sect).getPositionMap() - - # Process images. - proc = processHTML(files, metadata) - imgnames = proc.processImages(mu.firstimg, sect) - - # Process print replica book. - if printReplica: - try: - mu.processPrintReplica() - except Exception, e: - print 'Error processing Print Replica: ' + str(e) - - else: - # Find anchors and insert hrefs in links. - srctext = proc.findAnchors(rawtext, indx_data, positionMap) - srctext = proc.insertHREFS() - - # Create the opf file. - if printReplica: - proc.processOPF(printReplica, ncx.isNCX, mu.codec) - else: - proc.processOPF(printReplica, ncx.isNCX, mu.codec, srctext) - -def main(argv=sys.argv): - print "MobiUnpack 0.32" - print " Copyright (c) 2009 Charles M. Hannum " - print " With Additions by P. Durrant, K. Hendricks, S. Siebert, fandrieu and DiapDealer." - if len(argv) < 2: - print "" - print "Description:" - print " Unpacks an unencrypted Kindle/MobiPocket ebook to html and images" - print " or an unencrypted Kindle/Print Replica ebook to PDF and images" - print " in a folder of the same name as the original ebook." - print "Usage:" - print " mobiunpack.py infile [outdir]" - return 1 - else: - if len(argv) >= 3: - infile, outdir = argv[1:] - else: - infile = argv[1] - outdir = os.path.splitext(infile)[0] - infileext = os.path.splitext(infile)[1].upper() - if infileext not in ['.MOBI', '.PRC', '.AZW', '.AZW4']: - print "Error: first parameter must be a Kindle/Mobipocket ebook or a Kindle/Print Replica ebook." - return 1 - - try: - print 'Unpacking Book...' - unpackBook(infile, outdir) - print 'Completed' - - except ValueError, e: - print "Error: %s" % e - return 1 - return 0 - -if __name__ == "__main__": - sys.exit(main()) - -# For execution runtime tests start mobiunpack as follows: -# python -m timeit -r 3 -n 1 -v "import mobiunpack; mobiunpack.main([None, ''])" diff --git a/Other_Tools/Additional_Tools/older_tools/reindent.py b/Other_Tools/Additional_Tools/older_tools/reindent.py deleted file mode 100644 index 3e9affb..0000000 --- a/Other_Tools/Additional_Tools/older_tools/reindent.py +++ /dev/null @@ -1,304 +0,0 @@ -#! /usr/bin/env python - -# Released to the public domain, by Tim Peters, 03 October 2000. - -"""reindent [-d][-r][-v] [ path ... ] - --d (--dryrun) Dry run. Analyze, but don't make any changes to, files. --r (--recurse) Recurse. Search for all .py files in subdirectories too. --n (--nobackup) No backup. Does not make a ".bak" file before reindenting. --v (--verbose) Verbose. Print informative msgs; else no output. --h (--help) Help. Print this usage information and exit. - -Change Python (.py) files to use 4-space indents and no hard tab characters. -Also trim excess spaces and tabs from ends of lines, and remove empty lines -at the end of files. Also ensure the last line ends with a newline. - -If no paths are given on the command line, reindent operates as a filter, -reading a single source file from standard input and writing the transformed -source to standard output. In this case, the -d, -r and -v flags are -ignored. - -You can pass one or more file and/or directory paths. When a directory -path, all .py files within the directory will be examined, and, if the -r -option is given, likewise recursively for subdirectories. - -If output is not to standard output, reindent overwrites files in place, -renaming the originals with a .bak extension. If it finds nothing to -change, the file is left alone. If reindent does change a file, the changed -file is a fixed-point for future runs (i.e., running reindent on the -resulting .py file won't change it again). - -The hard part of reindenting is figuring out what to do with comment -lines. So long as the input files get a clean bill of health from -tabnanny.py, reindent should do a good job. - -The backup file is a copy of the one that is being reindented. The ".bak" -file is generated with shutil.copy(), but some corner cases regarding -user/group and permissions could leave the backup file more readable that -you'd prefer. You can always use the --nobackup option to prevent this. -""" - -__version__ = "1" - -import tokenize -import os, shutil -import sys - -verbose = 0 -recurse = 0 -dryrun = 0 -makebackup = True - -def usage(msg=None): - if msg is not None: - print >> sys.stderr, msg - print >> sys.stderr, __doc__ - -def errprint(*args): - sep = "" - for arg in args: - sys.stderr.write(sep + str(arg)) - sep = " " - sys.stderr.write("\n") - -def main(): - import getopt - global verbose, recurse, dryrun, makebackup - try: - opts, args = getopt.getopt(sys.argv[1:], "drnvh", - ["dryrun", "recurse", "nobackup", "verbose", "help"]) - except getopt.error, msg: - usage(msg) - return - for o, a in opts: - if o in ('-d', '--dryrun'): - dryrun += 1 - elif o in ('-r', '--recurse'): - recurse += 1 - elif o in ('-n', '--nobackup'): - makebackup = False - elif o in ('-v', '--verbose'): - verbose += 1 - elif o in ('-h', '--help'): - usage() - return - if not args: - r = Reindenter(sys.stdin) - r.run() - r.write(sys.stdout) - return - for arg in args: - check(arg) - -def check(file): - if os.path.isdir(file) and not os.path.islink(file): - if verbose: - print "listing directory", file - names = os.listdir(file) - for name in names: - fullname = os.path.join(file, name) - if ((recurse and os.path.isdir(fullname) and - not os.path.islink(fullname) and - not os.path.split(fullname)[1].startswith(".")) - or name.lower().endswith(".py")): - check(fullname) - return - - if verbose: - print "checking", file, "...", - try: - f = open(file) - except IOError, msg: - errprint("%s: I/O Error: %s" % (file, str(msg))) - return - - r = Reindenter(f) - f.close() - if r.run(): - if verbose: - print "changed." - if dryrun: - print "But this is a dry run, so leaving it alone." - if not dryrun: - bak = file + ".bak" - if makebackup: - shutil.copyfile(file, bak) - if verbose: - print "backed up", file, "to", bak - f = open(file, "w") - r.write(f) - f.close() - if verbose: - print "wrote new", file - return True - else: - if verbose: - print "unchanged." - return False - -def _rstrip(line, JUNK='\n \t'): - """Return line stripped of trailing spaces, tabs, newlines. - - Note that line.rstrip() instead also strips sundry control characters, - but at least one known Emacs user expects to keep junk like that, not - mentioning Barry by name or anything . - """ - - i = len(line) - while i > 0 and line[i-1] in JUNK: - i -= 1 - return line[:i] - -class Reindenter: - - def __init__(self, f): - self.find_stmt = 1 # next token begins a fresh stmt? - self.level = 0 # current indent level - - # Raw file lines. - self.raw = f.readlines() - - # File lines, rstripped & tab-expanded. Dummy at start is so - # that we can use tokenize's 1-based line numbering easily. - # Note that a line is all-blank iff it's "\n". - self.lines = [_rstrip(line).expandtabs() + "\n" - for line in self.raw] - self.lines.insert(0, None) - self.index = 1 # index into self.lines of next line - - # List of (lineno, indentlevel) pairs, one for each stmt and - # comment line. indentlevel is -1 for comment lines, as a - # signal that tokenize doesn't know what to do about them; - # indeed, they're our headache! - self.stats = [] - - def run(self): - tokenize.tokenize(self.getline, self.tokeneater) - # Remove trailing empty lines. - lines = self.lines - while lines and lines[-1] == "\n": - lines.pop() - # Sentinel. - stats = self.stats - stats.append((len(lines), 0)) - # Map count of leading spaces to # we want. - have2want = {} - # Program after transformation. - after = self.after = [] - # Copy over initial empty lines -- there's nothing to do until - # we see a line with *something* on it. - i = stats[0][0] - after.extend(lines[1:i]) - for i in range(len(stats)-1): - thisstmt, thislevel = stats[i] - nextstmt = stats[i+1][0] - have = getlspace(lines[thisstmt]) - want = thislevel * 4 - if want < 0: - # A comment line. - if have: - # An indented comment line. If we saw the same - # indentation before, reuse what it most recently - # mapped to. - want = have2want.get(have, -1) - if want < 0: - # Then it probably belongs to the next real stmt. - for j in xrange(i+1, len(stats)-1): - jline, jlevel = stats[j] - if jlevel >= 0: - if have == getlspace(lines[jline]): - want = jlevel * 4 - break - if want < 0: # Maybe it's a hanging - # comment like this one, - # in which case we should shift it like its base - # line got shifted. - for j in xrange(i-1, -1, -1): - jline, jlevel = stats[j] - if jlevel >= 0: - want = have + getlspace(after[jline-1]) - \ - getlspace(lines[jline]) - break - if want < 0: - # Still no luck -- leave it alone. - want = have - else: - want = 0 - assert want >= 0 - have2want[have] = want - diff = want - have - if diff == 0 or have == 0: - after.extend(lines[thisstmt:nextstmt]) - else: - for line in lines[thisstmt:nextstmt]: - if diff > 0: - if line == "\n": - after.append(line) - else: - after.append(" " * diff + line) - else: - remove = min(getlspace(line), -diff) - after.append(line[remove:]) - return self.raw != self.after - - def write(self, f): - f.writelines(self.after) - - # Line-getter for tokenize. - def getline(self): - if self.index >= len(self.lines): - line = "" - else: - line = self.lines[self.index] - self.index += 1 - return line - - # Line-eater for tokenize. - def tokeneater(self, type, token, (sline, scol), end, line, - INDENT=tokenize.INDENT, - DEDENT=tokenize.DEDENT, - NEWLINE=tokenize.NEWLINE, - COMMENT=tokenize.COMMENT, - NL=tokenize.NL): - - if type == NEWLINE: - # A program statement, or ENDMARKER, will eventually follow, - # after some (possibly empty) run of tokens of the form - # (NL | COMMENT)* (INDENT | DEDENT+)? - self.find_stmt = 1 - - elif type == INDENT: - self.find_stmt = 1 - self.level += 1 - - elif type == DEDENT: - self.find_stmt = 1 - self.level -= 1 - - elif type == COMMENT: - if self.find_stmt: - self.stats.append((sline, -1)) - # but we're still looking for a new stmt, so leave - # find_stmt alone - - elif type == NL: - pass - - elif self.find_stmt: - # This is the first "real token" following a NEWLINE, so it - # must be the first token of the next program statement, or an - # ENDMARKER. - self.find_stmt = 0 - if line: # not endmarker - self.stats.append((sline, self.level)) - -# Count number of leading blanks. -def getlspace(line): - i, n = 0, len(line) - while i < n and line[i] == " ": - i += 1 - return i - -if __name__ == '__main__': - main() diff --git a/Other_Tools/Adobe_PDF_Tools/README_ineptpdf.txt b/Other_Tools/Adobe_PDF_Tools/README_ineptpdf.txt deleted file mode 100644 index 2b03d83..0000000 --- a/Other_Tools/Adobe_PDF_Tools/README_ineptpdf.txt +++ /dev/null @@ -1,18 +0,0 @@ -From Apprentice Alf's Blog - -Adobe Adept PDF, .pdf - -This directory includes modified versions of the I♥CABBAGES Adobe Adept inept scripts for pdfs. These scripts have been modified to work with OpenSSL on Windows as well as Linux and Mac OS X. 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_vX.X.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 ineptpdf_vX.X.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. diff --git a/Other_Tools/Adobe_PDF_Tools/ineptkey_v5.5.pyw b/Other_Tools/Adobe_PDF_Tools/ineptkey_v5.5.pyw deleted file mode 100755 index daa9889..0000000 --- a/Other_Tools/Adobe_PDF_Tools/ineptkey_v5.5.pyw +++ /dev/null @@ -1,468 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import with_statement - -# ineptkey.pyw, version 5.5 -# Copyright © 2009-2010 i♥cabbages - -# Released under the terms of the GNU General Public Licence, version 3 or -# later. - -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make certain -# to install the version for Python 2.6). Then save this script file as -# ineptkey.pyw and double-click on it to run it. It will create a file named -# adeptkey.der in the same directory. This is your ADEPT user key. -# -# Mac OS X users: Save this script file as ineptkey.pyw. You can run this -# program from the command line (pythonw ineptkey.pyw) or by double-clicking -# it when it has been associated with PythonLauncher. It will create a file -# named adeptkey.der in the same directory. This is your ADEPT user key. - -# Revision history: -# 1 - Initial release, for Adobe Digital Editions 1.7 -# 2 - Better algorithm for finding pLK; improved error handling -# 3 - Rename to INEPT -# 4 - Series of changes by joblack (and others?) -- -# 4.1 - quick beta fix for ADE 1.7.2 (anon) -# 4.2 - added old 1.7.1 processing -# 4.3 - better key search -# 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 -# 5.2 - added support for output of key to a particular file -# 5.3 - On Windows try PyCrypto first, OpenSSL next -# 5.4 - Modify interface to allow use of import -# 5.5 - Fix for potential problem with PyCrypto - -""" -Retrieve Adobe ADEPT user key. -""" - -__license__ = 'GPL v3' - -import sys -import os -import struct -import Tkinter -import Tkconstants -import tkMessageBox -import traceback - -class ADEPTError(Exception): - pass - -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, 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) - - 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, '\x00'*16) - def decrypt(self, data): - return self._aes.decrypt(data) - return AES - - def _load_crypto(): - AES = None - for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto): - 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] - 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() - - PAGE_EXECUTE_READWRITE = 0x40 - MEM_COMMIT = 0x1000 - MEM_RESERVE = 0x2000 - - def VirtualAlloc(): - _VirtualAlloc = kernel32.VirtualAlloc - _VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD] - _VirtualAlloc.restype = LPVOID - def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE), - protect=PAGE_EXECUTE_READWRITE): - 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] - _VirtualFree.restype = BOOL - def VirtualFree(addr, size=0, freetype=MEM_RELEASE): - 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 - "\x31\xc0" # xor %eax,%eax - "\x0f\xa2" # cpuid - "\x8b\x44\x24\x08" # mov 0x8(%esp),%eax - "\x89\x18" # mov %ebx,0x0(%eax) - "\x89\x50\x04" # mov %edx,0x4(%eax) - "\x89\x48\x08" # mov %ecx,0x8(%eax) - "\x5b" # pop %ebx - "\xc3" # ret - ) - CPUID1_INSNS = ( - "\x53" # push %ebx - "\x31\xc0" # xor %eax,%eax - "\x40" # inc %eax - "\x0f\xa2" # cpuid - "\x5b" # pop %ebx - "\xc3" # ret - ) - else: - CPUID0_INSNS = ( - "\x49\x89\xd8" # mov %rbx,%r8 - "\x49\x89\xc9" # mov %rcx,%r9 - "\x48\x31\xc0" # xor %rax,%rax - "\x0f\xa2" # cpuid - "\x4c\x89\xc8" # mov %r9,%rax - "\x89\x18" # mov %ebx,0x0(%rax) - "\x89\x50\x04" # mov %edx,0x4(%rax) - "\x89\x48\x08" # mov %ecx,0x8(%rax) - "\x4c\x89\xc3" # mov %r8,%rbx - "\xc3" # retq - ) - CPUID1_INSNS = ( - "\x53" # push %rbx - "\x48\x31\xc0" # xor %rax,%rax - "\x48\xff\xc0" # inc %rax - "\x0f\xa2" # cpuid - "\x5b" # pop %rbx - "\xc3" # retq - ) - - def cpuid0(): - _cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS) - buf = create_string_buffer(12) - def cpuid0(): - _cpuid0(buf) - 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, - 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 ADEPTError("Failed to decrypt user key key (sic)") - return string_at(outdata.pbData, outdata.cbData) - return CryptUnprotectData - CryptUnprotectData = CryptUnprotectData() - - def retrieve_key(keypath): - 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() - signature = struct.pack('>I', cpuid1())[1:] - user = GetUserName() - entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) - cuser = winreg.HKEY_CURRENT_USER - try: - regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH) - except WindowsError: - raise ADEPTError("Adobe Digital Editions not activated") - device = winreg.QueryValueEx(regkey, 'key')[0] - keykey = CryptUnprotectData(device, entropy) - userkey = None - try: - plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH) - except WindowsError: - raise ADEPTError("Could not locate ADE activation") - for i in xrange(0, 16): - try: - plkparent = winreg.OpenKey(plkroot, "%04d" % (i,)) - except WindowsError: - break - ktype = winreg.QueryValueEx(plkparent, None)[0] - if ktype != 'credentials': - continue - for j in xrange(0, 16): - try: - plkkey = winreg.OpenKey(plkparent, "%04d" % (j,)) - except WindowsError: - break - ktype = winreg.QueryValueEx(plkkey, None)[0] - if ktype != 'privateLicenseKey': - continue - userkey = winreg.QueryValueEx(plkkey, 'value')[0] - break - if userkey is not None: - break - if userkey is None: - raise ADEPTError('Could not locate privateLicenseKey') - userkey = userkey.decode('base64') - aes = AES(keykey) - userkey = aes.decrypt(userkey) - userkey = userkey[26:-ord(userkey[-1])] - with open(keypath, 'wb') as f: - f.write(userkey) - return True - -elif sys.platform.startswith('darwin'): - import xml.etree.ElementTree as etree - import Carbon.File - import Carbon.Folder - import Carbon.Folders - import MacOS - - ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat' - NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - - def find_folder(domain, dtype): - try: - fsref = Carbon.Folder.FSFindFolder(domain, dtype, False) - return Carbon.File.pathname(fsref) - except MacOS.Error: - return None - - def find_app_support_file(subpath): - dtype = Carbon.Folders.kApplicationSupportFolderType - for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain: - path = find_folder(domain, dtype) - if path is None: - continue - path = os.path.join(path, subpath) - if os.path.isfile(path): - return path - return None - - def retrieve_key(keypath): - actpath = find_app_support_file(ACTIVATION_PATH) - if actpath is None: - raise ADEPTError("Could not locate ADE activation") - tree = etree.parse(actpath) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey')) - userkey = tree.findtext(expr) - userkey = userkey.decode('base64') - userkey = userkey[26:] - with open(keypath, 'wb') as f: - f.write(userkey) - return True - -elif sys.platform.startswith('cygwin'): - def retrieve_key(keypath): - tkMessageBox.showerror( - "ADEPT Key", - "This script requires a Windows-native Python, and cannot be run " - "under Cygwin. Please install a Windows-native Python and/or " - "check your file associations.") - return False - -else: - def retrieve_key(keypath): - tkMessageBox.showerror( - "ADEPT Key", - "This script only supports Windows and Mac OS X. For Linux " - "you should be able to run ADE and this script under Wine (with " - "an appropriate version of Windows Python installed).") - return False - -class ExceptionDialog(Tkinter.Frame): - def __init__(self, root, text): - Tkinter.Frame.__init__(self, root, border=5) - label = Tkinter.Label(self, text="Unexpected error:", - anchor=Tkconstants.W, justify=Tkconstants.LEFT) - label.pack(fill=Tkconstants.X, expand=0) - self.text = Tkinter.Text(self) - self.text.pack(fill=Tkconstants.BOTH, expand=1) - - self.text.insert(Tkconstants.END, text) - - -def extractKeyfile(keypath): - try: - success = retrieve_key(keypath) - except ADEPTError, e: - print "Key generation Error: " + str(e) - return 1 - except Exception, e: - print "General Error: " + str(e) - return 1 - if not success: - return 1 - return 0 - - -def cli_main(argv=sys.argv): - keypath = argv[1] - return extractKeyfile(keypath) - - -def main(argv=sys.argv): - root = Tkinter.Tk() - root.withdraw() - progname = os.path.basename(argv[0]) - keypath = 'adeptkey.der' - success = False - try: - success = retrieve_key(keypath) - except ADEPTError, e: - tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) - except Exception: - root.wm_state('normal') - root.title('ADEPT Key') - text = traceback.format_exc() - ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) - root.mainloop() - if not success: - return 1 - tkMessageBox.showinfo( - "ADEPT Key", "Key successfully retrieved to %s" % (keypath)) - return 0 - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(main()) diff --git a/Other_Tools/Adobe_PDF_Tools/ineptpdf_v7.11.pyw b/Other_Tools/Adobe_PDF_Tools/ineptpdf_v7.11.pyw deleted file mode 100644 index 20721d1..0000000 --- a/Other_Tools/Adobe_PDF_Tools/ineptpdf_v7.11.pyw +++ /dev/null @@ -1,2255 +0,0 @@ -#! /usr/bin/env python -# ineptpdf.pyw, version 7.11 - -from __future__ import with_statement - -# To run this program install Python 2.6 from http://www.python.org/download/ -# and OpenSSL (already installed on Mac OS X and Linux) OR -# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ineptpdf.pyw and double-click on it to run it. - -# Revision history: -# 1 - Initial release -# 2 - Improved determination of key-generation algorithm -# 3 - Correctly handle PDF >=1.5 cross-reference streams -# 4 - Removal of ciando's personal ID -# 5 - Automated decryption of a complete directory -# 6.1 - backward compatibility for 1.7.1 and old adeptkey.der -# 7 - Get cross reference streams and object streams working for input. -# Not yet supported on output but this only effects file size, -# not functionality. (anon2) -# 7.1 - Correct a problem when an old trailer is not followed by startxref -# 7.2 - Correct malformed Mac OS resource forks for Stanza (anon2) -# - Support for cross ref streams on output (decreases file size) -# 7.3 - Correct bug in trailer with cross ref stream that caused the error -# "The root object is missing or invalid" in Adobe Reader. (anon2) -# 7.4 - Force all generation numbers in output file to be 0, like in v6. -# Fallback code for wrong xref improved (search till last trailer -# instead of first) (anon2) -# 7.5 - allow support for OpenSSL to replace pycrypto on all platforms -# implemented ARC4 interface to OpenSSL -# fixed minor typos -# 7.6 - backported AES and other fixes from version 8.4.48 -# 7.7 - On Windows try PyCrypto first and OpenSSL next -# 7.8 - Modify interface to allow use of import -# 7.9 - Bug fix for some session key errors when len(bookkey) > length required -# 7.10 - Various tweaks to fix minor problems. -# 7.11 - More tweaks to fix minor problems. - -""" -Decrypts Adobe ADEPT-encrypted PDF files. -""" - -__license__ = 'GPL v3' - -import sys -import os -import re -import zlib -import struct -import hashlib -from itertools import chain, islice -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class ADEPTError(Exception): - pass - - -import hashlib - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - 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) - - AES_MAXNR = 14 - - RSA_NO_PADDING = 3 - - 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) - - class RC4_KEY(Structure): - _fields_ = [('x', c_int), ('y', c_int), ('box', c_int * 256)] - RC4_KEY_p = POINTER(RC4_KEY) - - class RSA(Structure): - pass - RSA_p = POINTER(RSA) - - 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]) - - RC4_set_key = F(None,'RC4_set_key',[RC4_KEY_p, c_int, c_char_p]) - RC4_crypt = F(None,'RC4',[RC4_KEY_p, c_int, c_char_p, c_char_p]) - - d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', - [RSA_p, c_char_pp, c_long]) - RSA_size = F(c_int, 'RSA_size', [RSA_p]) - RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', - [c_int, c_char_p, c_char_p, RSA_p, c_int]) - RSA_free = F(None, 'RSA_free', [RSA_p]) - - class RSA(object): - def __init__(self, der): - buf = create_string_buffer(der) - pp = c_char_pp(cast(buf, c_char_p)) - rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) - if rsa is None: - raise ADEPTError('Error parsing ADEPT user key DER') - - def decrypt(self, from_): - rsa = self._rsa - to = create_string_buffer(RSA_size(rsa)) - dlen = RSA_private_decrypt(len(from_), from_, to, rsa, - RSA_NO_PADDING) - if dlen < 0: - raise ADEPTError('RSA decryption failed') - return to[1:dlen] - - def __del__(self): - if self._rsa is not None: - RSA_free(self._rsa) - self._rsa = None - - class ARC4(object): - @classmethod - def new(cls, userkey): - self = ARC4() - self._blocksize = len(userkey) - key = self._key = RC4_KEY() - RC4_set_key(key, self._blocksize, userkey) - return self - def __init__(self): - self._blocksize = 0 - self._key = None - def decrypt(self, data): - out = create_string_buffer(len(data)) - RC4_crypt(self._key, len(data), data, out) - return out.raw - - class AES(object): - MODE_CBC = 0 - @classmethod - def new(cls, userkey, mode, iv): - self = AES() - self._blocksize = len(userkey) - # mode is ignored since CBCMODE is only thing supported/used so far - self._mode = mode - if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : - raise ADEPTError('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 ADEPTError('Failed to initialize AES key') - return self - def __init__(self): - self._blocksize = 0 - self._keyctx = None - self._iv = 0 - self._mode = 0 - 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 ADEPTError('AES decryption failed') - return out.raw - - return (ARC4, RSA, AES) - - -def _load_crypto_pycrypto(): - from Crypto.PublicKey import RSA as _RSA - from Crypto.Cipher import ARC4 as _ARC4 - from Crypto.Cipher import AES as _AES - - # ASN.1 parsing code from tlslite - class ASN1Error(Exception): - pass - - class ASN1Parser(object): - class Parser(object): - def __init__(self, bytes): - self.bytes = bytes - self.index = 0 - - def get(self, length): - if self.index + length > len(self.bytes): - raise ASN1Error("Error decoding ASN.1") - x = 0 - for count in range(length): - x <<= 8 - x |= self.bytes[self.index] - self.index += 1 - return x - - def getFixBytes(self, lengthBytes): - bytes = self.bytes[self.index : self.index+lengthBytes] - self.index += lengthBytes - return bytes - - def getVarBytes(self, lengthLength): - lengthBytes = self.get(lengthLength) - return self.getFixBytes(lengthBytes) - - def getFixList(self, length, lengthList): - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def getVarList(self, length, lengthLength): - lengthList = self.get(lengthLength) - if lengthList % length != 0: - raise ASN1Error("Error decoding ASN.1") - lengthList = int(lengthList/length) - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def startLengthCheck(self, lengthLength): - self.lengthCheck = self.get(lengthLength) - self.indexCheck = self.index - - def setLengthCheck(self, length): - self.lengthCheck = length - self.indexCheck = self.index - - def stopLengthCheck(self): - if (self.index - self.indexCheck) != self.lengthCheck: - raise ASN1Error("Error decoding ASN.1") - - def atLengthCheck(self): - if (self.index - self.indexCheck) < self.lengthCheck: - return False - elif (self.index - self.indexCheck) == self.lengthCheck: - return True - else: - raise ASN1Error("Error decoding ASN.1") - - def __init__(self, bytes): - p = self.Parser(bytes) - p.get(1) - self.length = self._getASN1Length(p) - self.value = p.getFixBytes(self.length) - - def getChild(self, which): - p = self.Parser(self.value) - for x in range(which+1): - markIndex = p.index - p.get(1) - length = self._getASN1Length(p) - p.getFixBytes(length) - return ASN1Parser(p.bytes[markIndex:p.index]) - - def _getASN1Length(self, p): - firstLength = p.get(1) - if firstLength<=127: - return firstLength - else: - lengthLength = firstLength & 0x7F - return p.get(lengthLength) - - class ARC4(object): - @classmethod - def new(cls, userkey): - self = ARC4() - self._arc4 = _ARC4.new(userkey) - return self - def __init__(self): - self._arc4 = None - def decrypt(self, data): - return self._arc4.decrypt(data) - - class AES(object): - MODE_CBC = _AES.MODE_CBC - @classmethod - def new(cls, userkey, mode, iv): - self = AES() - self._aes = _AES.new(userkey, mode, iv) - return self - def __init__(self): - self._aes = None - def decrypt(self, data): - return self._aes.decrypt(data) - - class RSA(object): - def __init__(self, der): - key = ASN1Parser([ord(x) for x in der]) - key = [key.getChild(x).value for x in xrange(1, 4)] - key = [self.bytesToNumber(v) for v in key] - self._rsa = _RSA.construct(key) - - def bytesToNumber(self, bytes): - total = 0L - for byte in bytes: - total = (total << 8) + byte - return total - - def decrypt(self, data): - return self._rsa.decrypt(data) - - return (ARC4, RSA, AES) - -def _load_crypto(): - ARC4 = RSA = AES = None - cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) - if sys.platform.startswith('win'): - cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) - for loader in cryptolist: - try: - ARC4, RSA, AES = loader() - break - except (ImportError, ADEPTError): - pass - return (ARC4, RSA, AES) -ARC4, RSA, AES = _load_crypto() - - -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - - -# Do we generate cross reference streams on output? -# 0 = never -# 1 = only if present in input -# 2 = always - -GEN_XREF_STM = 1 - -# This is the value for the current document -gen_xref_stm = False # will be set in PDFSerializer - -# PDF parsing routines from pdfminer, with changes for EBX_HANDLER - -# Utilities - -def choplist(n, seq): - '''Groups every n elements of the list.''' - r = [] - for x in seq: - r.append(x) - if len(r) == n: - yield tuple(r) - r = [] - return - -def nunpack(s, default=0): - '''Unpacks up to 4 bytes big endian.''' - l = len(s) - if not l: - return default - elif l == 1: - return ord(s) - elif l == 2: - return struct.unpack('>H', s)[0] - elif l == 3: - return struct.unpack('>L', '\x00'+s)[0] - elif l == 4: - return struct.unpack('>L', s)[0] - else: - return TypeError('invalid length: %d' % l) - - -STRICT = 0 - - -# PS Exceptions - -class PSException(Exception): pass -class PSEOF(PSException): pass -class PSSyntaxError(PSException): pass -class PSTypeError(PSException): pass -class PSValueError(PSException): pass - - -# Basic PostScript Types - - -# PSLiteral -class PSObject(object): pass - -class PSLiteral(PSObject): - ''' - PS literals (e.g. "/Name"). - Caution: Never create these objects directly. - Use PSLiteralTable.intern() instead. - ''' - def __init__(self, name): - self.name = name - return - - def __repr__(self): - name = [] - for char in self.name: - if not char.isalnum(): - char = '#%02x' % ord(char) - name.append(char) - return '/%s' % ''.join(name) - -# PSKeyword -class PSKeyword(PSObject): - ''' - PS keywords (e.g. "showpage"). - Caution: Never create these objects directly. - Use PSKeywordTable.intern() instead. - ''' - def __init__(self, name): - self.name = name - return - - def __repr__(self): - return self.name - -# PSSymbolTable -class PSSymbolTable(object): - - ''' - Symbol table that stores PSLiteral or PSKeyword. - ''' - - def __init__(self, classe): - self.dic = {} - self.classe = classe - return - - def intern(self, name): - if name in self.dic: - lit = self.dic[name] - else: - lit = self.classe(name) - self.dic[name] = lit - return lit - -PSLiteralTable = PSSymbolTable(PSLiteral) -PSKeywordTable = PSSymbolTable(PSKeyword) -LIT = PSLiteralTable.intern -KWD = PSKeywordTable.intern -KEYWORD_BRACE_BEGIN = KWD('{') -KEYWORD_BRACE_END = KWD('}') -KEYWORD_ARRAY_BEGIN = KWD('[') -KEYWORD_ARRAY_END = KWD(']') -KEYWORD_DICT_BEGIN = KWD('<<') -KEYWORD_DICT_END = KWD('>>') - - -def literal_name(x): - if not isinstance(x, PSLiteral): - if STRICT: - raise PSTypeError('Literal required: %r' % x) - else: - return str(x) - return x.name - -def keyword_name(x): - if not isinstance(x, PSKeyword): - if STRICT: - raise PSTypeError('Keyword required: %r' % x) - else: - return str(x) - return x.name - - -## PSBaseParser -## -EOL = re.compile(r'[\r\n]') -SPC = re.compile(r'\s') -NONSPC = re.compile(r'\S') -HEX = re.compile(r'[0-9a-fA-F]') -END_LITERAL = re.compile(r'[#/%\[\]()<>{}\s]') -END_HEX_STRING = re.compile(r'[^\s0-9a-fA-F]') -HEX_PAIR = re.compile(r'[0-9a-fA-F]{2}|.') -END_NUMBER = re.compile(r'[^0-9]') -END_KEYWORD = re.compile(r'[#/%\[\]()<>{}\s]') -END_STRING = re.compile(r'[()\134]') -OCT_STRING = re.compile(r'[0-7]') -ESC_STRING = { 'b':8, 't':9, 'n':10, 'f':12, 'r':13, '(':40, ')':41, '\\':92 } - -class PSBaseParser(object): - - ''' - Most basic PostScript parser that performs only basic tokenization. - ''' - BUFSIZ = 4096 - - def __init__(self, fp): - self.fp = fp - self.seek(0) - return - - def __repr__(self): - return '' % (self.fp, self.bufpos) - - def flush(self): - return - - def close(self): - self.flush() - return - - def tell(self): - return self.bufpos+self.charpos - - def poll(self, pos=None, n=80): - pos0 = self.fp.tell() - if not pos: - pos = self.bufpos+self.charpos - self.fp.seek(pos) - ##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n)) - self.fp.seek(pos0) - return - - def seek(self, pos): - ''' - Seeks the parser to the given position. - ''' - self.fp.seek(pos) - # reset the status for nextline() - self.bufpos = pos - self.buf = '' - self.charpos = 0 - # reset the status for nexttoken() - self.parse1 = self.parse_main - self.tokens = [] - return - - def fillbuf(self): - if self.charpos < len(self.buf): return - # fetch next chunk. - self.bufpos = self.fp.tell() - self.buf = self.fp.read(self.BUFSIZ) - if not self.buf: - raise PSEOF('Unexpected EOF') - self.charpos = 0 - return - - def parse_main(self, s, i): - m = NONSPC.search(s, i) - if not m: - return (self.parse_main, len(s)) - j = m.start(0) - c = s[j] - self.tokenstart = self.bufpos+j - if c == '%': - self.token = '%' - return (self.parse_comment, j+1) - if c == '/': - self.token = '' - return (self.parse_literal, j+1) - if c in '-+' or c.isdigit(): - self.token = c - return (self.parse_number, j+1) - if c == '.': - self.token = c - return (self.parse_float, j+1) - if c.isalpha(): - self.token = c - return (self.parse_keyword, j+1) - if c == '(': - self.token = '' - self.paren = 1 - return (self.parse_string, j+1) - if c == '<': - self.token = '' - return (self.parse_wopen, j+1) - if c == '>': - self.token = '' - return (self.parse_wclose, j+1) - self.add_token(KWD(c)) - return (self.parse_main, j+1) - - def add_token(self, obj): - self.tokens.append((self.tokenstart, obj)) - return - - def parse_comment(self, s, i): - m = EOL.search(s, i) - if not m: - self.token += s[i:] - return (self.parse_comment, len(s)) - j = m.start(0) - self.token += s[i:j] - # We ignore comments. - #self.tokens.append(self.token) - return (self.parse_main, j) - - def parse_literal(self, s, i): - m = END_LITERAL.search(s, i) - if not m: - self.token += s[i:] - return (self.parse_literal, len(s)) - j = m.start(0) - self.token += s[i:j] - c = s[j] - if c == '#': - self.hex = '' - return (self.parse_literal_hex, j+1) - self.add_token(LIT(self.token)) - return (self.parse_main, j) - - def parse_literal_hex(self, s, i): - c = s[i] - if HEX.match(c) and len(self.hex) < 2: - self.hex += c - return (self.parse_literal_hex, i+1) - if self.hex: - self.token += chr(int(self.hex, 16)) - return (self.parse_literal, i) - - def parse_number(self, s, i): - m = END_NUMBER.search(s, i) - if not m: - self.token += s[i:] - return (self.parse_number, len(s)) - j = m.start(0) - self.token += s[i:j] - c = s[j] - if c == '.': - self.token += c - return (self.parse_float, j+1) - try: - self.add_token(int(self.token)) - except ValueError: - pass - return (self.parse_main, j) - def parse_float(self, s, i): - m = END_NUMBER.search(s, i) - if not m: - self.token += s[i:] - return (self.parse_float, len(s)) - j = m.start(0) - self.token += s[i:j] - self.add_token(float(self.token)) - return (self.parse_main, j) - - def parse_keyword(self, s, i): - m = END_KEYWORD.search(s, i) - if not m: - self.token += s[i:] - return (self.parse_keyword, len(s)) - j = m.start(0) - self.token += s[i:j] - if self.token == 'true': - token = True - elif self.token == 'false': - token = False - else: - token = KWD(self.token) - self.add_token(token) - return (self.parse_main, j) - - def parse_string(self, s, i): - m = END_STRING.search(s, i) - if not m: - self.token += s[i:] - return (self.parse_string, len(s)) - j = m.start(0) - self.token += s[i:j] - c = s[j] - if c == '\\': - self.oct = '' - return (self.parse_string_1, j+1) - if c == '(': - self.paren += 1 - self.token += c - return (self.parse_string, j+1) - if c == ')': - self.paren -= 1 - if self.paren: - self.token += c - return (self.parse_string, j+1) - self.add_token(self.token) - return (self.parse_main, j+1) - def parse_string_1(self, s, i): - c = s[i] - if OCT_STRING.match(c) and len(self.oct) < 3: - self.oct += c - return (self.parse_string_1, i+1) - if self.oct: - self.token += chr(int(self.oct, 8)) - return (self.parse_string, i) - if c in ESC_STRING: - self.token += chr(ESC_STRING[c]) - return (self.parse_string, i+1) - - def parse_wopen(self, s, i): - c = s[i] - if c.isspace() or HEX.match(c): - return (self.parse_hexstring, i) - if c == '<': - self.add_token(KEYWORD_DICT_BEGIN) - i += 1 - return (self.parse_main, i) - - def parse_wclose(self, s, i): - c = s[i] - if c == '>': - self.add_token(KEYWORD_DICT_END) - i += 1 - return (self.parse_main, i) - - def parse_hexstring(self, s, i): - m = END_HEX_STRING.search(s, i) - if not m: - self.token += s[i:] - return (self.parse_hexstring, len(s)) - j = m.start(0) - self.token += s[i:j] - token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)), - SPC.sub('', self.token)) - self.add_token(token) - return (self.parse_main, j) - - def nexttoken(self): - while not self.tokens: - self.fillbuf() - (self.parse1, self.charpos) = self.parse1(self.buf, self.charpos) - token = self.tokens.pop(0) - return token - - def nextline(self): - ''' - Fetches a next line that ends either with \\r or \\n. - ''' - linebuf = '' - linepos = self.bufpos + self.charpos - eol = False - while 1: - self.fillbuf() - if eol: - c = self.buf[self.charpos] - # handle '\r\n' - if c == '\n': - linebuf += c - self.charpos += 1 - break - m = EOL.search(self.buf, self.charpos) - if m: - linebuf += self.buf[self.charpos:m.end(0)] - self.charpos = m.end(0) - if linebuf[-1] == '\r': - eol = True - else: - break - else: - linebuf += self.buf[self.charpos:] - self.charpos = len(self.buf) - return (linepos, linebuf) - - def revreadlines(self): - ''' - Fetches a next line backword. This is used to locate - the trailers at the end of a file. - ''' - self.fp.seek(0, 2) - pos = self.fp.tell() - buf = '' - while 0 < pos: - prevpos = pos - pos = max(0, pos-self.BUFSIZ) - self.fp.seek(pos) - s = self.fp.read(prevpos-pos) - if not s: break - while 1: - n = max(s.rfind('\r'), s.rfind('\n')) - if n == -1: - buf = s + buf - break - yield s[n:]+buf - s = s[:n] - buf = '' - return - - -## PSStackParser -## -class PSStackParser(PSBaseParser): - - def __init__(self, fp): - PSBaseParser.__init__(self, fp) - self.reset() - return - - def reset(self): - self.context = [] - self.curtype = None - self.curstack = [] - self.results = [] - return - - def seek(self, pos): - PSBaseParser.seek(self, pos) - self.reset() - return - - def push(self, *objs): - self.curstack.extend(objs) - return - def pop(self, n): - objs = self.curstack[-n:] - self.curstack[-n:] = [] - return objs - def popall(self): - objs = self.curstack - self.curstack = [] - return objs - def add_results(self, *objs): - self.results.extend(objs) - return - - def start_type(self, pos, type): - self.context.append((pos, self.curtype, self.curstack)) - (self.curtype, self.curstack) = (type, []) - return - def end_type(self, type): - if self.curtype != type: - raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type)) - objs = [ obj for (_,obj) in self.curstack ] - (pos, self.curtype, self.curstack) = self.context.pop() - return (pos, objs) - - def do_keyword(self, pos, token): - return - - def nextobject(self, direct=False): - ''' - Yields a list of objects: keywords, literals, strings, - numbers, arrays and dictionaries. Arrays and dictionaries - are represented as Python sequence and dictionaries. - ''' - while not self.results: - (pos, token) = self.nexttoken() - ##print (pos,token), (self.curtype, self.curstack) - if (isinstance(token, int) or - isinstance(token, float) or - isinstance(token, bool) or - isinstance(token, str) or - isinstance(token, PSLiteral)): - # normal token - self.push((pos, token)) - elif token == KEYWORD_ARRAY_BEGIN: - # begin array - self.start_type(pos, 'a') - elif token == KEYWORD_ARRAY_END: - # end array - try: - self.push(self.end_type('a')) - except PSTypeError: - if STRICT: raise - elif token == KEYWORD_DICT_BEGIN: - # begin dictionary - self.start_type(pos, 'd') - elif token == KEYWORD_DICT_END: - # end dictionary - try: - (pos, objs) = self.end_type('d') - if len(objs) % 2 != 0: - raise PSSyntaxError( - 'Invalid dictionary construct: %r' % objs) - d = dict((literal_name(k), v) \ - for (k,v) in choplist(2, objs)) - self.push((pos, d)) - except PSTypeError: - if STRICT: raise - else: - self.do_keyword(pos, token) - if self.context: - continue - else: - if direct: - return self.pop(1)[0] - self.flush() - obj = self.results.pop(0) - return obj - - -LITERAL_CRYPT = PSLiteralTable.intern('Crypt') -LITERALS_FLATE_DECODE = (PSLiteralTable.intern('FlateDecode'), PSLiteralTable.intern('Fl')) -LITERALS_LZW_DECODE = (PSLiteralTable.intern('LZWDecode'), PSLiteralTable.intern('LZW')) -LITERALS_ASCII85_DECODE = (PSLiteralTable.intern('ASCII85Decode'), PSLiteralTable.intern('A85')) - - -## PDF Objects -## -class PDFObject(PSObject): pass - -class PDFException(PSException): pass -class PDFTypeError(PDFException): pass -class PDFValueError(PDFException): pass -class PDFNotImplementedError(PSException): pass - - -## PDFObjRef -## -class PDFObjRef(PDFObject): - - def __init__(self, doc, objid, genno): - if objid == 0: - if STRICT: - raise PDFValueError('PDF object id cannot be 0.') - self.doc = doc - self.objid = objid - self.genno = genno - return - - def __repr__(self): - return '' % (self.objid, self.genno) - - def resolve(self): - return self.doc.getobj(self.objid) - - -# resolve -def resolve1(x): - ''' - Resolve an object. If this is an array or dictionary, - it may still contains some indirect objects inside. - ''' - while isinstance(x, PDFObjRef): - x = x.resolve() - return x - -def resolve_all(x): - ''' - Recursively resolve X and all the internals. - Make sure there is no indirect reference within the nested object. - This procedure might be slow. - ''' - while isinstance(x, PDFObjRef): - x = x.resolve() - if isinstance(x, list): - x = [ resolve_all(v) for v in x ] - elif isinstance(x, dict): - for (k,v) in x.iteritems(): - x[k] = resolve_all(v) - return x - -def decipher_all(decipher, objid, genno, x): - ''' - Recursively decipher X. - ''' - if isinstance(x, str): - return decipher(objid, genno, x) - decf = lambda v: decipher_all(decipher, objid, genno, v) - if isinstance(x, list): - x = [decf(v) for v in x] - elif isinstance(x, dict): - x = dict((k, decf(v)) for (k, v) in x.iteritems()) - return x - - -# Type cheking -def int_value(x): - x = resolve1(x) - if not isinstance(x, int): - if STRICT: - raise PDFTypeError('Integer required: %r' % x) - return 0 - return x - -def float_value(x): - x = resolve1(x) - if not isinstance(x, float): - if STRICT: - raise PDFTypeError('Float required: %r' % x) - return 0.0 - return x - -def num_value(x): - x = resolve1(x) - if not (isinstance(x, int) or isinstance(x, float)): - if STRICT: - raise PDFTypeError('Int or Float required: %r' % x) - return 0 - return x - -def str_value(x): - x = resolve1(x) - if not isinstance(x, str): - if STRICT: - raise PDFTypeError('String required: %r' % x) - return '' - return x - -def list_value(x): - x = resolve1(x) - if not (isinstance(x, list) or isinstance(x, tuple)): - if STRICT: - raise PDFTypeError('List required: %r' % x) - return [] - return x - -def dict_value(x): - x = resolve1(x) - if not isinstance(x, dict): - if STRICT: - raise PDFTypeError('Dict required: %r' % x) - return {} - return x - -def stream_value(x): - x = resolve1(x) - if not isinstance(x, PDFStream): - if STRICT: - raise PDFTypeError('PDFStream required: %r' % x) - return PDFStream({}, '') - return x - -# ascii85decode(data) -def ascii85decode(data): - n = b = 0 - out = '' - for c in data: - if '!' <= c and c <= 'u': - n += 1 - b = b*85+(ord(c)-33) - if n == 5: - out += struct.pack('>L',b) - n = b = 0 - elif c == 'z': - assert n == 0 - out += '\0\0\0\0' - elif c == '~': - if n: - for _ in range(5-n): - b = b*85+84 - out += struct.pack('>L',b)[:n-1] - break - return out - - -## PDFStream type -class PDFStream(PDFObject): - def __init__(self, dic, rawdata, decipher=None): - length = int_value(dic.get('Length', 0)) - eol = rawdata[length:] - # quick and dirty fix for false length attribute, - # might not work if the pdf stream parser has a problem - if decipher != None and decipher.__name__ == 'decrypt_aes': - if (len(rawdata) % 16) != 0: - cutdiv = len(rawdata) // 16 - rawdata = rawdata[:16*cutdiv] - else: - if eol in ('\r', '\n', '\r\n'): - rawdata = rawdata[:length] - - self.dic = dic - self.rawdata = rawdata - self.decipher = decipher - self.data = None - self.decdata = None - self.objid = None - self.genno = None - return - - def set_objid(self, objid, genno): - self.objid = objid - self.genno = genno - return - - def __repr__(self): - if self.rawdata: - return '' % \ - (self.objid, len(self.rawdata), self.dic) - else: - return '' % \ - (self.objid, len(self.data), self.dic) - - def decode(self): - assert self.data is None and self.rawdata is not None - data = self.rawdata - if self.decipher: - # Handle encryption - data = self.decipher(self.objid, self.genno, data) - if gen_xref_stm: - self.decdata = data # keep decrypted data - if 'Filter' not in self.dic: - self.data = data - self.rawdata = None - ##print self.dict - return - filters = self.dic['Filter'] - if not isinstance(filters, list): - filters = [ filters ] - for f in filters: - if f in LITERALS_FLATE_DECODE: - # will get errors if the document is encrypted. - data = zlib.decompress(data) - elif f in LITERALS_LZW_DECODE: - data = ''.join(LZWDecoder(StringIO(data)).run()) - elif f in LITERALS_ASCII85_DECODE: - data = ascii85decode(data) - elif f == LITERAL_CRYPT: - raise PDFNotImplementedError('/Crypt filter is unsupported') - else: - raise PDFNotImplementedError('Unsupported filter: %r' % f) - # apply predictors - if 'DP' in self.dic: - params = self.dic['DP'] - else: - params = self.dic.get('DecodeParms', {}) - if 'Predictor' in params: - pred = int_value(params['Predictor']) - if pred: - if pred != 12: - raise PDFNotImplementedError( - 'Unsupported predictor: %r' % pred) - if 'Columns' not in params: - raise PDFValueError( - 'Columns undefined for predictor=12') - columns = int_value(params['Columns']) - buf = '' - ent0 = '\x00' * columns - for i in xrange(0, len(data), columns+1): - pred = data[i] - ent1 = data[i+1:i+1+columns] - if pred == '\x02': - ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \ - for (a,b) in zip(ent0,ent1)) - buf += ent1 - ent0 = ent1 - data = buf - self.data = data - self.rawdata = None - return - - def get_data(self): - if self.data is None: - self.decode() - return self.data - - def get_rawdata(self): - return self.rawdata - - def get_decdata(self): - if self.decdata is not None: - return self.decdata - data = self.rawdata - if self.decipher and data: - # Handle encryption - data = self.decipher(self.objid, self.genno, data) - return data - - -## PDF Exceptions -## -class PDFSyntaxError(PDFException): pass -class PDFNoValidXRef(PDFSyntaxError): pass -class PDFEncryptionError(PDFException): pass -class PDFPasswordIncorrect(PDFEncryptionError): pass - -# some predefined literals and keywords. -LITERAL_OBJSTM = PSLiteralTable.intern('ObjStm') -LITERAL_XREF = PSLiteralTable.intern('XRef') -LITERAL_PAGE = PSLiteralTable.intern('Page') -LITERAL_PAGES = PSLiteralTable.intern('Pages') -LITERAL_CATALOG = PSLiteralTable.intern('Catalog') - - -## XRefs -## - -## PDFXRef -## -class PDFXRef(object): - - def __init__(self): - self.offsets = None - return - - def __repr__(self): - return '' % len(self.offsets) - - def objids(self): - return self.offsets.iterkeys() - - def load(self, parser): - self.offsets = {} - while 1: - try: - (pos, line) = parser.nextline() - except PSEOF: - raise PDFNoValidXRef('Unexpected EOF - file corrupted?') - if not line: - raise PDFNoValidXRef('Premature eof: %r' % parser) - if line.startswith('trailer'): - parser.seek(pos) - break - f = line.strip().split(' ') - if len(f) != 2: - raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line)) - try: - (start, nobjs) = map(int, f) - except ValueError: - raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line)) - for objid in xrange(start, start+nobjs): - try: - (_, line) = parser.nextline() - except PSEOF: - raise PDFNoValidXRef('Unexpected EOF - file corrupted?') - f = line.strip().split(' ') - if len(f) != 3: - raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line)) - (pos, genno, use) = f - if use != 'n': continue - self.offsets[objid] = (int(genno), int(pos)) - self.load_trailer(parser) - return - - KEYWORD_TRAILER = PSKeywordTable.intern('trailer') - def load_trailer(self, parser): - try: - (_,kwd) = parser.nexttoken() - assert kwd is self.KEYWORD_TRAILER - (_,dic) = parser.nextobject(direct=True) - except PSEOF: - x = parser.pop(1) - if not x: - raise PDFNoValidXRef('Unexpected EOF - file corrupted') - (_,dic) = x[0] - self.trailer = dict_value(dic) - return - - def getpos(self, objid): - try: - (genno, pos) = self.offsets[objid] - except KeyError: - raise - return (None, pos) - - -## PDFXRefStream -## -class PDFXRefStream(object): - - def __init__(self): - self.index = None - self.data = None - self.entlen = None - self.fl1 = self.fl2 = self.fl3 = None - return - - def __repr__(self): - return '' % self.index - - def objids(self): - for first, size in self.index: - for objid in xrange(first, first + size): - yield objid - - def load(self, parser, debug=0): - (_,objid) = parser.nexttoken() # ignored - (_,genno) = parser.nexttoken() # ignored - (_,kwd) = parser.nexttoken() - (_,stream) = parser.nextobject() - if not isinstance(stream, PDFStream) or \ - stream.dic['Type'] is not LITERAL_XREF: - raise PDFNoValidXRef('Invalid PDF stream spec.') - size = stream.dic['Size'] - index = stream.dic.get('Index', (0,size)) - self.index = zip(islice(index, 0, None, 2), - islice(index, 1, None, 2)) - (self.fl1, self.fl2, self.fl3) = stream.dic['W'] - self.data = stream.get_data() - self.entlen = self.fl1+self.fl2+self.fl3 - self.trailer = stream.dic - return - - def getpos(self, objid): - offset = 0 - for first, size in self.index: - if first <= objid and objid < (first + size): - break - offset += size - else: - raise KeyError(objid) - i = self.entlen * ((objid - first) + offset) - ent = self.data[i:i+self.entlen] - f1 = nunpack(ent[:self.fl1], 1) - if f1 == 1: - pos = nunpack(ent[self.fl1:self.fl1+self.fl2]) - genno = nunpack(ent[self.fl1+self.fl2:]) - return (None, pos) - elif f1 == 2: - objid = nunpack(ent[self.fl1:self.fl1+self.fl2]) - index = nunpack(ent[self.fl1+self.fl2:]) - return (objid, index) - # this is a free object - raise KeyError(objid) - - -## PDFDocument -## -## A PDFDocument object represents a PDF document. -## Since a PDF file is usually pretty big, normally it is not loaded -## at once. Rather it is parsed dynamically as processing goes. -## A PDF parser is associated with the document. -## -class PDFDocument(object): - - def __init__(self): - self.xrefs = [] - self.objs = {} - self.parsed_objs = {} - self.root = None - self.catalog = None - self.parser = None - self.encryption = None - self.decipher = None - return - - # set_parser(parser) - # Associates the document with an (already initialized) parser object. - def set_parser(self, parser): - if self.parser: return - self.parser = parser - # The document is set to be temporarily ready during collecting - # all the basic information about the document, e.g. - # the header, the encryption information, and the access rights - # for the document. - self.ready = True - # Retrieve the information of each header that was appended - # (maybe multiple times) at the end of the document. - self.xrefs = parser.read_xref() - for xref in self.xrefs: - trailer = xref.trailer - if not trailer: continue - - # If there's an encryption info, remember it. - if 'Encrypt' in trailer: - #assert not self.encryption - try: - self.encryption = (list_value(trailer['ID']), - dict_value(trailer['Encrypt'])) - # fix for bad files - except: - self.encryption = ('ffffffffffffffffffffffffffffffffffff', - dict_value(trailer['Encrypt'])) - if 'Root' in trailer: - self.set_root(dict_value(trailer['Root'])) - break - else: - raise PDFSyntaxError('No /Root object! - Is this really a PDF?') - # The document is set to be non-ready again, until all the - # proper initialization (asking the password key and - # verifying the access permission, so on) is finished. - self.ready = False - return - - # set_root(root) - # Set the Root dictionary of the document. - # Each PDF file must have exactly one /Root dictionary. - def set_root(self, root): - self.root = root - self.catalog = dict_value(self.root) - if self.catalog.get('Type') is not LITERAL_CATALOG: - if STRICT: - raise PDFSyntaxError('Catalog not found!') - return - # initialize(password='') - # Perform the initialization with a given password. - # This step is mandatory even if there's no password associated - # with the document. - def initialize(self, password=''): - if not self.encryption: - self.is_printable = self.is_modifiable = self.is_extractable = True - self.ready = True - return - (docid, param) = self.encryption - type = literal_name(param['Filter']) - if type == 'Adobe.APS': - return self.initialize_adobe_ps(password, docid, param) - if type == 'Standard': - return self.initialize_standard(password, docid, param) - if type == 'EBX_HANDLER': - return self.initialize_ebx(password, docid, param) - raise PDFEncryptionError('Unknown filter: param=%r' % param) - - def initialize_adobe_ps(self, password, docid, param): - global KEYFILEPATH - self.decrypt_key = self.genkey_adobe_ps(param) - self.genkey = self.genkey_v4 - self.decipher = self.decrypt_aes - self.ready = True - return - - def genkey_adobe_ps(self, param): - # nice little offline principal keys dictionary - # global static principal key for German Onleihe / Bibliothek Digital - principalkeys = { 'bibliothek-digital.de': 'rRwGv2tbpKov1krvv7PO0ws9S436/lArPlfipz5Pqhw='.decode('base64')} - self.is_printable = self.is_modifiable = self.is_extractable = True - length = int_value(param.get('Length', 0)) / 8 - edcdata = str_value(param.get('EDCData')).decode('base64') - pdrllic = str_value(param.get('PDRLLic')).decode('base64') - pdrlpol = str_value(param.get('PDRLPol')).decode('base64') - edclist = [] - for pair in edcdata.split('\n'): - edclist.append(pair) - # principal key request - for key in principalkeys: - if key in pdrllic: - principalkey = principalkeys[key] - else: - raise ADEPTError('Cannot find principal key for this pdf') - shakey = SHA256(principalkey) - ivector = 16 * chr(0) - plaintext = AES.new(shakey,AES.MODE_CBC,ivector).decrypt(edclist[9].decode('base64')) - if plaintext[-16:] != 16 * chr(16): - raise ADEPTError('Offlinekey cannot be decrypted, aborting ...') - pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol) - if ord(pdrlpol[-1]) < 1 or ord(pdrlpol[-1]) > 16: - raise ADEPTError('Could not decrypt PDRLPol, aborting ...') - else: - cutter = -1 * ord(pdrlpol[-1]) - pdrlpol = pdrlpol[:cutter] - return plaintext[:16] - - PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \ - '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz' - # experimental aes pw support - def initialize_standard(self, password, docid, param): - # copy from a global variable - V = int_value(param.get('V', 0)) - if (V <=0 or V > 4): - raise PDFEncryptionError('Unknown algorithm: param=%r' % param) - length = int_value(param.get('Length', 40)) # Key length (bits) - O = str_value(param['O']) - R = int_value(param['R']) # Revision - if 5 <= R: - raise PDFEncryptionError('Unknown revision: %r' % R) - U = str_value(param['U']) - P = int_value(param['P']) - try: - EncMetadata = str_value(param['EncryptMetadata']) - except: - EncMetadata = 'True' - self.is_printable = bool(P & 4) - self.is_modifiable = bool(P & 8) - self.is_extractable = bool(P & 16) - self.is_annotationable = bool(P & 32) - self.is_formsenabled = bool(P & 256) - self.is_textextractable = bool(P & 512) - self.is_assemblable = bool(P & 1024) - self.is_formprintable = bool(P & 2048) - # Algorithm 3.2 - password = (password+self.PASSWORD_PADDING)[:32] # 1 - hash = hashlib.md5(password) # 2 - hash.update(O) # 3 - hash.update(struct.pack('= 3: - # Algorithm 3.5 - hash = hashlib.md5(self.PASSWORD_PADDING) # 2 - hash.update(docid[0]) # 3 - x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4 - for i in xrange(1,19+1): - k = ''.join( chr(ord(c) ^ i) for c in key ) - x = ARC4.new(k).decrypt(x) - u1 = x+x # 32bytes total - if R == 2: - is_authenticated = (u1 == U) - else: - is_authenticated = (u1[:16] == U[:16]) - if not is_authenticated: - raise ADEPTError('Password is not correct.') - self.decrypt_key = key - # genkey method - if V == 1 or V == 2: - self.genkey = self.genkey_v2 - elif V == 3: - self.genkey = self.genkey_v3 - elif V == 4: - self.genkey = self.genkey_v2 - #self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2 - # rc4 - if V != 4: - self.decipher = self.decipher_rc4 # XXX may be AES - # aes - elif V == 4 and Length == 128: - elf.decipher = self.decipher_aes - elif V == 4 and Length == 256: - raise PDFNotImplementedError('AES256 encryption is currently unsupported') - self.ready = True - return - - def initialize_ebx(self, password, docid, param): - self.is_printable = self.is_modifiable = self.is_extractable = True - with open(password, 'rb') as f: - keyder = f.read() - rsa = RSA(keyder) - length = int_value(param.get('Length', 0)) / 8 - rights = str_value(param.get('ADEPT_LICENSE')).decode('base64') - rights = zlib.decompress(rights, -15) - rights = etree.fromstring(rights) - expr = './/{http://ns.adobe.com/adept}encryptedKey' - bookkey = ''.join(rights.findtext(expr)).decode('base64') - bookkey = rsa.decrypt(bookkey) - if bookkey[0] != '\x02': - raise ADEPTError('error decrypting book session key') - index = bookkey.index('\0') + 1 - bookkey = bookkey[index:] - ebx_V = int_value(param.get('V', 4)) - ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6)) - # added because of improper booktype / decryption book session key errors - if length > 0: - if len(bookkey) == length: - if ebx_V == 3: - V = 3 - else: - V = 2 - elif len(bookkey) == length + 1: - V = ord(bookkey[0]) - bookkey = bookkey[1:] - else: - print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type) - print "length is %d and len(bookkey) is %d" % (length, len(bookkey)) - print "bookkey[0] is %d" % ord(bookkey[0]) - raise ADEPTError('error decrypting book session key - mismatched length') - else: - # proper length unknown try with whatever you have - print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type) - print "length is %d and len(bookkey) is %d" % (length, len(bookkey)) - print "bookkey[0] is %d" % ord(bookkey[0]) - if ebx_V == 3: - V = 3 - else: - V = 2 - self.decrypt_key = bookkey - self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2 - self.decipher = self.decrypt_rc4 - self.ready = True - return - - # genkey functions - def genkey_v2(self, objid, genno): - objid = struct.pack(' PDFObjStmRef.maxindex: - PDFObjStmRef.maxindex = index - - -## PDFParser -## -class PDFParser(PSStackParser): - - def __init__(self, doc, fp): - PSStackParser.__init__(self, fp) - self.doc = doc - self.doc.set_parser(self) - return - - def __repr__(self): - return '' - - KEYWORD_R = PSKeywordTable.intern('R') - KEYWORD_ENDOBJ = PSKeywordTable.intern('endobj') - KEYWORD_STREAM = PSKeywordTable.intern('stream') - KEYWORD_XREF = PSKeywordTable.intern('xref') - KEYWORD_STARTXREF = PSKeywordTable.intern('startxref') - def do_keyword(self, pos, token): - if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF): - self.add_results(*self.pop(1)) - return - if token is self.KEYWORD_ENDOBJ: - self.add_results(*self.pop(4)) - return - - if token is self.KEYWORD_R: - # reference to indirect object - try: - ((_,objid), (_,genno)) = self.pop(2) - (objid, genno) = (int(objid), int(genno)) - obj = PDFObjRef(self.doc, objid, genno) - self.push((pos, obj)) - except PSSyntaxError: - pass - return - - if token is self.KEYWORD_STREAM: - # stream object - ((_,dic),) = self.pop(1) - dic = dict_value(dic) - try: - objlen = int_value(dic['Length']) - except KeyError: - if STRICT: - raise PDFSyntaxError('/Length is undefined: %r' % dic) - objlen = 0 - self.seek(pos) - try: - (_, line) = self.nextline() # 'stream' - except PSEOF: - if STRICT: - raise PDFSyntaxError('Unexpected EOF') - return - pos += len(line) - self.fp.seek(pos) - data = self.fp.read(objlen) - self.seek(pos+objlen) - while 1: - try: - (linepos, line) = self.nextline() - except PSEOF: - if STRICT: - raise PDFSyntaxError('Unexpected EOF') - break - if 'endstream' in line: - i = line.index('endstream') - objlen += i - data += line[:i] - break - objlen += len(line) - data += line - self.seek(pos+objlen) - obj = PDFStream(dic, data, self.doc.decipher) - self.push((pos, obj)) - return - - # others - self.push((pos, token)) - return - - def find_xref(self): - # search the last xref table by scanning the file backwards. - prev = None - for line in self.revreadlines(): - line = line.strip() - if line == 'startxref': break - if line: - prev = line - else: - raise PDFNoValidXRef('Unexpected EOF') - return int(prev) - - # read xref table - def read_xref_from(self, start, xrefs): - self.seek(start) - self.reset() - try: - (pos, token) = self.nexttoken() - except PSEOF: - raise PDFNoValidXRef('Unexpected EOF') - if isinstance(token, int): - # XRefStream: PDF-1.5 - if GEN_XREF_STM == 1: - global gen_xref_stm - gen_xref_stm = True - self.seek(pos) - self.reset() - xref = PDFXRefStream() - xref.load(self) - else: - if token is not self.KEYWORD_XREF: - raise PDFNoValidXRef('xref not found: pos=%d, token=%r' % - (pos, token)) - self.nextline() - xref = PDFXRef() - xref.load(self) - xrefs.append(xref) - trailer = xref.trailer - if 'XRefStm' in trailer: - pos = int_value(trailer['XRefStm']) - self.read_xref_from(pos, xrefs) - if 'Prev' in trailer: - # find previous xref - pos = int_value(trailer['Prev']) - self.read_xref_from(pos, xrefs) - return - - # read xref tables and trailers - def read_xref(self): - xrefs = [] - trailerpos = None - try: - pos = self.find_xref() - self.read_xref_from(pos, xrefs) - except PDFNoValidXRef: - # fallback - self.seek(0) - pat = re.compile(r'^(\d+)\s+(\d+)\s+obj\b') - offsets = {} - xref = PDFXRef() - while 1: - try: - (pos, line) = self.nextline() - except PSEOF: - break - if line.startswith('trailer'): - trailerpos = pos # remember last trailer - m = pat.match(line) - if not m: continue - (objid, genno) = m.groups() - offsets[int(objid)] = (0, pos) - if not offsets: raise - xref.offsets = offsets - if trailerpos: - self.seek(trailerpos) - xref.load_trailer(self) - xrefs.append(xref) - return xrefs - -## PDFObjStrmParser -## -class PDFObjStrmParser(PDFParser): - - def __init__(self, data, doc): - PSStackParser.__init__(self, StringIO(data)) - self.doc = doc - return - - def flush(self): - self.add_results(*self.popall()) - return - - KEYWORD_R = KWD('R') - def do_keyword(self, pos, token): - if token is self.KEYWORD_R: - # reference to indirect object - try: - ((_,objid), (_,genno)) = self.pop(2) - (objid, genno) = (int(objid), int(genno)) - obj = PDFObjRef(self.doc, objid, genno) - self.push((pos, obj)) - except PSSyntaxError: - pass - return - # others - self.push((pos, token)) - return - -### -### My own code, for which there is none else to blame - -class PDFSerializer(object): - def __init__(self, inf, keypath): - global GEN_XREF_STM, gen_xref_stm - gen_xref_stm = GEN_XREF_STM > 1 - self.version = inf.read(8) - inf.seek(0) - self.doc = doc = PDFDocument() - parser = PDFParser(doc, inf) - doc.initialize(keypath) - self.objids = objids = set() - for xref in reversed(doc.xrefs): - trailer = xref.trailer - for objid in xref.objids(): - objids.add(objid) - trailer = dict(trailer) - trailer.pop('Prev', None) - trailer.pop('XRefStm', None) - if 'Encrypt' in trailer: - objids.remove(trailer.pop('Encrypt').objid) - self.trailer = trailer - - def dump(self, outf): - self.outf = outf - self.write(self.version) - self.write('\n%\xe2\xe3\xcf\xd3\n') - doc = self.doc - objids = self.objids - xrefs = {} - maxobj = max(objids) - trailer = dict(self.trailer) - trailer['Size'] = maxobj + 1 - for objid in objids: - obj = doc.getobj(objid) - if isinstance(obj, PDFObjStmRef): - xrefs[objid] = obj - continue - if obj is not None: - try: - genno = obj.genno - except AttributeError: - genno = 0 - xrefs[objid] = (self.tell(), genno) - self.serialize_indirect(objid, obj) - startxref = self.tell() - - if not gen_xref_stm: - self.write('xref\n') - self.write('0 %d\n' % (maxobj + 1,)) - for objid in xrange(0, maxobj + 1): - if objid in xrefs: - # force the genno to be 0 - self.write("%010d 00000 n \n" % xrefs[objid][0]) - else: - self.write("%010d %05d f \n" % (0, 65535)) - - self.write('trailer\n') - self.serialize_object(trailer) - self.write('\nstartxref\n%d\n%%%%EOF' % startxref) - - else: # Generate crossref stream. - - # Calculate size of entries - maxoffset = max(startxref, maxobj) - maxindex = PDFObjStmRef.maxindex - fl2 = 2 - power = 65536 - while maxoffset >= power: - fl2 += 1 - power *= 256 - fl3 = 1 - power = 256 - while maxindex >= power: - fl3 += 1 - power *= 256 - - index = [] - first = None - prev = None - data = [] - # Put the xrefstream's reference in itself - startxref = self.tell() - maxobj += 1 - xrefs[maxobj] = (startxref, 0) - for objid in sorted(xrefs): - if first is None: - first = objid - elif objid != prev + 1: - index.extend((first, prev - first + 1)) - first = objid - prev = objid - objref = xrefs[objid] - if isinstance(objref, PDFObjStmRef): - f1 = 2 - f2 = objref.stmid - f3 = objref.index - else: - f1 = 1 - f2 = objref[0] - # we force all generation numbers to be 0 - # f3 = objref[1] - f3 = 0 - - data.append(struct.pack('>B', f1)) - data.append(struct.pack('>L', f2)[-fl2:]) - data.append(struct.pack('>L', f3)[-fl3:]) - index.extend((first, prev - first + 1)) - data = zlib.compress(''.join(data)) - dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index, - 'W': [1, fl2, fl3], 'Length': len(data), - 'Filter': LITERALS_FLATE_DECODE[0], - 'Root': trailer['Root'],} - if 'Info' in trailer: - dic['Info'] = trailer['Info'] - xrefstm = PDFStream(dic, data) - self.serialize_indirect(maxobj, xrefstm) - self.write('startxref\n%d\n%%%%EOF' % startxref) - def write(self, data): - self.outf.write(data) - self.last = data[-1:] - - def tell(self): - return self.outf.tell() - - def escape_string(self, string): - string = string.replace('\\', '\\\\') - string = string.replace('\n', r'\n') - string = string.replace('(', r'\(') - string = string.replace(')', r'\)') - # get rid of ciando id - regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}') - if regularexp.match(string): return ('http://www.ciando.com') - return string - - def serialize_object(self, obj): - if isinstance(obj, dict): - # Correct malformed Mac OS resource forks for Stanza - if 'ResFork' in obj and 'Type' in obj and 'Subtype' not in obj \ - and isinstance(obj['Type'], int): - obj['Subtype'] = obj['Type'] - del obj['Type'] - # end - hope this doesn't have bad effects - self.write('<<') - for key, val in obj.items(): - self.write('/%s' % key) - self.serialize_object(val) - self.write('>>') - elif isinstance(obj, list): - self.write('[') - for val in obj: - self.serialize_object(val) - self.write(']') - elif isinstance(obj, str): - self.write('(%s)' % self.escape_string(obj)) - elif isinstance(obj, bool): - if self.last.isalnum(): - self.write(' ') - self.write(str(obj).lower()) - elif isinstance(obj, (int, long, float)): - if self.last.isalnum(): - self.write(' ') - self.write(str(obj)) - elif isinstance(obj, PDFObjRef): - if self.last.isalnum(): - self.write(' ') - self.write('%d %d R' % (obj.objid, 0)) - elif isinstance(obj, PDFStream): - ### If we don't generate cross ref streams the object streams - ### are no longer useful, as we have extracted all objects from - ### them. Therefore leave them out from the output. - if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm: - self.write('(deleted)') - else: - data = obj.get_decdata() - self.serialize_object(obj.dic) - self.write('stream\n') - self.write(data) - self.write('\nendstream') - else: - data = str(obj) - if data[0].isalnum() and self.last.isalnum(): - self.write(' ') - self.write(data) - - def serialize_indirect(self, objid, obj): - self.write('%d 0 obj' % (objid,)) - self.serialize_object(obj) - if self.last.isalnum(): - self.write('\n') - self.write('endobj\n') - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - ltext='Select file for decryption\n' - self.status = Tkinter.Label(self, text=ltext) - self.status.pack(fill=Tkconstants.X, expand=1) - body = Tkinter.Frame(self) - body.pack(fill=Tkconstants.X, expand=1) - sticky = Tkconstants.E + Tkconstants.W - body.grid_columnconfigure(1, weight=2) - Tkinter.Label(body, text='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('adeptkey.der'): - self.keypath.insert(0, 'adeptkey.der') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - - - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT key file', - defaultextension='.der', filetypes=[('DER-encoded files', '.der'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(os.path.realpath(keypath)) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT encrypted PDF file to decrypt', - defaultextension='.pdf', filetypes=[('PDF files', '.pdf'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(os.path.realpath(inpath)) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted PDF file to produce', - defaultextension='.pdf', filetypes=[('PDF files', '.pdf'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(os.path.realpath(outpath)) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - # keyfile doesn't exist - self.status['text'] = 'Specified Adept key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - # patch for non-ascii characters - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Processing ...' - try: - cli_main(argv) - except Exception, a: - self.status['text'] = 'Error: ' + str(a) - return - self.status['text'] = 'File successfully decrypted.\n'+\ - 'Close this window or decrypt another pdf file.' - return - - -def decryptBook(keypath, inpath, outpath): - with open(inpath, 'rb') as inf: - try: - serializer = PDFSerializer(inf, keypath) - except: - print "Error serializing pdf. Probably wrong key." - return 1 - # hope this will fix the 'bad file descriptor' problem - with open(outpath, 'wb') as outf: - # help construct to make sure the method runs to the end - try: - serializer.dump(outf) - except: - print "error writing pdf." - return 1 - return 0 - - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if RSA is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) - - -def gui_main(): - root = Tkinter.Tk() - if RSA is None: - root.withdraw() - tkMessageBox.showerror( - "INEPT PDF", - "This script requires OpenSSL or PyCrypto, which must be installed " - "separately. Read the top-of-script comment for details.") - return 1 - root.title('INEPT PDF Decrypter') - root.resizable(True, False) - root.minsize(370, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/Other_Tools/Adobe_ePub_Tools/Original_IHeart_Cabbages_Scripts/ineptepub_v5.2.pyw b/Other_Tools/Adobe_ePub_Tools/Original_IHeart_Cabbages_Scripts/ineptepub_v5.2.pyw deleted file mode 100755 index d6c5f7d..0000000 --- a/Other_Tools/Adobe_ePub_Tools/Original_IHeart_Cabbages_Scripts/ineptepub_v5.2.pyw +++ /dev/null @@ -1,455 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -# ineptepub.pyw, version 5.2 -# Copyright © 2009-2010 i♥cabbages - -# Released under the terms of the GNU General Public Licence, version 3 or -# later. - -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make sure to -# install the version for Python 2.6). Save this script file as -# ineptepub.pyw and double-click on it to run it. -# -# Mac OS X users: Save this script file as ineptepub.pyw. You can run this -# program from the command line (pythonw ineptepub.pyw) or by double-clicking -# it when it has been associated with PythonLauncher. - -# Revision history: -# 1 - Initial release -# 2 - Rename to INEPT, fix exit code -# 5 - Version bump to avoid (?) confusion; -# 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 - -""" -Decrypt Adobe ADEPT-encrypted EPUB books. -""" - -from __future__ import with_statement - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class ADEPTError(Exception): - pass - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - libcrypto = find_library('crypto') - if libcrypto is None: - raise ADEPTError('libcrypto not found') - libcrypto = CDLL(libcrypto) - - RSA_NO_PADDING = 3 - AES_MAXNR = 14 - - c_char_pp = POINTER(c_char_p) - c_int_p = POINTER(c_int) - - class RSA(Structure): - pass - RSA_p = POINTER(RSA) - - 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 - - d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', - [RSA_p, c_char_pp, c_long]) - RSA_size = F(c_int, 'RSA_size', [RSA_p]) - RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', - [c_int, c_char_p, c_char_p, RSA_p, c_int]) - RSA_free = F(None, 'RSA_free', [RSA_p]) - 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 RSA(object): - def __init__(self, der): - buf = create_string_buffer(der) - pp = c_char_pp(cast(buf, c_char_p)) - rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) - if rsa is None: - raise ADEPTError('Error parsing ADEPT user key DER') - - def decrypt(self, from_): - rsa = self._rsa - to = create_string_buffer(RSA_size(rsa)) - dlen = RSA_private_decrypt(len(from_), from_, to, rsa, - RSA_NO_PADDING) - if dlen < 0: - raise ADEPTError('RSA decryption failed') - return to[:dlen] - - def __del__(self): - if self._rsa is not None: - RSA_free(self._rsa) - self._rsa = None - - class AES(object): - def __init__(self, userkey): - self._blocksize = len(userkey) - 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, RSA) - -def _load_crypto_pycrypto(): - from Crypto.Cipher import AES as _AES - from Crypto.PublicKey import RSA as _RSA - - # ASN.1 parsing code from tlslite - class ASN1Error(Exception): - pass - - class ASN1Parser(object): - class Parser(object): - def __init__(self, bytes): - self.bytes = bytes - self.index = 0 - - def get(self, length): - if self.index + length > len(self.bytes): - raise ASN1Error("Error decoding ASN.1") - x = 0 - for count in range(length): - x <<= 8 - x |= self.bytes[self.index] - self.index += 1 - return x - - def getFixBytes(self, lengthBytes): - bytes = self.bytes[self.index : self.index+lengthBytes] - self.index += lengthBytes - return bytes - - def getVarBytes(self, lengthLength): - lengthBytes = self.get(lengthLength) - return self.getFixBytes(lengthBytes) - - def getFixList(self, length, lengthList): - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def getVarList(self, length, lengthLength): - lengthList = self.get(lengthLength) - if lengthList % length != 0: - raise ASN1Error("Error decoding ASN.1") - lengthList = int(lengthList/length) - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def startLengthCheck(self, lengthLength): - self.lengthCheck = self.get(lengthLength) - self.indexCheck = self.index - - def setLengthCheck(self, length): - self.lengthCheck = length - self.indexCheck = self.index - - def stopLengthCheck(self): - if (self.index - self.indexCheck) != self.lengthCheck: - raise ASN1Error("Error decoding ASN.1") - - def atLengthCheck(self): - if (self.index - self.indexCheck) < self.lengthCheck: - return False - elif (self.index - self.indexCheck) == self.lengthCheck: - return True - else: - raise ASN1Error("Error decoding ASN.1") - - def __init__(self, bytes): - p = self.Parser(bytes) - p.get(1) - self.length = self._getASN1Length(p) - self.value = p.getFixBytes(self.length) - - def getChild(self, which): - p = self.Parser(self.value) - for x in range(which+1): - markIndex = p.index - p.get(1) - length = self._getASN1Length(p) - p.getFixBytes(length) - return ASN1Parser(p.bytes[markIndex:p.index]) - - def _getASN1Length(self, p): - firstLength = p.get(1) - if firstLength<=127: - return firstLength - else: - lengthLength = firstLength & 0x7F - return p.get(lengthLength) - - class AES(object): - def __init__(self, key): - self._aes = _AES.new(key, _AES.MODE_CBC) - - def decrypt(self, data): - return self._aes.decrypt(data) - - class RSA(object): - def __init__(self, der): - key = ASN1Parser([ord(x) for x in der]) - key = [key.getChild(x).value for x in xrange(1, 4)] - key = [self.bytesToNumber(v) for v in key] - self._rsa = _RSA.construct(key) - - def bytesToNumber(self, bytes): - total = 0L - for byte in bytes: - total = (total << 8) + byte - return total - - def decrypt(self, data): - return self._rsa.decrypt(data) - - return (AES, RSA) - -def _load_crypto(): - AES = RSA = None - for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): - try: - AES, RSA = loader() - break - except (ImportError, ADEPTError): - pass - return (AES, RSA) -AES, RSA = _load_crypto() - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - self._aes = AES(bookkey) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - if path is not None: - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be" \ - " installed separately. Read the top-of-script comment for" \ - " details." % (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - with open(keypath, 'rb') as f: - keyder = f.read() - rsa = RSA(keyder) - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = rsa.decrypt(bookkey.decode('base64')) - # Padded as per RSAES-PKCS1-v1_5 - if bookkey[-17] != '\x00': - raise ADEPTError('problem decrypting session key') - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('adeptkey.der'): - self.keypath.insert(0, 'adeptkey.der') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT key file', - defaultextension='.der', filetypes=[('DER-encoded files', '.der'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "INEPT EPUB Decrypter", - "This script requires OpenSSL or PyCrypto, which must be" - " installed separately. Read the top-of-script comment for" - " details.") - return 1 - root.title('INEPT EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/Other_Tools/Adobe_ePub_Tools/Original_IHeart_Cabbages_Scripts/ineptkey_v5.pyw b/Other_Tools/Adobe_ePub_Tools/Original_IHeart_Cabbages_Scripts/ineptkey_v5.pyw deleted file mode 100755 index 3756ae3..0000000 --- a/Other_Tools/Adobe_ePub_Tools/Original_IHeart_Cabbages_Scripts/ineptkey_v5.pyw +++ /dev/null @@ -1,377 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -# ineptkey.pyw, version 5 -# Copyright © 2009-2010 i♥cabbages - -# Released under the terms of the GNU General Public Licence, version 3 or -# later. - -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make certain -# to install the version for Python 2.6). Then save this script file as -# ineptkey.pyw and double-click on it to run it. It will create a file named -# adeptkey.der in the same directory. This is your ADEPT user key. -# -# Mac OS X users: Save this script file as ineptkey.pyw. You can run this -# program from the command line (pythonw ineptkey.pyw) or by double-clicking -# it when it has been associated with PythonLauncher. It will create a file -# named adeptkey.der in the same directory. This is your ADEPT user key. - -# Revision history: -# 1 - Initial release, for Adobe Digital Editions 1.7 -# 2 - Better algorithm for finding pLK; improved error handling -# 3 - Rename to INEPT -# 4 - Series of changes by joblack (and others?) -- -# 4.1 - quick beta fix for ADE 1.7.2 (anon) -# 4.2 - added old 1.7.1 processing -# 4.3 - better key search -# 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 - -""" -Retrieve Adobe ADEPT user key. -""" - -from __future__ import with_statement - -__license__ = 'GPL v3' - -import sys -import os -import struct -import Tkinter -import Tkconstants -import tkMessageBox -import traceback - -class ADEPTError(Exception): - pass - -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 - from ctypes.wintypes import LPVOID, DWORD, BOOL - import _winreg as winreg - - try: - from Crypto.Cipher import AES - except ImportError: - AES = None - - 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] - 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() - - PAGE_EXECUTE_READWRITE = 0x40 - MEM_COMMIT = 0x1000 - MEM_RESERVE = 0x2000 - - def VirtualAlloc(): - _VirtualAlloc = kernel32.VirtualAlloc - _VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD] - _VirtualAlloc.restype = LPVOID - def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE), - protect=PAGE_EXECUTE_READWRITE): - 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] - _VirtualFree.restype = BOOL - def VirtualFree(addr, size=0, freetype=MEM_RELEASE): - 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 - "\x31\xc0" # xor %eax,%eax - "\x0f\xa2" # cpuid - "\x8b\x44\x24\x08" # mov 0x8(%esp),%eax - "\x89\x18" # mov %ebx,0x0(%eax) - "\x89\x50\x04" # mov %edx,0x4(%eax) - "\x89\x48\x08" # mov %ecx,0x8(%eax) - "\x5b" # pop %ebx - "\xc3" # ret - ) - CPUID1_INSNS = ( - "\x53" # push %ebx - "\x31\xc0" # xor %eax,%eax - "\x40" # inc %eax - "\x0f\xa2" # cpuid - "\x5b" # pop %ebx - "\xc3" # ret - ) - else: - CPUID0_INSNS = ( - "\x49\x89\xd8" # mov %rbx,%r8 - "\x49\x89\xc9" # mov %rcx,%r9 - "\x48\x31\xc0" # xor %rax,%rax - "\x0f\xa2" # cpuid - "\x4c\x89\xc8" # mov %r9,%rax - "\x89\x18" # mov %ebx,0x0(%rax) - "\x89\x50\x04" # mov %edx,0x4(%rax) - "\x89\x48\x08" # mov %ecx,0x8(%rax) - "\x4c\x89\xc3" # mov %r8,%rbx - "\xc3" # retq - ) - CPUID1_INSNS = ( - "\x53" # push %rbx - "\x48\x31\xc0" # xor %rax,%rax - "\x48\xff\xc0" # inc %rax - "\x0f\xa2" # cpuid - "\x5b" # pop %rbx - "\xc3" # retq - ) - - def cpuid0(): - _cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS) - buf = create_string_buffer(12) - def cpuid0(): - _cpuid0(buf) - 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, - 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 ADEPTError("Failed to decrypt user key key (sic)") - return string_at(outdata.pbData, outdata.cbData) - return CryptUnprotectData - CryptUnprotectData = CryptUnprotectData() - - def retrieve_key(keypath): - if AES is None: - tkMessageBox.showerror( - "ADEPT Key", - "This script requires PyCrypto, which must be installed " - "separately. Read the top-of-script comment for details.") - return False - root = GetSystemDirectory().split('\\')[0] + '\\' - serial = GetVolumeSerialNumber(root) - vendor = cpuid0() - signature = struct.pack('>I', cpuid1())[1:] - user = GetUserName() - entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) - cuser = winreg.HKEY_CURRENT_USER - try: - regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH) - except WindowsError: - raise ADEPTError("Adobe Digital Editions not activated") - device = winreg.QueryValueEx(regkey, 'key')[0] - keykey = CryptUnprotectData(device, entropy) - userkey = None - try: - plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH) - except WindowsError: - raise ADEPTError("Could not locate ADE activation") - for i in xrange(0, 16): - try: - plkparent = winreg.OpenKey(plkroot, "%04d" % (i,)) - except WindowsError: - break - ktype = winreg.QueryValueEx(plkparent, None)[0] - if ktype != 'credentials': - continue - for j in xrange(0, 16): - try: - plkkey = winreg.OpenKey(plkparent, "%04d" % (j,)) - except WindowsError: - break - ktype = winreg.QueryValueEx(plkkey, None)[0] - if ktype != 'privateLicenseKey': - continue - userkey = winreg.QueryValueEx(plkkey, 'value')[0] - break - if userkey is not None: - break - if userkey is None: - raise ADEPTError('Could not locate privateLicenseKey') - userkey = userkey.decode('base64') - userkey = AES.new(keykey, AES.MODE_CBC).decrypt(userkey) - userkey = userkey[26:-ord(userkey[-1])] - with open(keypath, 'wb') as f: - f.write(userkey) - return True - -elif sys.platform.startswith('darwin'): - import xml.etree.ElementTree as etree - import Carbon.File - import Carbon.Folder - import Carbon.Folders - import MacOS - - ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat' - NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - - def find_folder(domain, dtype): - try: - fsref = Carbon.Folder.FSFindFolder(domain, dtype, False) - return Carbon.File.pathname(fsref) - except MacOS.Error: - return None - - def find_app_support_file(subpath): - dtype = Carbon.Folders.kApplicationSupportFolderType - for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain: - path = find_folder(domain, dtype) - if path is None: - continue - path = os.path.join(path, subpath) - if os.path.isfile(path): - return path - return None - - def retrieve_key(keypath): - actpath = find_app_support_file(ACTIVATION_PATH) - if actpath is None: - raise ADEPTError("Could not locate ADE activation") - tree = etree.parse(actpath) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey')) - userkey = tree.findtext(expr) - userkey = userkey.decode('base64') - userkey = userkey[26:] - with open(keypath, 'wb') as f: - f.write(userkey) - return True - -elif sys.platform.startswith('cygwin'): - def retrieve_key(keypath): - tkMessageBox.showerror( - "ADEPT Key", - "This script requires a Windows-native Python, and cannot be run " - "under Cygwin. Please install a Windows-native Python and/or " - "check your file associations.") - return False - -else: - def retrieve_key(keypath): - tkMessageBox.showerror( - "ADEPT Key", - "This script only supports Windows and Mac OS X. For Linux " - "you should be able to run ADE and this script under Wine (with " - "an appropriate version of Windows Python installed).") - return False - -class ExceptionDialog(Tkinter.Frame): - def __init__(self, root, text): - Tkinter.Frame.__init__(self, root, border=5) - label = Tkinter.Label(self, text="Unexpected error:", - anchor=Tkconstants.W, justify=Tkconstants.LEFT) - label.pack(fill=Tkconstants.X, expand=0) - self.text = Tkinter.Text(self) - self.text.pack(fill=Tkconstants.BOTH, expand=1) - self.text.insert(Tkconstants.END, text) - -def main(argv=sys.argv): - root = Tkinter.Tk() - root.withdraw() - progname = os.path.basename(argv[0]) - keypath = 'adeptkey.der' - success = False - try: - success = retrieve_key(keypath) - except ADEPTError, e: - tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) - except Exception: - root.wm_state('normal') - root.title('ADEPT Key') - text = traceback.format_exc() - ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) - root.mainloop() - if not success: - return 1 - tkMessageBox.showinfo( - "ADEPT Key", "Key successfully retrieved to %s" % (keypath)) - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/Other_Tools/Adobe_ePub_Tools/README_ineptepub.txt b/Other_Tools/Adobe_ePub_Tools/README_ineptepub.txt deleted file mode 100644 index 25813a4..0000000 --- a/Other_Tools/Adobe_ePub_Tools/README_ineptepub.txt +++ /dev/null @@ -1,18 +0,0 @@ -From Apprentice Alf's Blog - -Adobe Adept ePub, .epub - -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_vX.X.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_vX.X.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. diff --git a/Other_Tools/Adobe_ePub_Tools/ineptepub_v5.7.pyw b/Other_Tools/Adobe_ePub_Tools/ineptepub_v5.7.pyw deleted file mode 100755 index 829f1b2..0000000 --- a/Other_Tools/Adobe_ePub_Tools/ineptepub_v5.7.pyw +++ /dev/null @@ -1,478 +0,0 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import with_statement - -# ineptepub.pyw, version 5.7 -# Copyright © 2009-2010 i♥cabbages - -# Released under the terms of the GNU General Public Licence, version 3 or -# later. - -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make sure to -# install the version for Python 2.6). Save this script file as -# ineptepub.pyw and double-click on it to run it. -# -# Mac OS X users: Save this script file as ineptepub.pyw. You can run this -# program from the command line (pythonw ineptepub.pyw) or by double-clicking -# it when it has been associated with PythonLauncher. - -# Revision history: -# 1 - Initial release -# 2 - Rename to INEPT, fix exit code -# 5 - Version bump to avoid (?) confusion; -# 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 -# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml -# 5.5 - On Windows try PyCrypto first, OpenSSL next -# 5.6 - Modify interface to allow use with import -# 5.7 - Fix for potential problem with PyCrypto - -""" -Decrypt Adobe ADEPT-encrypted EPUB books. -""" - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class ADEPTError(Exception): - pass - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - 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) - - RSA_NO_PADDING = 3 - AES_MAXNR = 14 - - c_char_pp = POINTER(c_char_p) - c_int_p = POINTER(c_int) - - class RSA(Structure): - pass - RSA_p = POINTER(RSA) - - 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 - - d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', - [RSA_p, c_char_pp, c_long]) - RSA_size = F(c_int, 'RSA_size', [RSA_p]) - RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', - [c_int, c_char_p, c_char_p, RSA_p, c_int]) - RSA_free = F(None, 'RSA_free', [RSA_p]) - 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 RSA(object): - def __init__(self, der): - buf = create_string_buffer(der) - pp = c_char_pp(cast(buf, c_char_p)) - rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) - if rsa is None: - raise ADEPTError('Error parsing ADEPT user key DER') - - def decrypt(self, from_): - rsa = self._rsa - to = create_string_buffer(RSA_size(rsa)) - dlen = RSA_private_decrypt(len(from_), from_, to, rsa, - RSA_NO_PADDING) - if dlen < 0: - raise ADEPTError('RSA decryption failed') - return to[:dlen] - - def __del__(self): - if self._rsa is not None: - RSA_free(self._rsa) - self._rsa = None - - 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: - 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, RSA) - -def _load_crypto_pycrypto(): - from Crypto.Cipher import AES as _AES - from Crypto.PublicKey import RSA as _RSA - - # ASN.1 parsing code from tlslite - class ASN1Error(Exception): - pass - - class ASN1Parser(object): - class Parser(object): - def __init__(self, bytes): - self.bytes = bytes - self.index = 0 - - def get(self, length): - if self.index + length > len(self.bytes): - raise ASN1Error("Error decoding ASN.1") - x = 0 - for count in range(length): - x <<= 8 - x |= self.bytes[self.index] - self.index += 1 - return x - - def getFixBytes(self, lengthBytes): - bytes = self.bytes[self.index : self.index+lengthBytes] - self.index += lengthBytes - return bytes - - def getVarBytes(self, lengthLength): - lengthBytes = self.get(lengthLength) - return self.getFixBytes(lengthBytes) - - def getFixList(self, length, lengthList): - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def getVarList(self, length, lengthLength): - lengthList = self.get(lengthLength) - if lengthList % length != 0: - raise ASN1Error("Error decoding ASN.1") - lengthList = int(lengthList/length) - l = [0] * lengthList - for x in range(lengthList): - l[x] = self.get(length) - return l - - def startLengthCheck(self, lengthLength): - self.lengthCheck = self.get(lengthLength) - self.indexCheck = self.index - - def setLengthCheck(self, length): - self.lengthCheck = length - self.indexCheck = self.index - - def stopLengthCheck(self): - if (self.index - self.indexCheck) != self.lengthCheck: - raise ASN1Error("Error decoding ASN.1") - - def atLengthCheck(self): - if (self.index - self.indexCheck) < self.lengthCheck: - return False - elif (self.index - self.indexCheck) == self.lengthCheck: - return True - else: - raise ASN1Error("Error decoding ASN.1") - - def __init__(self, bytes): - p = self.Parser(bytes) - p.get(1) - self.length = self._getASN1Length(p) - self.value = p.getFixBytes(self.length) - - def getChild(self, which): - p = self.Parser(self.value) - for x in range(which+1): - markIndex = p.index - p.get(1) - length = self._getASN1Length(p) - p.getFixBytes(length) - return ASN1Parser(p.bytes[markIndex:p.index]) - - def _getASN1Length(self, p): - firstLength = p.get(1) - if firstLength<=127: - return firstLength - else: - lengthLength = firstLength & 0x7F - return p.get(lengthLength) - - class AES(object): - def __init__(self, key): - self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16) - - def decrypt(self, data): - return self._aes.decrypt(data) - - class RSA(object): - def __init__(self, der): - key = ASN1Parser([ord(x) for x in der]) - key = [key.getChild(x).value for x in xrange(1, 4)] - key = [self.bytesToNumber(v) for v in key] - self._rsa = _RSA.construct(key) - - def bytesToNumber(self, bytes): - total = 0L - for byte in bytes: - total = (total << 8) + byte - return total - - def decrypt(self, data): - return self._rsa.decrypt(data) - - return (AES, RSA) - -def _load_crypto(): - AES = RSA = None - cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) - if sys.platform.startswith('win'): - cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) - for loader in cryptolist: - try: - AES, RSA = loader() - break - except (ImportError, ADEPTError): - pass - return (AES, RSA) -AES, RSA = _load_crypto() - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - self._aes = AES(bookkey) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - if path is not None: - path = path.encode('utf-8') - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('adeptkey.der'): - self.keypath.insert(0, 'adeptkey.der') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT key file', - defaultextension='.der', filetypes=[('DER-encoded files', '.der'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select ADEPT-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - - -def decryptBook(keypath, inpath, outpath): - with open(keypath, 'rb') as f: - keyder = f.read() - rsa = RSA(keyder) - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = rsa.decrypt(bookkey.decode('base64')) - # Padded as per RSAES-PKCS1-v1_5 - if bookkey[-17] != '\x00': - raise ADEPTError('problem decrypting session key') - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be" \ - " installed separately. Read the top-of-script comment for" \ - " details." % (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) - - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "INEPT EPUB Decrypter", - "This script requires OpenSSL or PyCrypto, which must be" - " installed separately. Read the top-of-script comment for" - " details.") - return 1 - root.title('INEPT EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/Other_Tools/B&N_Download_Helper/BN-Dload.user.js b/Other_Tools/B&N_Download_Helper/BN-Dload.user.js deleted file mode 100644 index 6bb028f..0000000 --- a/Other_Tools/B&N_Download_Helper/BN-Dload.user.js +++ /dev/null @@ -1,26 +0,0 @@ -// ==UserScript== -// @name BN-Dload -// @namespace http://www.mailinator.com/J-man -// @include https://mynook.barnesandnoble.com/library.html* -// @grant none -// @version 20121119 -// ==/UserScript== - -function doIt() { - if ($('#adl1').length == 0) { - $('[action$="deleteItem"]').each(function(index) { - if ($(this).parent().find('[action$="EDSDeliverItem.aspx"]').length == 0) { - var delid = $(this).find('input').attr('value'); - $(this).after('
'); - } - }); - - } - - setTimeout (function() { - doIt(); - }, 3000 ); -} - -doIt(); - diff --git a/Other_Tools/B&N_Download_Helper/BN-Dload.user_ReadMe.txt b/Other_Tools/B&N_Download_Helper/BN-Dload.user_ReadMe.txt deleted file mode 100644 index e0471f7..0000000 --- a/Other_Tools/B&N_Download_Helper/BN-Dload.user_ReadMe.txt +++ /dev/null @@ -1,22 +0,0 @@ -To obtain unencrypted content from the B&N, you have to download it directly from the website. Unrooted Nook devices will not let you save your content to your PC. - -If the downloaded file is encrypted, install and configure the ignoble plugin in Calibre to decrypt that. - -Some content is not downloadable from the website, for instance magazines. The Greasemonkey script included in the tools modifies the myNook page of the Barnes and Noble website to show a download button for non-downloadable content. This will work until Barnes & Noble changes their website. - -Prerequisites -1) Firefox: http://getfirefox.com -2) Greasemokey extension: https://addons.mozilla.org/nl/firefox/addon/greasemonkey/ - -One time installation -1) Install Firefox if not already done so; -2) Follow the above link to GreaseMonkey and click Add to Firefox -3) Restart Firefox -4) Go to (link to the script, best hosted somewhere, as .js usually opens in an editor) -5) A pop up should appear, stating you are about to install a GreaseMonkey user script. -6) Click on install - -Use -1) Log in into your B&N account -2) Go to MyNook -3) An “Alternative download” should apppear next to normally non-downloadable content. diff --git a/Other_Tools/B_and_N_Download_Helper/BN-Dload.user_ReadMe.txt b/Other_Tools/B_and_N_Download_Helper/BN-Dload.user_ReadMe.txt new file mode 100644 index 0000000..ab66f1c --- /dev/null +++ b/Other_Tools/B_and_N_Download_Helper/BN-Dload.user_ReadMe.txt @@ -0,0 +1,32 @@ +INTRODUCTION +============ + +To obtain unencrypted content from the B&N, you have to download it directly from the website. Unrooted Nook devices will not let you save your content to your PC. + +If the downloaded file is encrypted, install and configure the ignoble plugin in Calibre to decrypt that. + + +DOWNLOAD HIDDEN FILES FROM B&N +------------------------------ + +Some content is not downloadable from the B&N website, notably magazines. A Greasemonkey script (details below) modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website. + +Prerequisites +------------- +1) Firefox: http://www.getfirefox.com +2) Greasemokey extension: https://addons.mozilla.org/nl/firefox/addon/greasemonkey/ + +One time installation +--------------------- +1) Install Firefox if not already done so +2) Follow the above link to GreaseMonkey and click Add to Firefox +3) Restart Firefox +4) Go to http://userscripts.org/scripts/source/152985.user.js +5) A popup should appear, stating you are about to install a GreaseMonkey user script. +6) Click on install + +Use +--- +1) Log in into your B&N account +2) Go to MyNook +3) An “Alternative download” should appear next to normally non-downloadable content. Note that this will not work for content such as Nook applications, and some children books. diff --git a/Other_Tools/Barnes_and_Noble_ePub_Tools/Original_IHeart_Cabbages_Scripts/ignobleepub.pyw b/Other_Tools/Barnes_and_Noble_ePub_Tools/Original_IHeart_Cabbages_Scripts/ignobleepub.pyw deleted file mode 100755 index c5de3ae..0000000 --- a/Other_Tools/Barnes_and_Noble_ePub_Tools/Original_IHeart_Cabbages_Scripts/ignobleepub.pyw +++ /dev/null @@ -1,235 +0,0 @@ -#! /usr/bin/python - -# ignobleepub.pyw, version 1-rc2 - -# To run this program install Python 2.6 from -# and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignobleepub.pyw and double-click on it to run it. - -# Revision history: -# 1 - Initial release - -""" -Decrypt Barnes & Noble ADEPT encrypted EPUB books. -""" - -from __future__ import with_statement - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -try: - from Crypto.Cipher import AES -except ImportError: - AES = None - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - self._aes = AES.new(bookkey, AES.MODE_CBC) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - if path is not None: - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - - -class ADEPTError(Exception): - pass - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - with open(keypath, 'rb') as f: - keyb64 = f.read() - key = keyb64.decode('base64')[:16] - aes = AES.new(key, AES.MODE_CBC) - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise ADEPTError('%s: not an B&N ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = aes.decrypt(bookkey.decode('base64')) - bookkey = bookkey[:-ord(bookkey[-1])] - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('bnepubkey.b64'): - self.keypath.insert(0, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N EPUB key file', - defaultextension='.b64', - filetypes=[('base64-encoded files', '.b64'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "Ignoble EPUB Decrypter", - "This script requires PyCrypto, which must be installed " - "separately. Read the top-of-script comment for details.") - return 1 - root.title('Ignoble EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/Other_Tools/Barnes_and_Noble_ePub_Tools/Original_IHeart_Cabbages_Scripts/ignoblekeygen.pyw b/Other_Tools/Barnes_and_Noble_ePub_Tools/Original_IHeart_Cabbages_Scripts/ignoblekeygen.pyw deleted file mode 100755 index 9d5dc51..0000000 --- a/Other_Tools/Barnes_and_Noble_ePub_Tools/Original_IHeart_Cabbages_Scripts/ignoblekeygen.pyw +++ /dev/null @@ -1,147 +0,0 @@ -#! /usr/bin/python - -# ignoblekeygen.pyw, version 1 - -# To run this program install Python 2.6 from -# and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignoblekeygen.pyw and double-click on it to run it. - -# Revision history: -# 1 - Initial release - -""" -Generate Barnes & Noble EPUB user key from name and credit card number. -""" - -from __future__ import with_statement - -__license__ = 'GPL v3' - -import sys -import os -import hashlib -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -try: - from Crypto.Cipher import AES -except ImportError: - AES = None - -def normalize_name(name): - return ''.join(x for x in name.lower() if x != ' ') - -def generate_keyfile(name, ccn, outpath): - name = normalize_name(name) + '\x00' - ccn = ccn + '\x00' - name_sha = hashlib.sha1(name).digest()[:16] - ccn_sha = hashlib.sha1(ccn).digest()[:16] - both_sha = hashlib.sha1(name + ccn).digest() - aes = AES.new(ccn_sha, AES.MODE_CBC, name_sha) - crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c)) - userkey = hashlib.sha1(crypt).digest() - with open(outpath, 'wb') as f: - f.write(userkey.encode('base64')) - return userkey - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 - if len(argv) != 4: - print "usage: %s NAME CC# OUTFILE" % (progname,) - return 1 - name, ccn, outpath = argv[1:] - generate_keyfile(name, ccn, outpath) - return 0 - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Enter parameters') - 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='Name').grid(row=1) - self.name = Tkinter.Entry(body, width=30) - self.name.grid(row=1, column=1, sticky=sticky) - Tkinter.Label(body, text='CC#').grid(row=2) - self.ccn = Tkinter.Entry(body, width=30) - self.ccn.grid(row=2, column=1, sticky=sticky) - Tkinter.Label(body, text='Output file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - self.keypath.insert(0, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Generate", width=10, command=self.generate) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.asksaveasfilename( - parent=None, title='Select B&N EPUB key file to produce', - defaultextension='.b64', - filetypes=[('base64-encoded files', '.b64'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def generate(self): - name = self.name.get() - ccn = self.ccn.get() - keypath = self.keypath.get() - if not name: - self.status['text'] = 'Name not specified' - return - if not ccn: - self.status['text'] = 'Credit card number not specified' - return - if not keypath: - self.status['text'] = 'Output keyfile path not specified' - return - self.status['text'] = 'Generating...' - try: - generate_keyfile(name, ccn, keypath) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'Keyfile successfully generated' - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "Ignoble EPUB Keyfile Generator", - "This script requires PyCrypto, which must be installed " - "separately. Read the top-of-script comment for details.") - return 1 - root.title('Ignoble EPUB Keyfile Generator') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/Other_Tools/Barnes_and_Noble_ePub_Tools/README_ignoble_epub.txt b/Other_Tools/Barnes_and_Noble_ePub_Tools/README_ignoble_epub.txt deleted file mode 100644 index 0a67cf2..0000000 --- a/Other_Tools/Barnes_and_Noble_ePub_Tools/README_ignoble_epub.txt +++ /dev/null @@ -1,24 +0,0 @@ -Readme.txt - -Barnes and Noble EPUB ebooks use a form of Social DRM which requires information on your Credit Card Number and the Name on the Credit card used to purchase the book to actually unencrypt the book. - -For more info, see the author's blog: -http://i-u2665-cabbages.blogspot.com/2009_12_01_archive.html - -The original scripts by IHeartCabbages are available here as well. These scripts have been modified to allow the use of OpenSSL in place of PyCrypto to make them easier to run on Linux and Mac OS X, as well as to fix some minor bugs. - -There are 2 scripts: - -The first is ignoblekeygen_v2.4.pyw. Double-click to launch it and provide the required information, and this program will generate a key file needed to remove the DRM from the books. The require information is - -* Your Name: Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. -* Credit Card number: This is the credit card number that was on file with Barnes & Noble at the time of download of the ebooks. - -This key file need only be generated once unless either you change the default credit card number or your name on your B&N account. - -The second is ignobleepub_vX.X.pyw. Double-click it and it will ask for your key file and the path to the book to remove the DRM from. - -All 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. - -These scripts are based on the IHeartCabbages original scripts that allow the replacement of the requirement for PyCrypto with OpenSSL's libcrypto which is already installed on all Mac OS X machines and Linux Boxes. Window's Users will still have to install PyCrypto or OpenSSL to get these scripts to work properly. - diff --git a/Other_Tools/Barnes_and_Noble_ePub_Tools/ignobleepub_v3.5.pyw b/Other_Tools/Barnes_and_Noble_ePub_Tools/ignobleepub_v3.5.pyw deleted file mode 100755 index 6b1a1d2..0000000 --- a/Other_Tools/Barnes_and_Noble_ePub_Tools/ignobleepub_v3.5.pyw +++ /dev/null @@ -1,336 +0,0 @@ -#! /usr/bin/python - -from __future__ import with_statement - -# ignobleepub.pyw, version 3.5 - -# To run this program install Python 2.6 from -# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignobleepub.pyw and double-click on it to run it. - -# Revision history: -# 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 -# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml -# 3.3 - On Windows try PyCrypto first and OpenSSL next -# 3.4 - Modify interace to allow use with import -# 3.5 - Fix for potential problem with PyCrypto - -__license__ = 'GPL v3' - -import sys -import os -import zlib -import zipfile -from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED -from contextlib import closing -import xml.etree.ElementTree as etree -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox - -class IGNOBLEError(Exception): - pass - -def _load_crypto_libcrypto(): - from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, cast - from ctypes.util import find_library - - 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) - - 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]) - 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 IGNOBLEError('AES improper key used') - return - key = self._key = AES_KEY() - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) - if rv < 0: - raise IGNOBLEError('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 IGNOBLEError('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, '\x00'*16) - - def decrypt(self, data): - return self._aes.decrypt(data) - - return AES - -def _load_crypto(): - AES = None - cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto) - if sys.platform.startswith('win'): - cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto) - for loader in cryptolist: - try: - AES = loader() - break - except (ImportError, IGNOBLEError): - pass - return AES - -AES = _load_crypto() - - - -""" -Decrypt Barnes & Noble ADEPT encrypted EPUB books. -""" - - -META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') -NSMAP = {'adept': 'http://ns.adobe.com/adept', - 'enc': 'http://www.w3.org/2001/04/xmlenc#'} - -class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): - if 'compress_type' in kwargs: - compress_type = kwargs.pop('compress_type') - super(ZipInfo, self).__init__(*args, **kwargs) - self.compress_type = compress_type - -class Decryptor(object): - def __init__(self, bookkey, encryption): - enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) - # self._aes = AES.new(bookkey, AES.MODE_CBC, '\x00'*16) - self._aes = AES(bookkey) - encryption = etree.fromstring(encryption) - self._encrypted = encrypted = set() - expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), - enc('CipherReference')) - for elem in encryption.findall(expr): - path = elem.get('URI', None) - path = path.encode('utf-8') - if path is not None: - encrypted.add(path) - - def decompress(self, bytes): - dc = zlib.decompressobj(-15) - bytes = dc.decompress(bytes) - ex = dc.decompress('Z') + dc.flush() - if ex: - bytes = bytes + ex - return bytes - - def decrypt(self, path, data): - if path in self._encrypted: - data = self._aes.decrypt(data)[16:] - data = data[:-ord(data[-1])] - data = self.decompress(data) - return data - - -class DecryptionDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Select files for decryption') - 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='Key file').grid(row=0) - self.keypath = Tkinter.Entry(body, width=30) - self.keypath.grid(row=0, column=1, sticky=sticky) - if os.path.exists('bnepubkey.b64'): - self.keypath.insert(0, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) - button.grid(row=0, column=2) - Tkinter.Label(body, text='Input file').grid(row=1) - self.inpath = Tkinter.Entry(body, width=30) - self.inpath.grid(row=1, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_inpath) - button.grid(row=1, column=2) - Tkinter.Label(body, text='Output file').grid(row=2) - self.outpath = Tkinter.Entry(body, width=30) - self.outpath.grid(row=2, column=1, sticky=sticky) - button = Tkinter.Button(body, text="...", command=self.get_outpath) - button.grid(row=2, column=2) - buttons = Tkinter.Frame(self) - buttons.pack() - botton = Tkinter.Button( - buttons, text="Decrypt", width=10, command=self.decrypt) - botton.pack(side=Tkconstants.LEFT) - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) - button.pack(side=Tkconstants.RIGHT) - - def get_keypath(self): - keypath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N EPUB key file', - defaultextension='.b64', - filetypes=[('base64-encoded files', '.b64'), - ('All Files', '.*')]) - if keypath: - keypath = os.path.normpath(keypath) - self.keypath.delete(0, Tkconstants.END) - self.keypath.insert(0, keypath) - return - - def get_inpath(self): - inpath = tkFileDialog.askopenfilename( - parent=None, title='Select B&N-encrypted EPUB file to decrypt', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if inpath: - inpath = os.path.normpath(inpath) - self.inpath.delete(0, Tkconstants.END) - self.inpath.insert(0, inpath) - return - - def get_outpath(self): - outpath = tkFileDialog.asksaveasfilename( - parent=None, title='Select unencrypted EPUB file to produce', - defaultextension='.epub', filetypes=[('EPUB files', '.epub'), - ('All files', '.*')]) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def decrypt(self): - keypath = self.keypath.get() - inpath = self.inpath.get() - outpath = self.outpath.get() - if not keypath or not os.path.exists(keypath): - self.status['text'] = 'Specified key file does not exist' - return - if not inpath or not os.path.exists(inpath): - self.status['text'] = 'Specified input file does not exist' - return - if not outpath: - self.status['text'] = 'Output file not specified' - return - if inpath == outpath: - self.status['text'] = 'Must have different input and output files' - return - argv = [sys.argv[0], keypath, inpath, outpath] - self.status['text'] = 'Decrypting...' - try: - cli_main(argv) - except Exception, e: - self.status['text'] = 'Error: ' + str(e) - return - self.status['text'] = 'File successfully decrypted' - - -def decryptBook(keypath, inpath, outpath): - with open(keypath, 'rb') as f: - keyb64 = f.read() - key = keyb64.decode('base64')[:16] - # aes = AES.new(key, AES.MODE_CBC, '\x00'*16) - aes = AES(key) - - with closing(ZipFile(open(inpath, 'rb'))) as inf: - namelist = set(inf.namelist()) - if 'META-INF/rights.xml' not in namelist or \ - 'META-INF/encryption.xml' not in namelist: - raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,)) - for name in META_NAMES: - namelist.remove(name) - rights = etree.fromstring(inf.read('META-INF/rights.xml')) - adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) - expr = './/%s' % (adept('encryptedKey'),) - bookkey = ''.join(rights.findtext(expr)) - bookkey = aes.decrypt(bookkey.decode('base64')) - bookkey = bookkey[:-ord(bookkey[-1])] - encryption = inf.read('META-INF/encryption.xml') - decryptor = Decryptor(bookkey[-16:], encryption) - kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) - with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: - zi = ZipInfo('mimetype', compress_type=ZIP_STORED) - outf.writestr(zi, inf.read('mimetype')) - for path in namelist: - data = inf.read(path) - outf.writestr(path, decryptor.decrypt(path, data)) - return 0 - - -def cli_main(argv=sys.argv): - progname = os.path.basename(argv[0]) - if AES is None: - print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ - "separately. Read the top-of-script comment for details." % \ - (progname,) - return 1 - if len(argv) != 4: - print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) - return 1 - keypath, inpath, outpath = argv[1:] - return decryptBook(keypath, inpath, outpath) - - -def gui_main(): - root = Tkinter.Tk() - if AES is None: - root.withdraw() - tkMessageBox.showerror( - "Ignoble EPUB Decrypter", - "This script requires OpenSSL or PyCrypto, which must be installed " - "separately. Read the top-of-script comment for details.") - return 1 - root.title('Ignoble EPUB Decrypter') - root.resizable(True, False) - root.minsize(300, 0) - DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == '__main__': - if len(sys.argv) > 1: - sys.exit(cli_main()) - sys.exit(gui_main()) diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptkey.py b/Other_Tools/DRM_Key_Scripts/Adobe_Digital_Editions/adobekey.pyw old mode 100755 new mode 100644 similarity index 64% rename from DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptkey.py rename to Other_Tools/DRM_Key_Scripts/Adobe_Digital_Editions/adobekey.pyw index 723b7c6..bfa542b --- a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptkey.py +++ b/Other_Tools/DRM_Key_Scripts/Adobe_Digital_Editions/adobekey.pyw @@ -1,25 +1,31 @@ -#! /usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import with_statement -# ineptkey.pyw, version 5.6 +# adobekey.pyw, version 5.7 # Copyright © 2009-2010 i♥cabbages -# Released under the terms of the GNU General Public Licence, version 3 or -# later. +# Released under the terms of the GNU General Public Licence, version 3 +# -# Windows users: Before running this program, you must first install Python 2.6 -# from and PyCrypto from -# (make certain -# to install the version for Python 2.6). Then save this script file as -# ineptkey.pyw and double-click on it to run it. It will create a file named -# adeptkey.der in the same directory. This is your ADEPT user key. +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python. +# We recommend ActiveState Python 2.7.X for Windows (x86) from +# http://www.activestate.com/activepython/downloads. +# You must also install PyCrypto from +# http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make certain to install the version for Python 2.7). +# Then save this script file as adobekey.pyw and double-click on it to run it. +# It will create a file named adobekey_1.der in in the same directory as the script. +# This is your Adobe Digital Editions user key. # -# Mac OS X users: Save this script file as ineptkey.pyw. You can run this -# program from the command line (pythonw ineptkey.pyw) or by double-clicking +# Mac OS X users: Save this script file as adobekey.pyw. You can run this +# program from the command line (python adobekey.pyw) or by double-clicking # it when it has been associated with PythonLauncher. It will create a file -# named adeptkey.der in the same directory. This is your ADEPT user key. +# named adobekey_1.der in the same directory as the script. +# This is your Adobe Digital Editions user key. # Revision history: # 1 - Initial release, for Adobe Digital Editions 1.7 @@ -30,24 +36,44 @@ # 4.2 - added old 1.7.1 processing # 4.3 - better key search # 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 - 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 # 5.2 - added support for output of key to a particular file # 5.3 - On Windows try PyCrypto first, OpenSSL next # 5.4 - Modify interface to allow use of import # 5.5 - Fix for potential problem with PyCrypto -# 5.6 - Revise to allow use in Plugins to eliminate need for duplicate code +# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code +# 5.7 - Unicode support added, renamed adobekey from ineptkey +# 5.8 - Added getkey interface for Windows DeDRM application +# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 6.0 - Work if TkInter is missing """ Retrieve Adobe ADEPT user key. """ __license__ = 'GPL v3' - -import sys -import os -import struct +__version__ = '6.0' + +import sys, os, struct, getopt + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) try: from calibre.constants import iswindows, isosx @@ -55,6 +81,44 @@ iswindows = sys.platform.startswith('win') isosx = sys.platform.startswith('darwin') +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"adobekey.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + class ADEPTError(Exception): pass @@ -80,13 +144,13 @@ 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', @@ -296,7 +360,7 @@ def CryptUnprotectData(indata, entropy): return CryptUnprotectData CryptUnprotectData = CryptUnprotectData() - def retrieve_keys(): + def adeptkeys(): if AES is None: raise ADEPTError("PyCrypto or OpenSSL must be installed") root = GetSystemDirectory().split('\\')[0] + '\\' @@ -308,9 +372,9 @@ def retrieve_keys(): cuser = winreg.HKEY_CURRENT_USER try: regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH) + device = winreg.QueryValueEx(regkey, 'key')[0] except WindowsError: raise ADEPTError("Adobe Digital Editions not activated") - device = winreg.QueryValueEx(regkey, 'key')[0] keykey = CryptUnprotectData(device, entropy) userkey = None keys = [] @@ -339,11 +403,13 @@ def retrieve_keys(): aes = AES(keykey) userkey = aes.decrypt(userkey) userkey = userkey[26:-ord(userkey[-1])] + #print "found key:",userkey.encode('hex') keys.append(userkey) if len(keys) == 0: raise ADEPTError('Could not locate privateLicenseKey') + print u"Found {0:d} keys".format(len(keys)) return keys - + elif isosx: import xml.etree.ElementTree as etree @@ -353,6 +419,9 @@ def retrieve_keys(): 'enc': 'http://www.w3.org/2001/04/xmlenc#'} def findActivationDat(): + import warnings + warnings.filterwarnings('ignore', category=FutureWarning) + home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"' cmdline = cmdline.encode(sys.getfilesystemencoding()) @@ -360,6 +429,7 @@ def findActivationDat(): out1, out2 = p2.communicate() reslst = out1.split('\n') cnt = len(reslst) + ActDatPath = "activation.dat" for j in xrange(cnt): resline = reslst[j] pp = resline.find('activation.dat') @@ -370,10 +440,10 @@ def findActivationDat(): return ActDatPath return None - def retrieve_keys(): + def adeptkeys(): actpath = findActivationDat() if actpath is None: - raise ADEPTError("Could not locate ADE activation") + raise ADEPTError("Could not find ADE activation.dat file.") tree = etree.parse(actpath) adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey')) @@ -383,75 +453,151 @@ def retrieve_keys(): return [userkey] else: - def retrieve_keys(keypath): + def adeptkeys(): raise ADEPTError("This script only supports Windows and Mac OS X.") return [] - -def retrieve_key(keypath): - keys = retrieve_keys() - with open(keypath, 'wb') as f: - f.write(keys[0]) - return True - -def extractKeyfile(keypath): - try: - success = retrieve_key(keypath) - except ADEPTError, e: - print "Key generation Error: " + str(e) - return 1 - except Exception, e: - print "General Error: " + str(e) - return 1 - if not success: - return 1 - return 0 +# interface for Python DeDRM +def getkey(outpath): + keys = adeptkeys() + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'wb') as keyfileout: + keyfileout.write(keys[0]) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + print u"Saved a key to {0}".format(outfile) + return True + return False + +def usage(progname): + print u"Finds, decrypts and saves the default Adobe Adept encryption key(s)." + print u"Keys are saved to the current directory, or a specified output directory." + print u"If a file name is passed instead of a directory, only the first key is saved, in that file." + print u"Usage:" + print u" {0:s} [-h] []".format(progname) + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__) -def cli_main(argv=sys.argv): - keypath = argv[1] - return extractKeyfile(keypath) + try: + opts, args = getopt.getopt(argv[1:], "h") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + + for o, a in opts: + if o == "-h": + usage(progname) + sys.exit(0) + + if len(args) > 1: + usage(progname) + sys.exit(2) + + if len(args) == 1: + # save to the specified file or directory + outpath = args[0] + if not os.path.isabs(outpath): + outpath = os.path.abspath(outpath) + else: + # save to the same directory as the script + outpath = os.path.dirname(argv[0]) + + # make sure the outpath is the + outpath = os.path.realpath(os.path.normpath(outpath)) + + keys = adeptkeys() + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'wb') as keyfileout: + keyfileout.write(keys[0]) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + print u"Saved a key to {0}".format(outfile) + else: + print u"Could not retrieve Adobe Adept key." + return 0 -def main(argv=sys.argv): - import Tkinter - import Tkconstants - import tkMessageBox - import traceback +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() class ExceptionDialog(Tkinter.Frame): def __init__(self, root, text): Tkinter.Frame.__init__(self, root, border=5) - label = Tkinter.Label(self, text="Unexpected error:", + label = Tkinter.Label(self, text=u"Unexpected error:", anchor=Tkconstants.W, justify=Tkconstants.LEFT) label.pack(fill=Tkconstants.X, expand=0) self.text = Tkinter.Text(self) self.text.pack(fill=Tkconstants.BOTH, expand=1) - + self.text.insert(Tkconstants.END, text) + argv=unicode_argv() root = Tkinter.Tk() root.withdraw() - progname = os.path.basename(argv[0]) - keypath = os.path.abspath("adeptkey.der") + progpath, progname = os.path.split(argv[0]) success = False try: - success = retrieve_key(keypath) + keys = adeptkeys() + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(progpath,u"adobekey_{0:d}.der".format(keycount)) + if not os.path.exists(outfile): + break + + with file(outfile, 'wb') as keyfileout: + keyfileout.write(key) + success = True + tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) except ADEPTError, e: - tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) + tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) except Exception: root.wm_state('normal') - root.title('ADEPT Key') + root.title(progname) text = traceback.format_exc() ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) root.mainloop() if not success: return 1 - tkMessageBox.showinfo( - "ADEPT Key", "Key successfully retrieved to %s" % (keypath)) return 0 if __name__ == '__main__': if len(sys.argv) > 1: sys.exit(cli_main()) - sys.exit(main()) + sys.exit(gui_main()) diff --git a/Other_Tools/Barnes_and_Noble_ePub_Tools/ignoblekeygen_v2.4.pyw b/Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekeygen.pyw old mode 100755 new mode 100644 similarity index 53% rename from Other_Tools/Barnes_and_Noble_ePub_Tools/ignoblekeygen_v2.4.pyw rename to Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekeygen.pyw index e2c50e2..5118c87 --- a/Other_Tools/Barnes_and_Noble_ePub_Tools/ignoblekeygen_v2.4.pyw +++ b/Other_Tools/DRM_Key_Scripts/Barnes_and_Noble_ePubs/ignoblekeygen.pyw @@ -1,13 +1,27 @@ -#! /usr/bin/python +#!/usr/bin/env python +# -*- coding: utf-8 -*- from __future__ import with_statement -# ignoblekeygen.pyw, version 2.4 +# ignoblekeygen.pyw, version 2.5 +# Copyright © 2009-2010 i♥cabbages -# To run this program install Python 2.6 from -# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ignoblekeygen.pyw and double-click on it to run it. +# Released under the terms of the GNU General Public Licence, version 3 +# + +# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf + +# Windows users: Before running this program, you must first install Python. +# We recommend ActiveState Python 2.7.X for Windows (x86) from +# http://www.activestate.com/activepython/downloads. +# You must also install PyCrypto from +# http://www.voidspace.org.uk/python/modules.shtml#pycrypto +# (make certain to install the version for Python 2.7). +# Then save this script file as ignoblekeygen.pyw and double-click on it to run it. +# +# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this +# program from the command line (python ignoblekeygen.pyw) or by double-clicking +# it when it has been associated with PythonLauncher. # Revision history: # 1 - Initial release @@ -16,36 +30,97 @@ from __future__ import with_statement # 2.2 - On Windows try PyCrypto first and then OpenSSL next # 2.3 - Modify interface to allow use of import # 2.4 - Improvements to UI and now works in plugins +# 2.5 - Additional improvement for unicode and plugin support +# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 2.7 - Work if TkInter is missing """ Generate Barnes & Noble EPUB user key from name and credit card number. """ __license__ = 'GPL v3' +__version__ = "2.7" import sys import os import hashlib +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"ignoblekeygen.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] -# use openssl's libcrypt if it exists in place of pycrypto -# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages class IGNOBLEError(Exception): pass - def _load_crypto_libcrypto(): from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ Structure, c_ulong, create_string_buffer, cast from ctypes.util import find_library - if sys.platform.startswith('win'): + if iswindows: libcrypto = find_library('libeay32') else: libcrypto = find_library('crypto') + if libcrypto is None: - print 'libcrypto not found' raise IGNOBLEError('libcrypto not found') libcrypto = CDLL(libcrypto) @@ -70,6 +145,7 @@ def _load_crypto_libcrypto(): 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, iv): self._blocksize = len(userkey) @@ -88,7 +164,6 @@ def _load_crypto_libcrypto(): return AES - def _load_crypto_pycrypto(): from Crypto.Cipher import AES as _AES @@ -120,25 +195,31 @@ def normalize_name(name): return ''.join(x for x in name.lower() if x != ' ') -def generate_keyfile(name, ccn, outpath): +def generate_key(name, ccn): # remove spaces and case from name and CC numbers. + if type(name)==unicode: + name = name.encode('utf-8') + if type(ccn)==unicode: + ccn = ccn.encode('utf-8') + name = normalize_name(name) + '\x00' ccn = normalize_name(ccn) + '\x00' - + name_sha = hashlib.sha1(name).digest()[:16] ccn_sha = hashlib.sha1(ccn).digest()[:16] both_sha = hashlib.sha1(name + ccn).digest() aes = AES(ccn_sha, name_sha) crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c)) userkey = hashlib.sha1(crypt).digest() - with open(outpath, 'wb') as f: - f.write(userkey.encode('base64')) - return userkey + return userkey.encode('base64') -def cli_main(argv=sys.argv): +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() progname = os.path.basename(argv[0]) if AES is None: print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \ @@ -146,54 +227,58 @@ def cli_main(argv=sys.argv): (progname,) return 1 if len(argv) != 4: - print "usage: %s NAME CC# OUTFILE" % (progname,) + print u"usage: {0} ".format(progname) return 1 - name, ccn, outpath = argv[1:] - generate_keyfile(name, ccn, outpath) + name, ccn, keypath = argv[1:] + userkey = generate_key(name, ccn) + open(keypath,'wb').write(userkey) return 0 def gui_main(): - import Tkinter - import Tkconstants - import tkFileDialog - import tkMessageBox + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() class DecryptionDialog(Tkinter.Frame): def __init__(self, root): Tkinter.Frame.__init__(self, root, border=5) - self.status = Tkinter.Label(self, text='Enter parameters') + self.status = Tkinter.Label(self, text=u"Enter parameters") 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='Account Name').grid(row=0) + Tkinter.Label(body, text=u"Account Name").grid(row=0) self.name = Tkinter.Entry(body, width=40) self.name.grid(row=0, column=1, sticky=sticky) - Tkinter.Label(body, text='CC#').grid(row=1) + Tkinter.Label(body, text=u"CC#").grid(row=1) self.ccn = Tkinter.Entry(body, width=40) self.ccn.grid(row=1, column=1, sticky=sticky) - Tkinter.Label(body, text='Output file').grid(row=2) + Tkinter.Label(body, text=u"Output file").grid(row=2) self.keypath = Tkinter.Entry(body, width=40) self.keypath.grid(row=2, column=1, sticky=sticky) - self.keypath.insert(2, 'bnepubkey.b64') - button = Tkinter.Button(body, text="...", command=self.get_keypath) + self.keypath.insert(2, u"bnepubkey.b64") + button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button.grid(row=2, column=2) buttons = Tkinter.Frame(self) buttons.pack() botton = Tkinter.Button( - buttons, text="Generate", width=10, command=self.generate) + buttons, text=u"Generate", width=10, command=self.generate) botton.pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) button = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quit) + buttons, text=u"Quit", width=10, command=self.quit) button.pack(side=Tkconstants.RIGHT) - + def get_keypath(self): keypath = tkFileDialog.asksaveasfilename( - parent=None, title='Select B&N EPUB key file to produce', - defaultextension='.b64', + parent=None, title=u"Select B&N ePub key file to produce", + defaultextension=u".b64", filetypes=[('base64-encoded files', '.b64'), ('All Files', '.*')]) if keypath: @@ -201,27 +286,28 @@ def gui_main(): self.keypath.delete(0, Tkconstants.END) self.keypath.insert(0, keypath) return - + def generate(self): name = self.name.get() ccn = self.ccn.get() keypath = self.keypath.get() if not name: - self.status['text'] = 'Name not specified' + self.status['text'] = u"Name not specified" return if not ccn: - self.status['text'] = 'Credit card number not specified' + self.status['text'] = u"Credit card number not specified" return if not keypath: - self.status['text'] = 'Output keyfile path not specified' + self.status['text'] = u"Output keyfile path not specified" return - self.status['text'] = 'Generating...' + self.status['text'] = u"Generating..." try: - generate_keyfile(name, ccn, keypath) + userkey = generate_key(name, ccn) except Exception, e: - self.status['text'] = 'Error: ' + str(e) + self.status['text'] = u"Error: (0}".format(e.args[0]) return - self.status['text'] = 'Keyfile successfully generated' + open(keypath,'wb').write(userkey) + self.status['text'] = u"Keyfile successfully generated" root = Tkinter.Tk() if AES is None: @@ -231,7 +317,7 @@ def gui_main(): "This script requires OpenSSL or PyCrypto, which must be installed " "separately. Read the top-of-script comment for details.") return 1 - root.title('Ignoble EPUB Keyfile Generator') + root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__)) root.resizable(True, False) root.minsize(300, 0) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) diff --git a/Other_Tools/DRM_Key_Scripts/Kindle_for_Mac_and_PC/kindlekey.pyw b/Other_Tools/DRM_Key_Scripts/Kindle_for_Mac_and_PC/kindlekey.pyw new file mode 100644 index 0000000..f58e973 --- /dev/null +++ b/Other_Tools/DRM_Key_Scripts/Kindle_for_Mac_and_PC/kindlekey.pyw @@ -0,0 +1,1918 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +# kindlekey.py +# Copyright © 2010-2013 by some_updates and Apprentice Alf +# +# Currently requires alfcrypto.py which requires the alfcrypto library + +# Revision history: +# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc. +# 1.1 - Added Tkinter to match adobekey.py +# 1.2 - Fixed testing of successful retrieval on Mac +# 1.3 - Added getkey interface for Windows DeDRM application +# Simplified some of the Kindle for Mac code. +# 1.4 - Remove dependency on alfcrypto +# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility +# 1.6 - Fixed a problem getting the disk serial numbers +# 1.7 - Work if TkInter is missing +# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names + + +""" +Retrieve Kindle for PC/Mac user key. +""" + +__license__ = 'GPL v3' +__version__ = '1.8' + +import sys, os, re +from struct import pack, unpack, unpack_from +import json +import getopt + +# Routines common to Mac and PC + +# Wrap a stream so that output gets flushed immediately +# and also make sure that any unicode strings get +# encoded using "replace" before writing them. +class SafeUnbuffered: + def __init__(self, stream): + self.stream = stream + self.encoding = stream.encoding + if self.encoding == None: + self.encoding = "utf-8" + def write(self, data): + if isinstance(data,unicode): + data = data.encode(self.encoding,"replace") + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +try: + from calibre.constants import iswindows, isosx +except: + iswindows = sys.platform.startswith('win') + isosx = sys.platform.startswith('darwin') + +def unicode_argv(): + if iswindows: + # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode + # strings. + + # Versions 2.x of Python don't support Unicode in sys.argv on + # Windows, with the underlying Windows API instead replacing multi-byte + # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv + # as a list of Unicode strings and encode them as utf-8 + + from ctypes import POINTER, byref, cdll, c_int, windll + from ctypes.wintypes import LPCWSTR, LPWSTR + + GetCommandLineW = cdll.kernel32.GetCommandLineW + GetCommandLineW.argtypes = [] + GetCommandLineW.restype = LPCWSTR + + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPWSTR) + + cmd = GetCommandLineW() + argc = c_int(0) + argv = CommandLineToArgvW(cmd, byref(argc)) + if argc.value > 0: + # Remove Python executable and commands if present + start = argc.value - len(sys.argv) + return [argv[i] for i in + xrange(start, argc.value)] + # if we don't have any arguments at all, just pass back script name + # this should never happen + return [u"kindlekey.py"] + else: + argvencoding = sys.stdin.encoding + if argvencoding == None: + argvencoding = "utf-8" + return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + +class DrmException(Exception): + pass + +# crypto digestroutines +import hashlib + +def MD5(message): + ctx = hashlib.md5() + ctx.update(message) + return ctx.digest() + +def SHA1(message): + ctx = hashlib.sha1() + ctx.update(message) + return ctx.digest() + +def SHA256(message): + ctx = hashlib.sha256() + ctx.update(message) + return ctx.digest() + +# For K4M/PC 1.6.X and later +# generate table of prime number less than or equal to int n +def primes(n): + if n==2: return [2] + elif n<2: return [] + s=range(3,n+1,2) + mroot = n ** 0.5 + half=(n+1)/2-1 + i=0 + m=3 + while m <= mroot: + if s[i]: + j=(m*m-3)/2 + s[j]=0 + while j 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # no more data expected from caller + finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) + if len(finalBytes) > 0: + ctBlock = self.encryptBlock(finalBytes) + self.encryptBlockCount += 1 + cipherText += ctBlock + self.resetEncrypt() + return cipherText + + def decrypt(self, cipherText, more = None): + """ Decrypt a string and return a string """ + self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt + + numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) + if more == None: # no more calls to decrypt, should have all the data + if numExtraBytes != 0: + raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' + + # hold back some bytes in case last decrypt has zero len + if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : + numBlocks -= 1 + numExtraBytes = self.blockSize + + plainText = '' + for i in range(numBlocks): + bStart = i*self.blockSize + ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) + self.decryptBlockCount += 1 + plainText += ptBlock + + if numExtraBytes > 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # last decrypt remove padding + plainText = self.padding.removePad(plainText, self.blockSize) + self.resetDecrypt() + return plainText + + + class Pad: + def __init__(self): + pass # eventually could put in calculation of min and max size extension + + class padWithPadLen(Pad): + """ Pad a binary string with the length of the padding """ + + def addPad(self, extraBytes, blockSize): + """ Add padding to a binary string to make it an even multiple + of the block size """ + blocks, numExtraBytes = divmod(len(extraBytes), blockSize) + padLength = blockSize - numExtraBytes + return extraBytes + padLength*chr(padLength) + + def removePad(self, paddedBinaryString, blockSize): + """ Remove padding from a binary string """ + if not(0 6 and i%Nk == 4 : + temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) + w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) + return w + + Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! + 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, + 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) + + #------------------------------------- + def AddRoundKey(algInstance, keyBlock): + """ XOR the algorithm state with a block of key material """ + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] ^= keyBlock[column][row] + #------------------------------------- + + def SubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = Sbox[algInstance.state[column][row]] + + def InvSubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] + + Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, + 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, + 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, + 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, + 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, + 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, + 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, + 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, + 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, + 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, + 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, + 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, + 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, + 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, + 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, + 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, + 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) + + InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) + + #------------------------------------- + """ For each block size (Nb), the ShiftRow operation shifts row i + by the amount Ci. Note that row 0 is not shifted. + Nb C1 C2 C3 + ------------------- """ + shiftOffset = { 4 : ( 0, 1, 2, 3), + 5 : ( 0, 1, 2, 3), + 6 : ( 0, 1, 2, 3), + 7 : ( 0, 1, 2, 4), + 8 : ( 0, 1, 3, 4) } + def ShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] + def InvShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] + #------------------------------------- + def MixColumns(a): + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) + Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + + def InvMixColumns(a): + """ Mix the four bytes of every column in a linear way + This is the opposite operation of Mixcolumn """ + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) + Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) + Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) + Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + + #------------------------------------- + def mul(a, b): + """ Multiply two elements of GF(2^m) + needed for MixColumn and InvMixColumn """ + if (a !=0 and b!=0): + return Alogtable[(Logtable[a] + Logtable[b])%255] + else: + return 0 + + Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, + 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, + 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, + 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, + 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, + 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, + 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, + 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, + 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, + 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, + 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, + 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) + + Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, + 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, + 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, + 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, + 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, + 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, + 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, + 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, + 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, + 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) + + + + + """ + AES Encryption Algorithm + The AES algorithm is just Rijndael algorithm restricted to the default + blockSize of 128 bits. + """ + + class AES(Rijndael): + """ The AES algorithm is the Rijndael block cipher restricted to block + sizes of 128 bits and key sizes of 128, 192 or 256 bits + """ + def __init__(self, key = None, padding = padWithPadLen(), keySize=16): + """ Initialize AES, keySize is in bytes """ + if not (keySize == 16 or keySize == 24 or keySize == 32) : + raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' + + Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) + + self.name = 'AES' + + + """ + CBC mode of encryption for block ciphers. + This algorithm mode wraps any BlockCipher to make a + Cipher Block Chaining mode. + """ + from random import Random # should change to crypto.random!!! + + + class CBC(BlockCipher): + """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode + algorithms. The initialization (IV) is automatic if set to None. Padding + is also automatic based on the Pad class used to initialize the algorithm + """ + def __init__(self, blockCipherInstance, padding = padWithPadLen()): + """ CBC algorithms are created by initializing with a BlockCipher instance """ + self.baseCipher = blockCipherInstance + self.name = self.baseCipher.name + '_CBC' + self.blockSize = self.baseCipher.blockSize + self.keySize = self.baseCipher.keySize + self.padding = padding + self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! + self.r = Random() # for IV generation, currently uses + # mediocre standard distro version <---------------- + import time + newSeed = time.ctime()+str(self.r) # seed with instance location + self.r.seed(newSeed) # to make unique + self.reset() + + def setKey(self, key): + self.baseCipher.setKey(key) + + # Overload to reset both CBC state and the wrapped baseCipher + def resetEncrypt(self): + BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) + self.baseCipher.resetEncrypt() # reset base cipher encrypt state + + def resetDecrypt(self): + BlockCipher.resetDecrypt(self) # reset CBC state (super class) + self.baseCipher.resetDecrypt() # reset base cipher decrypt state + + def encrypt(self, plainText, iv=None, more=None): + """ CBC encryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.encryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to encrypt' + + return BlockCipher.encrypt(self,plainText, more=more) + + def decrypt(self, cipherText, iv=None, more=None): + """ CBC decryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.decryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to decrypt' + + return BlockCipher.decrypt(self, cipherText, more=more) + + def encryptBlock(self, plainTextBlock): + """ CBC block encryption, IV is set with 'encrypt' """ + auto_IV = '' + if self.encryptBlockCount == 0: + if self.iv == None: + # generate IV and use + self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) + self.prior_encr_CT_block = self.iv + auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic + else: # application provided IV + assert(len(self.iv) == self.blockSize ),'IV must be same length as block' + self.prior_encr_CT_block = self.iv + """ encrypt the prior CT XORed with the PT """ + ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) + self.prior_encr_CT_block = ct + return auto_IV+ct + + def decryptBlock(self, encryptedBlock): + """ Decrypt a single block """ + + if self.decryptBlockCount == 0: # first call, process IV + if self.iv == None: # auto decrypt IV? + self.prior_CT_block = encryptedBlock + return '' + else: + assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" + self.prior_CT_block = self.iv + + dct = self.baseCipher.decryptBlock(encryptedBlock) + """ XOR the prior decrypted CT with the prior CT """ + dct_XOR_priorCT = xor( self.prior_CT_block, dct ) + + self.prior_CT_block = encryptedBlock + + return dct_XOR_priorCT + + + """ + AES_CBC Encryption Algorithm + """ + + class aescbc_AES_CBC(CBC): + """ AES encryption in CBC feedback mode """ + def __init__(self, key=None, padding=padWithPadLen(), keySize=16): + CBC.__init__( self, AES(key, noPadding(), keySize), padding) + self.name = 'AES_CBC' + + class AES_CBC(object): + def __init__(self): + self._key = None + self._iv = None + self.aes = None + + def set_decrypt_key(self, userkey, iv): + self._key = userkey + self._iv = iv + self.aes = aescbc_AES_CBC(userkey, noPadding(), len(userkey)) + + def decrypt(self, data): + iv = self._iv + cleartext = self.aes.decrypt(iv + data) + return cleartext + + import hmac + + class KeyIVGen(object): + # this only exists in openssl so we will use pure python implementation instead + # 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]) + def pbkdf2(self, passwd, salt, iter, keylen): + + def xorstr( a, b ): + if len(a) != len(b): + raise Exception("xorstr(): lengths differ") + return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) + + def prf( h, data ): + hm = h.copy() + hm.update( data ) + return hm.digest() + + def pbkdf2_F( h, salt, itercount, blocknum ): + U = prf( h, salt + pack('>i',blocknum ) ) + T = U + for i in range(2, itercount+1): + U = prf( h, U ) + T = xorstr( T, U ) + return T + + sha = hashlib.sha1 + digest_size = sha().digest_size + # l - number of output blocks to produce + l = keylen / digest_size + if keylen % digest_size != 0: + l += 1 + h = hmac.new( passwd, None, sha ) + T = "" + for i in range(1, l+1): + T += pbkdf2_F( h, salt, iter, i ) + return T[0: keylen] + + def UnprotectHeaderData(encryptedData): + passwdData = 'header_key_data' + salt = 'HEADER.2011' + iter = 0x80 + keylen = 0x100 + key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen) + key = key_iv[0:32] + iv = key_iv[32:48] + aes=AES_CBC() + aes.set_decrypt_key(key, iv) + cleartext = aes.decrypt(encryptedData) + return cleartext + + # Various character maps used to decrypt kindle info values. + # Probably supposed to act as obfuscation + charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" + charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" + # New maps in K4PC 1.9.0 + testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" + testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG" + testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD" + + # interface with Windows OS Routines + 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 GetIDString(): + vsn = GetVolumeSerialNumber() + #print('Using Volume Serial Number for ID: '+vsn) + return vsn + + def getLastError(): + GetLastError = kernel32.GetLastError + GetLastError.argtypes = None + GetLastError.restype = c_uint + def getLastError(): + return GetLastError() + return getLastError + getLastError = getLastError() + + def GetUserName(): + GetUserNameW = advapi32.GetUserNameW + GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] + GetUserNameW.restype = c_uint + def GetUserName(): + buffer = create_unicode_buffer(2) + size = c_uint(len(buffer)) + while not GetUserNameW(buffer, byref(size)): + errcd = getLastError() + if errcd == 234: + # bad wine implementation up through wine 1.3.21 + return "AlternateUserName" + 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, flags): + 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, flags, byref(outdata)): + # raise DrmException("Failed to Unprotect Data") + return 'failed' + return string_at(outdata.pbData, outdata.cbData) + return CryptUnprotectData + CryptUnprotectData = CryptUnprotectData() + + + # Locate all of the kindle-info style files and return as list + def getKindleInfoFiles(): + kInfoFiles = [] + # some 64 bit machines do not have the proper registry key for some reason + # or the pythonn interface to the 32 vs 64 bit registry is broken + path = "" + if 'LOCALAPPDATA' in os.environ.keys(): + path = os.environ['LOCALAPPDATA'] + else: + # User Shell Folders show take precedent over Shell Folders if present + try: + regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\") + path = winreg.QueryValueEx(regkey, 'Local AppData')[0] + if not os.path.isdir(path): + path = "" + try: + regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") + path = winreg.QueryValueEx(regkey, 'Local AppData')[0] + if not os.path.isdir(path): + path = "" + except RegError: + pass + except RegError: + pass + + found = False + if path == "": + print ('Could not find the folder in which to look for kinfoFiles.') + else: + print('searching for kinfoFiles in ' + path) + + # look for (K4PC 1.9.0 and later) .kinf2011 file + kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file + kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.6-1.8 kinf file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for (K4PC 1.5.0 and later) rainier.2.1.1.kinf file + kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC 1.5 kinf file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + # look for original (earlier than K4PC 1.5.0) kindle-info files + kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info' + if os.path.isfile(kinfopath): + found = True + print('Found K4PC kindle.info file: ' + kinfopath) + kInfoFiles.append(kinfopath) + + if not found: + print('No K4PC kindle.info/kinf/kinf2011 files have been found.') + return kInfoFiles + + + # determine type of kindle info provided and return a + # database of keynames and values + def getDBfromFile(kInfoFile): + names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF'] + DB = {} + with open(kInfoFile, 'rb') as infoReader: + hdr = infoReader.read(1) + data = infoReader.read() + + if data.find('{') != -1 : + # older style kindle-info file + items = data.split('{') + for item in items: + if item != '': + keyhash, rawdata = item.split(':') + keyname = "unknown" + for name in names: + if encodeHash(name,charMap2) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + encryptedValue = decode(rawdata,charMap2) + DB[keyname] = CryptUnprotectData(encryptedValue, "", 0) + elif hdr == '/': + # else rainier-2-1-1 .kinf file + # the .kinf file uses "/" to separate it into records + # so remove the trailing "/" to make it easy to use split + data = data[:-1] + items = data.split('/') + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the raw keyhash string is used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = "unknown" + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == "unknown": + keyname = keyhash + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents)-largest prime number <= int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using Map5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) + else: + # else newest .kinf2011 style .kinf file + # the .kinf file uses "/" to separate it into records + # so remove the trailing "/" to make it easy to use split + # need to put back the first char read because it it part + # of the added entropy blob + data = hdr + data[:-1] + items = data.split('/') + + # starts with and encoded and encrypted header blob + headerblob = items.pop(0) + encryptedValue = decode(headerblob, testMap1) + cleartext = UnprotectHeaderData(encryptedValue) + # now extract the pieces that form the added entropy + pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) + for m in re.finditer(pattern, cleartext): + added_entropy = m.group(2) + m.group(4) + + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + + # the sha1 of raw keyhash string is used to create entropy along + # with the added entropy provided above from the headerblob + entropy = SHA1(keyhash) + added_entropy + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + # key names now use the new testMap8 encoding + keyname = "unknown" + for name in names: + if encodeHash(name,testMap8) == keyhash: + keyname = name + break + + # the testMap8 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using testMap8 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the testMap8 encoded contents seems to be: + # len(contents)-largest prime number <= int(len(content)/3) + # (in other words split "about" 2/3rds of the way through) + + # move first offsets chars to end to align for decode by testMap8 + # by moving noffset chars from the start of the + # string to the end of the string + encdata = "".join(edlst) + contlen = len(encdata) + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using new testMap8 to get the original CryptProtect Data + encryptedValue = decode(encdata,testMap8) + cleartext = CryptUnprotectData(encryptedValue, entropy, 1) + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1")) + # store values used in decryption + DB['IDString'] = GetIDString() + DB['UserName'] = GetUserName() + else: + DB = {} + return DB +elif isosx: + import copy + import subprocess + + # interface to needed routines in openssl's libcrypto + def _load_crypto_libcrypto(): + from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, addressof, string_at, cast + from ctypes.util import find_library + + libcrypto = find_library('crypto') + if libcrypto is None: + raise DrmException(u"libcrypto not found") + libcrypto = CDLL(libcrypto) + + # From OpenSSL's crypto aes header + # + # AES_ENCRYPT 1 + # AES_DECRYPT 0 + # AES_MAXNR 14 (in bytes) + # AES_BLOCK_SIZE 16 (in bytes) + # + # struct aes_key_st { + # unsigned long rd_key[4 *(AES_MAXNR + 1)]; + # int rounds; + # }; + # typedef struct aes_key_st AES_KEY; + # + # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); + # + # note: the ivec string, and output buffer are both mutable + # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc); + + 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]) + + # From OpenSSL's Crypto evp/p5_crpt2.c + # + # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, + # const unsigned char *salt, int saltlen, int iter, + # int keylen, unsigned char *out); + + PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', + [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) + + class LibCrypto(object): + def __init__(self): + self._blocksize = 0 + self._keyctx = None + self._iv = 0 + + def set_decrypt_key(self, userkey, iv): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise DrmException(u"AES improper key used") + return + keyctx = self._keyctx = AES_KEY() + self._iv = iv + self._userkey = userkey + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) + if rv < 0: + raise DrmException(u"Failed to initialize AES key") + + def decrypt(self, data): + out = create_string_buffer(len(data)) + mutable_iv = create_string_buffer(self._iv, len(self._iv)) + keyctx = self._keyctx + rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) + if rv == 0: + raise DrmException(u"AES decryption failed") + return out.raw + + def keyivgen(self, passwd, salt, iter, keylen): + saltlen = len(salt) + passlen = len(passwd) + out = create_string_buffer(keylen) + rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) + return out.raw + return LibCrypto + + def _load_crypto(): + LibCrypto = None + try: + LibCrypto = _load_crypto_libcrypto() + except (ImportError, DrmException): + pass + return LibCrypto + + LibCrypto = _load_crypto() + + # Various character maps used to decrypt books. Probably supposed to act as obfuscation + charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' + charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM' + + # For kinf approach of K4Mac 1.6.X or later + # On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE' + # For Mac they seem to re-use charMap2 here + charMap5 = charMap2 + + # new in K4M 1.9.X + testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD' + + # uses a sub process to get the Hard Drive Serial Number using ioreg + # returns serial numbers of all internal hard drive drives + def GetVolumesSerialNumbers(): + sernums = [] + sernum = os.getenv('MYSERIALNUMBER') + if sernum != None: + sernums.append(sernum.strip()) + cmdline = '/usr/sbin/ioreg -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + bsdname = None + sernum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('\"Serial Number\" = \"') + if pp >= 0: + sernum = resline[pp+19:-1] + sernums.append(sernum.strip()) + return sernums + + def GetUserHomeAppSupKindleDirParitionName(): + home = os.getenv('HOME') + dpath = home + '/Library' + cmdline = '/sbin/mount' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + disk = '' + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + if resline.startswith('/dev'): + (devpart, mpath) = resline.split(' on ') + dpart = devpart[5:] + pp = mpath.find('(') + if pp >= 0: + mpath = mpath[:pp-1] + if dpath.startswith(mpath): + disk = dpart + return disk + + # uses a sub process to get the UUID of the specified disk partition using ioreg + def GetDiskPartitionUUIDs(diskpart): + uuids = [] + uuidnum = os.getenv('MYUUIDNUMBER') + if uuidnum != None: + uuids.append(strip(uuidnum)) + cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + bsdname = None + uuidnum = None + foundIt = False + nest = 0 + uuidnest = -1 + partnest = -2 + for j in xrange(cnt): + resline = reslst[j] + if resline.find('{') >= 0: + nest += 1 + if resline.find('}') >= 0: + nest -= 1 + pp = resline.find('\"UUID\" = \"') + if pp >= 0: + uuidnum = resline[pp+10:-1] + uuidnum = uuidnum.strip() + uuidnest = nest + if partnest == uuidnest and uuidnest > 0: + foundIt = True + break + bb = resline.find('\"BSD Name\" = \"') + if bb >= 0: + bsdname = resline[bb+14:-1] + bsdname = bsdname.strip() + if (bsdname == diskpart): + partnest = nest + else : + partnest = -2 + if partnest == uuidnest and partnest > 0: + foundIt = True + break + if nest == 0: + partnest = -2 + uuidnest = -1 + uuidnum = None + bsdname = None + if foundIt: + uuids.append(uuidnum) + return uuids + + def GetMACAddressesMunged(): + macnums = [] + macnum = os.getenv('MYMACNUM') + if macnum != None: + macnums.append(macnum) + cmdline = '/sbin/ifconfig en0' + cmdline = cmdline.encode(sys.getfilesystemencoding()) + p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) + out1, out2 = p.communicate() + reslst = out1.split('\n') + cnt = len(reslst) + macnum = None + foundIt = False + for j in xrange(cnt): + resline = reslst[j] + pp = resline.find('ether ') + if pp >= 0: + macnum = resline[pp+6:-1] + macnum = macnum.strip() + # print 'original mac', macnum + # now munge it up the way Kindle app does + # by xoring it with 0xa5 and swapping elements 3 and 4 + maclst = macnum.split(':') + n = len(maclst) + if n != 6: + fountIt = False + break + for i in range(6): + maclst[i] = int('0x' + maclst[i], 0) + mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + mlst[5] = maclst[5] ^ 0xa5 + mlst[4] = maclst[3] ^ 0xa5 + mlst[3] = maclst[4] ^ 0xa5 + mlst[2] = maclst[2] ^ 0xa5 + mlst[1] = maclst[1] ^ 0xa5 + mlst[0] = maclst[0] ^ 0xa5 + macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5]) + foundIt = True + break + if foundIt: + macnums.append(macnum) + return macnums + + + # uses unix env to get username instead of using sysctlbyname + def GetUserName(): + username = os.getenv('USER') + return username + + def GetIDStrings(): + # Return all possible ID Strings + strings = [] + strings.extend(GetMACAddressesMunged()) + strings.extend(GetVolumesSerialNumbers()) + diskpart = GetUserHomeAppSupKindleDirParitionName() + strings.extend(GetDiskPartitionUUIDs(diskpart)) + strings.append('9999999999') + #print strings + return strings + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used by Kindle for Mac versions < 1.6.0 + class CryptUnprotectData(object): + def __init__(self, IDString): + sp = IDString + '!@#' + GetUserName() + passwdData = encode(SHA256(sp),charMap1) + salt = '16743' + self.crp = LibCrypto() + iter = 0x3e8 + keylen = 0x80 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext,charMap1) + return cleartext + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used for Kindle for Mac Versions >= 1.6.0 + class CryptUnprotectDataV2(object): + def __init__(self, IDString): + sp = GetUserName() + ':&%:' + IDString + passwdData = encode(SHA256(sp),charMap5) + # salt generation as per the code + salt = 0x0512981d * 2 * 1 * 1 + salt = str(salt) + GetUserName() + salt = encode(salt,charMap5) + self.crp = LibCrypto() + iter = 0x800 + keylen = 0x400 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext, charMap5) + return cleartext + + + # unprotect the new header blob in .kinf2011 + # used in Kindle for Mac Version >= 1.9.0 + def UnprotectHeaderData(encryptedData): + passwdData = 'header_key_data' + salt = 'HEADER.2011' + iter = 0x80 + keylen = 0x100 + crp = LibCrypto() + key_iv = crp.keyivgen(passwdData, salt, iter, keylen) + key = key_iv[0:32] + iv = key_iv[32:48] + crp.set_decrypt_key(key,iv) + cleartext = crp.decrypt(encryptedData) + return cleartext + + + # implements an Pseudo Mac Version of Windows built-in Crypto routine + # used for Kindle for Mac Versions >= 1.9.0 + class CryptUnprotectDataV3(object): + def __init__(self, entropy, IDString): + sp = GetUserName() + '+@#$%+' + IDString + passwdData = encode(SHA256(sp),charMap2) + salt = entropy + self.crp = LibCrypto() + iter = 0x800 + keylen = 0x400 + key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) + self.key = key_iv[0:32] + self.iv = key_iv[32:48] + self.crp.set_decrypt_key(self.key, self.iv) + + def decrypt(self, encryptedData): + cleartext = self.crp.decrypt(encryptedData) + cleartext = decode(cleartext, charMap2) + return cleartext + + + # Locate the .kindle-info files + def getKindleInfoFiles(): + # file searches can take a long time on some systems, so just look in known specific places. + kInfoFiles=[] + found = False + home = os.getenv('HOME') + # check for .kinf2011 file in new location (App Store Kindle for Mac) + testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kinf2011 file: ' + testpath) + found = True + # check for .kinf2011 files from 1.10 + testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kinf2011 file: ' + testpath) + found = True + # check for .rainier-2.1.1-kinf files from 1.6 + testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac rainier file: ' + testpath) + found = True + # check for .kindle-info files from 1.4 + testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + # check for .kindle-info file from 1.2.2 + testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + # check for .kindle-info file from 1.0 beta 1 (27214) + testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info' + if os.path.isfile(testpath): + kInfoFiles.append(testpath) + print('Found k4Mac kindle-info file: ' + testpath) + found = True + if not found: + print('No k4Mac kindle-info/rainier/kinf2011 files have been found.') + return kInfoFiles + + # determine type of kindle info provided and return a + # database of keynames and values + def getDBfromFile(kInfoFile): + names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF'] + with open(kInfoFile, 'rb') as infoReader: + filehdr = infoReader.read(1) + filedata = infoReader.read() + + IDStrings = GetIDStrings() + for IDString in IDStrings: + DB = {} + #print "trying IDString:",IDString + try: + hdr = filehdr + data = filedata + if data.find('[') != -1 : + # older style kindle-info file + cud = CryptUnprotectData(IDString) + items = data.split('[') + for item in items: + if item != '': + keyhash, rawdata = item.split(':') + keyname = 'unknown' + for name in names: + if encodeHash(name,charMap2) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + encryptedValue = decode(rawdata,charMap2) + cleartext = cud.decrypt(encryptedValue) + if len(cleartext) > 0: + DB[keyname] = cleartext + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + elif hdr == '/': + # else newer style .kinf file used by K4Mac >= 1.6.0 + # the .kinf file uses '/' to separate it into records + # so remove the trailing '/' to make it easy to use split + data = data[:-1] + items = data.split('/') + cud = CryptUnprotectDataV2(IDString) + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + keyname = 'unknown' + + # the raw keyhash string is also used to create entropy for the actual + # CryptProtectData Blob that represents that keys contents + # 'entropy' not used for K4Mac only K4PC + # entropy = SHA1(keyhash) + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = 'unknown' + for name in names: + if encodeHash(name,charMap5) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + + # the charMap5 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using charMap5 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the charMap5 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split 'about' 2/3rds of the way through) + + # move first offsets chars to end to align for decode by charMap5 + encdata = ''.join(edlst) + contlen = len(encdata) + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using charMap5 to get the CryptProtect Data + encryptedValue = decode(encdata,charMap5) + cleartext = cud.decrypt(encryptedValue) + if len(cleartext) > 0: + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + else: + # the latest .kinf2011 version for K4M 1.9.1 + # put back the hdr char, it is needed + data = hdr + data + data = data[:-1] + items = data.split('/') + + # the headerblob is the encrypted information needed to build the entropy string + headerblob = items.pop(0) + encryptedValue = decode(headerblob, charMap1) + cleartext = UnprotectHeaderData(encryptedValue) + + # now extract the pieces in the same way + # this version is different from K4PC it scales the build number by multipying by 735 + pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) + for m in re.finditer(pattern, cleartext): + entropy = str(int(m.group(2)) * 0x2df) + m.group(4) + + cud = CryptUnprotectDataV3(entropy,IDString) + + # loop through the item records until all are processed + while len(items) > 0: + + # get the first item record + item = items.pop(0) + + # the first 32 chars of the first record of a group + # is the MD5 hash of the key name encoded by charMap5 + keyhash = item[0:32] + keyname = 'unknown' + + # unlike K4PC the keyhash is not used in generating entropy + # entropy = SHA1(keyhash) + added_entropy + # entropy = added_entropy + + # the remainder of the first record when decoded with charMap5 + # has the ':' split char followed by the string representation + # of the number of records that follow + # and make up the contents + srcnt = decode(item[34:],charMap5) + rcnt = int(srcnt) + + # read and store in rcnt records of data + # that make up the contents value + edlst = [] + for i in xrange(rcnt): + item = items.pop(0) + edlst.append(item) + + keyname = 'unknown' + for name in names: + if encodeHash(name,testMap8) == keyhash: + keyname = name + break + if keyname == 'unknown': + keyname = keyhash + + # the testMap8 encoded contents data has had a length + # of chars (always odd) cut off of the front and moved + # to the end to prevent decoding using testMap8 from + # working properly, and thereby preventing the ensuing + # CryptUnprotectData call from succeeding. + + # The offset into the testMap8 encoded contents seems to be: + # len(contents) - largest prime number less than or equal to int(len(content)/3) + # (in other words split 'about' 2/3rds of the way through) + + # move first offsets chars to end to align for decode by testMap8 + encdata = ''.join(edlst) + contlen = len(encdata) + + # now properly split and recombine + # by moving noffset chars from the start of the + # string to the end of the string + noffset = contlen - primes(int(contlen/3))[-1] + pfx = encdata[0:noffset] + encdata = encdata[noffset:] + encdata = encdata + pfx + + # decode using testMap8 to get the CryptProtect Data + encryptedValue = decode(encdata,testMap8) + cleartext = cud.decrypt(encryptedValue) + # print keyname + # print cleartext + if len(cleartext) > 0: + DB[keyname] = cleartext + + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + break + except: + pass + if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB: + # store values used in decryption + print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName()) + DB['IDString'] = IDString + DB['UserName'] = GetUserName() + else: + print u"Couldn't decrypt file." + DB = {} + return DB +else: + def getDBfromFile(kInfoFile): + raise DrmException(u"This script only runs under Windows or Mac OS X.") + return {} + +def kindlekeys(files = []): + keys = [] + if files == []: + files = getKindleInfoFiles() + for file in files: + key = getDBfromFile(file) + if key: + # convert all values to hex, just in case. + for keyname in key: + key[keyname]=key[keyname].encode('hex') + keys.append(key) + return keys + +# interface for Python DeDRM +# returns single key or multiple keys, depending on path or file passed in +def getkey(outpath, files=[]): + keys = kindlekeys(files) + if len(keys) > 0: + if not os.path.isdir(outpath): + outfile = outpath + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(keys[0])) + print u"Saved a key to {0}".format(outfile) + else: + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount)) + if not os.path.exists(outfile): + break + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(key)) + print u"Saved a key to {0}".format(outfile) + return True + return False + +def usage(progname): + print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys." + print u"Keys are saved to the current directory, or a specified output directory." + print u"If a file name is passed instead of a directory, only the first key is saved, in that file." + print u"Usage:" + print u" {0:s} [-h] [-k ] []".format(progname) + + +def cli_main(): + sys.stdout=SafeUnbuffered(sys.stdout) + sys.stderr=SafeUnbuffered(sys.stderr) + argv=unicode_argv() + progname = os.path.basename(argv[0]) + print u"{0} v{1}\nCopyright © 2010-2013 some_updates and Apprentice Alf".format(progname,__version__) + + try: + opts, args = getopt.getopt(argv[1:], "hk:") + except getopt.GetoptError, err: + print u"Error in options or arguments: {0}".format(err.args[0]) + usage(progname) + sys.exit(2) + + files = [] + for o, a in opts: + if o == "-h": + usage(progname) + sys.exit(0) + if o == "-k": + files = [a] + + if len(args) > 1: + usage(progname) + sys.exit(2) + + if len(args) == 1: + # save to the specified file or directory + outpath = args[0] + if not os.path.isabs(outpath): + outpath = os.path.abspath(outpath) + else: + # save to the same directory as the script + outpath = os.path.dirname(argv[0]) + + # make sure the outpath is the + outpath = os.path.realpath(os.path.normpath(outpath)) + + if not getkey(outpath, files): + print u"Could not retrieve Kindle for Mac/PC key." + return 0 + + +def gui_main(): + try: + import Tkinter + import Tkconstants + import tkMessageBox + import traceback + except: + return cli_main() + + class ExceptionDialog(Tkinter.Frame): + def __init__(self, root, text): + Tkinter.Frame.__init__(self, root, border=5) + label = Tkinter.Label(self, text=u"Unexpected error:", + anchor=Tkconstants.W, justify=Tkconstants.LEFT) + label.pack(fill=Tkconstants.X, expand=0) + self.text = Tkinter.Text(self) + self.text.pack(fill=Tkconstants.BOTH, expand=1) + + self.text.insert(Tkconstants.END, text) + + + argv=unicode_argv() + root = Tkinter.Tk() + root.withdraw() + progpath, progname = os.path.split(argv[0]) + success = False + try: + keys = kindlekeys() + keycount = 0 + for key in keys: + while True: + keycount += 1 + outfile = os.path.join(progpath,u"kindlekey{0:d}.k4i".format(keycount)) + if not os.path.exists(outfile): + break + + with file(outfile, 'w') as keyfileout: + keyfileout.write(json.dumps(key)) + success = True + tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) + except DrmException, e: + tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) + except Exception: + root.wm_state('normal') + root.title(progname) + text = traceback.format_exc() + ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) + root.mainloop() + if not success: + return 1 + return 0 + +if __name__ == '__main__': + if len(sys.argv) > 1: + sys.exit(cli_main()) + sys.exit(gui_main()) diff --git a/Other_Tools/KindleBooks/KindleBooks.pyw b/Other_Tools/KindleBooks/KindleBooks.pyw deleted file mode 100644 index 0f8021f..0000000 --- a/Other_Tools/KindleBooks/KindleBooks.pyw +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import sys -sys.path.append('lib') -import os, os.path, urllib -os.environ['PYTHONIOENCODING'] = "utf-8" - -import Tkinter -import Tkconstants -import tkFileDialog -import tkMessageBox -from scrolltextwidget import ScrolledText -import subprocess -from subprocess import Popen, PIPE, STDOUT -import subasyncio -from subasyncio import Process - -class MainDialog(Tkinter.Frame): - def __init__(self, root): - Tkinter.Frame.__init__(self, root, border=5) - self.root = root - self.interval = 1000 - self.p2 = None - self.status = Tkinter.Label(self, text='Remove Encryption from a Kindle/Mobi/Topaz eBook') - self.status.pack(fill=Tkconstants.X, expand=1) - body = Tkinter.Frame(self) - body.pack(fill=Tkconstants.X, expand=1) - sticky = Tkconstants.E + Tkconstants.W - body.grid_columnconfigure(1, weight=2) - - Tkinter.Label(body, text='Kindle/Mobi/Topaz eBook input file').grid(row=0, sticky=Tkconstants.E) - self.mobipath = Tkinter.Entry(body, width=50) - self.mobipath.grid(row=0, column=1, sticky=sticky) - cwd = os.getcwdu() - cwd = cwd.encode('utf-8') - self.mobipath.insert(0, cwd) - button = Tkinter.Button(body, text="...", command=self.get_mobipath) - button.grid(row=0, column=2) - - Tkinter.Label(body, text='Directory for the Unencrypted Output File(s)').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') - 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='Optional Alternative Kindle.info file').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='Optional 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=3, column=1, sticky=sticky) - - Tkinter.Label(body, text='Optional Comma Separated List of 16 Character Kindle Serial Numbers (no spaces)').grid(row=4, sticky=Tkconstants.E) - self.sernums = Tkinter.StringVar() - self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums) - self.serinfo.grid(row=4, 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) - self.stext.grid(row=6, column=0, columnspan=2,sticky=sticky) - self.stext.insert(Tkconstants.END,msg1) - - buttons = Tkinter.Frame(self) - buttons.pack() - self.sbotton = Tkinter.Button( - buttons, text="Start", width=10, command=self.convertit) - self.sbotton.pack(side=Tkconstants.LEFT) - - Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) - self.qbutton = Tkinter.Button( - buttons, text="Quit", width=10, command=self.quitting) - self.qbutton.pack(side=Tkconstants.RIGHT) - - # read from subprocess pipe without blocking - # invoked every interval via the widget "after" - # option being used, so need to reset it for the next time - def processPipe(self): - poll = self.p2.wait('nowait') - if poll != None: - text = self.p2.readerr() - text += self.p2.read() - msg = text + '\n\n' + 'Encryption successfully removed\n' - if poll == 1: - msg = text + '\n\n' + 'Error: Encryption Removal Failed\n' - if poll == 2: - msg = text + '\n\n' + 'Input File was Not Encrypted - No Output File Needed\n' - self.showCmdOutput(msg) - self.p2 = None - self.sbotton.configure(state='normal') - return - text = self.p2.readerr() - text += self.p2.read() - self.showCmdOutput(text) - # make sure we get invoked again by event loop after interval - self.stext.after(self.interval,self.processPipe) - return - - # post output from subprocess in scrolled text widget - def showCmdOutput(self, msg): - if msg and msg !='': - if sys.platform.startswith('win'): - msg = msg.replace('\r\n','\n') - self.stext.insert(Tkconstants.END,msg) - self.stext.yview_pickplace(Tkconstants.END) - return - - # run as a subprocess via pipes and collect stdout - def mobirdr(self, infile, outfile, altinfopath, pidnums, sernums): - # os.putenv('PYTHONUNBUFFERED', '1') - tool = 'k4mobidedrm.py' - pidoption = '' - if pidnums and pidnums != '': - pidoption = ' -p "' + pidnums + '" ' - seroption = '' - if sernums and sernums != '': - seroption = ' -s "' + sernums + '" ' - infooption = '' - if altinfopath and altinfopath != '': - infooption = ' -k "' + altinfopath + '" ' - pengine = sys.executable - if pengine is None or pengine == '': - pengine = "python" - pengine = os.path.normpath(pengine) - cmdline = pengine + ' ./lib/' + tool + ' ' + pidoption + seroption + infooption + '"' + infile + '" "' + outfile + '"' - if sys.platform.startswith('win'): - cmdline = pengine + ' lib\\' + tool + ' ' + pidoption + seroption + infooption + '"' + infile + '" "' + outfile + '"' - print cmdline - 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_mobipath(self): - cpath = self.mobipath.get() - mobipath = tkFileDialog.askopenfilename( - initialdir = cpath, - parent=None, title='Select Kindle/Mobi/Topaz eBook File', - defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.azw'),('Mobi eBook File', '.mobi'),('Mobi eBook File', '.tpz'),('Mobi eBook File', '.azw1'),('Mobi azw4 eBook File', '.azw4'),('All Files', '.*')]) - if mobipath: - mobipath = os.path.normpath(mobipath) - self.mobipath.delete(0, Tkconstants.END) - self.mobipath.insert(0, mobipath) - return - - def get_outpath(self): - cwd = os.getcwdu() - cwd = cwd.encode('utf-8') - outpath = tkFileDialog.askdirectory( - parent=None, title='Directory to Store Unencrypted file(s) into', - initialdir=cwd, initialfile=None) - if outpath: - outpath = os.path.normpath(outpath) - self.outpath.delete(0, Tkconstants.END) - self.outpath.insert(0, outpath) - return - - def get_altinfopath(self): - cwd = os.getcwdu() - cwd = cwd.encode('utf-8') - altinfopath = tkFileDialog.askopenfilename( - parent=None, title='Select Alternative 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: - if (self.p2.wait('nowait') == None): - self.p2.terminate() - self.root.destroy() - - # actually ready to run the subprocess and get its output - def convertit(self): - self.status['text'] = '' - # now disable the button to prevent multiple launches - self.sbotton.configure(state='disabled') - mobipath = self.mobipath.get() - outpath = self.outpath.get() - altinfopath = self.altinfopath.get() - pidnums = self.pidinfo.get() - sernums = self.serinfo.get() - - if not mobipath or not os.path.exists(mobipath) or not os.path.isfile(mobipath): - self.status['text'] = 'Specified Kindle Mobi eBook file does not exist' - self.sbotton.configure(state='normal') - return - - tpz = False - # Identify any Topaz Files - f = file(mobipath, 'rb') - raw = f.read(3) - if raw.startswith('TPZ'): - tpz = True - f.close() - if not outpath: - self.status['text'] = 'No output directory specified' - self.sbotton.configure(state='normal') - return - if not os.path.isdir(outpath): - 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 - - log = 'Command = "python k4mobidedrm.py"\n' - if not tpz: - log += 'Kindle/Mobi Path = "'+ mobipath + '"\n' - else: - log += 'Topaz Path = "'+ mobipath + '"\n' - log += 'Output Directory = "' + outpath + '"\n' - log += 'Kindle.info file = "' + altinfopath + '"\n' - log += 'PID list = "' + pidnums + '"\n' - log += 'Serial Number list = "' + sernums + '"\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, altinfopath, pidnums, sernums) - - # python does not seem to allow you to create - # your own eventloop which every other gui does - strange - # so need to use the widget "after" command to force - # event loop to run non-gui events every interval - self.stext.after(self.interval,self.processPipe) - return - - -def main(argv=None): - root = Tkinter.Tk() - root.title('Kindle/Mobi/Topaz eBook Encryption Removal') - root.resizable(True, False) - root.minsize(300, 0) - MainDialog(root).pack(fill=Tkconstants.X, expand=1) - root.mainloop() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/KindleBooks/README_KindleBooks.txt b/Other_Tools/KindleBooks/README_KindleBooks.txt deleted file mode 100644 index 59fe31d..0000000 --- a/Other_Tools/KindleBooks/README_KindleBooks.txt +++ /dev/null @@ -1,58 +0,0 @@ -KindleBooks (Originally called K4MobiDeDRM and Topaz_Tools) - -Most users will be better off using the DeDRM applications or the calibre plugin. This script is provided more for historical interest than anything else. - - -This tools combines functionality of MobiDeDRM with that of K4PCDeDRM, K4MDeDRM, and K4DeDRM. Effectively, it provides one-stop shopping for all your Mobipocket, Kindle for iPhone/iPad/iPodTouch, Kindle for PC, and Kindle for Mac needs and should work for both Mobi and Topaz ebooks. - -Preliminary Steps: - -1. Make sure you have Python 2.5, 2.6 or 2.7 installed (32 bit) and properly set as part of your SYSTEM PATH environment variable (On Windows I recommend ActiveState's ActivePython. See their web pages for instructions on how to install and how to properly set your PATH). On Mac OSX 10.5 and later everything you need is already installed. - - -Instructions: - -1. double-click on KindleBooks.pyw - -2. In the window that opens: -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. 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 - - -6. If you have standalone Kindles, add in any 16 digit Serial Numbers as a comma separated list. If this is a Kindle for Mac or a Kindle for PC book then you can leave this box blank - -7. 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. - - - -If your book was a normal Mobi style ebook: - If successful, you should see a "_nodrm" named version Mobi ebook. - If not please examine the Conversion Log window for any errors. - - - -If your book was actually a Topaz book: - -Please note that Topaz is most similar to a poor man's image only PDF in style. It has glyphs and x,y positions, ocrText used just for searching, that describe the image each page all encoded into a binary xml-like set of files. - -If successful, you will have 3 zip archives created. - -1. The first is BOOKNAME_nodrm.zip. - You can import this into calibre as is or unzip it and edit the book.html file you find inside. To create the book.html, Amazon's ocrText is combined with other information to recreate as closely as possible what the original book looked like. Unfortunately most bolding, italics is lost. Also, Amazon's ocrText can be absolutely horrible at times. Much work will be needed to clean up and correct Topaz books. - -2. The second is BOOKNAME_SVG.zip - You can also import this into calibre or unzip it and open the indexsvg.xhtml file in any good Browser (Safari, Firefox, etc). This zip contains a set of svg images (one for each pages is created) and it shows the page exactly how it appeared. This zip can be used to create an image only pdf file via post conversion. - -3. The third is BOOKNAME_XML.zip - This is a zip archive of the decrypted and translated xml-like descriptions of each page and can be archived/saved in case later code can do a better job converting these files. These are exactly what a Topaz books guts are. You should take a look at them in any text editor to see what they look like. - -If the Topaz book conversion is not successful, a large _DEBUG.zip archive of all of the pieces is created and this can examined along with the Conversion Log window contents to determine the cause of the error and hopefully get it fixed in the next release. - - diff --git a/Other_Tools/KindleBooks/lib/aescbc.py b/Other_Tools/KindleBooks/lib/aescbc.py deleted file mode 100755 index 5667511..0000000 --- a/Other_Tools/KindleBooks/lib/aescbc.py +++ /dev/null @@ -1,568 +0,0 @@ -#! /usr/bin/env python - -""" - Routines for doing AES CBC in one file - - Modified by some_updates to extract - and combine only those parts needed for AES CBC - into one simple to add python file - - Original Version - Copyright (c) 2002 by Paul A. Lambert - Under: - CryptoPy Artisitic License Version 1.0 - See the wonderful pure python package cryptopy-1.2.5 - and read its LICENSE.txt for complete license details. -""" - -class CryptoError(Exception): - """ Base class for crypto exceptions """ - def __init__(self,errorMessage='Error!'): - self.message = errorMessage - def __str__(self): - return self.message - -class InitCryptoError(CryptoError): - """ Crypto errors during algorithm initialization """ -class BadKeySizeError(InitCryptoError): - """ Bad key size error """ -class EncryptError(CryptoError): - """ Error in encryption processing """ -class DecryptError(CryptoError): - """ Error in decryption processing """ -class DecryptNotBlockAlignedError(DecryptError): - """ Error in decryption processing """ - -def xorS(a,b): - """ XOR two strings """ - assert len(a)==len(b) - x = [] - for i in range(len(a)): - x.append( chr(ord(a[i])^ord(b[i]))) - return ''.join(x) - -def xor(a,b): - """ XOR two strings """ - x = [] - for i in range(min(len(a),len(b))): - x.append( chr(ord(a[i])^ord(b[i]))) - return ''.join(x) - -""" - Base 'BlockCipher' and Pad classes for cipher instances. - BlockCipher supports automatic padding and type conversion. The BlockCipher - class was written to make the actual algorithm code more readable and - not for performance. -""" - -class BlockCipher: - """ Block ciphers """ - def __init__(self): - self.reset() - - def reset(self): - self.resetEncrypt() - self.resetDecrypt() - def resetEncrypt(self): - self.encryptBlockCount = 0 - self.bytesToEncrypt = '' - def resetDecrypt(self): - self.decryptBlockCount = 0 - self.bytesToDecrypt = '' - - def encrypt(self, plainText, more = None): - """ Encrypt a string and return a binary string """ - self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt - numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize) - cipherText = '' - for i in range(numBlocks): - bStart = i*self.blockSize - ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize]) - self.encryptBlockCount += 1 - cipherText += ctBlock - if numExtraBytes > 0: # save any bytes that are not block aligned - self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] - else: - self.bytesToEncrypt = '' - - if more == None: # no more data expected from caller - finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) - if len(finalBytes) > 0: - ctBlock = self.encryptBlock(finalBytes) - self.encryptBlockCount += 1 - cipherText += ctBlock - self.resetEncrypt() - return cipherText - - def decrypt(self, cipherText, more = None): - """ Decrypt a string and return a string """ - self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt - - numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) - if more == None: # no more calls to decrypt, should have all the data - if numExtraBytes != 0: - raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' - - # hold back some bytes in case last decrypt has zero len - if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : - numBlocks -= 1 - numExtraBytes = self.blockSize - - plainText = '' - for i in range(numBlocks): - bStart = i*self.blockSize - ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) - self.decryptBlockCount += 1 - plainText += ptBlock - - if numExtraBytes > 0: # save any bytes that are not block aligned - self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] - else: - self.bytesToEncrypt = '' - - if more == None: # last decrypt remove padding - plainText = self.padding.removePad(plainText, self.blockSize) - self.resetDecrypt() - return plainText - - -class Pad: - def __init__(self): - pass # eventually could put in calculation of min and max size extension - -class padWithPadLen(Pad): - """ Pad a binary string with the length of the padding """ - - def addPad(self, extraBytes, blockSize): - """ Add padding to a binary string to make it an even multiple - of the block size """ - blocks, numExtraBytes = divmod(len(extraBytes), blockSize) - padLength = blockSize - numExtraBytes - return extraBytes + padLength*chr(padLength) - - def removePad(self, paddedBinaryString, blockSize): - """ Remove padding from a binary string """ - if not(0 6 and i%Nk == 4 : - temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) - w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) - return w - -Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! - 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, - 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) - -#------------------------------------- -def AddRoundKey(algInstance, keyBlock): - """ XOR the algorithm state with a block of key material """ - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] ^= keyBlock[column][row] -#------------------------------------- - -def SubBytes(algInstance): - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] = Sbox[algInstance.state[column][row]] - -def InvSubBytes(algInstance): - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] - -Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, - 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, - 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, - 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, - 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, - 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, - 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, - 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, - 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, - 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, - 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, - 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, - 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, - 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, - 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, - 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, - 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, - 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, - 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, - 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, - 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, - 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, - 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, - 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, - 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, - 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, - 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, - 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, - 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, - 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, - 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, - 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) - -InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, - 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, - 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, - 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, - 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, - 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, - 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, - 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, - 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, - 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, - 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, - 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, - 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, - 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, - 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, - 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, - 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, - 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, - 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, - 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, - 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, - 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, - 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, - 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, - 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, - 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, - 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, - 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, - 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, - 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, - 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, - 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) - -#------------------------------------- -""" For each block size (Nb), the ShiftRow operation shifts row i - by the amount Ci. Note that row 0 is not shifted. - Nb C1 C2 C3 - ------------------- """ -shiftOffset = { 4 : ( 0, 1, 2, 3), - 5 : ( 0, 1, 2, 3), - 6 : ( 0, 1, 2, 3), - 7 : ( 0, 1, 2, 4), - 8 : ( 0, 1, 3, 4) } -def ShiftRows(algInstance): - tmp = [0]*algInstance.Nb # list of size Nb - for r in range(1,4): # row 0 reamains unchanged and can be skipped - for c in range(algInstance.Nb): - tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] - for c in range(algInstance.Nb): - algInstance.state[c][r] = tmp[c] -def InvShiftRows(algInstance): - tmp = [0]*algInstance.Nb # list of size Nb - for r in range(1,4): # row 0 reamains unchanged and can be skipped - for c in range(algInstance.Nb): - tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] - for c in range(algInstance.Nb): - algInstance.state[c][r] = tmp[c] -#------------------------------------- -def MixColumns(a): - Sprime = [0,0,0,0] - for j in range(a.Nb): # for each column - Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) - Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) - Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) - Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) - for i in range(4): - a.state[j][i] = Sprime[i] - -def InvMixColumns(a): - """ Mix the four bytes of every column in a linear way - This is the opposite operation of Mixcolumn """ - Sprime = [0,0,0,0] - for j in range(a.Nb): # for each column - Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) - Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) - Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) - Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) - for i in range(4): - a.state[j][i] = Sprime[i] - -#------------------------------------- -def mul(a, b): - """ Multiply two elements of GF(2^m) - needed for MixColumn and InvMixColumn """ - if (a !=0 and b!=0): - return Alogtable[(Logtable[a] + Logtable[b])%255] - else: - return 0 - -Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, - 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, - 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, - 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, - 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, - 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, - 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, - 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, - 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, - 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, - 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, - 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, - 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, - 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, - 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, - 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) - -Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, - 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, - 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, - 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, - 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, - 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, - 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, - 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, - 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, - 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, - 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, - 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, - 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, - 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, - 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, - 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) - - - - -""" - AES Encryption Algorithm - The AES algorithm is just Rijndael algorithm restricted to the default - blockSize of 128 bits. -""" - -class AES(Rijndael): - """ The AES algorithm is the Rijndael block cipher restricted to block - sizes of 128 bits and key sizes of 128, 192 or 256 bits - """ - def __init__(self, key = None, padding = padWithPadLen(), keySize=16): - """ Initialize AES, keySize is in bytes """ - if not (keySize == 16 or keySize == 24 or keySize == 32) : - raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' - - Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) - - self.name = 'AES' - - -""" - CBC mode of encryption for block ciphers. - This algorithm mode wraps any BlockCipher to make a - Cipher Block Chaining mode. -""" -from random import Random # should change to crypto.random!!! - - -class CBC(BlockCipher): - """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode - algorithms. The initialization (IV) is automatic if set to None. Padding - is also automatic based on the Pad class used to initialize the algorithm - """ - def __init__(self, blockCipherInstance, padding = padWithPadLen()): - """ CBC algorithms are created by initializing with a BlockCipher instance """ - self.baseCipher = blockCipherInstance - self.name = self.baseCipher.name + '_CBC' - self.blockSize = self.baseCipher.blockSize - self.keySize = self.baseCipher.keySize - self.padding = padding - self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! - self.r = Random() # for IV generation, currently uses - # mediocre standard distro version <---------------- - import time - newSeed = time.ctime()+str(self.r) # seed with instance location - self.r.seed(newSeed) # to make unique - self.reset() - - def setKey(self, key): - self.baseCipher.setKey(key) - - # Overload to reset both CBC state and the wrapped baseCipher - def resetEncrypt(self): - BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) - self.baseCipher.resetEncrypt() # reset base cipher encrypt state - - def resetDecrypt(self): - BlockCipher.resetDecrypt(self) # reset CBC state (super class) - self.baseCipher.resetDecrypt() # reset base cipher decrypt state - - def encrypt(self, plainText, iv=None, more=None): - """ CBC encryption - overloads baseCipher to allow optional explicit IV - when iv=None, iv is auto generated! - """ - if self.encryptBlockCount == 0: - self.iv = iv - else: - assert(iv==None), 'IV used only on first call to encrypt' - - return BlockCipher.encrypt(self,plainText, more=more) - - def decrypt(self, cipherText, iv=None, more=None): - """ CBC decryption - overloads baseCipher to allow optional explicit IV - when iv=None, iv is auto generated! - """ - if self.decryptBlockCount == 0: - self.iv = iv - else: - assert(iv==None), 'IV used only on first call to decrypt' - - return BlockCipher.decrypt(self, cipherText, more=more) - - def encryptBlock(self, plainTextBlock): - """ CBC block encryption, IV is set with 'encrypt' """ - auto_IV = '' - if self.encryptBlockCount == 0: - if self.iv == None: - # generate IV and use - self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) - self.prior_encr_CT_block = self.iv - auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic - else: # application provided IV - assert(len(self.iv) == self.blockSize ),'IV must be same length as block' - self.prior_encr_CT_block = self.iv - """ encrypt the prior CT XORed with the PT """ - ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) - self.prior_encr_CT_block = ct - return auto_IV+ct - - def decryptBlock(self, encryptedBlock): - """ Decrypt a single block """ - - if self.decryptBlockCount == 0: # first call, process IV - if self.iv == None: # auto decrypt IV? - self.prior_CT_block = encryptedBlock - return '' - else: - assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" - self.prior_CT_block = self.iv - - dct = self.baseCipher.decryptBlock(encryptedBlock) - """ XOR the prior decrypted CT with the prior CT """ - dct_XOR_priorCT = xor( self.prior_CT_block, dct ) - - self.prior_CT_block = encryptedBlock - - return dct_XOR_priorCT - - -""" - AES_CBC Encryption Algorithm -""" - -class AES_CBC(CBC): - """ AES encryption in CBC feedback mode """ - def __init__(self, key=None, padding=padWithPadLen(), keySize=16): - CBC.__init__( self, AES(key, noPadding(), keySize), padding) - self.name = 'AES_CBC' diff --git a/Other_Tools/KindleBooks/lib/alfcrypto.dll b/Other_Tools/KindleBooks/lib/alfcrypto.dll deleted file mode 100755 index 26d740d..0000000 Binary files a/Other_Tools/KindleBooks/lib/alfcrypto.dll and /dev/null differ diff --git a/Other_Tools/KindleBooks/lib/alfcrypto64.dll b/Other_Tools/KindleBooks/lib/alfcrypto64.dll deleted file mode 100644 index 7bef68e..0000000 Binary files a/Other_Tools/KindleBooks/lib/alfcrypto64.dll and /dev/null differ diff --git a/Other_Tools/KindleBooks/lib/alfcrypto_src.zip b/Other_Tools/KindleBooks/lib/alfcrypto_src.zip deleted file mode 100644 index 269810c..0000000 Binary files a/Other_Tools/KindleBooks/lib/alfcrypto_src.zip and /dev/null differ diff --git a/Other_Tools/KindleBooks/lib/config.py b/Other_Tools/KindleBooks/lib/config.py deleted file mode 100644 index 9825878..0000000 --- a/Other_Tools/KindleBooks/lib/config.py +++ /dev/null @@ -1,59 +0,0 @@ -from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit - -from calibre.utils.config import JSONConfig - -# This is where all preferences for this plugin will be stored -# You should always prefix your config file name with plugins/, -# so as to ensure you dont accidentally clobber a calibre config file -prefs = JSONConfig('plugins/K4MobiDeDRM') - -# Set defaults -prefs.defaults['pids'] = "" -prefs.defaults['serials'] = "" -prefs.defaults['WINEPREFIX'] = None - - -class ConfigWidget(QWidget): - - def __init__(self): - QWidget.__init__(self) - self.l = QVBoxLayout() - self.setLayout(self.l) - - self.serialLabel = QLabel('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)') - self.l.addWidget(self.serialLabel) - - self.serials = QLineEdit(self) - self.serials.setText(prefs['serials']) - self.l.addWidget(self.serials) - self.serialLabel.setBuddy(self.serials) - - self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)') - self.l.addWidget(self.pidLabel) - - self.pids = QLineEdit(self) - self.pids.setText(prefs['pids']) - self.l.addWidget(self.pids) - self.pidLabel.setBuddy(self.serials) - - self.wpLabel = QLabel('For Linux only: WINEPREFIX (enter absolute path)') - self.l.addWidget(self.wpLabel) - - self.wineprefix = QLineEdit(self) - wineprefix = prefs['WINEPREFIX'] - if wineprefix is not None: - self.wineprefix.setText(wineprefix) - else: - self.wineprefix.setText('') - - self.l.addWidget(self.wineprefix) - self.wpLabel.setBuddy(self.wineprefix) - - def save_settings(self): - prefs['pids'] = str(self.pids.text()).replace(" ","") - prefs['serials'] = str(self.serials.text()).replace(" ","") - winepref=str(self.wineprefix.text()) - if winepref.strip() != '': - prefs['WINEPREFIX'] = winepref - else: - prefs['WINEPREFIX'] = None diff --git a/Other_Tools/KindleBooks/lib/flatxml2svg.py b/Other_Tools/KindleBooks/lib/flatxml2svg.py deleted file mode 100644 index 4dfd6c7..0000000 --- a/Other_Tools/KindleBooks/lib/flatxml2svg.py +++ /dev/null @@ -1,249 +0,0 @@ -#! /usr/bin/python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import sys -import csv -import os -import getopt -from struct import pack -from struct import unpack - - -class PParser(object): - def __init__(self, gd, flatxml, meta_array): - self.gd = gd - self.flatdoc = flatxml.split('\n') - self.docSize = len(self.flatdoc) - self.temp = [] - - self.ph = -1 - self.pw = -1 - startpos = self.posinDoc('page.h') or self.posinDoc('book.h') - for p in startpos: - (name, argres) = self.lineinDoc(p) - self.ph = max(self.ph, int(argres)) - startpos = self.posinDoc('page.w') or self.posinDoc('book.w') - for p in startpos: - (name, argres) = self.lineinDoc(p) - self.pw = max(self.pw, int(argres)) - - if self.ph <= 0: - self.ph = int(meta_array.get('pageHeight', '11000')) - if self.pw <= 0: - self.pw = int(meta_array.get('pageWidth', '8500')) - - res = [] - startpos = self.posinDoc('info.glyph.x') - for p in startpos: - argres = self.getDataatPos('info.glyph.x', p) - res.extend(argres) - self.gx = res - - res = [] - startpos = self.posinDoc('info.glyph.y') - for p in startpos: - argres = self.getDataatPos('info.glyph.y', p) - res.extend(argres) - self.gy = res - - res = [] - startpos = self.posinDoc('info.glyph.glyphID') - for p in startpos: - argres = self.getDataatPos('info.glyph.glyphID', p) - res.extend(argres) - self.gid = res - - - # return tag at line pos in document - def lineinDoc(self, pos) : - if (pos >= 0) and (pos < self.docSize) : - item = self.flatdoc[pos] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - return name, argres - - # find tag in doc if within pos to end inclusive - def findinDoc(self, tagpath, pos, end) : - result = None - if end == -1 : - end = self.docSize - else: - end = min(self.docSize, end) - foundat = -1 - for j in xrange(pos, end): - item = self.flatdoc[j] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - if name.endswith(tagpath) : - result = argres - foundat = j - break - return foundat, result - - # return list of start positions for the tagpath - def posinDoc(self, tagpath): - startpos = [] - pos = 0 - res = "" - while res != None : - (foundpos, res) = self.findinDoc(tagpath, pos, -1) - if res != None : - startpos.append(foundpos) - pos = foundpos + 1 - return startpos - - def getData(self, path): - result = None - cnt = len(self.flatdoc) - for j in xrange(cnt): - item = self.flatdoc[j] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (name.endswith(path)): - result = argres - break - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - return result - - def getDataatPos(self, path, pos): - result = None - item = self.flatdoc[pos] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - if (name.endswith(path)): - result = argres - return result - - def getDataTemp(self, path): - result = None - cnt = len(self.temp) - for j in xrange(cnt): - item = self.temp[j] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (name.endswith(path)): - result = argres - self.temp.pop(j) - break - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - return result - - def getImages(self): - result = [] - self.temp = self.flatdoc - while (self.getDataTemp('img') != None): - h = self.getDataTemp('img.h')[0] - w = self.getDataTemp('img.w')[0] - x = self.getDataTemp('img.x')[0] - y = self.getDataTemp('img.y')[0] - src = self.getDataTemp('img.src')[0] - result.append('\n' % (src, x, y, w, h)) - return result - - def getGlyphs(self): - result = [] - if (self.gid != None) and (len(self.gid) > 0): - glyphs = [] - for j in set(self.gid): - glyphs.append(j) - glyphs.sort() - for gid in glyphs: - id='id="gl%d"' % gid - path = self.gd.lookup(id) - if path: - result.append(id + ' ' + path) - return result - - -def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi): - mlst = [] - pp = PParser(gdict, flat_xml, meta_array) - mlst.append('\n') - if (raw): - mlst.append('\n') - mlst.append('\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1)) - mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) - else: - mlst.append('\n') - mlst.append('\n') - mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) - mlst.append('\n') - mlst.append('\n') - mlst.append('\n') - mlst.append('
\n') - mlst.append('\n') - mlst.append('\n') - mlst.append('\n') - return "".join(mlst) diff --git a/Other_Tools/KindleBooks/lib/getk4pcpids.py b/Other_Tools/KindleBooks/lib/getk4pcpids.py deleted file mode 100644 index cc8bcd4..0000000 --- a/Other_Tools/KindleBooks/lib/getk4pcpids.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 1.00 - Initial version -# 1.01 - getPidList interface change - -__version__ = '1.01' - -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii -import kgenpids -import topazextract -import mobidedrm -from alfcrypto import Pukall_Cipher - -class DrmException(Exception): - pass - -def getK4PCpids(path_to_ebook): - # Return Kindle4PC PIDs. Assumes that the caller checked that we are not on Linux, which will raise an exception - - mobi = True - magic3 = file(path_to_ebook,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - if mobi: - mb = mobidedrm.MobiBook(path_to_ebook,False) - else: - mb = topazextract.TopazBook(path_to_ebook) - - md1, md2 = mb.getPIDMetaInfo() - - return kgenpids.getPidList(md1, md2) - - -def main(argv=sys.argv): - print ('getk4pcpids.py v%(__version__)s. ' - 'Copyright 2012 Apprentice Alf' % globals()) - - if len(argv)<2 or len(argv)>3: - print "Gets the possible book-specific PIDs from K4PC for a particular book" - print "Usage:" - print " %s []" % sys.argv[0] - return 1 - else: - infile = argv[1] - try: - pidlist = getK4PCpids(infile) - except DrmException, e: - print "Error: %s" % e - return 1 - pidstring = ','.join(pidlist) - print "Possible PIDs are: ", pidstring - if len(argv) is 3: - outfile = argv[2] - file(outfile, 'w').write(pidstring) - - return 0 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/Other_Tools/KindleBooks/lib/k4mobidedrm.py b/Other_Tools/KindleBooks/lib/k4mobidedrm.py deleted file mode 100755 index 717b0d0..0000000 --- a/Other_Tools/KindleBooks/lib/k4mobidedrm.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python - -from __future__ import with_statement - -# engine to remove drm from Kindle for Mac and Kindle for PC books -# for personal use for archiving and converting your ebooks - -# PLEASE DO NOT PIRATE EBOOKS! - -# We want all authors and publishers, and eBook stores to live -# long and prosperous lives but at the same time we just want to -# be able to read OUR books on whatever device we want and to keep -# readable for a long, long time - -# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, -# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates -# and many many others - - -__version__ = '4.4' - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -import os, csv, getopt -import string -import re -import traceback -import time - -buildXML = False - -class DrmException(Exception): - pass - -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -if inCalibre: - from calibre_plugins.k4mobidedrm import mobidedrm - from calibre_plugins.k4mobidedrm import topazextract - from calibre_plugins.k4mobidedrm import kgenpids -else: - import mobidedrm - import topazextract - import kgenpids - - -# cleanup bytestring filenames -# borrowed from calibre from calibre/src/calibre/__init__.py -# added in removal of non-printing chars -# and removal of . at start -# convert underscores to spaces (we're OK with spaces in file names) -def cleanup_name(name): - _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') - substitute='_' - one = ''.join(char for char in name if char in string.printable) - one = _filename_sanitize.sub(substitute, one) - one = re.sub(r'\s', ' ', one).strip() - one = re.sub(r'^\.+$', '_', one) - one = one.replace('..', substitute) - # Windows doesn't like path components that end with a period - if one.endswith('.'): - one = one[:-1]+substitute - # Mac and Unix don't like file names that begin with a full stop - if len(one) > 0 and one[0] == '.': - one = substitute+one[1:] - one = one.replace('_',' ') - return one - -def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids): - global buildXML - - - # handle the obvious cases at the beginning - if not os.path.isfile(infile): - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist" - return 1 - - starttime = time.time() - print "Starting decryptBook routine." - - - mobi = True - magic3 = file(infile,'rb').read(3) - if magic3 == 'TPZ': - mobi = False - - bookname = os.path.splitext(os.path.basename(infile))[0] - - if mobi: - mb = mobidedrm.MobiBook(infile) - else: - mb = topazextract.TopazBook(infile) - - title = mb.getBookTitle() - print "Processing Book: ", title - filenametitle = cleanup_name(title) - outfilename = cleanup_name(bookname) - - # generate 'sensible' filename, that will sort with the original name, - # but is close to the name from the file. - outlength = len(outfilename) - comparelength = min(8,min(outlength,len(filenametitle))) - copylength = min(max(outfilename.find(' '),8),len(outfilename)) - if outlength==0: - outfilename = filenametitle - elif comparelength > 0: - if outfilename[:comparelength] == filenametitle[:comparelength]: - outfilename = filenametitle - else: - outfilename = outfilename[:copylength] + " " + filenametitle - - # avoid excessively long file names - if len(outfilename)>150: - outfilename = outfilename[:150] - - # build pid list - md1, md2 = mb.getPIDMetaInfo() - pids.extend(kgenpids.getPidList(md1, md2, k4, serials, kInfoFiles)) - - print "Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids)) - - - try: - mb.processBook(pids) - - except mobidedrm.DrmException, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - except topazextract.TpzDRMError, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - except Exception, e: - print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n" - print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime) - return 1 - - print "Successfully decrypted book after {0:.1f} seconds".format(time.time()-starttime) - - if mobi: - if mb.getPrintReplica(): - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4') - elif mb.getMobiVersion() >= 8: - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3') - else: - outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi') - mb.getMobiFile(outfile) - print "Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename + '_nodrm') - return 0 - - # topaz: - print " Creating NoDRM HTMLZ Archive" - zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz') - mb.getHTMLZip(zipname) - - print " Creating SVG ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip') - mb.getSVGZip(zipname) - - if buildXML: - print " Creating XML ZIP Archive" - zipname = os.path.join(outdir, outfilename + '_XML' + '.zip') - mb.getXMLZip(zipname) - - # remove internal temporary directory of Topaz pieces - mb.cleanup() - print "Saved decrypted Topaz book parts after {0:.1f} seconds".format(time.time()-starttime) - return 0 - - -def usage(progname): - print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks" - print "Usage:" - print " %s [-k ] [-p ] [-s ] " % progname - -# -# Main -# -def main(argv=sys.argv): - progname = os.path.basename(argv[0]) - - k4 = False - kInfoFiles = [] - serials = [] - pids = [] - - print ('K4MobiDeDrm v%(__version__)s ' - 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals()) - - try: - opts, args = getopt.getopt(sys.argv[1:], "k:p:s:") - except getopt.GetoptError, err: - print str(err) - usage(progname) - sys.exit(2) - if len(args)<2: - usage(progname) - sys.exit(2) - - for o, a in opts: - if o == "-k": - if a == None : - raise DrmException("Invalid parameter for -k") - kInfoFiles.append(a) - if o == "-p": - if a == None : - raise DrmException("Invalid parameter for -p") - pids = a.split(',') - if o == "-s": - if a == None : - raise DrmException("Invalid parameter for -s") - serials = a.split(',') - - # try with built in Kindle Info files - k4 = True - if sys.platform.startswith('linux'): - k4 = False - kInfoFiles = None - infile = args[0] - outdir = args[1] - return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids) - - -if __name__ == '__main__': - sys.stdout=Unbuffered(sys.stdout) - sys.exit(main()) diff --git a/Other_Tools/KindleBooks/lib/k4mutils.py b/Other_Tools/KindleBooks/lib/k4mutils.py deleted file mode 100644 index 1fc08cb..0000000 --- a/Other_Tools/KindleBooks/lib/k4mutils.py +++ /dev/null @@ -1,730 +0,0 @@ -# standlone set of Mac OSX specific routines needed for KindleBooks - -from __future__ import with_statement - -import sys -import os -import os.path -import re -import copy -import subprocess -from struct import pack, unpack, unpack_from - -class DrmException(Exception): - pass - - -# interface to needed routines in openssl's libcrypto -def _load_crypto_libcrypto(): - from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, addressof, string_at, cast - from ctypes.util import find_library - - libcrypto = find_library('crypto') - if libcrypto is None: - raise DrmException('libcrypto not found') - libcrypto = CDLL(libcrypto) - - # From OpenSSL's crypto aes header - # - # AES_ENCRYPT 1 - # AES_DECRYPT 0 - # AES_MAXNR 14 (in bytes) - # AES_BLOCK_SIZE 16 (in bytes) - # - # struct aes_key_st { - # unsigned long rd_key[4 *(AES_MAXNR + 1)]; - # int rounds; - # }; - # typedef struct aes_key_st AES_KEY; - # - # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); - # - # note: the ivec string, and output buffer are both mutable - # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, - # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc); - - 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]) - - # From OpenSSL's Crypto evp/p5_crpt2.c - # - # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, - # const unsigned char *salt, int saltlen, int iter, - # int keylen, unsigned char *out); - - PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', - [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) - - class LibCrypto(object): - def __init__(self): - self._blocksize = 0 - self._keyctx = None - self._iv = 0 - - def set_decrypt_key(self, userkey, iv): - self._blocksize = len(userkey) - if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : - raise DrmException('AES improper key used') - return - keyctx = self._keyctx = AES_KEY() - self._iv = iv - self._userkey = userkey - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) - if rv < 0: - raise DrmException('Failed to initialize AES key') - - def decrypt(self, data): - out = create_string_buffer(len(data)) - mutable_iv = create_string_buffer(self._iv, len(self._iv)) - keyctx = self._keyctx - rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) - if rv == 0: - raise DrmException('AES decryption failed') - return out.raw - - def keyivgen(self, passwd, salt, iter, keylen): - saltlen = len(salt) - passlen = len(passwd) - out = create_string_buffer(keylen) - rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) - return out.raw - return LibCrypto - -def _load_crypto(): - LibCrypto = None - try: - LibCrypto = _load_crypto_libcrypto() - except (ImportError, DrmException): - pass - return LibCrypto - -LibCrypto = _load_crypto() - -# -# Utility Routines -# - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" - -# For kinf approach of K4Mac 1.6.X or later -# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" -# For Mac they seem to re-use charMap2 here -charMap5 = charMap2 - -# new in K4M 1.9.X -testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD" - - -def encode(data, map): - result = "" - for char in data: - value = ord(char) - Q = (value ^ 0x80) // len(map) - R = value % len(map) - result += map[Q] - result += map[R] - return result - -# Hash the bytes in data and then encode the digest with the characters in map -def encodeHash(data,map): - return encode(MD5(data),map) - -# Decode the string in data with the characters in map. Returns the decoded bytes -def decode(data,map): - result = "" - for i in range (0,len(data)-1,2): - high = map.find(data[i]) - low = map.find(data[i+1]) - if (high == -1) or (low == -1) : - break - value = (((high * len(map)) ^ 0x80) & 0xFF) + low - result += pack("B",value) - return result - -# For K4M 1.6.X and later -# generate table of prime number less than or equal to int n -def primes(n): - if n==2: return [2] - elif n<2: return [] - s=range(3,n+1,2) - mroot = n ** 0.5 - half=(n+1)/2-1 - i=0 - m=3 - while m <= mroot: - if s[i]: - j=(m*m-3)/2 - s[j]=0 - while j 7: - print('Using Munged MAC Address for ID: '+mungedmac) - return mungedmac - sernum = GetVolumeSerialNumber() - if len(sernum) > 7: - print('Using Volume Serial Number for ID: '+sernum) - return sernum - diskpart = GetUserHomeAppSupKindleDirParitionName() - uuidnum = GetDiskPartitionUUID(diskpart) - if len(uuidnum) > 7: - print('Using Disk Partition UUID for ID: '+uuidnum) - return uuidnum - mungedmac = GetMACAddressMunged() - if len(mungedmac) > 7: - print('Using Munged MAC Address for ID: '+mungedmac) - return mungedmac - print('Using Fixed constant 9999999999 for ID.') - return '9999999999' - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used by Kindle for Mac versions < 1.6.0 -class CryptUnprotectData(object): - def __init__(self): - sernum = GetVolumeSerialNumber() - if sernum == '': - sernum = '9999999999' - sp = sernum + '!@#' + GetUserName() - passwdData = encode(SHA256(sp),charMap1) - salt = '16743' - self.crp = LibCrypto() - iter = 0x3e8 - keylen = 0x80 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext,charMap1) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.6.0 -class CryptUnprotectDataV2(object): - def __init__(self): - sp = GetUserName() + ':&%:' + GetIDString() - passwdData = encode(SHA256(sp),charMap5) - # salt generation as per the code - salt = 0x0512981d * 2 * 1 * 1 - salt = str(salt) + GetUserName() - salt = encode(salt,charMap5) - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap5) - return cleartext - - -# unprotect the new header blob in .kinf2011 -# used in Kindle for Mac Version >= 1.9.0 -def UnprotectHeaderData(encryptedData): - passwdData = 'header_key_data' - salt = 'HEADER.2011' - iter = 0x80 - keylen = 0x100 - crp = LibCrypto() - key_iv = crp.keyivgen(passwdData, salt, iter, keylen) - key = key_iv[0:32] - iv = key_iv[32:48] - crp.set_decrypt_key(key,iv) - cleartext = crp.decrypt(encryptedData) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.9.0 -class CryptUnprotectDataV3(object): - def __init__(self, entropy): - sp = GetUserName() + '+@#$%+' + GetIDString() - passwdData = encode(SHA256(sp),charMap2) - salt = entropy - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap2) - return cleartext - - -# Locate the .kindle-info files -def getKindleInfoFiles(): - # file searches can take a long time on some systems, so just look in known specific places. - kInfoFiles=[] - found = False - home = os.getenv('HOME') - # check for .kinf2011 file in new location (App Store Kindle for Mac) - testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kinf2011 file: ' + testpath) - found = True - # check for .kinf2011 files - testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kinf2011 file: ' + testpath) - found = True - # check for .rainier-2.1.1-kinf files - testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac rainier file: ' + testpath) - found = True - # check for .rainier-2.1.1-kinf files - testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info' - if os.path.isfile(testpath): - kInfoFiles.append(testpath) - print('Found k4Mac kindle-info file: ' + testpath) - found = True - if not found: - print('No k4Mac kindle-info/rainier/kinf2011 files have been found.') - return kInfoFiles - -# determine type of kindle info provided and return a -# database of keynames and values -def getDBfromFile(kInfoFile): - names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"] - DB = {} - cnt = 0 - infoReader = open(kInfoFile, 'r') - hdr = infoReader.read(1) - data = infoReader.read() - - if data.find('[') != -1 : - - # older style kindle-info file - cud = CryptUnprotectData() - items = data.split('[') - for item in items: - if item != '': - keyhash, rawdata = item.split(':') - keyname = "unknown" - for name in names: - if encodeHash(name,charMap2) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - encryptedValue = decode(rawdata,charMap2) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - if cnt == 0: - DB = None - return DB - - if hdr == '/': - - # else newer style .kinf file used by K4Mac >= 1.6.0 - # the .kinf file uses "/" to separate it into records - # so remove the trailing "/" to make it easy to use split - data = data[:-1] - items = data.split('/') - cud = CryptUnprotectDataV2() - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # the raw keyhash string is also used to create entropy for the actual - # CryptProtectData Blob that represents that keys contents - # "entropy" not used for K4Mac only K4PC - # entropy = SHA1(keyhash) - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,charMap5) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the charMap5 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using charMap5 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the charMap5 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by charMap5 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using charMap5 to get the CryptProtect Data - encryptedValue = decode(encdata,charMap5) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB - - # the latest .kinf2011 version for K4M 1.9.1 - # put back the hdr char, it is needed - data = hdr + data - data = data[:-1] - items = data.split('/') - - # the headerblob is the encrypted information needed to build the entropy string - headerblob = items.pop(0) - encryptedValue = decode(headerblob, charMap1) - cleartext = UnprotectHeaderData(encryptedValue) - - # now extract the pieces in the same way - # this version is different from K4PC it scales the build number by multipying by 735 - pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) - for m in re.finditer(pattern, cleartext): - entropy = str(int(m.group(2)) * 0x2df) + m.group(4) - - cud = CryptUnprotectDataV3(entropy) - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # unlike K4PC the keyhash is not used in generating entropy - # entropy = SHA1(keyhash) + added_entropy - # entropy = added_entropy - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,testMap8) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the testMap8 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using testMap8 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the testMap8 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by testMap8 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using testMap8 to get the CryptProtect Data - encryptedValue = decode(encdata,testMap8) - cleartext = cud.decrypt(encryptedValue) - # print keyname - # print cleartext - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB diff --git a/Other_Tools/KindleBooks/lib/k4pcutils.py b/Other_Tools/KindleBooks/lib/k4pcutils.py deleted file mode 100755 index 9f9ca07..0000000 --- a/Other_Tools/KindleBooks/lib/k4pcutils.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/env python -# K4PC Windows specific routines - -from __future__ import with_statement - -import sys, os, re -from struct import pack, unpack, unpack_from - -from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ - create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ - string_at, Structure, c_void_p, cast - -import _winreg as winreg -MAX_PATH = 255 -kernel32 = windll.kernel32 -advapi32 = windll.advapi32 -crypt32 = windll.crypt32 - -import traceback - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# For K4PC 1.9.X -# use routines in alfcrypto: -# AES_cbc_encrypt -# AES_set_decrypt_key -# PKCS5_PBKDF2_HMAC_SHA1 - -from alfcrypto import AES_CBC, KeyIVGen - -def UnprotectHeaderData(encryptedData): - passwdData = 'header_key_data' - salt = 'HEADER.2011' - iter = 0x80 - keylen = 0x100 - key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen) - key = key_iv[0:32] - iv = key_iv[32:48] - aes=AES_CBC() - aes.set_decrypt_key(key, iv) - cleartext = aes.decrypt(encryptedData) - return cleartext - - -# simple primes table (<= n) calculator -def primes(n): - if n==2: return [2] - elif n<2: return [] - s=range(3,n+1,2) - mroot = n ** 0.5 - half=(n+1)/2-1 - i=0 - m=3 - while m <= mroot: - if s[i]: - j=(m*m-3)/2 - s[j]=0 - while j 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - - # the raw keyhash string is used to create entropy for the actual - # CryptProtectData Blob that represents that keys contents - entropy = SHA1(keyhash) - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,charMap5) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - # the charMap5 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using charMap5 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the charMap5 encoded contents seems to be: - # len(contents)-largest prime number <= int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by charMap5 - encdata = "".join(edlst) - contlen = len(encdata) - noffset = contlen - primes(int(contlen/3))[-1] - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using Map5 to get the CryptProtect Data - encryptedValue = decode(encdata,charMap5) - DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1) - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB - - # else newest .kinf2011 style .kinf file - # the .kinf file uses "/" to separate it into records - # so remove the trailing "/" to make it easy to use split - # need to put back the first char read because it it part - # of the added entropy blob - data = hdr + data[:-1] - items = data.split('/') - - # starts with and encoded and encrypted header blob - headerblob = items.pop(0) - encryptedValue = decode(headerblob, testMap1) - cleartext = UnprotectHeaderData(encryptedValue) - # now extract the pieces that form the added entropy - pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) - for m in re.finditer(pattern, cleartext): - added_entropy = m.group(2) + m.group(4) - - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - - # the sha1 of raw keyhash string is used to create entropy along - # with the added entropy provided above from the headerblob - entropy = SHA1(keyhash) + added_entropy - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - # key names now use the new testMap8 encoding - keyname = "unknown" - for name in names: - if encodeHash(name,testMap8) == keyhash: - keyname = name - break - - # the testMap8 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using testMap8 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the testMap8 encoded contents seems to be: - # len(contents)-largest prime number <= int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by testMap8 - # by moving noffset chars from the start of the - # string to the end of the string - encdata = "".join(edlst) - contlen = len(encdata) - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using new testMap8 to get the original CryptProtect Data - encryptedValue = decode(encdata,testMap8) - cleartext = CryptUnprotectData(encryptedValue, entropy, 1) - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB diff --git a/Other_Tools/KindleBooks/lib/libalfcrypto.dylib b/Other_Tools/KindleBooks/lib/libalfcrypto.dylib deleted file mode 100755 index 01c348c..0000000 Binary files a/Other_Tools/KindleBooks/lib/libalfcrypto.dylib and /dev/null differ diff --git a/Other_Tools/KindleBooks/lib/libalfcrypto32.so b/Other_Tools/KindleBooks/lib/libalfcrypto32.so deleted file mode 100755 index 9a5a442..0000000 Binary files a/Other_Tools/KindleBooks/lib/libalfcrypto32.so and /dev/null differ diff --git a/Other_Tools/KindleBooks/lib/libalfcrypto64.so b/Other_Tools/KindleBooks/lib/libalfcrypto64.so deleted file mode 100755 index a08ac28..0000000 Binary files a/Other_Tools/KindleBooks/lib/libalfcrypto64.so and /dev/null differ diff --git a/Other_Tools/KindleBooks/lib/scrolltextwidget.py b/Other_Tools/KindleBooks/lib/scrolltextwidget.py deleted file mode 100644 index 98b4147..0000000 --- a/Other_Tools/KindleBooks/lib/scrolltextwidget.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -import Tkinter -import Tkconstants - -# basic scrolled text widget -class ScrolledText(Tkinter.Text): - def __init__(self, master=None, **kw): - self.frame = Tkinter.Frame(master) - self.vbar = Tkinter.Scrollbar(self.frame) - self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) - kw.update({'yscrollcommand': self.vbar.set}) - Tkinter.Text.__init__(self, self.frame, **kw) - self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) - self.vbar['command'] = self.yview - # Copy geometry methods of self.frame without overriding Text - # methods = hack! - text_meths = vars(Tkinter.Text).keys() - methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() - methods = set(methods).difference(text_meths) - for m in methods: - if m[0] != '_' and m != 'config' and m != 'configure': - setattr(self, m, getattr(self.frame, m)) - - def __str__(self): - return str(self.frame) diff --git a/Other_Tools/KindleBooks/lib/stylexml2css.py b/Other_Tools/KindleBooks/lib/stylexml2css.py deleted file mode 100644 index 2347f6a..0000000 --- a/Other_Tools/KindleBooks/lib/stylexml2css.py +++ /dev/null @@ -1,266 +0,0 @@ -#! /usr/bin/python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.6 - -import csv -import sys -import os -import getopt -import re -from struct import pack -from struct import unpack - - -class DocParser(object): - def __init__(self, flatxml, fontsize, ph, pw): - self.flatdoc = flatxml.split('\n') - self.fontsize = int(fontsize) - self.ph = int(ph) * 1.0 - self.pw = int(pw) * 1.0 - - stags = { - 'paragraph' : 'p', - 'graphic' : '.graphic' - } - - attr_val_map = { - 'hang' : 'text-indent: ', - 'indent' : 'text-indent: ', - 'line-space' : 'line-height: ', - 'margin-bottom' : 'margin-bottom: ', - 'margin-left' : 'margin-left: ', - 'margin-right' : 'margin-right: ', - 'margin-top' : 'margin-top: ', - 'space-after' : 'padding-bottom: ', - } - - attr_str_map = { - 'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;', - 'align-left' : 'text-align: left;', - 'align-right' : 'text-align: right;', - 'align-justify' : 'text-align: justify;', - 'display-inline' : 'display: inline;', - 'pos-left' : 'text-align: left;', - 'pos-right' : 'text-align: right;', - 'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;', - } - - - # find tag if within pos to end inclusive - def findinDoc(self, tagpath, pos, end) : - result = None - docList = self.flatdoc - cnt = len(docList) - if end == -1 : - end = cnt - else: - end = min(cnt,end) - foundat = -1 - for j in xrange(pos, end): - item = docList[j] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - if name.endswith(tagpath) : - result = argres - foundat = j - break - return foundat, result - - - # return list of start positions for the tagpath - def posinDoc(self, tagpath): - startpos = [] - pos = 0 - res = "" - while res != None : - (foundpos, res) = self.findinDoc(tagpath, pos, -1) - if res != None : - startpos.append(foundpos) - pos = foundpos + 1 - return startpos - - # returns a vector of integers for the tagpath - def getData(self, tagpath, pos, end, clean=False): - if clean: - digits_only = re.compile(r'''([0-9]+)''') - argres=[] - (foundat, argt) = self.findinDoc(tagpath, pos, end) - if (argt != None) and (len(argt) > 0) : - argList = argt.split('|') - for strval in argList: - if clean: - m = re.search(digits_only, strval) - if m != None: - strval = m.group() - argres.append(int(strval)) - return argres - - def process(self): - - classlst = '' - csspage = '.cl-center { text-align: center; margin-left: auto; margin-right: auto; }\n' - csspage += '.cl-right { text-align: right; }\n' - csspage += '.cl-left { text-align: left; }\n' - csspage += '.cl-justify { text-align: justify; }\n' - - # generate a list of each \n' - final += '\n\n' - in_tags = [] - st_tags = [] - - def inSet(slist): - rval = False - j = len(in_tags) - if j == 0: - return False - while True: - j = j - 1 - if in_tags[j][0] in slist: - rval = True - break - if j == 0: - break - return rval - - def inBlock(): - return inSet(self.html_block_tags) - - def inLink(): - return inSet(self.html_link_tags) - - def inComment(): - return inSet(self.html_comment_tags) - - def inParaNow(): - j = len(in_tags) - if j == 0: - return False - if in_tags[j-1][0] == 'P': - return True - return False - - def getTag(ti, end): - cmd, attr = ti - r = self.html_tags[cmd][end] - if type(r) != str: - r = r(attr) - return r - - def getSTag(ti, end): - cmd, attr = ti - r = self.html_style_tags[cmd][end] - if type(r) != str: - r = r(attr) - return r - - def applyStyles(ending): - s = '' - j = len(st_tags) - if j > 0: - if ending: - while True: - j = j - 1 - s += getSTag(st_tags[j], True) - if j == 0: - break - else: - k = 0 - while True: - s += getSTag(st_tags[k], False) - k = k + 1 - if k == j: - break - return s - - def indentLevel(line_start): - nb = 0 - while line_start[nb:nb+1] == ' ': - nb = nb + 1 - line_start = line_start[nb:] - if nb > 5: - nb = 5 - return nb, line_start - - - def makeText(s): - # handle replacements required for html - s = s.replace('&', '&') - s = s.replace('<', '<') - s = s.replace('>', '>') - return_s ='' - # parse the text line by line - lp = s.find('\n') - while lp != -1: - line = s[0:lp] - s = s[lp+1:] - if not inBlock() and not inLink() and not inComment(): - if len(line) > 0: - # text should not exist in the tag level unless it is in a comment - nb, line = indentLevel(line) - return_s += '

' % nb - return_s += applyStyles(False) - return_s += line - return_s += applyStyles(True) - return_s += '

\n' - else: - return_s += '

 

\n' - elif inParaNow(): - # text is a continuation of a previously started paragraph - return_s += line - return_s += applyStyles(True) - return_s += '

\n' - j = len(in_tags) - del in_tags[j-1] - else: - if len(line) > 0: - return_s += line + '
\n' - else: - return_s += '
\n' - lp = s.find('\n') - linefrag = s - if len(linefrag) > 0: - if not inBlock() and not inLink() and not inComment(): - nb, linefrag = indentLevel(linefrag) - return_s += '

' % nb - return_s += applyStyles(False) - return_s += linefrag - ppair = ('P', None) - in_tags.append(ppair) - else: - return_s += linefrag - return return_s - - while True: - r = self.next() - if not r: - break - text, cmd, attr = r - - if text: - final += makeText(text) - - if cmd: - - # handle pseudo paragraph P tags - # close if starting a new block element - if cmd in self.html_block_tags or cmd == 'w': - j = len(in_tags) - if j > 0: - if in_tags[j-1][0] == 'P': - final += applyStyles(True) - final += getTag(in_tags[j-1],True) - del in_tags[j-1] - - if cmd in self.html_block_tags: - pair = (cmd, attr) - if cmd not in [a for (a,b) in in_tags]: - # starting a new block tag - final += getTag(pair, False) - final += applyStyles(False) - in_tags.append(pair) - else: - # process ending tag for a tag pair - # ending tag should be for the most recently added start tag - j = len(in_tags) - if cmd == in_tags[j-1][0]: - final += applyStyles(True) - final += getTag(in_tags[j-1], True) - del in_tags[j-1] - else: - # ow: things are not properly nested - # process ending tag for block - # ending tag **should** be for the most recently added block tag - # but in too many cases it is not so we must fix this by - # closing all open tags up to the current one and then - # reopen all of the tags we had to close due to improper nesting of styles - print 'Warning: Improperly Nested Block Tags: expected %s found %s' % (cmd, in_tags[j-1][0]) - print 'after processing %s' % final[-40:] - j = len(in_tags) - while True: - j = j - 1 - final += applyStyles(True) - final += getTag(in_tags[j], True) - if in_tags[j][0] == cmd: - break - del in_tags[j] - # now create new block start tags if they were previously open - while j < len(st_tags): - final += getTag(in_tags[j], False) - final += applyStyles(False) - j = j + 1 - self.skipNewLine() - - elif cmd in self.html_link_tags: - pair = (cmd, attr) - if cmd not in [a for (a,b) in in_tags]: - # starting a new link tag - # first close out any still open styles - if inBlock(): - final += applyStyles(True) - # output start tag and styles needed - final += getTag(pair, False) - final += applyStyles(False) - in_tags.append(pair) - else: - # process ending tag for a tag pair - # ending tag should be for the most recently added start tag - j = len(in_tags) - if cmd == in_tags[j-1][0]: - j = len(in_tags) - # apply closing styles and tag - final += applyStyles(True) - final += getTag(in_tags[j-1], True) - # if needed reopen any style tags - if inBlock(): - final += applyStyles(False) - del in_tags[j-1] - else: - # ow: things are not properly nested - print 'Error: Improperly Nested Link Tags: expected %s found %s' % (cmd, in_tags[j-1][0]) - print 'after processing %s' % final[-40:] - - elif cmd in self.html_style_tags: - spair = (cmd, attr) - if cmd not in [a for (a,b) in st_tags]: - # starting a new style - if inBlock() or inLink(): - final += getSTag(spair,False) - st_tags.append(spair) - else: - # process ending tag for style - # ending tag **should** be for the most recently added style tag - # but in too many cases it is not so we must fix this by - # closing all open tags up to the current one and then - # reopen all of the tags we had to close due to improper nesting of styles - j = len(st_tags) - while True: - j = j - 1 - if inBlock() or inLink(): - final += getSTag(st_tags[j], True) - if st_tags[j][0] == cmd: - break - del st_tags[j] - # now create new style start tags if they were previously open - while j < len(st_tags): - if inBlock() or inLink(): - final += getSTag(st_tags[j], False) - j = j + 1 - - elif cmd in self.html_one_tags: - final += self.html_one_tags[cmd] - - elif cmd == 'p': - # create page breaks at the level so - # they can be easily used for safe html file segmentation breakpoints - # first close any open tags - j = len(in_tags) - if j > 0: - while True: - j = j - 1 - if in_tags[j][0] in self.html_block_tags: - final += applyStyles(True) - final += getTag(in_tags[j], True) - if j == 0: - break - - # insert the page break tag - final += '\n

\n' - - if sigil_breaks: - if (len(final) - lastbreaksize) > 3000: - final += '
\n' - lastbreaksize = len(final) - - # now create new start tags for all tags that - # were previously open - while j < len(in_tags): - final += getTag(in_tags[j], False) - if in_tags[j][0] in self.html_block_tags: - final += applyStyles(False) - j = j + 1 - self.skipNewLine() - - elif cmd[0:1] == 'C': - if self.markChapters: - # create toc entries at the level - # since they will be in an invisible block - # first close any open tags - j = len(in_tags) - if j > 0: - while True: - j = j - 1 - if in_tags[j][0] in self.html_block_tags: - final += applyStyles(True) - final += getTag(in_tags[j], True) - if j == 0: - break - level = int(cmd[1:2]) + 1 - final += '' % (level, attr, level) - # now create new start tags for all tags that - # were previously open - while j < len(in_tags): - final += getTag(in_tags[j], False) - if in_tags[j][0] in self.html_block_tags: - final += applyStyles(False) - j = j + 1 - else: - final += '' % (cmd[1:2], attr) - - # now handle single tags (non-paired) that have attributes - elif cmd == 'm': - unquotedimagepath = bookname + '_img/' + attr - imagepath = urllib.quote( unquotedimagepath ) - final += '' % imagepath - - elif cmd == 'Q': - final += ' ' % attr - - elif cmd == 'a': - if not inBlock() and not inLink() and not inComment(): - final += '

' - final += applyStyles(False) - final += self.pml_chars.get(attr, '&#%d;' % attr) - ppair = ('P', None) - in_tags.append(ppair) - else: - final += self.pml_chars.get(attr, '&#%d;' % attr) - - elif cmd == 'U': - if not inBlock() and not inLink() and not inComment(): - final += '

' - final += applyStyles(False) - final += '&#%d;' % attr - ppair = ('P', None) - in_tags.append(ppair) - else: - final += makeText('&#%d;' % attr) - - elif cmd == 'w': - # hr width and align parameters are not allowed in strict xhtml but style widths are possible - final += '\n


' % attr - # final += '
 
' % attr - self.skipNewLine() - - elif cmd == 'T': - if inBlock() or inLink() or inComment(): - final += ' ' % attr - else: - final += '

' % attr - final += applyStyles(False) - ppair = ('P', None) - in_tags.append(ppair) - - else: - logging.warning("Unknown tag: %s-%s", cmd, attr) - - - # handle file ending condition for imputed P tags - j = len(in_tags) - if (j > 0): - if in_tags[j-1][0] == 'P': - final += '

' - - final += '\n\n' - - # recode html back to a single slash - final = final.replace('_amp#92_', '\\') - - # cleanup the html code for issues specifically generated by this translation process - # ending divs already break the line at the end so we don't need the
we added - final = final.replace('
\n','\n') - - # clean up empty elements that can be created when fixing improperly nested pml tags - # and by moving page break tags to the body level so that they can be used as html file split points - while True: - s = final - final = final.replace('','') - final = final.replace('','') - final = final.replace('','') - final = final.replace('','') - final = final.replace('','') - final = final.replace('','') - final = final.replace('','') - final = final.replace('','') - final = final.replace(' ','') - final = final.replace(' ','') - final = final.replace(' ','') - final = final.replace('

','') - final = final.replace('

','') - final = final.replace('

','') - final = final.replace('

','') - final = final.replace('

','') - final = final.replace('

','') - final = final.replace('

','') - final = final.replace('

','') - final = final.replace('

\n','') - final = final.replace('

\n','') - final = final.replace('

\n','') - final = final.replace('

\n','') - final = final.replace('
\n','') - final = final.replace('
\n','') - final = final.replace('
\n','') - final = final.replace('
\n','') - final = final.replace('
\n','') - if s == final: - break - return final - - -def tidy(rawhtmlfile): - # processes rawhtmlfile through command line tidy via pipes - rawfobj = file(rawhtmlfile,'rb') - # --doctype strict forces strict dtd checking - # --enclose-text yes - enclosees non-block electment text inside into its own

block to meet xhtml spec - # -w 100 -i will wrap text at column 120 and indent it to indicate level of nesting to make structure clearer - # -win1252 sets the input encoding of pml files - # -asxhtml convert to xhtml - # -q (quiet) - cmdline = 'tidy -w 120 -i -q -asxhtml -win1252 --enclose-text yes --doctype strict ' - if sys.platform[0:3] == 'win': - cmdline = 'tidy.exe -w 120 -i -q -asxhtml -win1252 --enclose-text yes --doctype strict ' - p2 = Popen(cmdline, shell=True, stdin=rawfobj, stdout=PIPE, stderr=PIPE, close_fds=False) - stdout, stderr = p2.communicate() - # print "Tidy Original Conversion Warnings and Errors" - # print stderr - return stdout - -def usage(): - print "Converts PML file to XHTML" - print "Usage:" - print " xpml2xhtml [options] infile.pml outfile.html " - print " " - print "Options: " - print " -h prints this message" - print " --sigil-breaks insert Sigil Chapterbbreaks" - print " --use-tidy use tidy to further clean up the html " - print " " - return - -def main(argv=None): - global bookname - global footnote_ids - global sidebar_ids - global sigil_breaks - try: - opts, args = getopt.getopt(sys.argv[1:], "h", ["sigil-breaks", "use-tidy"]) - except getopt.GetoptError, err: - print str(err) - usage() - return 1 - if len(args) != 2: - usage() - return 1 - sigil_breaks = False - use_tidy = False - for o, a in opts: - if o == "-h": - usage() - return 0 - elif o == "--sigil-breaks": - sigil_breaks = True - elif o == "--use-tidy": - use_tidy = True - infile, outfile = args[0], args[1] - bookname = os.path.splitext(os.path.basename(infile))[0] - footnote_ids = { } - sidebar_ids = { } - try: - print "Processing..." - import time - start_time = time.time() - print " Converting pml to raw html" - pml_string = file(infile,'rb').read() - pml = PmlConverter(pml_string) - html_src = pml.process() - if use_tidy: - print " Tidying html to xhtml" - fobj = tempfile.NamedTemporaryFile(mode='w+b',suffix=".html",delete=False) - tempname = fobj.name - fobj.write(html_src) - fobj.close() - html_src = tidy(tempname) - os.remove(tempname) - file(outfile,'wb').write(html_src) - end_time = time.time() - convert_time = end_time - start_time - print 'elapsed time: %.2f seconds' % (convert_time, ) - print 'output is in file %s' % outfile - print "Finished Processing" - except ValueError, e: - print "Error: %s" % e - return 1 - 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/readme.md b/readme.md index 1b9ff71..2fa1065 100644 --- a/readme.md +++ b/readme.md @@ -1,352 +1,164 @@ -# DRM Removal Tools for eBooks v5.4.1 -This repository is a copy of the tools downloaded from [Apprentice Alf's Blog](http://www.apprenticealf.wordpress.com). I am not the author of these tools. I just wanted to keep them in a safe place. More info is at [Apprentice Alf's v5.4.1 blog post](http://apprenticealf.wordpress.com/2012/09/10/drm-removal-tools-for-ebooks/). - -# Notes -- I've changed the ReadMe_First.txt to readme.md -- Updated the readme.md with change notes and also citing the original author of the tools - -## Changes in 5.4.1: -- Updated Kindle tools to fix a problem with long delays on some Mac systems -- Updated ineptpdf and ignobleepub plugins -- Added (Windows-only) Scuolabooks tool by Hex -- Added B&N Download Helper by J-man -- Updated Android Patch readme - -## Changes in 5.4a: -- No changes to the tools. -- Changed one folder name to no longer contain a colon (:) - -## Changes in 5.4: -- Improved ReadMes, Improved dialogs, improved sanity-checking of configuration strings -- Improved zipfix for ‘corrupt’ ePubs -- Improved Amazon Topaz error reporting -- Improved retrieval of decryption key from Kindle for PC -- Improved reporting of decryption keys from Kindle for Mac -- Improved ineptkey, fixing problem with PyCrypto implementation change -- Improved ineptepub, fixing problem with PyCrypto implementation change -- Improved ignobleepub, fixing problem with PyCrypto implementation change -- Complete overhaul of ignobleepub plugin, now with secure configuration dialog -- New patch for the latest Kindle for Android to allow display of device PID -- In short, all plugins and DeDRM apps updated - -Welcome to the tools! -===================== - -This readme.md is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v5.4.1 archive. - -The is archive includes tools to remove DRM from: - - - Kindle ebooks (Mobi, Topaz, Print Replica and KF8). - - Barnes and Noble ePubs - - Adobe Digital Editions ePubs (including Sony and Kobo ePubs downloaded to ADE) - - Adobe Digital Editions PDFs - - Mobipocket ebooks - - eReader PDB books - - Scuolabooks (Windows only solution by Hex) - -These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.) - - -About the tools ---------------- -These tools have been updated and maintained by Apprentice Alf, DiapDealer and some_updates. - -You can find the latest updates and get support at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/ - -If you re-post these tools, a link to the blog would be appreciated. - -The original inept and ignoble scripts were by I♥cabbages -The original mobidedrm and erdr2pml scripts were by The Dark Reverser -The original topaz DRM removal script was by CMBDTC -The original topaz format conversion scripts were by some_updates, clarknova and Bart Simpson -The Scuolabooks tool is by Hex - -The calibre plugin conversions were originally by DiapDealer -The DeDRM AppleScript application was by Apprentice Alf -The DeDRM python GUI was by some_updates - -Many fixes, updates and enhancements to the scripts and applicatons have been by Apprentice Alf, some_updates and DiapDealer. - - -Calibre Users (Mac OS X, Windows, and Linux) --------------------------------------------- -If you are a calibre user, the quickest and easiest way, especially on Windows, to remove DRM from your ebooks is to install each of the plugins in the Calibre_Plugins folder, following the instructions and configuration directions provided in each plugin's ReadMe file. - -Once installed and configured, you can simply add a DRM book to calibre and the DeDRMed version will be imported into the calibre database. Note that DRM removal ONLY occurs on import. If you have already imported DRM books you'll need to remove them from calibre and re-import them. - -These plugins work for Windows, Mac OS X and Linux. For ebooks from Kindle 4 PC and Adobe Digital Editions, Linux users should read the section at the end of this ReadMe. - - - -DeDRM application for Mac OS X users: (Mac OS X 10.4 and above) ----------------------------------------------------------------------- -This application combines all the tools into one easy-to-use tool for Mac OS X users. - -Drag the "DeDRM 5.4.1.app" application from the DeDRM_Applications/Macintosh folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and it will guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe. - -To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM application and it will remove the DRM of the kinds listed above. - -For more detailed instructions, see the "DeDRM ReadMe.rtf" file in the DeDRM_Applications/Macintosh folder, including details of the extra step that Mac OS X 10.4 users need to take to use the application. - - - - -DeDRM application for Windows users: (Windows XP through Windows 7) ------------------------------------------------------------------- -***This program requires that Python and PyCrypto be properly installed.*** -***See below for details on recommended versions are where to get them.*** - -This application combines all the tools into one easy-to-use tool for Windows users. - -Drag the DeDRM_5.4.1 folder that's in the DeDRM_Applications/Windows folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_5.4.1 folder. Double-click on the shortcut and the DeDRM application will run and guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe. - -To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM of the kinds listed above. - -For more detailed instructions, see the DeDRM_ReadMe.txt file in the DeDRM_Applications/Windows folder. - - - -Other_Tools ------------ -This folder includes two non-python tools: - -Kindle_for_Android_Patches --------------------------- -Definitely only for the adventurous, this folder contains information on how to modify the Kindel for Android app to b able to get a PID for use with the other Kindle tools (DeDRM apps and calibre plugin). - -B&N_Download_Helper -------------------- -A Javascript to enable a download button at the B&N website for ebooks that normally won't download to your PC. Another one only for the adventurous. - - -And then there are a number of other python based tools that have graphical user interfaces to make them easy to use. To use any of these tools, you need to have Python 2.5, 2.6, or 2.7 for 32 bits installed on your machine as well as a matching PyCrypto or OpenSSL for some tools. - -On Mac OS X (10.5, 10.6 and 10.7), your systems already have the proper Python and OpenSSL installed. So nothing need be done, you can already run these tools by double-clicking on the .pyw python scripts. - -Users of Mac OS X 10.3 and 10.4, need to download and install the "32-bit Mac Installer disk Image (2.7.3) for OS X 10.3 and later from http://www.python.org/ftp/python/2.7.3/python-2.7.3-macosx10.3.dmg. - -On Windows, you need to install a 32 bit version of Python (even on Windows 64) plus a matching 32 bit version of PyCrypto *OR* OpenSSL. We ***strongly*** recommend the free community edition of ActiveState's Active Python version. See the end of this document for details. - -Linux users should have python 2.7, and openssl installed, but may need to run some of these tools under recent versions of Wine. See the Linux_Users section below: - -The scripts in the Other_Tools folder are organized by type of ebook you need to remove the DRM from. Choose from among: - - "Adobe_ePub_Tools" - "Adobe_PDF_Tools" - "Barnes_and_Noble_ePub_Tools" - "ePub_Fixer" (for fixing incorrectly made Adobe and Barnes and Noble ePubs) - "eReader_PDB_Tools" - "Kindle/Mobi_Tools" - "KindleBooks" - -by simply opening that folder. - -Look for a README inside of the relevant folder to get you started. - - - -Additional Tools ----------------- -Some additional useful tools **unrelated to DRM** are also provided in the "Additional_Tools" folder inside the "Other_Tools" folder. There are tools for working with finding Topaz ebooks, unpacking Kindle/Mobipocket ebooks (without DRM) to get to the Mobipocket markup language inside, tools to strip source archive from Kindlegen generated mobis, tools to work with Kindle for iPhone/iPad, etc, and tools to dump the contents of mobi headers to see all EXTH (metadata) and related values. - - -Scuolabook_DRM -------------- -This is a Windows-only tool produced by Hex and included with permission. - - -Windows and Python ------------------- -We **strongly** recommend ActiveState's Active Python 2.7 Community Edition for Windows (x86) 32 bits. This can be downloaded for free from: - - http://www.activestate.com/activepython/downloads - -We do **NOT** recommend the version of Python from python.org. - -The version from python.org is not as complete as most normal Python installations on Linux and even Mac OS X. It is missing various Windows specific libraries, does not install the default Tk Widget kit (for graphical user interfaces) unless you select it as an option in the installer, and does not properly update the system PATH environment variable. Therefore using the default python.org build on Windows is simply an exercise in frustration for most Windows users. - -In addition, Windows Users need one of PyCrypto OR OpenSSL. - -For OpenSSL: - - 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 - -For PyCrypto: - - There are many places to get PyCrypto installers for Windows. One such place is: - - http://www.voidspace.org.uk/python/modules.shtml - - Please get the latest PyCrypto meant for Windows 32 bit that matches the version of Python you installed (2.7) - -Once Windows users have installed Python 2.X for 32 bits, and the matching OpenSSL OR PyCrypto pieces, they too are ready to run the scripts. - - - - - -Linux Users Only -================ - -Since Kindle for PC and Adobe Digital Editions do not offer native Linux versions, here are instructions for using Windows versions under Wine as well as related instructions for the special way to handle some of these tools: - - - -Linux and Kindle for PC ------------------------ - -It is possible to run the Kindle for PC application under Wine. - -1. Install a recent version of Wine (>=1.3.15) - -2. Some versions of winecfg have a bug in setting the volume serial number, so create a .windows-serial file at root of drive_c to set a proper windows volume serial number (8 digit hex value for unsigned integer). -cd ~ -cd .wine -cd drive_c -echo deadbeef > .windows-serial - -Replace "deadbeef" with whatever hex value you want but I would stay away from the default setting of "ffffffff" which does not seem to work. BTW: deadbeef is itself a valid possible hex value if you want to use it - -3. Download and install Kindle for PC under Wine. - - - - -Linux and Kindle for PC (Other_Tools/KindleBooks/) --------------------------------------------------- - -Here are the instructions for using Kindle for PC and KindleBooks.pyw on Linux under Wine. (Thank you Eyeless and Pete) - -1. upgrade to very recent versions of Wine; This has been tested with Wine 1.3.15 – 1.3.2X. It may work with earlier versions but no promises. It does not work with wine 1.2.X versions. - -If you have not already installed Kindle for PC under wine, follow steps 2 and 3 otherwise jump to step 4 - -2. Some versions of winecfg have a bug in setting the volume serial number, so create a .windows-serial file at root of drive_c to set a proper windows volume serial number (8 digit hex value for unsigned integer). -cd ~ -cd .wine -cd drive_c -echo deadbeef > .windows-serial - -Replace "deadbeef" with whatever hex value you want but I would stay away from the default setting of "ffffffff" which does not seem to work. BTW: deadbeef is itself a valid possible hex value if you want to use it - -3. Only ***after*** setting the volume serial number properly – download and install under wine K4PC version for Windows. Register it and download from your Archive one of your Kindle ebooks. Versions known to work are K4PC 1.7.1 and earlier. Later version may work but no promises. - -4. Download and install under wine ActiveState Active Python 2.7 for Windows 32bit - -5. Download and unzip tools_vX.X.zip - -6. Now make sure the executable bit is NOT set for KindleBooks.pyw as Linux will actually keep trying to ignore wine and launch it under Linux python which will cause it to fail. - -cd tools_vX.X/KindleBooks/ -chmod ugo-x KindleBooks.pyw - -7. Then run KindleBook.pyw ***under python running on wine*** using the Linux shell as follows: - -wine python KindleBooks.pyw - -Select the ebook file directly from your “My Kindle Content” folder, select a new/unused directory for the output. You should not need to enter any PID or Serial Number for Kindle for PC. - - - - -Linux and Adobe Digital Editions ePubs --------------------------------------- - -Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien!) - - -1. download the most recent version of wine from winehq.org (1.3.29 in my case) - -For debian users: - -to get a recent version of wine I decited to use aptosid (2011-02, xfce) -(because I’m used to debian) -install aptosid and upgrade it (see aptosid site for detaild instructions) - - -2. properly install Wine (see the Wine site for details) - -For debian users: - -cd to this dir and install the packages as root: -‘dpkg -i *.deb’ -you will get some error messages, which can be ignored. -again as root use -‘apt-get -f install’ to correct this errors - -3. python 2.7 should already be installed on your system but you may need the following additional python package - -'apt-get install python-tk’ - -4. all programms need to be installed as normal user. All these programm are installed the same way: -‘wine ‘ -we need: -a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html) -(there is a “can’t install ADE” site, where the setup.exe hides) - -b) ActivePython-2.7.2.5-win32-x86.msi (from: http://www.activestate.com/activepython/downloads) - -c) Win32OpenSSL_Light-0_9_8r.exe (from: http://www.slproweb.com/) - -d) pycrypto-2.3.win32-py2.7.msi (from: http://www.voidspace.org.uk/python/modules.shtml) - -5. now get and unpack the very latest tools_vX.X (from Apprentice Alf) in the users drive_c of wine -(~/.wine/drive_c/) - -6. start ADE with: -‘wine digitaleditions.exe’ or from the start menue wine-adobe-digital.. - -7. register this instance of ADE with your adobeID and close it - change to the tools_vX.X dir: -cd ~/.wine/drive_c/tools_vX.X/Other_Tools/Adobe_ePub_Tools - -8. create the adeptkey.der with: -‘wine python ineptkey_v5.4.1.pyw’ (only need once!) -(key will be here: ~/.wine/drive_c/tools_v4.X/Other_Tools/Adobe_ePub_Tools/adeptkey.der) - -9. Use ADE running under Wine to dowload all of your purchased ePub ebooks - -10. for each book you have downloaded via Adobe Digital Editions -There is no need to use Wine for this step! - -'python ineptpub_v5.6.pyw’ -this will launch a window with 3 lines -1. key: (allready filled in, otherwise it’s in the path where you did step 8. -2. input file: drmbook.epub -3. output file: name-ypu-want_for_free_book.epub - -Also… once you successfully generate your adept.der keyfile using Wine, you can use the regular ineptepub plugin with the standard Linux calibre. Just put the *.der file(s) in your calibre configuration directory. -so if you want you can use calibre in Linux: - -11. install the plugins from the tools as discribed in the readmes for win - -12. copy the adeptkey.der into the config dir of calibre (~/.config/calibre in debian). Every book imported to calibre will automaticly freed from DRM. - - -Apple's iBooks FairPlay DRM ---------------------------- - -The only tool that removes Apple's iBooks Fairplay DRM that is Requiem by Brahms version 3.3 or later. Requiem is NOT included in this tools package. It is under active development because Apple constantly updates its DRM scheme to stop Requiem from working. -The latest version as of October 2012 is 3.3.5 and works with iTunes 10.5 and above. - -Requiem has a Tor website: http://tag3ulp55xczs3pn.onion. To reach the site using Tor, you will need to install Tor (http://www.torproject.org). If you're willing to sacrifice your anonymity, you can use the regular web with tor2web. Just go to http://tag3ulp55xczs3pn.tor2web.com. - -Alternatively, you can download the 3.3.5 version from the following locationss: - -Requiem Windows application: http://www.datafilehost.com/download-b015485b.html -MD5: 954f9ecf42635fae77afbc3a24489004 - -Requiem Mac OS X application: http://www.datafilehost.com/download-50608ba6.html -MD5: 4e7dc46ad7e0b54bea6182c5ad024ffe - -Requiem source code: http://www.datafilehost.com/download-af8f91a1.html -MD5: e175560590a154859c0344e30870ac73 - -No support for requiem is provided at Apprentice Alf's blog. +# DRM Removal Tools for eBooks v6.0.4 +This repository is a copy of the tools downloaded from [Apprentice Alf's Blog](http://www.apprenticealf.wordpress.com). I am not the author of these tools. I just wanted to keep them in a safe place. More info is at Apprentice Alf's blog post ["DRM Removal Tools for eBooks"](http://apprenticealf.wordpress.com/2012/09/10/drm-removal-tools-for-ebooks/). + +# Notes +- I've changed the ReadMe_First.txt to readme.md +- Updated the readme.md with change notes and also citing the original author of the tools + +## Changes in 6.0.4: +- Fixed a problem in the plugin converting earlier preferences (thanks, enno) +- Fixed a problem in the plugin with importing pdb files (thanks, Tina Bird) +- Fixed a problem with unicode characters in path names for the Mac application (thanks, K) + +## Changes in 6.0.3: +- Fixed a problem with non-ascii characters in Windows user name +- Fixed a problem early versions of Kindle for Mac +- Fix for location of DeDRMed PDF files with Macintosh DeDRM Application +- Fix in Windows DeDRM application for paths with spaces +- Restored ability for calibre plugin to call wine for Kindle for PC decryption key +- Added ability for calibre plugin to call wine for Adobe Digital Edition key +- Hopefully removed any dependency on tinter (although still used if available). +- Updated the readmes + +Welcome to the tools! +===================== + +This readme.md is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.0.4 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/ + +The is archive includes tools to remove DRM from: + + - Kindle ebooks (Mobi, Topaz, Print Replica and KF8). + - Barnes and Noble ePubs + - Adobe Digital Editions ePubs (including Sony and Kobo ePubs downloaded to ADE) + - Adobe Digital Editions PDFs + - Mobipocket ebooks + - eReader PDB books + - Scuolabooks (Windows only solution by Hex) + +These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.) + +About the tools +--------------- +These tools have been updated and maintained by Apprentice Alf, DiapDealer and some_updates. You can find the latest updates and get support at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/ +If you re-post these tools, a link to the blog would be appreciated. + +The original inept and ignoble scripts were by i♥cabbages +The original mobidedrm and erdr2pml scripts were by The Dark Reverser +The original topaz DRM removal script was by CMBDTC +The original topaz format conversion scripts were by some_updates, clarknova and Bart Simpson +The Scuolabooks tool is by Hex +The calibre plugin conversions were originally by DiapDealer + +The DeDRM plugin is by Apprentice Alf based on plugins by DiapDealer +The DeDRM AppleScript application is by Apprentice Alf +The DeDRM python GUI is by some_updates and Apprentice Alf + +Many fixes, updates and enhancements to the scripts and applicatons have been by Apprentice Alf, some_updates and DiapDealer and others. + + +DeDRM plugin for calibre (Mac OS X, Windows, and Linux) +------------------------------------------------------- +If you already use calibre, the quickest and easiest way, especially on Windows, to remove DRM from your ebooks is to install the DeDRM plugin from the `DeDRM_calibre_plugin` folder, following the instructions and configuration directions provided in the ReadMe and the help links. + +Once installed and configured, you can simply add a DRM book to calibre and the DeDRMed version will be imported into the calibre database. Note that DRM removal ONLY occurs on import. If you have already imported DRM books you'll need to remove them from calibre and re-import them. + +These plugins work for Windows, Mac OS X and Linux. For ebooks from Kindle 4 PC and Adobe Digital Editions, Linux users should read the section at the end the `DeDRM_plugin_ReadMe.txt` file. + + +DeDRM application for Mac OS X users: (Mac OS X 10.4 and above) +--------------------------------------------------------------- +This application is a stand-alone application for Mac OS X users. + +Drag the `DeDRM.app` application from the `DeDRM_Macintosh_Application` folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and you will be able to enter any configuration data needed to remove DRM from your type of DRMed ebooks. + +To use the DeDRM application simply drag ebooks and/or folders containing ebooks onto the DeDRM application and it will remove the DRM and save DRM-free versions of the books wherever you selected in the Output Folder configuration dialog. + +For more detailed instructions, see the `DeDRM ReadMe.rtf` file in the `DeDRM_Application_Macintosh` folder, including details of the extra step that Mac OS X 10.4 users need to take to use the application. + + +DeDRM application for Windows users: (Windows XP through Windows 8) +------------------------------------------------------------------ +***This program requires that Python and PyCrypto be properly installed.*** +***See below for details on recommended versions and how to install them.*** + +This application is a stand-alone application for Windows users. + +Drag the `DeDRM_App` folder that's in the `DeDRM_Windows_Application` folder, to your `My Documents` folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the `DeDRM_Drop_Target.bat` file that's in the `DeDRM_App` folder. Double-click on the shortcut and the DeDRM application will run and you will be able to enter any configuration data needed to remove DRM from your type of DRMed ebooks. + +To use the DeDRM application simply drag ebooks or folders containing ebooks onto the `DeDRM_Drop_Target.bat` shortcut and it will remove the DRM and save DRM-free versions of the books wherever you selected in the Output Folder configuration dialog. + +For more detailed instructions, see the `DeDRM_App_ReadMe.txt` file in the `DeDRM_Windows_Applications` folder. + + +Other_Tools +----------- +This folder includes other tools that may be useful for DRMed ebooks from certain sources or for Linux users. Most users won't need any of these tools. + +**Key_Generation_Scripts** +This folder contains python scripts that creates a keyfiles for Barnes and Noble ePubs, Adobe Digital Editions ePubs and Kindle for Mac/PC ebooks. + +**Kindle_for_Android_Patches** +Definitely only for the adventurous, this folder contains information on how to modify the Kindel for Android app to b able to get a PID for use with the other Kindle tools (DeDRM apps and calibre plugin). + +**B&N_Download_Helper** +A Javascript to enable a download button at the B&N website for ebooks that normally won't download to your PC. Another one only for the adventurous. + +**Scuolabook_DRM** +A windows-only application (including source code) for removing DRM from ScuolaBooks PDFs, created by "Hex" and included with permission. + +**Rocket_ebooks** +Information about the now-obsolete Rocket ebook format and DRM, along with source for a tool to remove the DRM. + + + + +Windows and Python +------------------ +We **strongly** recommend ActiveState's Active Python 2.7 Community Edition for Windows (x86) 32 bits. This can be downloaded for free from: + +http://www.activestate.com/activepython/downloads + +We do **NOT** recommend the version of Python from python.org. + +The version from python.org is not as complete as most normal Python installations on Linux and even Mac OS X. It is missing various Windows specific libraries, does not install the default Tk Widget kit (for graphical user interfaces) unless you select it as an option in the installer, and does not properly update the system PATH environment variable. Therefore using the default python.org build on Windows is simply an exercise in frustration for most Windows users. + +In addition, Windows Users need one of PyCrypto OR OpenSSL. Because of potential conflicts with other software, we recommend using PyCrypto. + +### For PyCrypto: +There are many places to get PyCrypto installers for Windows. One such place is: +http://www.voidspace.org.uk/python/modules.shtml +Please get the latest PyCrypto meant for Windows 32 bit that matches the version of Python you installed (2.7) + +### For OpenSSL: +**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 + +Once Windows users have installed Python 2.X for 32 bits, and the matching OpenSSL OR PyCrypto pieces, they too are ready to run a DeDRM application. + + + +Apple's iBooks FairPlay DRM +--------------------------- + +The only tool that removes Apple's iBooks Fairplay DRM is Requiem by Brahms version 3.3.6 and works with iTunes 10.5. Requiem 4.0 and later do not remove DRM from ebooks. + +Requiem has a Tor website: http://tag3ulp55xczs3pn.onion. To reach the site using Tor, you will need to install Tor (http://www.torproject.org). If you're willing to sacrifice your anonymity, you can use the regular web with tor2web. Just go to http://tag3ulp55xczs3pn.tor2web.com. + +Alternatively, you can download it from these download links: + +Requiem 3.3.6 for Windows: http://www.datafilehost.com/download-f7916922.html +MD5: 10ab191f2d86c692d57f6a07b4622cf8 + +Requiem 3.3.6 for Mac OS X: http://www.datafilehost.com/download-47fce8b7.html +MD5: 6d4167d47e6982ddbb8528212198b520 + +Requiem 3.3.6 source code: http://www.datafilehost.com/download-172920e9.html +MD5: 1636862796d573c693d56bcc526b60bd + +If you have any problems with Requiem, I suggest you contact Brahms directly through their Tor website. + +No support for requiem is provided at Apprentice Alf's blog.