tools v5.2

pull/6/head v5.2
Apprentice Alf 12 years ago
parent 0812438b9d
commit f3f02adc98

@ -1,40 +1,38 @@
Ignoble Epub DeDRM - ignobleepub_vXX_plugin.zip
Requires Calibre version 0.6.44 or higher.
All credit given to I <3 Cabbages for the original standalone scripts.
I had the much easier job of converting them to a Calibre plugin.
This plugin is meant to decrypt Barnes & Noble Epubs that are protected
with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.
Installation:
Go to Calibre's Preferences page. 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_vXX_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:
1) The easiest way to configure the plugin is to enter your name (Barnes & Noble account name) and credit card number (the one used to purchase the books) into the plugin's customization window. It's the same info you would enter into the ignoblekeygen script. Highlight the plugin (Ignoble Epub DeDRM) and click the "Customize Plugin" button on
Calibre's Preferences->Plugins page. Enter the name and credit card number separated by a comma: Your Name,1234123412341234
If you've purchased books with more than one credit card, separate that other info with a colon: Your Name,1234123412341234:Other Name,2345234523452345
** NOTE ** The above method is your only option if you don't have/can't run the original I <3 Cabbages scripts on your particular machine.
** NOTE ** Your credit card number will be on display in Calibre's Plugin configuration page when using the above method. If other people have access to your computer, you may want to use the second configuration method below.
2) If you already have keyfiles generated with I <3 Cabbages' ignoblekeygen.pyw script, you can put those keyfiles into Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre
configuration directory' button. Paste your keyfiles in there. Just make sure that they have different names and are saved with the '.b64' extension (like the ignoblekeygen script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
All keyfiles from method 2 and all data entered from method 1 will be used to attempt to decrypt a book. You can use method 1 or method 2, or a combination of both.
Troubleshooting:
If you find that it's not working for you (imported epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub". Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.
Ignoble Epub DeDRM - ignobleepub_v01.6_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_vXX_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:
1) The easiest way to configure the plugin is to enter your name (Barnes & Noble account name) and credit card number (the one used to purchase the books) into the plugin's customization window. It's the same info you would enter into the ignoblekeygen script. Highlight the plugin (Ignoble Epub DeDRM) and click the "Customize Plugin" button on calibre's Preferences->Plugins page. Enter the name and credit card number separated by a comma: Your Name,1234123412341234
If you've purchased books with more than one credit card, separate that other info with a colon: Your Name,1234123412341234:Other Name,2345234523452345
** NOTE ** The above method is your only option if you don't have/can't run the original I♥Cabbages scripts on your particular machine. Your credit card number will be on display in calibre's Plugin configuration page when using the above method. If other people have access to your computer, you may want to use the second configuration method below.
2) If you already have keyfiles generated with I <3 Cabbages' ignoblekeygen.pyw script, you can put those keyfiles into calibre's configuration directory. The easiest way to find the correct directory is to go to calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open calibre configuration directory' button. Paste your keyfiles in there. Just make sure that they have different names and are saved with the '.b64' extension (like the ignoblekeygen script produces). This directory isn't touched when upgrading calibre, so it's quite safe to leave them there.
All keyfiles from method 2 and all data entered from method 1 will be used to attempt to decrypt a book. You can use method 1 or method 2, or a combination of both.
Troubleshooting:
If you find that it's not working for you (imported epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub". Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

@ -1,39 +1,39 @@
Inept Epub DeDRM - ineptepub_vXX_plugin.zip
Requires Calibre version 0.6.44 or higher.
All credit given to I <3 Cabbages for the original standalone scripts.
I had the much easier job of converting them to a Calibre plugin.
This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.
Installation:
Go to Calibre's Preferences page. 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_vXX_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's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to
find the Adobe Digital Editions installation installation.
So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that
they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory.
All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book.
** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
Troubleshooting:
If you find that it's not working for you (imported epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub". Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.
Inept Epub DeDRM - ineptepub_v01.7_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_vXX_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's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to find the Adobe Digital Editions installation installation.
So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
If you already have keyfiles generated with I♥Cabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory.
All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book.
** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
Troubleshooting:
If you find that it's not working for you (imported epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub". Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

@ -1,39 +1,39 @@
Inept PDF Plugin - ineptpdf_vXX_plugin.zip
Requires Calibre version 0.6.44 or higher.
All credit given to IHeartCabbages 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" 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_vXX_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's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to
find the Adobe Digital Editions installation installation.
So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that
they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory.
All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book.
** NOTE ** There is no plugin customization data for the Inept PDF plugin.
Troubleshooting:
If you find that it's not working for you (imported PDFs still have DRM), you can save a lot of time and trouble by trying to add the PDF to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdf". Don't type the quotes and obviously change the 'your_ebook.pdf' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.
Inept PDF Plugin - ineptpdf_v01.5_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" 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_vXX_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's). If successful, it will create an 'adeptkey.der' file and save it in Calibre's configuration directory. It will use that file on subsequent runs. If there are already '*.der' files in the directory, the plugin won't attempt to find the Adobe Digital Editions installation installation.
So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that
they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory.
All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book.
** NOTE ** There is no plugin customization data for the Inept PDF plugin.
Troubleshooting:
If you find that it's not working for you (imported PDFs still have DRM), you can save a lot of time and trouble by trying to add the PDF to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdf". Don't type the quotes and obviously change the 'your_ebook.pdf' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

@ -1,22 +1,37 @@
Plugin for K4PC, K4Mac, standalone Kindles, Mobi Books, and for Devices with Fixed PIDs.
This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins can be safely removed.
This plugin is meant to remove the DRM from .prc, .azw, .azw1, and .tpz ebooks. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books.
Installation:
Go to Calibre's Preferences page. 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 on the Plugins button. Click on the "Load plugin from file" button at the bottom of the screen. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the "Add" (or it may say "Open" button. Then click on the "Yes" button in the warning dialog that appears. A Confirmation dialog appears that says the plugin has been installed.
Configuration:
Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your 10 digit PID. If you have more than one PID separate them with a comma (no spaces). If you have a standalone Kindle include the 16 digit serial number (these typically begin "B0...") in this list (again separated from the PIDs or other serial numbers with a comma (no spaces). This configuration is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
Troubleshooting:
If you find that it's not working for you, you can save a lot of time and trouble by trying to add the azw file to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.azw". Don't type the quotes and obviously change the 'your_ebook.azw' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.
K4MobiDeDRM_v04.4_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 can be safely 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 on the Plugins button. Click on the "Load plugin from file" button at the bottom of the screen. Use the file dialog button to select the plugin's zip file (K4MobiDeDRM_vXX_plugin.zip) and click the "Add" (or it may say "Open" button. Then click on the "Yes" button in the warning dialog that appears. A Confirmation dialog appears that says the plugin has been installed.
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 digit serial number (these typically begin "B0..."). If you have more than one eInk Kindle, you can enter multiple serial numbers separated by commas (no spaces). If you have Mobipocket books, enter your 10 digit PID. If you have more than one PID, separate them with commax (no spaces).
This configuration step is 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, you can save a lot of time and trouble by trying to add the DRMed 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. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook_file". Don't type the quotes and obviously change the 'your_ebook_file' to whatever the filename of your book is (including any file name extension like .azw). Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

@ -15,16 +15,16 @@ import re
from zipfile import ZipFile
class K4DeDRM(FileTypePlugin):
name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin
description = 'Removes DRM from Mobipocket, Kindle/Mobi, Kindle/Topaz and Kindle/Print Replica files. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
name = 'Kindle and Mobipocket DeDRM' # Name of the plugin
description = 'Removes DRM from eInk Kindle, Kindle 4 Mac and Kindle 4 PC ebooks, and from Mobipocket ebooks. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, mdlnx, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 4, 2) # The version number of this plugin
author = 'DiapDealer, SomeUpdates, mdlnx, Apprentice Alf' # The author of this plugin
version = (0, 4, 4) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','azw3','azw4','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
priority = 520 # run this plugin before earlier versions
minimum_calibre_version = (0, 7, 55)
def initialize(self):
"""
Dynamic modules can't be imported/loaded from a zipfile... so this routine
@ -39,7 +39,7 @@ class K4DeDRM(FileTypePlugin):
elif isosx:
names = ['libalfcrypto.dylib']
else:
names = ['libalfcrypto32.so','libalfcrypto64.so']
names = ['libalfcrypto32.so','libalfcrypto64.so','alfcrypto.py','alfcrypto.dll','alfcrypto64.dll','getk4pcpids.py','mobidedrm.py','kgenpids.py','k4pcutils.py','topazextract.py']
lib_dict = self.load_resources(names)
self.alfdir = os.path.join(config_dir, 'alfcrypto')
if not os.path.exists(self.alfdir):
@ -62,35 +62,45 @@ class K4DeDRM(FileTypePlugin):
plug_ver = '.'.join(str(self.version).strip('()').replace(' ', '').split(','))
k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = []
serials = []
kInfoFiles = []
self.config()
# Get supplied list of PIDs to try from plugin customization.
customvalues = self.site_customization.split(',')
for customvalue in customvalues:
customvalue = str(customvalue)
customvalue = customvalue.strip()
if len(customvalue) == 10 or len(customvalue) == 8:
pids.append(customvalue)
else :
if len(customvalue) == 16 and customvalue[0] == 'B':
serials.append(customvalue)
else:
print "%s is not a valid Kindle serial number or PID." % str(customvalue)
pidstringlistt = self.pids_string.split(',')
for pid in pidstringlistt:
pid = str(pid).strip()
if len(pid) == 10 or len(pid) == 8:
pids.append(pid)
else:
if len(pid) > 0:
print "'%s' is not a valid Mobipocket PID." % pid
# For linux, get PIDs by calling the right routines under WINE
if sys.platform.startswith('linux'):
k4 = False
pids.extend(self.WINEgetPIDs(path_to_ebook))
# Get supplied list of Kindle serial numbers to try from plugin customization.
serialstringlistt = self.serials_string.split(',')
for serial in serialstringlistt:
serial = str(serial).strip()
if len(serial) == 16 and serial[0] == 'B':
serials.append(serial)
else:
if len(serial) > 0:
print "'%s' is not a valid Kindle serial number." % serial
# Load any kindle info files (*.info) included Calibre's config directory.
try:
# Find Calibre's configuration directory.
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, confpath)
files = os.listdir(confpath)
print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, config_dir)
files = os.listdir(config_dir)
filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
fpath = os.path.join(confpath, filename)
fpath = os.path.join(config_dir, filename)
kInfoFiles.append(fpath)
print 'K4MobiDeDRM v%s: Kindle info/kinf file %s found in config folder.' % (plug_ver, filename)
except IOError:
@ -152,8 +162,77 @@ class K4DeDRM(FileTypePlugin):
mb.cleanup()
return of.name
def customization_help(self, gui=False):
return 'Enter 10 character PIDs and/or Kindle serial numbers, use a comma (no spaces) to separate each PID or SerialNumber from the next.'
def WINEgetPIDs(self, infile):
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
print " Getting PIDs from WINE"
outfile = os.path.join(self.alfdir + 'winepids.txt')
cmdline = 'wine python.exe ' \
+ '"'+self.alfdir + '/getk4pcpids.py"' \
+ ' "' + infile + '"' \
+ ' "' + outfile + '"'
env = os.environ
print "My wine_prefix from tweaks is ", self.wine_prefix
if ("WINEPREFIX" in env):
print "Using WINEPREFIX from the environment: ", env["WINEPREFIX"]
elif (self.wine_prefix is not None):
env['WINEPREFIX'] = self.wine_prefix
print "Using WINEPREFIX from tweaks: ", self.wine_prefix
else:
print "No wine prefix used"
print cmdline
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")
print "Conversion returned ", result
WINEpids = []
customvalues = file(outfile, 'r').readline().split(',')
for customvalue in customvalues:
customvalue = str(customvalue)
customvalue = customvalue.strip()
if len(customvalue) == 10 or len(customvalue) == 8:
WINEpids.append(customvalue)
else:
print "'%s' is not a valid PID." % customvalue
return WINEpids
def is_customizable(self):
# return true to allow customization via the Plugin->Preferences.
return True
def config_widget(self):
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.k4mobidedrm.config import ConfigWidget
return config.ConfigWidget()
def config(self):
from calibre_plugins.k4mobidedrm.config import prefs
self.pids_string = prefs['pids']
self.serials_string = prefs['serials']
self.wine_prefix = prefs['WINEPREFIX']
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.
'''
config_widget.save_settings()
self.config()
def load_resources(self, names):
ans = {}
@ -161,4 +240,4 @@ class K4DeDRM(FileTypePlugin):
for candidate in zf.namelist():
if candidate in names:
ans[candidate] = zf.read(candidate)
return ans
return ans

@ -0,0 +1,59 @@
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('Kindle Serial numbers (separate with commas, no spaces)')
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 (separate with commas, no spaces)')
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())
prefs['serials'] = str(self.serials.text())
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref
else:
prefs['WINEPREFIX'] = None

@ -214,6 +214,7 @@ class PageParser(object):
'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),
@ -239,6 +240,7 @@ class PageParser(object):
'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),
@ -246,7 +248,7 @@ class PageParser(object):
'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_number', 0, 0),
'region.orientation' : (1, 'scalar_text', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0),

@ -0,0 +1,77 @@
#!/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
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, True, [], [], [])
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic 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 <bookfile> [<outfile>]" % 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())

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others
__version__ = '4.2'
__version__ = '4.3'
class Unbuffered:
def __init__(self, stream):
@ -58,7 +58,7 @@ else:
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars
# and removal of . at start
# convert spaces to underscores
# convert underscores to spaces (we're OK with spaces in file names)
def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_'
@ -73,7 +73,7 @@ def cleanup_name(name):
# 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(' ','_')
one = one.replace('_',' ')
return one
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
title = mb.getBookTitle()
print "Processing Book: ", title
filenametitle = cleanup_name(title)
outfilename = bookname
if len(outfilename)<=8 or len(filenametitle)<=8:
outfilename = outfilename + "_" + filenametitle
elif outfilename[:8] != filenametitle[:8]:
outfilename = outfilename[:8] + "_" + filenametitle
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:

@ -262,9 +262,15 @@ def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
for serialnum in serials:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids:
pidlst.append(pid)
return pidlst

@ -164,6 +164,9 @@ class DocParser(object):
scale = self.pw
elif attr == 'line-space':
scale = self.fontsize * 2.0
if val == "":
val = 0
if not ((attr == 'hang') and (int(val) == 0)) :
pv = float(val)/scale

@ -31,11 +31,8 @@ class TpzDRMError(Exception):
# local support routines
if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import genbook
else:
import kgenpids
import genbook
# recursive zip creation support routine
def zipUpDir(myzip, tdir, localname):
@ -271,6 +268,11 @@ class TopazBook:
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"
@ -300,6 +302,11 @@ class TopazBook:
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"

@ -1,23 +1,31 @@
eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip
All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
Installation:
Go to Calibre's Preferences page. 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_vXX_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 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
Troubleshooting:
If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.
eReader PDB2PML - eReaderPDB2PML_v06_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_vXX_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 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
Troubleshooting:
If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

@ -1,6 +1,8 @@
#! /usr/bin/env python
# ineptpdf plugin __init__.py, version 0.1.5
from __future__ import with_statement
# ineptpdf plugin __init__.py
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
@ -52,13 +54,11 @@
# 0.1.2 - back port ineptpdf 8.4.X bug fixes
# 0.1.3 - add in fix for improper rejection of session bookkeys with len(bookkey) = length + 1
# 0.1.4 - update to the new calibre plugin interface
# 0.1.5 - synced to ineptpdf 7.11
"""
Decrypts Adobe ADEPT-encrypted PDF files.
"""
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
@ -116,13 +116,13 @@ def _load_crypto_libcrypto():
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])
@ -143,7 +143,7 @@ def _load_crypto_libcrypto():
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))
@ -152,7 +152,7 @@ def _load_crypto_libcrypto():
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)
@ -214,13 +214,13 @@ def _load_crypto_pycrypto():
# 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")
@ -230,22 +230,22 @@ def _load_crypto_pycrypto():
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:
@ -255,19 +255,19 @@ def _load_crypto_pycrypto():
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
@ -275,13 +275,13 @@ def _load_crypto_pycrypto():
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):
@ -290,7 +290,7 @@ def _load_crypto_pycrypto():
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:
@ -311,6 +311,7 @@ def _load_crypto_pycrypto():
return self._arc4.decrypt(data)
class AES(object):
MODE_CBC = _AES.MODE_CBC
@classmethod
def new(cls, userkey, mode, iv):
self = AES()
@ -333,7 +334,7 @@ def _load_crypto_pycrypto():
for byte in bytes:
total = (total << 8) + byte
return total
def decrypt(self, data):
return self._rsa.decrypt(data)
@ -426,7 +427,7 @@ class PSLiteral(PSObject):
def __init__(self, name):
self.name = name
return
def __repr__(self):
name = []
for char in self.name:
@ -445,22 +446,22 @@ class PSKeyword(PSObject):
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]
@ -530,11 +531,11 @@ class PSBaseParser(object):
def flush(self):
return
def close(self):
self.flush()
return
def tell(self):
return self.bufpos+self.charpos
@ -570,7 +571,7 @@ class PSBaseParser(object):
raise PSEOF('Unexpected EOF')
self.charpos = 0
return
def parse_main(self, s, i):
m = NONSPC.search(s, i)
if not m:
@ -605,11 +606,11 @@ class PSBaseParser(object):
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:
@ -620,7 +621,7 @@ class PSBaseParser(object):
# 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:
@ -634,7 +635,7 @@ class PSBaseParser(object):
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:
@ -669,7 +670,7 @@ class PSBaseParser(object):
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:
@ -817,7 +818,7 @@ class PSStackParser(PSBaseParser):
PSBaseParser.__init__(self, fp)
self.reset()
return
def reset(self):
self.context = []
self.curtype = None
@ -858,10 +859,10 @@ class PSStackParser(PSBaseParser):
def do_keyword(self, pos, token):
return
def nextobject(self, direct=False):
'''
Yields a list of objects: keywords, literals, strings,
Yields a list of objects: keywords, literals, strings,
numbers, arrays and dictionaries. Arrays and dictionaries
are represented as Python sequence and dictionaries.
'''
@ -930,7 +931,7 @@ class PDFNotImplementedError(PSException): pass
## PDFObjRef
##
class PDFObjRef(PDFObject):
def __init__(self, doc, objid, genno):
if objid == 0:
if STRICT:
@ -1045,25 +1046,25 @@ def stream_value(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
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
@ -1080,7 +1081,7 @@ class PDFStream(PDFObject):
else:
if eol in ('\r', '\n', '\r\n'):
rawdata = rawdata[:length]
self.dic = dic
self.rawdata = rawdata
self.decipher = decipher
@ -1094,7 +1095,7 @@ class PDFStream(PDFObject):
self.objid = objid
self.genno = genno
return
def __repr__(self):
if self.rawdata:
return '<PDFStream(%r): raw=%d, %r>' % \
@ -1178,7 +1179,7 @@ class PDFStream(PDFObject):
data = self.decipher(self.objid, self.genno, data)
return data
## PDF Exceptions
##
class PDFSyntaxError(PDFException): pass
@ -1243,7 +1244,7 @@ class PDFXRef(object):
self.offsets[objid] = (int(genno), int(pos))
self.load_trailer(parser)
return
KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
def load_trailer(self, parser):
try:
@ -1284,7 +1285,7 @@ class PDFXRefStream(object):
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
@ -1302,7 +1303,7 @@ class PDFXRefStream(object):
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:
@ -1353,7 +1354,7 @@ class PDFDocument(object):
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
# the header, the encryption information, and the access rights
# for the document.
self.ready = True
# Retrieve the information of each header that was appended
@ -1429,7 +1430,7 @@ class PDFDocument(object):
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')
pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
edclist = []
for pair in edcdata.split('\n'):
edclist.append(pair)
@ -1449,9 +1450,9 @@ class PDFDocument(object):
raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
else:
cutter = -1 * ord(pdrlpol[-1])
pdrlpol = pdrlpol[:cutter]
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
@ -1471,14 +1472,14 @@ class PDFDocument(object):
EncMetadata = str_value(param['EncryptMetadata'])
except:
EncMetadata = 'True'
self.is_printable = bool(P & 4)
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)
self.is_formprintable = bool(P & 2048)
# Algorithm 3.2
password = (password+self.PASSWORD_PADDING)[:32] # 1
hash = hashlib.md5(password) # 2
@ -1587,7 +1588,7 @@ class PDFDocument(object):
hash = hashlib.md5(key)
key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
return key
def genkey_v3(self, objid, genno):
objid = struct.pack('<L', objid ^ 0x3569ac)
genno = struct.pack('<L', genno ^ 0xca96)
@ -1627,14 +1628,14 @@ class PDFDocument(object):
#print cutter
plaintext = plaintext[:cutter]
return plaintext
def decrypt_rc4(self, objid, genno, data):
key = self.genkey(objid, genno)
return ARC4.new(key).decrypt(data)
KEYWORD_OBJ = PSKeywordTable.intern('obj')
def getobj(self, objid):
if not self.ready:
raise PDFException('PDFDocument not initialized')
@ -1704,7 +1705,7 @@ class PDFDocument(object):
## if x:
## objid1 = x[-2]
## genno = x[-1]
##
##
if kwd is not self.KEYWORD_OBJ:
raise PDFSyntaxError(
'Invalid object spec: offset=%r' % index)
@ -1716,7 +1717,7 @@ class PDFDocument(object):
self.objs[objid] = obj
return obj
class PDFObjStmRef(object):
maxindex = 0
def __init__(self, objid, stmid, index):
@ -1726,7 +1727,7 @@ class PDFObjStmRef(object):
if index > PDFObjStmRef.maxindex:
PDFObjStmRef.maxindex = index
## PDFParser
##
class PDFParser(PSStackParser):
@ -1752,7 +1753,7 @@ class PDFParser(PSStackParser):
if token is self.KEYWORD_ENDOBJ:
self.add_results(*self.pop(4))
return
if token is self.KEYWORD_R:
# reference to indirect object
try:
@ -1763,7 +1764,7 @@ class PDFParser(PSStackParser):
except PSSyntaxError:
pass
return
if token is self.KEYWORD_STREAM:
# stream object
((_,dic),) = self.pop(1)
@ -1803,7 +1804,7 @@ class PDFParser(PSStackParser):
obj = PDFStream(dic, data, self.doc.decipher)
self.push((pos, obj))
return
# others
self.push((pos, token))
return
@ -1839,7 +1840,7 @@ class PDFParser(PSStackParser):
xref.load(self)
else:
if token is not self.KEYWORD_XREF:
raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
(pos, token))
self.nextline()
xref = PDFXRef()
@ -1854,7 +1855,7 @@ class PDFParser(PSStackParser):
pos = int_value(trailer['Prev'])
self.read_xref_from(pos, xrefs)
return
# read xref tables and trailers
def read_xref(self):
xrefs = []
@ -1973,7 +1974,7 @@ class PDFSerializer(object):
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)
@ -1993,7 +1994,7 @@ class PDFSerializer(object):
while maxindex >= power:
fl3 += 1
power *= 256
index = []
first = None
prev = None
@ -2020,14 +2021,14 @@ class PDFSerializer(object):
# 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),
'W': [1, fl2, fl3], 'Length': len(data),
'Filter': LITERALS_FLATE_DECODE[0],
'Root': trailer['Root'],}
if 'Info' in trailer:
@ -2049,9 +2050,9 @@ class PDFSerializer(object):
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')
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
@ -2075,21 +2076,21 @@ class PDFSerializer(object):
elif isinstance(obj, bool):
if self.last.isalnum():
self.write(' ')
self.write(str(obj).lower())
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(' ')
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)')
self.write('(deleted)')
else:
data = obj.get_decdata()
self.serialize_object(obj.dic)
@ -2101,7 +2102,7 @@ class PDFSerializer(object):
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)
@ -2136,7 +2137,7 @@ class IneptPDFDeDRM(FileTypePlugin):
Credit given to I <3 Cabbages for the original stand-alone scripts.'
supported_platforms = ['linux', 'osx', 'windows']
author = 'DiapDealer'
version = (0, 1, 4)
version = (0, 1, 5)
minimum_calibre_version = (0, 7, 55) # for the new plugin interface
file_types = set(['pdf'])
on_import = True

@ -385,17 +385,22 @@ def GetIDString():
if isNewInstall():
mungedmac = GetMACAddressMunged()
if len(mungedmac) > 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'
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True
# add any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf file: ' + resline)
found = True
# add any .kinf2011 files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
if not found:
print('No kindle-info files have been found.')
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
return kInfoFiles
# determine type of kindle info provided and return a

@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
vsn = GetVolumeSerialNumber()
print('Using Volume Serial Number for ID: '+vsn)
return vsn
def getLastError():
GetLastError = kernel32.GetLastError
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
print "searching for kinfoFiles in ", path
print('searching for kinfoFiles in ' + path)
found = False
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
print('No kindle.info files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for newer (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 not os.path.isfile(kinfopath):
print('No K4PC 1.5.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath):
print('No K4PC 1.6.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
if not os.path.isfile(kinfopath):
print('No K4PC 1.9.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kinf2011 file: ' + kinfopath)
kInfoFiles.append(kinfopath)
if not found:
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
return kInfoFiles

@ -57,8 +57,11 @@
# 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.35'
__version__ = '0.37'
import sys
@ -168,9 +171,10 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
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()
@ -198,6 +202,7 @@ class MobiBook:
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
@ -248,18 +253,19 @@ class MobiBook:
65001 : 'utf-8',
}
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
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]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
return unicode(title, codec).encode('utf-8')
def getPIDMetaInfo(self):
@ -375,7 +381,7 @@ class MobiBook:
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. Please report this failure for help.")
raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
@ -411,26 +417,26 @@ class MobiBook:
print "done"
return
def getUnencryptedBook(infile,pid):
def getUnencryptedBook(infile,pid,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile,announce)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist):
def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile, announce)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1
@ -442,7 +448,7 @@ def main(argv=sys.argv):
else:
pidlist = {}
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist)
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e

@ -1,49 +1,46 @@
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\paperw11900\paperh16840\margl1440\margr1440\vieww10320\viewh9840\viewkind0
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\paperw11900\paperh16840\margl1440\margr1440\vieww12360\viewh16560\viewkind0
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qc\pardirnatural
\f0\b\fs24 \cf0 ReadMe_DeDRM_X.X
\f0\b\fs24 \cf0 DeDRM ReadMe
\b0 \
\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\cf0 \
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qj\pardirnatural
\cf0 DeDRM_X.X is an AppleScript droplet that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM droplet to have the DRM removed. It repackages the all the "tools" DeDRM python software in one easy to use program that remembers preferences and settings.\
\cf0 DeDRM is an application that packs all of the python drm-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 ePub and PDF ebooks.\
\
It should work without manual configuration with Kindle for Mac ebooks and Adobe Adept epub and pdf ebooks.\
To remove the DRM of Kindle ebooks from eInk Kindles, eReader pdb ebooks, Barnes and Noble ePubs, or Mobipocket ebooks, you must first run DeDRM application (by double-clicking it) and set some additional Preferences including:\
\
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 droplet and set some additional Preferences including:\
\
Mobipocket, Kindle for iPhone/iPad/iPodTouch: 10 digit PID\
Kindle (not Kindle Fire): 16 digit Serial Number\
Barnes & Noble key files (bnepubkey.b64)\
eReader Social DRM: (Name:Last 8 digits of CC number)\
Additional Above Adept key files (.der)\
Location for DRM-free ebooks.\
Barnes & Noble ePub: Name and CC number or key file (bnepubkey.b64)\
eReader Social DRM: Name and 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 droplet to remove the DRM.\
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.\
\
This program requires Mac OS X 10.5, 10.5 or 10.7 (Leopard, Snow Leopard or Lion)\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\cf0 \
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.5 or above. \
\
\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
\b Installation\
\b \cf0 Installation
\b0 \
1. From tools_vX.X\\DeDRM_Applications\\, double click on DeDRM_X.X.zip to extract its contents. \
Drag the DeDRM application from from tools_vX.X\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\
\
2. Move the resulting DeDRM X.X.app AppleScript droplet to wherever you keep you other applications. (Typically your Applications folder.)\
\
3. Optionally drag it into your dock, to make it easily available.\
\
\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 Use\
\b Troubleshooting\
\b0 \
1. To set the preferences simply double-click the Applescript droplet in your Applications folder or click on its icon in your dock, and follow the instructions in the dialogs.\
\
2. Drag & Drop DRMed ebooks or folders containing DRMed ebooks onto the Application, either in your Applications folder, or the icon in your dock.}
\b0 A log is created on your desktop containing detailed information from all the scripts. If you have any problems decrypting your ebooks, quote the contents of this log in a comment at Apprentice Alf's blog.}

@ -24,17 +24,17 @@
<key>CFBundleExecutable</key>
<string>droplet</string>
<key>CFBundleGetInfoString</key>
<string>DeDRM 5.1, Written 20102012 by Apprentice Alf and others.</string>
<string>DeDRM 5.2, Written 20102012 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key>
<string>droplet</string>
<string>DeDRM</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>DeDRM 5.1</string>
<string>DeDRM 5.2</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.1</string>
<string>5.2</string>
<key>CFBundleSignature</key>
<string>dplt</string>
<key>LSMinimumSystemVersion</key>
@ -43,16 +43,12 @@
<true/>
<key>WindowState</key>
<dict>
<key>dividerCollapsed</key>
<false/>
<key>eventLogLevel</key>
<integer>-1</integer>
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>460</real>
<real>554</real>
<key>savedFrame</key>
<string>1518 90 1316 746 1440 -150 1680 1050 </string>
<string>42 60 922 818 0 0 1440 878 </string>
<key>selectedTabView</key>
<string>event log</string>
</dict>

@ -2,10 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>10K549</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>DeDRM Progress</string>
<key>CFBundleGetInfoString</key>
<string>DeDRM Progress 1.1, Written 2010, 2012 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key>
<string>DeDRM Progress</string>
<key>CFBundleIdentifier</key>
<string>com.apprenticealf.DeDRMProgress</string>
<key>CFBundleInfoDictionaryVersion</key>
@ -15,11 +21,25 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>DTCompiler</key>
<string></string>
<key>DTPlatformBuild</key>
<string>10M2518</string>
<key>DTPlatformVersion</key>
<string>PG</string>
<key>DTSDKBuild</key>
<string>9L31a</string>
<key>DTSDKName</key>
<string>macosx10.5</string>
<key>DTXcode</key>
<string>0400</string>
<key>DTXcodeBuild</key>
<string>10M2518</string>
<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>NSMainNibFile</key>

@ -0,0 +1,899 @@
#! /usr/bin/python
"""
Comprehensive Mazama Book DRM with Topaz Cryptography V2.2
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
y2/pHuYme7U1TsgSjwIDAQAB
-----END PUBLIC KEY-----
"""
from __future__ import with_statement
import csv
import sys
import os
import getopt
import zlib
from struct import pack
from struct import unpack
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import Tkinter
import Tkconstants
import tkMessageBox
import traceback
import hashlib
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
global kindleDatabase
global bookFile
global bookPayloadOffset
global bookHeaderRecords
global bookMetadata
global bookKey
global command
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class CMBDTCError(Exception):
pass
class CMBDTCFatal(Exception):
pass
#
# Stolen stuff
#
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return vsn.value
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise CMBDTCFatal("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Returns the MD5 digest of "message"
#
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
#
# Returns the MD5 digest of "message"
#
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
#
# Open the book file at path
#
def openBook(path):
try:
return open(path,'rb')
except:
raise CMBDTCFatal("Could not open book file: " + path)
#
# Encode the bytes in data with the characters in map
#
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
#
# Hash the bytes in data and then encode the digest with the characters in map
#
def encodeHash(data,map):
return encode(MD5(data),map)
#
# Decode the string in data with the characters in map. Returns the decoded bytes
#
def decode(data,map):
result = ""
for i in range (0,len(data),2):
high = map.find(data[i])
low = map.find(data[i+1])
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
#
# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
#
# Parse the Kindle.info file and return the records as a list of key-values
#
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
#
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
#
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return name
#
# Print all the records from the kindle.info file (option -i)
#
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------\n")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
#
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
return CryptUnprotectData(encryptedValue,"")
#
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
#
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
#
# Get a 7 bit encoded number from the book file
#
def bookReadEncodedNumber():
flag = False
data = ord(bookFile.read(1))
if data == 0xFF:
flag = True
data = ord(bookFile.read(1))
if data >= 0x80:
datax = (data & 0x7F)
while data >= 0x80 :
data = ord(bookFile.read(1))
datax = (datax <<7) + (data & 0x7F)
data = datax
if flag:
data = -data
return data
#
# Encode a number in 7 bit format
#
def encodeNumber(number):
result = ""
negative = False
flag = 0
if number < 0 :
number = -number + 1
negative = True
while True:
byte = number & 0x7F
number = number >> 7
byte += flag
result += chr(byte)
flag = 0x80
if number == 0 :
if (byte == 0xFF and negative == False) :
result += chr(0x80)
break
if negative:
result += chr(0xFF)
return result[::-1]
#
# Get a length prefixed string from the file
#
def bookReadString():
stringLength = bookReadEncodedNumber()
return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]
#
# Returns a length prefixed string
#
def lengthPrefixString(data):
return encodeNumber(len(data))+data
#
# Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...]
#
def bookReadHeaderRecordData():
nbValues = bookReadEncodedNumber()
values = []
for i in range (0,nbValues):
values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
return values
#
# Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...]
#
def parseTopazHeaderRecord():
if ord(bookFile.read(1)) != 0x63:
raise CMBDTCFatal("Parse Error : Invalid Header")
tag = bookReadString()
record = bookReadHeaderRecordData()
return [tag,record]
#
# Parse the header of a Topaz file, get all the header records and the offset for the payload
#
def parseTopazHeader():
global bookHeaderRecords
global bookPayloadOffset
magic = unpack("4s",bookFile.read(4))[0]
if magic != 'TPZ0':
raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file")
nbRecords = bookReadEncodedNumber()
bookHeaderRecords = {}
for i in range (0,nbRecords):
result = parseTopazHeaderRecord()
bookHeaderRecords[result[0]] = result[1]
if ord(bookFile.read(1)) != 0x64 :
raise CMBDTCFatal("Parse Error : Invalid Header")
bookPayloadOffset = bookFile.tell()
#
# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
#
def getBookPayloadRecord(name, index):
encrypted = False
try:
recordOffset = bookHeaderRecords[name][index][0]
except:
raise CMBDTCFatal("Parse Error : Invalid Record, record not found")
bookFile.seek(bookPayloadOffset + recordOffset)
tag = bookReadString()
if tag != name :
raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match")
recordIndex = bookReadEncodedNumber()
if recordIndex < 0 :
encrypted = True
recordIndex = -recordIndex -1
if recordIndex != index :
raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match")
if bookHeaderRecords[name][index][2] != 0 :
record = bookFile.read(bookHeaderRecords[name][index][2])
else:
record = bookFile.read(bookHeaderRecords[name][index][1])
if encrypted:
ctx = topazCryptoInit(bookKey)
record = topazCryptoDecrypt(record,ctx)
return record
#
# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
#
def extractBookPayloadRecord(name, index, filename):
compressed = False
try:
compressed = bookHeaderRecords[name][index][2] != 0
record = getBookPayloadRecord(name,index)
except:
print("Could not find record")
if compressed:
try:
record = zlib.decompress(record)
except:
raise CMBDTCFatal("Could not decompress record")
if filename != "":
try:
file = open(filename,"wb")
file.write(record)
file.close()
except:
raise CMBDTCFatal("Could not write to destination file")
else:
print(record)
#
# return next record [key,value] from the book metadata from the current book position
#
def readMetadataRecord():
return [bookReadString(),bookReadString()]
#
# Parse the metadata record from the book payload and return a list of [key,values]
#
def parseMetadata():
global bookHeaderRecords
global bookPayloadAddress
global bookMetadata
bookMetadata = {}
bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0])
tag = bookReadString()
if tag != "metadata" :
raise CMBDTCFatal("Parse Error : Record Names Don't Match")
flags = ord(bookFile.read(1))
nbRecords = ord(bookFile.read(1))
for i in range (0,nbRecords) :
record =readMetadataRecord()
bookMetadata[record[0]] = record[1]
#
# Returns two bit at offset from a bit field
#
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
#
# Returns the six bits at offset from a bit field
#
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
#
# 8 bits to six bits encoding from hash to generate PID string
#
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Context initialisation for the Topaz Crypto
#
def topazCryptoInit(key):
ctx1 = 0x0CAFFE19E
for keyChar in key:
keyByte = ord(keyChar)
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
return [ctx1,ctx2]
#
# decrypt data with the context prepared by topazCryptoInit()
#
def topazCryptoDecrypt(data, ctx):
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
for dataChar in data:
dataByte = ord(dataChar)
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
#
# Decrypt a payload record with the PID
#
def decryptRecord(data,PID):
ctx = topazCryptoInit(PID)
return topazCryptoDecrypt(data, ctx)
#
# Try to decrypt a dkey record (contains the book PID)
#
def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID)
fields = unpack("3sB8sB8s3s",record)
if fields[0] != "PID" or fields[5] != "pid" :
raise CMBDTCError("Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 :
raise CMBDTCError("Record didn't contain correct length fields")
elif fields[2] != PID :
raise CMBDTCError("Record didn't contain PID")
return fields[4]
#
# Decrypt all the book's dkey records (contain the book PID)
#
def decryptDkeyRecords(data,PID):
nbKeyRecords = ord(data[0])
records = []
data = data[1:]
for i in range (0,nbKeyRecords):
length = ord(data[0])
try:
key = decryptDkeyRecord(data[1:length+1],PID)
records.append(key)
except CMBDTCError:
pass
data = data[1+length:]
return records
#
# Encryption table used to generate the device PID
#
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
#
# Seed value used to generate the device PID
#
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
#
# Generate the device PID
#
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
#
# Create decrypted book payload
#
def createDecryptedPayload(payload):
# store data to be able to create the header later
headerData= []
currentOffset = 0
# Add social DRM to decrypted files
try:
data = getKindleInfoValueForKey("kindle.name.info")+":"+ getKindleInfoValueForKey("login")
if payload!= None:
payload.write(lengthPrefixString("sdrm"))
payload.write(encodeNumber(0))
payload.write(data)
else:
currentOffset += len(lengthPrefixString("sdrm"))
currentOffset += len(encodeNumber(0))
currentOffset += len(data)
except:
pass
for headerRecord in bookHeaderRecords:
name = headerRecord
newRecord = []
if name != "dkey" :
for index in range (0,len(bookHeaderRecords[name])) :
offset = currentOffset
if payload != None:
# write tag
payload.write(lengthPrefixString(name))
# write data
payload.write(encodeNumber(index))
payload.write(getBookPayloadRecord(name, index))
else :
currentOffset += len(lengthPrefixString(name))
currentOffset += len(encodeNumber(index))
currentOffset += len(getBookPayloadRecord(name, index))
newRecord.append([offset,bookHeaderRecords[name][index][1],bookHeaderRecords[name][index][2]])
headerData.append([name,newRecord])
return headerData
#
# Create decrypted book
#
def createDecryptedBook(outputFile):
outputFile = open(outputFile,"wb")
# Write the payload in a temporary file
headerData = createDecryptedPayload(None)
outputFile.write("TPZ0")
outputFile.write(encodeNumber(len(headerData)))
for header in headerData :
outputFile.write(chr(0x63))
outputFile.write(lengthPrefixString(header[0]))
outputFile.write(encodeNumber(len(header[1])))
for numbers in header[1] :
outputFile.write(encodeNumber(numbers[0]))
outputFile.write(encodeNumber(numbers[1]))
outputFile.write(encodeNumber(numbers[2]))
outputFile.write(chr(0x64))
createDecryptedPayload(outputFile)
outputFile.close()
#
# Set the command to execute by the programm according to cmdLine parameters
#
def setCommand(name) :
global command
if command != "" :
raise CMBDTCFatal("Invalid command line parameters")
else :
command = name
#
# Program usage
#
def usage():
print("\nUsage:")
print("\nCMBDTC.py [options] bookFileName\n")
print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
print("-d Saves a decrypted copy of the book")
print("-r Prints or writes to disk a record indicated in the form name:index (e.g \"img:0\")")
print("-o Output file name to write records and decrypted books")
print("-v Verbose (can be used several times)")
print("-i Prints kindle.info database")
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
global bookMetadata
global bookKey
global bookFile
global command
progname = os.path.basename(argv[0])
verbose = 0
recordName = ""
recordIndex = 0
outputFile = ""
PIDs = []
kindleDatabase = None
command = ""
try:
opts, args = getopt.getopt(sys.argv[1:], "vdir:o:p:")
except getopt.GetoptError, err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
if len(opts) == 0 and len(args) == 0 :
usage()
sys.exit(2)
for o, a in opts:
if o == "-v":
verbose+=1
if o == "-i":
setCommand("printInfo")
if o =="-o":
if a == None :
raise CMBDTCFatal("Invalid parameter for -o")
outputFile = a
if o =="-r":
setCommand("printRecord")
try:
recordName,recordIndex = a.split(':')
except:
raise CMBDTCFatal("Invalid parameter for -r")
if o =="-p":
PIDs.append(a)
if o =="-d":
setCommand("doit")
if command == "" :
raise CMBDTCFatal("No action supplied on command line")
#
# Read the encrypted database
#
try:
kindleDatabase = parseKindleInfo()
except Exception, message:
if verbose>0:
print(message)
if kindleDatabase != None :
if command == "printInfo" :
printKindleInfo()
#
# Compute the DSN
#
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
if verbose >1:
print("DSN: " + DSN)
#
# Compute the device PID
#
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
PIDs.append(devicePID)
if verbose > 0:
print("Device PID: " + devicePID)
#
# Open book and parse metadata
#
if len(args) == 1:
bookFile = openBook(args[0])
parseTopazHeader()
parseMetadata()
#
# Compute book PID
#
# Get the account token
if kindleDatabase != None:
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
if verbose >1:
print("Account Token: " + kindleAccountToken)
keysRecord = bookMetadata["keys"]
keysRecordRecord = bookMetadata[keysRecord]
pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
bookPID = encodePID(pidHash)
PIDs.append(bookPID)
if verbose > 0:
print ("Book PID: " + bookPID )
#
# Decrypt book key
#
dkey = getBookPayloadRecord('dkey', 0)
bookKeys = []
for PID in PIDs :
bookKeys+=decryptDkeyRecords(dkey,PID)
if len(bookKeys) == 0 :
if verbose > 0 :
print ("Book key could not be found. Maybe this book is not registered with this device.")
else :
bookKey = bookKeys[0]
if verbose > 0:
print("Book key: " + bookKey.encode('hex'))
if command == "printRecord" :
extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
if outputFile != "" and verbose>0 :
print("Wrote record to file: "+outputFile)
elif command == "doit" :
if outputFile!="" :
createDecryptedBook(outputFile)
if verbose >0 :
print ("Decrypted book saved. Don't pirate!")
elif verbose > 0:
print("Output file name was not supplied.")
return 0
if __name__ == '__main__':
sys.exit(main())

@ -0,0 +1,59 @@
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('Kindle Serial numbers (separate with commas, no spaces)')
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 (separate with commas, no spaces)')
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())
prefs['serials'] = str(self.serials.text())
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref
else:
prefs['WINEPREFIX'] = None

@ -214,6 +214,7 @@ class PageParser(object):
'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),
@ -239,6 +240,7 @@ class PageParser(object):
'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),
@ -246,7 +248,7 @@ class PageParser(object):
'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_number', 0, 0),
'region.orientation' : (1, 'scalar_text', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

@ -0,0 +1,77 @@
#!/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
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, True, [], [], [])
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic 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 <bookfile> [<outfile>]" % 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())

@ -1,5 +1,5 @@
#! /usr/bin/env python
# ineptpdf.pyw, version 7.9
# ineptpdf.pyw, version 7.11
from __future__ import with_statement
@ -34,6 +34,8 @@ from __future__ import with_statement
# 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.
@ -293,6 +295,7 @@ def _load_crypto_pycrypto():
return self._arc4.decrypt(data)
class AES(object):
MODE_CBC = _AES.MODE_CBC
@classmethod
def new(cls, userkey, mode, iv):
self = AES()
@ -2199,11 +2202,19 @@ class DecryptionDialog(Tkinter.Frame):
def decryptBook(keypath, inpath, outpath):
with open(inpath, 'rb') as inf:
serializer = PDFSerializer(inf, keypath)
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
serializer.dump(outf)
try:
serializer.dump(outf)
except:
print "error writing pdf."
return 1
return 0

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others
__version__ = '4.2'
__version__ = '4.4'
class Unbuffered:
def __init__(self, stream):
@ -58,7 +58,7 @@ else:
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars
# and removal of . at start
# convert spaces to underscores
# convert underscores to spaces (we're OK with spaces in file names)
def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_'
@ -73,7 +73,7 @@ def cleanup_name(name):
# 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(' ','_')
one = one.replace('_',' ')
return one
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
title = mb.getBookTitle()
print "Processing Book: ", title
filenametitle = cleanup_name(title)
outfilename = bookname
if len(outfilename)<=8 or len(filenametitle)<=8:
outfilename = outfilename + "_" + filenametitle
elif outfilename[:8] != filenametitle[:8]:
outfilename = outfilename[:8] + "_" + filenametitle
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:

@ -385,17 +385,22 @@ def GetIDString():
if isNewInstall():
mungedmac = GetMACAddressMunged()
if len(mungedmac) > 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'
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True
# add any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf file: ' + resline)
found = True
# add any .kinf2011 files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
if not found:
print('No kindle-info files have been found.')
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
return kInfoFiles
# determine type of kindle info provided and return a

@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
vsn = GetVolumeSerialNumber()
print('Using Volume Serial Number for ID: '+vsn)
return vsn
def getLastError():
GetLastError = kernel32.GetLastError
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
print "searching for kinfoFiles in ", path
print('searching for kinfoFiles in ' + path)
found = False
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
print('No kindle.info files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for newer (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 not os.path.isfile(kinfopath):
print('No K4PC 1.5.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath):
print('No K4PC 1.6.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
if not os.path.isfile(kinfopath):
print('No K4PC 1.9.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kinf2011 file: ' + kinfopath)
kInfoFiles.append(kinfopath)
if not found:
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
return kInfoFiles

@ -262,9 +262,15 @@ def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
for serialnum in serials:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids:
pidlst.append(pid)
return pidlst

@ -0,0 +1,91 @@
#!/usr/bin/python
# Mobipocket PID calculator v0.2 for Amazon Kindle.
# Copyright (c) 2007, 2009 Igor Skochinsky <skochinsky@mail.ru>
# 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 <Kindle Serial Number>/<iPhone/iPod Touch UDID>"
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())

@ -57,8 +57,11 @@
# 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.35'
__version__ = '0.37'
import sys
@ -168,9 +171,10 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
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()
@ -198,6 +202,7 @@ class MobiBook:
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
@ -248,18 +253,19 @@ class MobiBook:
65001 : 'utf-8',
}
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
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]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
return unicode(title, codec).encode('utf-8')
def getPIDMetaInfo(self):
@ -375,7 +381,7 @@ class MobiBook:
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. Please report this failure for help.")
raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
@ -411,26 +417,26 @@ class MobiBook:
print "done"
return
def getUnencryptedBook(infile,pid):
def getUnencryptedBook(infile,pid,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile,announce)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist):
def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile, announce)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1
@ -442,7 +448,7 @@ def main(argv=sys.argv):
else:
pidlist = {}
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist)
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e

@ -1,444 +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
__version__ = '0.32'
import sys
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
class DrmException(Exception):
pass
#
# MobiBook Utility Routines
#
# Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in xrange(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
def checksumPid(s):
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
crc = crc ^ (crc >> 16)
res = s
l = len(letters)
for i in (0,1):
b = crc & 0xff
pos = (b // l) ^ (b % l)
res += letters[pos%l]
crc >>= 8
return res
def getSizeOfTrailingDataEntries(ptr, size, flags):
def getSizeOfTrailingDataEntry(ptr, size):
bitpos, result = 0, 0
if size <= 0:
return result
while True:
v = ord(ptr[size-1])
result |= (v & 0x7F) << bitpos
bitpos += 7
size -= 1
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
return result
num = 0
testflags = flags >> 1
while testflags:
if testflags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num)
testflags >>= 1
# Check the low bit to see if there's multibyte data present.
# if multibyte data is included in the encryped data, we'll
# have already cleared this flag.
if flags & 1:
num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num
class MobiBook:
def loadSection(self, section):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
else:
endoff = self.sections[section + 1][0]
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 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_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 = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
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 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. Most likely the correct PID has not been given.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
if pid=="00000000":
print "File has default encryption, no specific PID."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)
# decrypt sections
print "Decrypting. Please wait . . .",
self.mobi_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0:
print ".",
# print "record %d, extra_size %d" %(i,extra_size)
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1:
self.print_replica = (decoded_data[0:4] == '%MOP')
self.mobi_data += decoded_data
if extra_size > 0:
self.mobi_data += data[-extra_size:]
if self.num_sections > self.records+1:
self.mobi_data += self.data_file[self.sections[self.records+1][0]:]
print "done"
return
def getUnencryptedBook(infile,pid):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1
else:
infile = argv[1]
outfile = argv[2]
if len(argv) is 4:
pidlist = argv[3].split(',')
else:
pidlist = {}
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

@ -164,6 +164,9 @@ class DocParser(object):
scale = self.pw
elif attr == 'line-space':
scale = self.fontsize * 2.0
if val == "":
val = 0
if not ((attr == 'hang') and (int(val) == 0)) :
pv = float(val)/scale

@ -31,11 +31,8 @@ class TpzDRMError(Exception):
# local support routines
if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import genbook
else:
import kgenpids
import genbook
# recursive zip creation support routine
def zipUpDir(myzip, tdir, localname):
@ -271,6 +268,11 @@ class TopazBook:
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"
@ -300,6 +302,11 @@ class TopazBook:
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"

@ -1,35 +1,29 @@
ReadMe_DeDRM_vX.X_WinApp
ReadMe_DeDRM_v5.2_WinApp
-----------------------
DeDRM_vX.X_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.
DeDRM_v5.2_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:
Kindle 16 digit Serial Number
Barnes & Noble key files (bnepubkey.b64)
eReader Social DRM: (Name:Last 8 digits of CC number)
MobiPocket, Kindle for iPhone/iPad/iPodTouch 10 digit PID
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 the proper 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
------------
1. From tools_vX.X\DeDRM_Applications\, right click on DeDRM_v_X.X_WinApp.zip and fully Extract its contents.
2. Move the resulting DeDRM_vX.X_WinApp folder to whereever you keep you other programs.
(I typically use an "Applications" folder inside of my home directory)
3. Open the folder, and create a short-cut to DeDRM_Drop_Target and move that short-cut to your Desktop.
1. In tools_v5.2\DeDRM_Applications\Windows, right click on DeDRM_5.2_Win.zip and fully extract its contents using "Extract All...", saving to your "My Documents" folder.
4. To set the preferences simply double-click on your just created short-cut.
2. Open the DeDRM_5.2_Win folder you've just created, 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.
If you already have a correct version of Python and PyCrypto installed and in your path, you are ready to go!

@ -21,7 +21,7 @@ import re
import simpleprefs
__version__ = '5.0'
__version__ = '5.2'
class DrmException(Exception):
pass
@ -38,7 +38,7 @@ class MainApp(Tk):
['serials', 'seriallist.txt'],
['sdrms' , 'sdrmlist.txt' ],
['outdir' , 'outdir.txt' ]]
self.po = simpleprefs.SimplePrefs('DeDRM',description)
self.po = simpleprefs.SimplePrefs("DeDRM",description)
if self.dnd:
self.cd = ConvDialog(self)
prefs = self.getPreferences()
@ -95,7 +95,7 @@ class PrefsDialog(Toplevel):
Toplevel.__init__(self, mainapp)
self.withdraw()
self.protocol("WM_DELETE_WINDOW", self.withdraw)
self.title("DeDRM")
self.title("DeDRM " + __version__)
self.prefs_array = prefs_array
self.status = Tkinter.Label(self, text='Setting Preferences')
self.status.pack(fill=Tkconstants.X, expand=1)
@ -566,6 +566,7 @@ def main(argv=sys.argv):
infilelst = argv[1:]
filenames = []
for infile in infilelst:
infile = infile.decode(sys.getfilesystemencoding())
print infile
infile = infile.replace('"','')
infile = os.path.abspath(infile)

@ -0,0 +1,59 @@
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('Kindle Serial numbers (separate with commas, no spaces)')
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 (separate with commas, no spaces)')
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())
prefs['serials'] = str(self.serials.text())
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref
else:
prefs['WINEPREFIX'] = None

@ -214,6 +214,7 @@ class PageParser(object):
'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),
@ -239,6 +240,7 @@ class PageParser(object):
'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),
@ -246,7 +248,7 @@ class PageParser(object):
'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_number', 0, 0),
'region.orientation' : (1, 'scalar_text', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0),

@ -0,0 +1,77 @@
#!/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
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, True, [], [], [])
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic 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 <bookfile> [<outfile>]" % 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())

@ -1,5 +1,5 @@
#! /usr/bin/env python
# ineptpdf.pyw, version 7.9
# ineptpdf.pyw, version 7.11
from __future__ import with_statement
@ -34,6 +34,8 @@ from __future__ import with_statement
# 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.
@ -2200,11 +2202,19 @@ class DecryptionDialog(Tkinter.Frame):
def decryptBook(keypath, inpath, outpath):
with open(inpath, 'rb') as inf:
serializer = PDFSerializer(inf, keypath)
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
serializer.dump(outf)
try:
serializer.dump(outf)
except:
print "error writing pdf."
return 1
return 0

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others
__version__ = '4.2'
__version__ = '4.4'
class Unbuffered:
def __init__(self, stream):
@ -58,7 +58,7 @@ else:
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars
# and removal of . at start
# convert spaces to underscores
# convert underscores to spaces (we're OK with spaces in file names)
def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_'
@ -73,7 +73,7 @@ def cleanup_name(name):
# 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(' ','_')
one = one.replace('_',' ')
return one
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
title = mb.getBookTitle()
print "Processing Book: ", title
filenametitle = cleanup_name(title)
outfilename = bookname
if len(outfilename)<=8 or len(filenametitle)<=8:
outfilename = outfilename + "_" + filenametitle
elif outfilename[:8] != filenametitle[:8]:
outfilename = outfilename[:8] + "_" + filenametitle
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:

@ -40,7 +40,7 @@ def _load_crypto_libcrypto():
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
# note: the ivec string, and output buffer are mutable
# 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);
@ -385,17 +385,22 @@ def GetIDString():
if isNewInstall():
mungedmac = GetMACAddressMunged()
if len(mungedmac) > 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'
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True
# add any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf file: ' + resline)
found = True
# add any .kinf2011 files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
if not found:
print('No kindle-info files have been found.')
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
return kInfoFiles
# determine type of kindle info provided and return a

@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
vsn = GetVolumeSerialNumber()
print('Using Volume Serial Number for ID: '+vsn)
return vsn
def getLastError():
GetLastError = kernel32.GetLastError
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
print "searching for kinfoFiles in ", path
print('searching for kinfoFiles in ' + path)
found = False
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
print('No kindle.info files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for newer (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 not os.path.isfile(kinfopath):
print('No K4PC 1.5.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath):
print('No K4PC 1.6.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
if not os.path.isfile(kinfopath):
print('No K4PC 1.9.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kinf2011 file: ' + kinfopath)
kInfoFiles.append(kinfopath)
if not found:
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
return kInfoFiles

@ -262,9 +262,15 @@ def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
for serialnum in serials:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids:
pidlst.append(pid)
return pidlst

@ -57,8 +57,11 @@
# 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.35'
__version__ = '0.37'
import sys
@ -168,9 +171,10 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
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()
@ -198,6 +202,7 @@ class MobiBook:
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
@ -248,18 +253,19 @@ class MobiBook:
65001 : 'utf-8',
}
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
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]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
return unicode(title, codec).encode('utf-8')
def getPIDMetaInfo(self):
@ -375,7 +381,7 @@ class MobiBook:
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. Please report this failure for help.")
raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
@ -411,26 +417,26 @@ class MobiBook:
print "done"
return
def getUnencryptedBook(infile,pid):
def getUnencryptedBook(infile,pid,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile,announce)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist):
def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile, announce)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1
@ -442,7 +448,7 @@ def main(argv=sys.argv):
else:
pidlist = {}
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist)
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e

@ -164,6 +164,9 @@ class DocParser(object):
scale = self.pw
elif attr == 'line-space':
scale = self.fontsize * 2.0
if val == "":
val = 0
if not ((attr == 'hang') and (int(val) == 0)) :
pv = float(val)/scale

@ -31,11 +31,8 @@ class TpzDRMError(Exception):
# local support routines
if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import genbook
else:
import kgenpids
import genbook
# recursive zip creation support routine
def zipUpDir(myzip, tdir, localname):
@ -271,6 +268,11 @@ class TopazBook:
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"
@ -300,6 +302,11 @@ class TopazBook:
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"

@ -27,8 +27,8 @@
# 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.
# 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
@ -42,7 +42,7 @@
# 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.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%
@ -55,8 +55,13 @@
# 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.33'
__version__ = '0.37'
import sys
@ -73,6 +78,7 @@ sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
from alfcrypto import Pukall_Cipher
class DrmException(Exception):
pass
@ -84,36 +90,37 @@ class DrmException(Exception):
# 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
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"
@ -164,9 +171,10 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
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()
@ -194,6 +202,7 @@ class MobiBook:
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
@ -237,25 +246,26 @@ class MobiBook:
self.meta_array = {}
pass
self.print_replica = False
def getBookTitle(self):
codec_map = {
1252 : 'windows-1252',
65001 : 'utf-8',
}
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
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]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
return unicode(title, codec).encode('utf-8')
def getPIDMetaInfo(self):
@ -320,6 +330,9 @@ class MobiBook:
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
@ -356,9 +369,9 @@ class MobiBook:
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
elif self.mobi_version < 0:
bookkey_data = self.sect[0x90:0x90+16]
bookkey_data = self.sect[0x90:0x90+16]
else:
bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
@ -368,12 +381,12 @@ class MobiBook:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.")
raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please 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:
@ -404,26 +417,26 @@ class MobiBook:
print "done"
return
def getUnencryptedBook(infile,pid):
def getUnencryptedBook(infile,pid,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile,announce)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist):
def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile, announce)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1
@ -435,7 +448,7 @@ def main(argv=sys.argv):
else:
pidlist = {}
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist)
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e

@ -1,5 +1,5 @@
#! /usr/bin/env python
# ineptpdf.pyw, version 7.9
# ineptpdf.pyw, version 7.11
from __future__ import with_statement
@ -34,6 +34,8 @@ from __future__ import with_statement
# 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.
@ -2200,11 +2202,19 @@ class DecryptionDialog(Tkinter.Frame):
def decryptBook(keypath, inpath, outpath):
with open(inpath, 'rb') as inf:
serializer = PDFSerializer(inf, keypath)
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
serializer.dump(outf)
try:
serializer.dump(outf)
except:
print "error writing pdf."
return 1
return 0

@ -0,0 +1,59 @@
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('Kindle Serial numbers (separate with commas, no spaces)')
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 (separate with commas, no spaces)')
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())
prefs['serials'] = str(self.serials.text())
winepref=str(self.wineprefix.text())
if winepref.strip() != '':
prefs['WINEPREFIX'] = winepref
else:
prefs['WINEPREFIX'] = None

@ -214,6 +214,7 @@ class PageParser(object):
'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),
@ -239,6 +240,7 @@ class PageParser(object):
'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),
@ -246,7 +248,7 @@ class PageParser(object):
'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_number', 0, 0),
'region.orientation' : (1, 'scalar_text', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0),

@ -0,0 +1,77 @@
#!/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
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, True, [], [], [])
def main(argv=sys.argv):
print ('getk4pcpids.py v%(__version__)s. '
'Copyright 2012 Apprentic 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 <bookfile> [<outfile>]" % 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())

@ -17,7 +17,7 @@ from __future__ import with_statement
# and many many others
__version__ = '4.2'
__version__ = '4.4'
class Unbuffered:
def __init__(self, stream):
@ -58,7 +58,7 @@ else:
# borrowed from calibre from calibre/src/calibre/__init__.py
# added in removal of non-printing chars
# and removal of . at start
# convert spaces to underscores
# convert underscores to spaces (we're OK with spaces in file names)
def cleanup_name(name):
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
substitute='_'
@ -73,7 +73,7 @@ def cleanup_name(name):
# 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(' ','_')
one = one.replace('_',' ')
return one
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
title = mb.getBookTitle()
print "Processing Book: ", title
filenametitle = cleanup_name(title)
outfilename = bookname
if len(outfilename)<=8 or len(filenametitle)<=8:
outfilename = outfilename + "_" + filenametitle
elif outfilename[:8] != filenametitle[:8]:
outfilename = outfilename[:8] + "_" + filenametitle
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:

@ -385,17 +385,22 @@ def GetIDString():
if isNewInstall():
mungedmac = GetMACAddressMunged()
if len(mungedmac) > 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'
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found K4Mac kindle-info file: ' + resline)
found = True
# add any .rainier*-kinf files
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf file: ' + resline)
found = True
# add any .kinf2011 files
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
for resline in reslst:
if os.path.isfile(resline):
kInfoFiles.append(resline)
print('Found k4Mac kinf2011 file: ' + resline)
found = True
if not found:
print('No kindle-info files have been found.')
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
return kInfoFiles
# determine type of kindle info provided and return a

@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetIDString():
return GetVolumeSerialNumber()
vsn = GetVolumeSerialNumber()
print('Using Volume Serial Number for ID: '+vsn)
return vsn
def getLastError():
GetLastError = kernel32.GetLastError
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
if 'LOCALAPPDATA' in os.environ.keys():
path = os.environ['LOCALAPPDATA']
print "searching for kinfoFiles in ", path
print('searching for kinfoFiles in ' + path)
found = False
# first look for older kindle-info files
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
print('No kindle.info files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kindle.info file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for newer (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 not os.path.isfile(kinfopath):
print('No K4PC 1.5.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
if not os.path.isfile(kinfopath):
print('No K4PC 1.6.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
kInfoFiles.append(kinfopath)
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
if not os.path.isfile(kinfopath):
print('No K4PC 1.9.X .kinf files have not been found.')
else:
if os.path.isfile(kinfopath):
found = True
print('Found K4PC kinf2011 file: ' + kinfopath)
kInfoFiles.append(kinfopath)
if not found:
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
return kInfoFiles

@ -62,7 +62,7 @@ def encode(data, 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)
@ -78,11 +78,11 @@ def decode(data,map):
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
@ -91,10 +91,10 @@ def getTwoBitsFromBitField(bitField,offset):
# 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
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
@ -121,8 +121,8 @@ def generatePidEncryptionTable() :
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
# Generate the device PID
@ -141,7 +141,7 @@ def generateDevicePID(table,dsn,nbRoll):
return pidAscii
def crc32(s):
return (~binascii.crc32(s,-1))&0xFFFFFFFF
return (~binascii.crc32(s,-1))&0xFFFFFFFF
# convert from 8 digit PID to 10 digit PID with checksum
def checksumPid(s):
@ -204,10 +204,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
print(message)
kindleDatabase = None
pass
if kindleDatabase == None :
return pidlst
try:
# Get the Mazama Random number
MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
@ -217,7 +217,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the ID string used
encodedIDString = encodeHash(GetIDString(),charMap1)
@ -226,7 +226,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
# 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)
@ -258,13 +258,19 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = []
if kInfoFiles is None:
kInfoFiles = []
kInfoFiles = []
if k4:
kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
try:
pidlst = getK4Pids(pidlst, md1, md2, infoFile)
except Exception, message:
print("Error getting PIDs from " + infoFile + ": " + message)
for serialnum in serials:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
try:
pidlst = getKindlePid(pidlst, md1, md2, serialnum)
except Exception, message:
print("Error getting PIDs from " + serialnum + ": " + message)
for pid in pids:
pidlst.append(pid)
return pidlst

@ -57,8 +57,11 @@
# 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.35'
__version__ = '0.37'
import sys
@ -168,9 +171,10 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
def __init__(self, infile):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
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()
@ -198,6 +202,7 @@ class MobiBook:
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
@ -248,18 +253,19 @@ class MobiBook:
65001 : 'utf-8',
}
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
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]
codec = 'windows-1252'
if self.mobi_codepage in codec_map.keys():
codec = codec_map[self.mobi_codepage]
return unicode(title, codec).encode('utf-8')
def getPIDMetaInfo(self):
@ -375,7 +381,7 @@ class MobiBook:
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. Please report this failure for help.")
raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
@ -411,26 +417,26 @@ class MobiBook:
print "done"
return
def getUnencryptedBook(infile,pid):
def getUnencryptedBook(infile,pid,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile,announce)
book.processBook([pid])
return book.mobi_data
def getUnencryptedBookWithList(infile,pidlist):
def getUnencryptedBookWithList(infile,pidlist,announce=True):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
book = MobiBook(infile, announce)
book.processBook(pidlist)
return book.mobi_data
def main(argv=sys.argv):
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
if len(argv)<3 or len(argv)>4:
print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
print "Usage:"
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1
@ -442,7 +448,7 @@ def main(argv=sys.argv):
else:
pidlist = {}
try:
stripped_file = getUnencryptedBookWithList(infile, pidlist)
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e

@ -164,6 +164,9 @@ class DocParser(object):
scale = self.pw
elif attr == 'line-space':
scale = self.fontsize * 2.0
if val == "":
val = 0
if not ((attr == 'hang') and (int(val) == 0)) :
pv = float(val)/scale

@ -31,11 +31,8 @@ class TpzDRMError(Exception):
# local support routines
if inCalibre:
from calibre_plugins.k4mobidedrm import kgenpids
from calibre_plugins.k4mobidedrm import genbook
else:
import kgenpids
import genbook
# recursive zip creation support routine
def zipUpDir(myzip, tdir, localname):
@ -271,6 +268,11 @@ class TopazBook:
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"
@ -300,6 +302,11 @@ class TopazBook:
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"

@ -1,7 +1,7 @@
Kindle for iPhone, iPod Touch, iPad
------------------------------------
The Kindle application for iOS (iPhone/iPod Touch/iPad) uses a PID derived from the serial number of the iPhone/iPod Touch/iPad. Kindlepid.pyw (see Other_Tools/Additional_Tools) is a python script that turns the serial number into the equivalent PID, which can then be used with the Calibre Plugins and DeDRM Applications.
The Kindle application for iOS (iPhone/iPod Touch/iPad) uses a PID derived from the UDID number of the iPhone/iPod Touch/iPad. Kindlepid.pyw (see Other_Tools/Additional_Tools) is a python script that turns the serial number into the equivalent PID, which can then be used with the Calibre Plugins and DeDRM Applications.
So, to remove the DRM from (Mobipocket) Kindle books downloaded to your iPhone/iPodTouch/iPad, youll need the latest tools_vX.X.zip archive and
some way to extract the book files from the backup of your device on your computer. There are several free tools around to do this.
@ -19,10 +19,19 @@ You can then add this fixed PID to the DeDRM Applications or Calibre_Plugins or
You next need to find an "iPhone Extractor" or "iPhone Explorer" program for your OS that will allow you to mount your iPhone/iPad/iPod Touch as a usb device on your machine and copy your Kindle ebooks from the device back to your home machine.
IMPORTANT UPDATE
----------------
Apple has recently (2012) restricted access to a device's UDID. This means that recent (2012+) versions of Kindle for iOS no longer use this method of generating an encryption key. If you had already installed and registered Kindle for iOS before this change, the key based on your device's UDID will continue to be used. Otherwise, it is not currently (2012) possible to remove the DRM of Kindle eBooks from a newly installed and registered copy of Kindle for iOS.
---
Kindlefix gives you another way to use the PID generated by kindlepid. Some ebook stores and libraries will accept the PIDs generated by kindlepid (some wont), and you can then download ebooks from the store or library encrypted to work with your Kindle. Except they dont. Theres a flag byte set in encrypted Kindle ebooks, and Kindles and the Kindle app wont read encrypted mobipocket ebooks unless that flag is set. Kindlefix will set that flag for you. If your library have Mobipocket ebooks to lend and will accept your Kindles PID, you can now check out library ebooks, run kindlefix on them, and then read them on your Kindle, and when your loan period ends, theyll automatically become unreadable.
(2012 note: Few, if any, libraries now use this method of loaning ebooks.)

@ -1,18 +1,21 @@
Welcome to the tools!
=====================
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started.
This ReadMe_First.txt 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.2 archive.
The is archive includes tools to remove DRM from:
- eReader PDB books
- Kindle ebooks (including Mobi, Topaz, Print Replica and KF8).
- Barnes and Noble ePubs
- Adobe Digitial Editions ePubs
- Adobe Digitial Editions PDFs
- Kindle/Mobipocket ebooks (including Topaz, Print Replica and KF8).
- Adobe Digital Editions ePubs
- Adobe Digital Editions PDFs
- Mobipocket ebooks
- eReader PDB books
These tools do NOT work with 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 September 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.
@ -20,30 +23,34 @@ Requiem has a Tor website: http://tag3ulp55xczs3pn.onion. To reach the site usin
Calibre Users (Mac OS X, Windows, and Linux)
--------------------------------------------
If you are a calibre user, the quickest and easiest way to remove DRM from your ebooks is to open the Calibre_Plugins folder and install each of the plugins following the instructions and configuration directions provided in each plugins' README file.
If you are a calibre user, the quickest and easiest way 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 DeDRM 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.
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 and Mac OS X. All plugins (including the K4MobiDeDRM plugin when used on books from Kindle for PC under wine) should also work on Linux. Linux users should read the section at the end of this ReadMe.
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.5, 10.6, and 10.7)
DeDRM application for Mac OS X users: (Mac OS X 10.5 and above)
----------------------------------------------------------------------
From the DeDRM_Applications folder, drag the DeDRM_X.X.app.zip droplet to your Desktop. Double-click on it once to unzip it to create the DeDRM X.X.app droplet. Double-click on the droplet once and it will guide you through collecting the data it needs to remove the DRM.
Drag the "DeDRM 5.2.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 it simply drag ebooks or folders containing ebooks onto the DeDRM X.X.app droplet and it will process the ebooks.
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.
DeDRM Application for Windows Users: (Windows XP through Windows 7)
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 which versions are recommended.
***This program requires that Python and PyCrypto be properly installed.***
***See below for details on recommended versions are where to get them.***
Unzip the DeDRM_5.2_Win.zip archive that's in the DeDRM_Applications/Windows folder, saving the resulting DeDRM_5.2_Win folder in 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.2_Win 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.
From the DeDRM_Applications folder, fully extract the DeDRM_vX.X_WinApp.zip. Drag the resulting DeDRM_vx.x_WinApp folder to someplace out of the way on your machine. Open the folder and make a short-cut from the DeDRM_Drop_Target onto your Desktop. Double-click on the short-cut and DeDRM will launch and it will guide you through collecting the data it needs to remove the DRM.
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.
To use it simply drag ebooks or folders containing ebooks onto the DeDRM_Drop_Target short-cut, and it will process the ebooks.
For more detailed instructions, see the DeDRM_ReadMe.txt file in the DeDRM_Applications/Windows folder.
@ -77,7 +84,7 @@ 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. 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.
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.
@ -124,6 +131,27 @@ 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/)
--------------------------------------------------
@ -227,7 +255,7 @@ this will launch a window with 3 lines
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.
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

Loading…
Cancel
Save