tools v5.2
This commit is contained in:
parent
0812438b9d
commit
f3f02adc98
|
@ -1,40 +1,38 @@
|
||||||
Ignoble Epub DeDRM - ignobleepub_vXX_plugin.zip
|
Ignoble Epub DeDRM - ignobleepub_v01.6_plugin.zip
|
||||||
Requires Calibre version 0.6.44 or higher.
|
|
||||||
|
All credit given to I♥Cabbages for the original standalone scripts.
|
||||||
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.
|
||||||
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.
|
||||||
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:
|
||||||
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.
|
||||||
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.
|
||||||
|
|
||||||
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:
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
** 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.
|
|
||||||
|
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.
|
||||||
** 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.
|
|
||||||
|
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.
|
||||||
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.
|
|
||||||
|
Troubleshooting:
|
||||||
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.
|
|
||||||
|
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. ;)
|
||||||
Troubleshooting:
|
|
||||||
|
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.
|
||||||
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. ;)
|
** 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.
|
||||||
|
|
||||||
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
|
Inept Epub DeDRM - ineptepub_v01.7_plugin.zip
|
||||||
Requires Calibre version 0.6.44 or higher.
|
|
||||||
|
All credit given to I♥Cabbages for the original standalone scripts.
|
||||||
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.
|
||||||
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.
|
||||||
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:
|
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.
|
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.
|
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:
|
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
|
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.
|
||||||
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.
|
||||||
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.
|
||||||
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.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
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.
|
||||||
** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
|
|
||||||
|
|
||||||
Troubleshooting:
|
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
|
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. ;)
|
||||||
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.
|
||||||
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.
|
||||||
** 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
|
Inept PDF Plugin - ineptpdf_v01.5_plugin.zip
|
||||||
Requires Calibre version 0.6.44 or higher.
|
|
||||||
|
All credit given to I♥Cabbages for the original standalone scripts.
|
||||||
All credit given to IHeartCabbages for the original standalone scripts.
|
I had the much easier job of converting them to a Calibre plugin.
|
||||||
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.
|
||||||
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:
|
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.
|
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.
|
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:
|
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
|
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.
|
||||||
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.
|
||||||
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
|
||||||
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.
|
||||||
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.
|
||||||
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.
|
||||||
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.
|
||||||
** NOTE ** There is no plugin customization data for the Inept PDF plugin.
|
|
||||||
|
|
||||||
Troubleshooting:
|
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
|
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. ;)
|
||||||
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.
|
||||||
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.
|
||||||
** 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.
|
K4MobiDeDRM_v04.4_plugin.zip
|
||||||
|
|
||||||
This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins can be safely removed.
|
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.
|
||||||
|
|
||||||
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.
|
Plugin for K4PC, K4Mac, eInk Kindles and Mobipocket.
|
||||||
|
|
||||||
Installation:
|
This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins can be safely removed.
|
||||||
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.
|
|
||||||
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
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. ;)
|
Configuration:
|
||||||
|
|
||||||
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.
|
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).
|
||||||
|
|
||||||
** 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.
|
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
|
from zipfile import ZipFile
|
||||||
|
|
||||||
class K4DeDRM(FileTypePlugin):
|
class K4DeDRM(FileTypePlugin):
|
||||||
name = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin
|
name = 'Kindle and Mobipocket 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.'
|
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
|
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
||||||
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
author = 'DiapDealer, SomeUpdates, mdlnx, Apprentice Alf' # The author of this plugin
|
||||||
version = (0, 4, 2) # The version number 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
|
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
|
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)
|
minimum_calibre_version = (0, 7, 55)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""
|
"""
|
||||||
Dynamic modules can't be imported/loaded from a zipfile... so this routine
|
Dynamic modules can't be imported/loaded from a zipfile... so this routine
|
||||||
|
@ -39,7 +39,7 @@ class K4DeDRM(FileTypePlugin):
|
||||||
elif isosx:
|
elif isosx:
|
||||||
names = ['libalfcrypto.dylib']
|
names = ['libalfcrypto.dylib']
|
||||||
else:
|
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)
|
lib_dict = self.load_resources(names)
|
||||||
self.alfdir = os.path.join(config_dir, 'alfcrypto')
|
self.alfdir = os.path.join(config_dir, 'alfcrypto')
|
||||||
if not os.path.exists(self.alfdir):
|
if not os.path.exists(self.alfdir):
|
||||||
|
@ -62,35 +62,45 @@ class K4DeDRM(FileTypePlugin):
|
||||||
|
|
||||||
plug_ver = '.'.join(str(self.version).strip('()').replace(' ', '').split(','))
|
plug_ver = '.'.join(str(self.version).strip('()').replace(' ', '').split(','))
|
||||||
k4 = True
|
k4 = True
|
||||||
if sys.platform.startswith('linux'):
|
|
||||||
k4 = False
|
|
||||||
pids = []
|
pids = []
|
||||||
serials = []
|
serials = []
|
||||||
kInfoFiles = []
|
kInfoFiles = []
|
||||||
|
self.config()
|
||||||
|
|
||||||
# Get supplied list of PIDs to try from plugin customization.
|
# Get supplied list of PIDs to try from plugin customization.
|
||||||
customvalues = self.site_customization.split(',')
|
pidstringlistt = self.pids_string.split(',')
|
||||||
for customvalue in customvalues:
|
for pid in pidstringlistt:
|
||||||
customvalue = str(customvalue)
|
pid = str(pid).strip()
|
||||||
customvalue = customvalue.strip()
|
if len(pid) == 10 or len(pid) == 8:
|
||||||
if len(customvalue) == 10 or len(customvalue) == 8:
|
pids.append(pid)
|
||||||
pids.append(customvalue)
|
else:
|
||||||
else :
|
if len(pid) > 0:
|
||||||
if len(customvalue) == 16 and customvalue[0] == 'B':
|
print "'%s' is not a valid Mobipocket PID." % pid
|
||||||
serials.append(customvalue)
|
|
||||||
else:
|
# For linux, get PIDs by calling the right routines under WINE
|
||||||
print "%s is not a valid Kindle serial number or PID." % str(customvalue)
|
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.
|
# Load any kindle info files (*.info) included Calibre's config directory.
|
||||||
try:
|
try:
|
||||||
# Find Calibre's configuration directory.
|
print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, config_dir)
|
||||||
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
|
files = os.listdir(config_dir)
|
||||||
print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, confpath)
|
|
||||||
files = os.listdir(confpath)
|
|
||||||
filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
|
filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
|
||||||
files = filter(filefilter.search, files)
|
files = filter(filefilter.search, files)
|
||||||
if files:
|
if files:
|
||||||
for filename in files:
|
for filename in files:
|
||||||
fpath = os.path.join(confpath, filename)
|
fpath = os.path.join(config_dir, filename)
|
||||||
kInfoFiles.append(fpath)
|
kInfoFiles.append(fpath)
|
||||||
print 'K4MobiDeDRM v%s: Kindle info/kinf file %s found in config folder.' % (plug_ver, filename)
|
print 'K4MobiDeDRM v%s: Kindle info/kinf file %s found in config folder.' % (plug_ver, filename)
|
||||||
except IOError:
|
except IOError:
|
||||||
|
@ -152,8 +162,77 @@ class K4DeDRM(FileTypePlugin):
|
||||||
mb.cleanup()
|
mb.cleanup()
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
def WINEgetPIDs(self, infile):
|
||||||
return 'Enter 10 character PIDs and/or Kindle serial numbers, use a comma (no spaces) to separate each PID or SerialNumber from the next.'
|
|
||||||
|
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):
|
def load_resources(self, names):
|
||||||
ans = {}
|
ans = {}
|
||||||
|
@ -161,4 +240,4 @@ class K4DeDRM(FileTypePlugin):
|
||||||
for candidate in zf.namelist():
|
for candidate in zf.namelist():
|
||||||
if candidate in names:
|
if candidate in names:
|
||||||
ans[candidate] = zf.read(candidate)
|
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.title' : (1, 'text', 0, 0),
|
||||||
'links.href' : (1, 'text', 0, 0),
|
'links.href' : (1, 'text', 0, 0),
|
||||||
'links.type' : (1, 'text', 0, 0),
|
'links.type' : (1, 'text', 0, 0),
|
||||||
|
'links.id' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
'paraCont' : (0, 'number', 1, 1),
|
'paraCont' : (0, 'number', 1, 1),
|
||||||
'paraCont.rootID' : (1, 'number', 0, 0),
|
'paraCont.rootID' : (1, 'number', 0, 0),
|
||||||
|
@ -239,6 +240,7 @@ class PageParser(object):
|
||||||
'group' : (1, 'snippets', 1, 0),
|
'group' : (1, 'snippets', 1, 0),
|
||||||
'group.type' : (1, 'scalar_text', 0, 0),
|
'group.type' : (1, 'scalar_text', 0, 0),
|
||||||
'group._tag' : (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' : (1, 'snippets', 1, 0),
|
||||||
'region.type' : (1, 'scalar_text', 0, 0),
|
'region.type' : (1, 'scalar_text', 0, 0),
|
||||||
|
@ -246,7 +248,7 @@ class PageParser(object):
|
||||||
'region.y' : (1, 'scalar_number', 0, 0),
|
'region.y' : (1, 'scalar_number', 0, 0),
|
||||||
'region.h' : (1, 'scalar_number', 0, 0),
|
'region.h' : (1, 'scalar_number', 0, 0),
|
||||||
'region.w' : (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),
|
'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
|
# and many many others
|
||||||
|
|
||||||
|
|
||||||
__version__ = '4.2'
|
__version__ = '4.3'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
|
@ -58,7 +58,7 @@ else:
|
||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# 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):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
|
@ -73,7 +73,7 @@ def cleanup_name(name):
|
||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
one = one.replace(' ','_')
|
one = one.replace('_',' ')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
|
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
title = mb.getBookTitle()
|
title = mb.getBookTitle()
|
||||||
print "Processing Book: ", title
|
print "Processing Book: ", title
|
||||||
filenametitle = cleanup_name(title)
|
filenametitle = cleanup_name(title)
|
||||||
outfilename = bookname
|
outfilename = cleanup_name(bookname)
|
||||||
if len(outfilename)<=8 or len(filenametitle)<=8:
|
|
||||||
outfilename = outfilename + "_" + filenametitle
|
# generate 'sensible' filename, that will sort with the original name,
|
||||||
elif outfilename[:8] != filenametitle[:8]:
|
# but is close to the name from the file.
|
||||||
outfilename = outfilename[:8] + "_" + filenametitle
|
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
|
# avoid excessively long file names
|
||||||
if len(outfilename)>150:
|
if len(outfilename)>150:
|
||||||
|
|
|
@ -262,9 +262,15 @@ def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
|
||||||
if k4:
|
if k4:
|
||||||
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
||||||
for infoFile in 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:
|
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:
|
for pid in pids:
|
||||||
pidlst.append(pid)
|
pidlst.append(pid)
|
||||||
return pidlst
|
return pidlst
|
||||||
|
|
|
@ -164,6 +164,9 @@ class DocParser(object):
|
||||||
scale = self.pw
|
scale = self.pw
|
||||||
elif attr == 'line-space':
|
elif attr == 'line-space':
|
||||||
scale = self.fontsize * 2.0
|
scale = self.fontsize * 2.0
|
||||||
|
|
||||||
|
if val == "":
|
||||||
|
val = 0
|
||||||
|
|
||||||
if not ((attr == 'hang') and (int(val) == 0)) :
|
if not ((attr == 'hang') and (int(val) == 0)) :
|
||||||
pv = float(val)/scale
|
pv = float(val)/scale
|
||||||
|
|
|
@ -31,11 +31,8 @@ class TpzDRMError(Exception):
|
||||||
# local support routines
|
# local support routines
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.k4mobidedrm import kgenpids
|
from calibre_plugins.k4mobidedrm import kgenpids
|
||||||
from calibre_plugins.k4mobidedrm import genbook
|
|
||||||
else:
|
else:
|
||||||
import kgenpids
|
import kgenpids
|
||||||
import genbook
|
|
||||||
|
|
||||||
|
|
||||||
# recursive zip creation support routine
|
# recursive zip creation support routine
|
||||||
def zipUpDir(myzip, tdir, localname):
|
def zipUpDir(myzip, tdir, localname):
|
||||||
|
@ -271,6 +268,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
print "\nBook Successfully generated"
|
||||||
|
@ -300,6 +302,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
print "\nBook Successfully generated"
|
||||||
|
|
|
@ -1,23 +1,31 @@
|
||||||
eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip
|
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.
|
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.
|
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.
|
Installation:
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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
|
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.
|
||||||
|
|
||||||
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!!)
|
|
||||||
|
Configuration:
|
||||||
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
|
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
|
||||||
as well get used to it. ;)
|
|
||||||
|
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!!)
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -1,6 +1,8 @@
|
||||||
#! /usr/bin/env python
|
#! /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
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
# later. <http://www.gnu.org/licenses/>
|
# later. <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
@ -52,13 +54,11 @@
|
||||||
# 0.1.2 - back port ineptpdf 8.4.X bug fixes
|
# 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.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.4 - update to the new calibre plugin interface
|
||||||
|
# 0.1.5 - synced to ineptpdf 7.11
|
||||||
"""
|
"""
|
||||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
@ -116,13 +116,13 @@ def _load_crypto_libcrypto():
|
||||||
class RSA(Structure):
|
class RSA(Structure):
|
||||||
pass
|
pass
|
||||||
RSA_p = POINTER(RSA)
|
RSA_p = POINTER(RSA)
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
def F(restype, name, argtypes):
|
||||||
func = getattr(libcrypto, name)
|
func = getattr(libcrypto, name)
|
||||||
func.restype = restype
|
func.restype = restype
|
||||||
func.argtypes = argtypes
|
func.argtypes = argtypes
|
||||||
return func
|
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_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
AES_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))
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
if rsa is None:
|
if rsa is None:
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def decrypt(self, from_):
|
def decrypt(self, from_):
|
||||||
rsa = self._rsa
|
rsa = self._rsa
|
||||||
to = create_string_buffer(RSA_size(rsa))
|
to = create_string_buffer(RSA_size(rsa))
|
||||||
|
@ -152,7 +152,7 @@ def _load_crypto_libcrypto():
|
||||||
if dlen < 0:
|
if dlen < 0:
|
||||||
raise ADEPTError('RSA decryption failed')
|
raise ADEPTError('RSA decryption failed')
|
||||||
return to[1:dlen]
|
return to[1:dlen]
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self._rsa is not None:
|
if self._rsa is not None:
|
||||||
RSA_free(self._rsa)
|
RSA_free(self._rsa)
|
||||||
|
@ -214,13 +214,13 @@ def _load_crypto_pycrypto():
|
||||||
# ASN.1 parsing code from tlslite
|
# ASN.1 parsing code from tlslite
|
||||||
class ASN1Error(Exception):
|
class ASN1Error(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ASN1Parser(object):
|
class ASN1Parser(object):
|
||||||
class Parser(object):
|
class Parser(object):
|
||||||
def __init__(self, bytes):
|
def __init__(self, bytes):
|
||||||
self.bytes = bytes
|
self.bytes = bytes
|
||||||
self.index = 0
|
self.index = 0
|
||||||
|
|
||||||
def get(self, length):
|
def get(self, length):
|
||||||
if self.index + length > len(self.bytes):
|
if self.index + length > len(self.bytes):
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
raise ASN1Error("Error decoding ASN.1")
|
||||||
|
@ -230,22 +230,22 @@ def _load_crypto_pycrypto():
|
||||||
x |= self.bytes[self.index]
|
x |= self.bytes[self.index]
|
||||||
self.index += 1
|
self.index += 1
|
||||||
return x
|
return x
|
||||||
|
|
||||||
def getFixBytes(self, lengthBytes):
|
def getFixBytes(self, lengthBytes):
|
||||||
bytes = self.bytes[self.index : self.index+lengthBytes]
|
bytes = self.bytes[self.index : self.index+lengthBytes]
|
||||||
self.index += lengthBytes
|
self.index += lengthBytes
|
||||||
return bytes
|
return bytes
|
||||||
|
|
||||||
def getVarBytes(self, lengthLength):
|
def getVarBytes(self, lengthLength):
|
||||||
lengthBytes = self.get(lengthLength)
|
lengthBytes = self.get(lengthLength)
|
||||||
return self.getFixBytes(lengthBytes)
|
return self.getFixBytes(lengthBytes)
|
||||||
|
|
||||||
def getFixList(self, length, lengthList):
|
def getFixList(self, length, lengthList):
|
||||||
l = [0] * lengthList
|
l = [0] * lengthList
|
||||||
for x in range(lengthList):
|
for x in range(lengthList):
|
||||||
l[x] = self.get(length)
|
l[x] = self.get(length)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def getVarList(self, length, lengthLength):
|
def getVarList(self, length, lengthLength):
|
||||||
lengthList = self.get(lengthLength)
|
lengthList = self.get(lengthLength)
|
||||||
if lengthList % length != 0:
|
if lengthList % length != 0:
|
||||||
|
@ -255,19 +255,19 @@ def _load_crypto_pycrypto():
|
||||||
for x in range(lengthList):
|
for x in range(lengthList):
|
||||||
l[x] = self.get(length)
|
l[x] = self.get(length)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def startLengthCheck(self, lengthLength):
|
def startLengthCheck(self, lengthLength):
|
||||||
self.lengthCheck = self.get(lengthLength)
|
self.lengthCheck = self.get(lengthLength)
|
||||||
self.indexCheck = self.index
|
self.indexCheck = self.index
|
||||||
|
|
||||||
def setLengthCheck(self, length):
|
def setLengthCheck(self, length):
|
||||||
self.lengthCheck = length
|
self.lengthCheck = length
|
||||||
self.indexCheck = self.index
|
self.indexCheck = self.index
|
||||||
|
|
||||||
def stopLengthCheck(self):
|
def stopLengthCheck(self):
|
||||||
if (self.index - self.indexCheck) != self.lengthCheck:
|
if (self.index - self.indexCheck) != self.lengthCheck:
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
raise ASN1Error("Error decoding ASN.1")
|
||||||
|
|
||||||
def atLengthCheck(self):
|
def atLengthCheck(self):
|
||||||
if (self.index - self.indexCheck) < self.lengthCheck:
|
if (self.index - self.indexCheck) < self.lengthCheck:
|
||||||
return False
|
return False
|
||||||
|
@ -275,13 +275,13 @@ def _load_crypto_pycrypto():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
raise ASN1Error("Error decoding ASN.1")
|
||||||
|
|
||||||
def __init__(self, bytes):
|
def __init__(self, bytes):
|
||||||
p = self.Parser(bytes)
|
p = self.Parser(bytes)
|
||||||
p.get(1)
|
p.get(1)
|
||||||
self.length = self._getASN1Length(p)
|
self.length = self._getASN1Length(p)
|
||||||
self.value = p.getFixBytes(self.length)
|
self.value = p.getFixBytes(self.length)
|
||||||
|
|
||||||
def getChild(self, which):
|
def getChild(self, which):
|
||||||
p = self.Parser(self.value)
|
p = self.Parser(self.value)
|
||||||
for x in range(which+1):
|
for x in range(which+1):
|
||||||
|
@ -290,7 +290,7 @@ def _load_crypto_pycrypto():
|
||||||
length = self._getASN1Length(p)
|
length = self._getASN1Length(p)
|
||||||
p.getFixBytes(length)
|
p.getFixBytes(length)
|
||||||
return ASN1Parser(p.bytes[markIndex:p.index])
|
return ASN1Parser(p.bytes[markIndex:p.index])
|
||||||
|
|
||||||
def _getASN1Length(self, p):
|
def _getASN1Length(self, p):
|
||||||
firstLength = p.get(1)
|
firstLength = p.get(1)
|
||||||
if firstLength<=127:
|
if firstLength<=127:
|
||||||
|
@ -311,6 +311,7 @@ def _load_crypto_pycrypto():
|
||||||
return self._arc4.decrypt(data)
|
return self._arc4.decrypt(data)
|
||||||
|
|
||||||
class AES(object):
|
class AES(object):
|
||||||
|
MODE_CBC = _AES.MODE_CBC
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls, userkey, mode, iv):
|
def new(cls, userkey, mode, iv):
|
||||||
self = AES()
|
self = AES()
|
||||||
|
@ -333,7 +334,7 @@ def _load_crypto_pycrypto():
|
||||||
for byte in bytes:
|
for byte in bytes:
|
||||||
total = (total << 8) + byte
|
total = (total << 8) + byte
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
return self._rsa.decrypt(data)
|
return self._rsa.decrypt(data)
|
||||||
|
|
||||||
|
@ -426,7 +427,7 @@ class PSLiteral(PSObject):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
name = []
|
name = []
|
||||||
for char in self.name:
|
for char in self.name:
|
||||||
|
@ -445,22 +446,22 @@ class PSKeyword(PSObject):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
# PSSymbolTable
|
# PSSymbolTable
|
||||||
class PSSymbolTable(object):
|
class PSSymbolTable(object):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Symbol table that stores PSLiteral or PSKeyword.
|
Symbol table that stores PSLiteral or PSKeyword.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, classe):
|
def __init__(self, classe):
|
||||||
self.dic = {}
|
self.dic = {}
|
||||||
self.classe = classe
|
self.classe = classe
|
||||||
return
|
return
|
||||||
|
|
||||||
def intern(self, name):
|
def intern(self, name):
|
||||||
if name in self.dic:
|
if name in self.dic:
|
||||||
lit = self.dic[name]
|
lit = self.dic[name]
|
||||||
|
@ -530,11 +531,11 @@ class PSBaseParser(object):
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
return
|
return
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.flush()
|
self.flush()
|
||||||
return
|
return
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self.bufpos+self.charpos
|
return self.bufpos+self.charpos
|
||||||
|
|
||||||
|
@ -570,7 +571,7 @@ class PSBaseParser(object):
|
||||||
raise PSEOF('Unexpected EOF')
|
raise PSEOF('Unexpected EOF')
|
||||||
self.charpos = 0
|
self.charpos = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
def parse_main(self, s, i):
|
def parse_main(self, s, i):
|
||||||
m = NONSPC.search(s, i)
|
m = NONSPC.search(s, i)
|
||||||
if not m:
|
if not m:
|
||||||
|
@ -605,11 +606,11 @@ class PSBaseParser(object):
|
||||||
return (self.parse_wclose, j+1)
|
return (self.parse_wclose, j+1)
|
||||||
self.add_token(KWD(c))
|
self.add_token(KWD(c))
|
||||||
return (self.parse_main, j+1)
|
return (self.parse_main, j+1)
|
||||||
|
|
||||||
def add_token(self, obj):
|
def add_token(self, obj):
|
||||||
self.tokens.append((self.tokenstart, obj))
|
self.tokens.append((self.tokenstart, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
def parse_comment(self, s, i):
|
def parse_comment(self, s, i):
|
||||||
m = EOL.search(s, i)
|
m = EOL.search(s, i)
|
||||||
if not m:
|
if not m:
|
||||||
|
@ -620,7 +621,7 @@ class PSBaseParser(object):
|
||||||
# We ignore comments.
|
# We ignore comments.
|
||||||
#self.tokens.append(self.token)
|
#self.tokens.append(self.token)
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
def parse_literal(self, s, i):
|
def parse_literal(self, s, i):
|
||||||
m = END_LITERAL.search(s, i)
|
m = END_LITERAL.search(s, i)
|
||||||
if not m:
|
if not m:
|
||||||
|
@ -634,7 +635,7 @@ class PSBaseParser(object):
|
||||||
return (self.parse_literal_hex, j+1)
|
return (self.parse_literal_hex, j+1)
|
||||||
self.add_token(LIT(self.token))
|
self.add_token(LIT(self.token))
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
def parse_literal_hex(self, s, i):
|
def parse_literal_hex(self, s, i):
|
||||||
c = s[i]
|
c = s[i]
|
||||||
if HEX.match(c) and len(self.hex) < 2:
|
if HEX.match(c) and len(self.hex) < 2:
|
||||||
|
@ -669,7 +670,7 @@ class PSBaseParser(object):
|
||||||
self.token += s[i:j]
|
self.token += s[i:j]
|
||||||
self.add_token(float(self.token))
|
self.add_token(float(self.token))
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
def parse_keyword(self, s, i):
|
def parse_keyword(self, s, i):
|
||||||
m = END_KEYWORD.search(s, i)
|
m = END_KEYWORD.search(s, i)
|
||||||
if not m:
|
if not m:
|
||||||
|
@ -817,7 +818,7 @@ class PSStackParser(PSBaseParser):
|
||||||
PSBaseParser.__init__(self, fp)
|
PSBaseParser.__init__(self, fp)
|
||||||
self.reset()
|
self.reset()
|
||||||
return
|
return
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.context = []
|
self.context = []
|
||||||
self.curtype = None
|
self.curtype = None
|
||||||
|
@ -858,10 +859,10 @@ class PSStackParser(PSBaseParser):
|
||||||
|
|
||||||
def do_keyword(self, pos, token):
|
def do_keyword(self, pos, token):
|
||||||
return
|
return
|
||||||
|
|
||||||
def nextobject(self, direct=False):
|
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
|
numbers, arrays and dictionaries. Arrays and dictionaries
|
||||||
are represented as Python sequence and dictionaries.
|
are represented as Python sequence and dictionaries.
|
||||||
'''
|
'''
|
||||||
|
@ -930,7 +931,7 @@ class PDFNotImplementedError(PSException): pass
|
||||||
## PDFObjRef
|
## PDFObjRef
|
||||||
##
|
##
|
||||||
class PDFObjRef(PDFObject):
|
class PDFObjRef(PDFObject):
|
||||||
|
|
||||||
def __init__(self, doc, objid, genno):
|
def __init__(self, doc, objid, genno):
|
||||||
if objid == 0:
|
if objid == 0:
|
||||||
if STRICT:
|
if STRICT:
|
||||||
|
@ -1045,25 +1046,25 @@ def stream_value(x):
|
||||||
|
|
||||||
# ascii85decode(data)
|
# ascii85decode(data)
|
||||||
def ascii85decode(data):
|
def ascii85decode(data):
|
||||||
n = b = 0
|
n = b = 0
|
||||||
out = ''
|
out = ''
|
||||||
for c in data:
|
for c in data:
|
||||||
if '!' <= c and c <= 'u':
|
if '!' <= c and c <= 'u':
|
||||||
n += 1
|
n += 1
|
||||||
b = b*85+(ord(c)-33)
|
b = b*85+(ord(c)-33)
|
||||||
if n == 5:
|
if n == 5:
|
||||||
out += struct.pack('>L',b)
|
out += struct.pack('>L',b)
|
||||||
n = b = 0
|
n = b = 0
|
||||||
elif c == 'z':
|
elif c == 'z':
|
||||||
assert n == 0
|
assert n == 0
|
||||||
out += '\0\0\0\0'
|
out += '\0\0\0\0'
|
||||||
elif c == '~':
|
elif c == '~':
|
||||||
if n:
|
if n:
|
||||||
for _ in range(5-n):
|
for _ in range(5-n):
|
||||||
b = b*85+84
|
b = b*85+84
|
||||||
out += struct.pack('>L',b)[:n-1]
|
out += struct.pack('>L',b)[:n-1]
|
||||||
break
|
break
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
## PDFStream type
|
## PDFStream type
|
||||||
|
@ -1080,7 +1081,7 @@ class PDFStream(PDFObject):
|
||||||
else:
|
else:
|
||||||
if eol in ('\r', '\n', '\r\n'):
|
if eol in ('\r', '\n', '\r\n'):
|
||||||
rawdata = rawdata[:length]
|
rawdata = rawdata[:length]
|
||||||
|
|
||||||
self.dic = dic
|
self.dic = dic
|
||||||
self.rawdata = rawdata
|
self.rawdata = rawdata
|
||||||
self.decipher = decipher
|
self.decipher = decipher
|
||||||
|
@ -1094,7 +1095,7 @@ class PDFStream(PDFObject):
|
||||||
self.objid = objid
|
self.objid = objid
|
||||||
self.genno = genno
|
self.genno = genno
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.rawdata:
|
if self.rawdata:
|
||||||
return '<PDFStream(%r): raw=%d, %r>' % \
|
return '<PDFStream(%r): raw=%d, %r>' % \
|
||||||
|
@ -1178,7 +1179,7 @@ class PDFStream(PDFObject):
|
||||||
data = self.decipher(self.objid, self.genno, data)
|
data = self.decipher(self.objid, self.genno, data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
## PDF Exceptions
|
## PDF Exceptions
|
||||||
##
|
##
|
||||||
class PDFSyntaxError(PDFException): pass
|
class PDFSyntaxError(PDFException): pass
|
||||||
|
@ -1243,7 +1244,7 @@ class PDFXRef(object):
|
||||||
self.offsets[objid] = (int(genno), int(pos))
|
self.offsets[objid] = (int(genno), int(pos))
|
||||||
self.load_trailer(parser)
|
self.load_trailer(parser)
|
||||||
return
|
return
|
||||||
|
|
||||||
KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
|
KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
|
||||||
def load_trailer(self, parser):
|
def load_trailer(self, parser):
|
||||||
try:
|
try:
|
||||||
|
@ -1284,7 +1285,7 @@ class PDFXRefStream(object):
|
||||||
for first, size in self.index:
|
for first, size in self.index:
|
||||||
for objid in xrange(first, first + size):
|
for objid in xrange(first, first + size):
|
||||||
yield objid
|
yield objid
|
||||||
|
|
||||||
def load(self, parser, debug=0):
|
def load(self, parser, debug=0):
|
||||||
(_,objid) = parser.nexttoken() # ignored
|
(_,objid) = parser.nexttoken() # ignored
|
||||||
(_,genno) = parser.nexttoken() # ignored
|
(_,genno) = parser.nexttoken() # ignored
|
||||||
|
@ -1302,7 +1303,7 @@ class PDFXRefStream(object):
|
||||||
self.entlen = self.fl1+self.fl2+self.fl3
|
self.entlen = self.fl1+self.fl2+self.fl3
|
||||||
self.trailer = stream.dic
|
self.trailer = stream.dic
|
||||||
return
|
return
|
||||||
|
|
||||||
def getpos(self, objid):
|
def getpos(self, objid):
|
||||||
offset = 0
|
offset = 0
|
||||||
for first, size in self.index:
|
for first, size in self.index:
|
||||||
|
@ -1353,7 +1354,7 @@ class PDFDocument(object):
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
# The document is set to be temporarily ready during collecting
|
# The document is set to be temporarily ready during collecting
|
||||||
# all the basic information about the document, e.g.
|
# 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.
|
# for the document.
|
||||||
self.ready = True
|
self.ready = True
|
||||||
# Retrieve the information of each header that was appended
|
# Retrieve the information of each header that was appended
|
||||||
|
@ -1429,7 +1430,7 @@ class PDFDocument(object):
|
||||||
length = int_value(param.get('Length', 0)) / 8
|
length = int_value(param.get('Length', 0)) / 8
|
||||||
edcdata = str_value(param.get('EDCData')).decode('base64')
|
edcdata = str_value(param.get('EDCData')).decode('base64')
|
||||||
pdrllic = str_value(param.get('PDRLLic')).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 = []
|
edclist = []
|
||||||
for pair in edcdata.split('\n'):
|
for pair in edcdata.split('\n'):
|
||||||
edclist.append(pair)
|
edclist.append(pair)
|
||||||
|
@ -1449,9 +1450,9 @@ class PDFDocument(object):
|
||||||
raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
|
raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
|
||||||
else:
|
else:
|
||||||
cutter = -1 * ord(pdrlpol[-1])
|
cutter = -1 * ord(pdrlpol[-1])
|
||||||
pdrlpol = pdrlpol[:cutter]
|
pdrlpol = pdrlpol[:cutter]
|
||||||
return plaintext[:16]
|
return plaintext[:16]
|
||||||
|
|
||||||
PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
|
PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
|
||||||
'\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
|
'\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
|
||||||
# experimental aes pw support
|
# experimental aes pw support
|
||||||
|
@ -1471,14 +1472,14 @@ class PDFDocument(object):
|
||||||
EncMetadata = str_value(param['EncryptMetadata'])
|
EncMetadata = str_value(param['EncryptMetadata'])
|
||||||
except:
|
except:
|
||||||
EncMetadata = 'True'
|
EncMetadata = 'True'
|
||||||
self.is_printable = bool(P & 4)
|
self.is_printable = bool(P & 4)
|
||||||
self.is_modifiable = bool(P & 8)
|
self.is_modifiable = bool(P & 8)
|
||||||
self.is_extractable = bool(P & 16)
|
self.is_extractable = bool(P & 16)
|
||||||
self.is_annotationable = bool(P & 32)
|
self.is_annotationable = bool(P & 32)
|
||||||
self.is_formsenabled = bool(P & 256)
|
self.is_formsenabled = bool(P & 256)
|
||||||
self.is_textextractable = bool(P & 512)
|
self.is_textextractable = bool(P & 512)
|
||||||
self.is_assemblable = bool(P & 1024)
|
self.is_assemblable = bool(P & 1024)
|
||||||
self.is_formprintable = bool(P & 2048)
|
self.is_formprintable = bool(P & 2048)
|
||||||
# Algorithm 3.2
|
# Algorithm 3.2
|
||||||
password = (password+self.PASSWORD_PADDING)[:32] # 1
|
password = (password+self.PASSWORD_PADDING)[:32] # 1
|
||||||
hash = hashlib.md5(password) # 2
|
hash = hashlib.md5(password) # 2
|
||||||
|
@ -1587,7 +1588,7 @@ class PDFDocument(object):
|
||||||
hash = hashlib.md5(key)
|
hash = hashlib.md5(key)
|
||||||
key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
|
key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
|
||||||
return key
|
return key
|
||||||
|
|
||||||
def genkey_v3(self, objid, genno):
|
def genkey_v3(self, objid, genno):
|
||||||
objid = struct.pack('<L', objid ^ 0x3569ac)
|
objid = struct.pack('<L', objid ^ 0x3569ac)
|
||||||
genno = struct.pack('<L', genno ^ 0xca96)
|
genno = struct.pack('<L', genno ^ 0xca96)
|
||||||
|
@ -1627,14 +1628,14 @@ class PDFDocument(object):
|
||||||
#print cutter
|
#print cutter
|
||||||
plaintext = plaintext[:cutter]
|
plaintext = plaintext[:cutter]
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
def decrypt_rc4(self, objid, genno, data):
|
def decrypt_rc4(self, objid, genno, data):
|
||||||
key = self.genkey(objid, genno)
|
key = self.genkey(objid, genno)
|
||||||
return ARC4.new(key).decrypt(data)
|
return ARC4.new(key).decrypt(data)
|
||||||
|
|
||||||
|
|
||||||
KEYWORD_OBJ = PSKeywordTable.intern('obj')
|
KEYWORD_OBJ = PSKeywordTable.intern('obj')
|
||||||
|
|
||||||
def getobj(self, objid):
|
def getobj(self, objid):
|
||||||
if not self.ready:
|
if not self.ready:
|
||||||
raise PDFException('PDFDocument not initialized')
|
raise PDFException('PDFDocument not initialized')
|
||||||
|
@ -1704,7 +1705,7 @@ class PDFDocument(object):
|
||||||
## if x:
|
## if x:
|
||||||
## objid1 = x[-2]
|
## objid1 = x[-2]
|
||||||
## genno = x[-1]
|
## genno = x[-1]
|
||||||
##
|
##
|
||||||
if kwd is not self.KEYWORD_OBJ:
|
if kwd is not self.KEYWORD_OBJ:
|
||||||
raise PDFSyntaxError(
|
raise PDFSyntaxError(
|
||||||
'Invalid object spec: offset=%r' % index)
|
'Invalid object spec: offset=%r' % index)
|
||||||
|
@ -1716,7 +1717,7 @@ class PDFDocument(object):
|
||||||
self.objs[objid] = obj
|
self.objs[objid] = obj
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class PDFObjStmRef(object):
|
class PDFObjStmRef(object):
|
||||||
maxindex = 0
|
maxindex = 0
|
||||||
def __init__(self, objid, stmid, index):
|
def __init__(self, objid, stmid, index):
|
||||||
|
@ -1726,7 +1727,7 @@ class PDFObjStmRef(object):
|
||||||
if index > PDFObjStmRef.maxindex:
|
if index > PDFObjStmRef.maxindex:
|
||||||
PDFObjStmRef.maxindex = index
|
PDFObjStmRef.maxindex = index
|
||||||
|
|
||||||
|
|
||||||
## PDFParser
|
## PDFParser
|
||||||
##
|
##
|
||||||
class PDFParser(PSStackParser):
|
class PDFParser(PSStackParser):
|
||||||
|
@ -1752,7 +1753,7 @@ class PDFParser(PSStackParser):
|
||||||
if token is self.KEYWORD_ENDOBJ:
|
if token is self.KEYWORD_ENDOBJ:
|
||||||
self.add_results(*self.pop(4))
|
self.add_results(*self.pop(4))
|
||||||
return
|
return
|
||||||
|
|
||||||
if token is self.KEYWORD_R:
|
if token is self.KEYWORD_R:
|
||||||
# reference to indirect object
|
# reference to indirect object
|
||||||
try:
|
try:
|
||||||
|
@ -1763,7 +1764,7 @@ class PDFParser(PSStackParser):
|
||||||
except PSSyntaxError:
|
except PSSyntaxError:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
if token is self.KEYWORD_STREAM:
|
if token is self.KEYWORD_STREAM:
|
||||||
# stream object
|
# stream object
|
||||||
((_,dic),) = self.pop(1)
|
((_,dic),) = self.pop(1)
|
||||||
|
@ -1803,7 +1804,7 @@ class PDFParser(PSStackParser):
|
||||||
obj = PDFStream(dic, data, self.doc.decipher)
|
obj = PDFStream(dic, data, self.doc.decipher)
|
||||||
self.push((pos, obj))
|
self.push((pos, obj))
|
||||||
return
|
return
|
||||||
|
|
||||||
# others
|
# others
|
||||||
self.push((pos, token))
|
self.push((pos, token))
|
||||||
return
|
return
|
||||||
|
@ -1839,7 +1840,7 @@ class PDFParser(PSStackParser):
|
||||||
xref.load(self)
|
xref.load(self)
|
||||||
else:
|
else:
|
||||||
if token is not self.KEYWORD_XREF:
|
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))
|
(pos, token))
|
||||||
self.nextline()
|
self.nextline()
|
||||||
xref = PDFXRef()
|
xref = PDFXRef()
|
||||||
|
@ -1854,7 +1855,7 @@ class PDFParser(PSStackParser):
|
||||||
pos = int_value(trailer['Prev'])
|
pos = int_value(trailer['Prev'])
|
||||||
self.read_xref_from(pos, xrefs)
|
self.read_xref_from(pos, xrefs)
|
||||||
return
|
return
|
||||||
|
|
||||||
# read xref tables and trailers
|
# read xref tables and trailers
|
||||||
def read_xref(self):
|
def read_xref(self):
|
||||||
xrefs = []
|
xrefs = []
|
||||||
|
@ -1973,7 +1974,7 @@ class PDFSerializer(object):
|
||||||
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
||||||
else:
|
else:
|
||||||
self.write("%010d %05d f \n" % (0, 65535))
|
self.write("%010d %05d f \n" % (0, 65535))
|
||||||
|
|
||||||
self.write('trailer\n')
|
self.write('trailer\n')
|
||||||
self.serialize_object(trailer)
|
self.serialize_object(trailer)
|
||||||
self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
|
self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
|
||||||
|
@ -1993,7 +1994,7 @@ class PDFSerializer(object):
|
||||||
while maxindex >= power:
|
while maxindex >= power:
|
||||||
fl3 += 1
|
fl3 += 1
|
||||||
power *= 256
|
power *= 256
|
||||||
|
|
||||||
index = []
|
index = []
|
||||||
first = None
|
first = None
|
||||||
prev = None
|
prev = None
|
||||||
|
@ -2020,14 +2021,14 @@ class PDFSerializer(object):
|
||||||
# we force all generation numbers to be 0
|
# we force all generation numbers to be 0
|
||||||
# f3 = objref[1]
|
# f3 = objref[1]
|
||||||
f3 = 0
|
f3 = 0
|
||||||
|
|
||||||
data.append(struct.pack('>B', f1))
|
data.append(struct.pack('>B', f1))
|
||||||
data.append(struct.pack('>L', f2)[-fl2:])
|
data.append(struct.pack('>L', f2)[-fl2:])
|
||||||
data.append(struct.pack('>L', f3)[-fl3:])
|
data.append(struct.pack('>L', f3)[-fl3:])
|
||||||
index.extend((first, prev - first + 1))
|
index.extend((first, prev - first + 1))
|
||||||
data = zlib.compress(''.join(data))
|
data = zlib.compress(''.join(data))
|
||||||
dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
|
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],
|
'Filter': LITERALS_FLATE_DECODE[0],
|
||||||
'Root': trailer['Root'],}
|
'Root': trailer['Root'],}
|
||||||
if 'Info' in trailer:
|
if 'Info' in trailer:
|
||||||
|
@ -2049,9 +2050,9 @@ class PDFSerializer(object):
|
||||||
string = string.replace(')', r'\)')
|
string = string.replace(')', r'\)')
|
||||||
# get rid of ciando id
|
# get rid of ciando id
|
||||||
regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
|
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
|
return string
|
||||||
|
|
||||||
def serialize_object(self, obj):
|
def serialize_object(self, obj):
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
# Correct malformed Mac OS resource forks for Stanza
|
# Correct malformed Mac OS resource forks for Stanza
|
||||||
|
@ -2075,21 +2076,21 @@ class PDFSerializer(object):
|
||||||
elif isinstance(obj, bool):
|
elif isinstance(obj, bool):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(' ')
|
||||||
self.write(str(obj).lower())
|
self.write(str(obj).lower())
|
||||||
elif isinstance(obj, (int, long, float)):
|
elif isinstance(obj, (int, long, float)):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(' ')
|
||||||
self.write(str(obj))
|
self.write(str(obj))
|
||||||
elif isinstance(obj, PDFObjRef):
|
elif isinstance(obj, PDFObjRef):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(' ')
|
||||||
self.write('%d %d R' % (obj.objid, 0))
|
self.write('%d %d R' % (obj.objid, 0))
|
||||||
elif isinstance(obj, PDFStream):
|
elif isinstance(obj, PDFStream):
|
||||||
### If we don't generate cross ref streams the object streams
|
### If we don't generate cross ref streams the object streams
|
||||||
### are no longer useful, as we have extracted all objects from
|
### are no longer useful, as we have extracted all objects from
|
||||||
### them. Therefore leave them out from the output.
|
### them. Therefore leave them out from the output.
|
||||||
if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
|
if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
|
||||||
self.write('(deleted)')
|
self.write('(deleted)')
|
||||||
else:
|
else:
|
||||||
data = obj.get_decdata()
|
data = obj.get_decdata()
|
||||||
self.serialize_object(obj.dic)
|
self.serialize_object(obj.dic)
|
||||||
|
@ -2101,7 +2102,7 @@ class PDFSerializer(object):
|
||||||
if data[0].isalnum() and self.last.isalnum():
|
if data[0].isalnum() and self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(' ')
|
||||||
self.write(data)
|
self.write(data)
|
||||||
|
|
||||||
def serialize_indirect(self, objid, obj):
|
def serialize_indirect(self, objid, obj):
|
||||||
self.write('%d 0 obj' % (objid,))
|
self.write('%d 0 obj' % (objid,))
|
||||||
self.serialize_object(obj)
|
self.serialize_object(obj)
|
||||||
|
@ -2136,7 +2137,7 @@ class IneptPDFDeDRM(FileTypePlugin):
|
||||||
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = 'DiapDealer'
|
author = 'DiapDealer'
|
||||||
version = (0, 1, 4)
|
version = (0, 1, 5)
|
||||||
minimum_calibre_version = (0, 7, 55) # for the new plugin interface
|
minimum_calibre_version = (0, 7, 55) # for the new plugin interface
|
||||||
file_types = set(['pdf'])
|
file_types = set(['pdf'])
|
||||||
on_import = True
|
on_import = True
|
||||||
|
|
Binary file not shown.
|
@ -385,17 +385,22 @@ def GetIDString():
|
||||||
if isNewInstall():
|
if isNewInstall():
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
sernum = GetVolumeSerialNumber()
|
sernum = GetVolumeSerialNumber()
|
||||||
if len(sernum) > 7:
|
if len(sernum) > 7:
|
||||||
|
print('Using Volume Serial Number for ID: '+sernum)
|
||||||
return sernum
|
return sernum
|
||||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||||
if len(uuidnum) > 7:
|
if len(uuidnum) > 7:
|
||||||
|
print('Using Disk Partition UUID for ID: '+uuidnum)
|
||||||
return uuidnum
|
return uuidnum
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
|
print('Using Fixed constant 9999999999 for ID.')
|
||||||
return '9999999999'
|
return '9999999999'
|
||||||
|
|
||||||
|
|
||||||
|
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found K4Mac kindle-info file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .rainier*-kinf files
|
# add any .rainier*-kinf files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||||
|
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .kinf2011 files
|
# add any .kinf2011 files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
||||||
|
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf2011 file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
print('No kindle-info files have been found.')
|
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
# determine type of kindle info provided and return a
|
# determine type of kindle info provided and return a
|
||||||
|
|
|
@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
|
||||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||||
|
|
||||||
def GetIDString():
|
def GetIDString():
|
||||||
return GetVolumeSerialNumber()
|
vsn = GetVolumeSerialNumber()
|
||||||
|
print('Using Volume Serial Number for ID: '+vsn)
|
||||||
|
return vsn
|
||||||
|
|
||||||
def getLastError():
|
def getLastError():
|
||||||
GetLastError = kernel32.GetLastError
|
GetLastError = kernel32.GetLastError
|
||||||
|
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
path = os.environ['LOCALAPPDATA']
|
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
|
# first look for older kindle-info files
|
||||||
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No kindle.info files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kindle.info file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
||||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.9.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kinf2011 file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,8 +57,11 @@
|
||||||
# 0.33 - Performance improvements for large files (concatenation)
|
# 0.33 - Performance improvements for large files (concatenation)
|
||||||
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
||||||
# 0.35 - add interface to get mobi_version
|
# 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
|
import sys
|
||||||
|
|
||||||
|
@ -168,9 +171,10 @@ class MobiBook:
|
||||||
off = self.sections[section][0]
|
off = self.sections[section][0]
|
||||||
return self.data_file[off:endoff]
|
return self.data_file[off:endoff]
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile, announce = True):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
if announce:
|
||||||
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
|
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = file(infile, 'rb').read()
|
self.data_file = file(infile, 'rb').read()
|
||||||
|
@ -198,6 +202,7 @@ class MobiBook:
|
||||||
print "Book has format: ", self.magic
|
print "Book has format: ", self.magic
|
||||||
self.extra_data_flags = 0
|
self.extra_data_flags = 0
|
||||||
self.mobi_length = 0
|
self.mobi_length = 0
|
||||||
|
self.mobi_codepage = 1252
|
||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
self.meta_array = {}
|
self.meta_array = {}
|
||||||
return
|
return
|
||||||
|
@ -248,18 +253,19 @@ class MobiBook:
|
||||||
65001 : 'utf-8',
|
65001 : 'utf-8',
|
||||||
}
|
}
|
||||||
title = ''
|
title = ''
|
||||||
if 503 in self.meta_array:
|
codec = 'windows-1252'
|
||||||
title = self.meta_array[503]
|
if self.magic == 'BOOKMOBI':
|
||||||
else :
|
if 503 in self.meta_array:
|
||||||
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
|
title = self.meta_array[503]
|
||||||
tend = toff + tlen
|
else:
|
||||||
title = self.sect[toff:tend]
|
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 == '':
|
if title == '':
|
||||||
title = self.header[:32]
|
title = self.header[:32]
|
||||||
title = title.split("\0")[0]
|
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')
|
return unicode(title, codec).encode('utf-8')
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
def getPIDMetaInfo(self):
|
||||||
|
@ -375,7 +381,7 @@ class MobiBook:
|
||||||
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
|
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)
|
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||||
if not found_key:
|
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
|
# kill the drm keys
|
||||||
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||||||
# kill the drm pointers
|
# kill the drm pointers
|
||||||
|
@ -411,26 +417,26 @@ class MobiBook:
|
||||||
print "done"
|
print "done"
|
||||||
return
|
return
|
||||||
|
|
||||||
def getUnencryptedBook(infile,pid):
|
def getUnencryptedBook(infile,pid,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile,announce)
|
||||||
book.processBook([pid])
|
book.processBook([pid])
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
def getUnencryptedBookWithList(infile,pidlist):
|
def getUnencryptedBookWithList(infile,pidlist,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile, announce)
|
||||||
book.processBook(pidlist)
|
book.processBook(pidlist)
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
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:
|
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 "Usage:"
|
||||||
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
||||||
return 1
|
return 1
|
||||||
|
@ -442,7 +448,7 @@ def main(argv=sys.argv):
|
||||||
else:
|
else:
|
||||||
pidlist = {}
|
pidlist = {}
|
||||||
try:
|
try:
|
||||||
stripped_file = getUnencryptedBookWithList(infile, pidlist)
|
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
|
||||||
file(outfile, 'wb').write(stripped_file)
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print "Error: %s" % e
|
print "Error: %s" % e
|
||||||
|
|
|
@ -1,49 +1,46 @@
|
||||||
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
{\colortbl;\red255\green255\blue255;}
|
{\colortbl;\red255\green255\blue255;}
|
||||||
\paperw11900\paperh16840\margl1440\margr1440\vieww10320\viewh9840\viewkind0
|
\paperw11900\paperh16840\margl1440\margr1440\vieww12360\viewh16560\viewkind0
|
||||||
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
|
\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 \
|
\b0 \
|
||||||
\
|
|
||||||
\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.\
|
|
||||||
\
|
|
||||||
It should work without manual configuration with Kindle for Mac 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 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.\
|
|
||||||
\
|
|
||||||
Once these preferences have been set, the user can simply drag and drop ebooks onto the DeDRM droplet to remove the DRM.\
|
|
||||||
\
|
|
||||||
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
|
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
|
||||||
\cf0 \
|
\cf0 \
|
||||||
|
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qj\pardirnatural
|
||||||
|
\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.\
|
||||||
|
\
|
||||||
|
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:\
|
||||||
|
\
|
||||||
|
Kindle (not Kindle Fire): 16 digit Serial Number\
|
||||||
|
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\
|
||||||
|
\
|
||||||
|
A final preference is the destination folder for the DRM-free copies of your ebooks that the application produces. This can be either the same folder as the original ebook, or a folder of your choice.\
|
||||||
|
\
|
||||||
|
Once these preferences have been set, you can drag and drop ebooks (or folders of ebooks) onto the DeDRM droplet to remove the DRM.\
|
||||||
|
\
|
||||||
|
This program requires Mac OS X 10.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 \
|
\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\
|
\b Use
|
||||||
|
|
||||||
\b0 \
|
\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.\
|
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.\
|
||||||
\
|
\
|
||||||
2. Drag & Drop DRMed ebooks or folders containing DRMed ebooks onto the Application, either in your Applications folder, or the icon in your dock.}
|
\
|
||||||
|
|
||||||
|
\b Troubleshooting\
|
||||||
|
|
||||||
|
\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.}
|
Binary file not shown.
|
@ -24,17 +24,17 @@
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>droplet</string>
|
<string>droplet</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>DeDRM 5.1, Written 2010–2012 by Apprentice Alf and others.</string>
|
<string>DeDRM 5.2, Written 2010–2012 by Apprentice Alf and others.</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>droplet</string>
|
<string>DeDRM</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>DeDRM 5.1</string>
|
<string>DeDRM 5.2</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.1</string>
|
<string>5.2</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>dplt</string>
|
<string>dplt</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
@ -43,16 +43,12 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>WindowState</key>
|
<key>WindowState</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>dividerCollapsed</key>
|
|
||||||
<false/>
|
|
||||||
<key>eventLogLevel</key>
|
|
||||||
<integer>-1</integer>
|
|
||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>ScriptWindowState</string>
|
<string>ScriptWindowState</string>
|
||||||
<key>positionOfDivider</key>
|
<key>positionOfDivider</key>
|
||||||
<real>460</real>
|
<real>554</real>
|
||||||
<key>savedFrame</key>
|
<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>
|
<key>selectedTabView</key>
|
||||||
<string>event log</string>
|
<string>event log</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
Binary file not shown.
|
@ -2,10 +2,16 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>BuildMachineOSBuild</key>
|
||||||
|
<string>10K549</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>English</string>
|
<string>English</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>DeDRM Progress</string>
|
<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>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.apprenticealf.DeDRMProgress</string>
|
<string>com.apprenticealf.DeDRMProgress</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
@ -15,11 +21,25 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<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>
|
<key>NSAppleScriptEnabled</key>
|
||||||
<string>YES</string>
|
<string>YES</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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.title' : (1, 'text', 0, 0),
|
||||||
'links.href' : (1, 'text', 0, 0),
|
'links.href' : (1, 'text', 0, 0),
|
||||||
'links.type' : (1, 'text', 0, 0),
|
'links.type' : (1, 'text', 0, 0),
|
||||||
|
'links.id' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
'paraCont' : (0, 'number', 1, 1),
|
'paraCont' : (0, 'number', 1, 1),
|
||||||
'paraCont.rootID' : (1, 'number', 0, 0),
|
'paraCont.rootID' : (1, 'number', 0, 0),
|
||||||
|
@ -239,6 +240,7 @@ class PageParser(object):
|
||||||
'group' : (1, 'snippets', 1, 0),
|
'group' : (1, 'snippets', 1, 0),
|
||||||
'group.type' : (1, 'scalar_text', 0, 0),
|
'group.type' : (1, 'scalar_text', 0, 0),
|
||||||
'group._tag' : (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' : (1, 'snippets', 1, 0),
|
||||||
'region.type' : (1, 'scalar_text', 0, 0),
|
'region.type' : (1, 'scalar_text', 0, 0),
|
||||||
|
@ -246,7 +248,7 @@ class PageParser(object):
|
||||||
'region.y' : (1, 'scalar_number', 0, 0),
|
'region.y' : (1, 'scalar_number', 0, 0),
|
||||||
'region.h' : (1, 'scalar_number', 0, 0),
|
'region.h' : (1, 'scalar_number', 0, 0),
|
||||||
'region.w' : (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),
|
'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
|
#! /usr/bin/env python
|
||||||
# ineptpdf.pyw, version 7.9
|
# ineptpdf.pyw, version 7.11
|
||||||
|
|
||||||
from __future__ import with_statement
|
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.7 - On Windows try PyCrypto first and OpenSSL next
|
||||||
# 7.8 - Modify interface to allow use of import
|
# 7.8 - Modify interface to allow use of import
|
||||||
# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
|
# 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.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
|
@ -293,6 +295,7 @@ def _load_crypto_pycrypto():
|
||||||
return self._arc4.decrypt(data)
|
return self._arc4.decrypt(data)
|
||||||
|
|
||||||
class AES(object):
|
class AES(object):
|
||||||
|
MODE_CBC = _AES.MODE_CBC
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls, userkey, mode, iv):
|
def new(cls, userkey, mode, iv):
|
||||||
self = AES()
|
self = AES()
|
||||||
|
@ -2199,11 +2202,19 @@ class DecryptionDialog(Tkinter.Frame):
|
||||||
|
|
||||||
def decryptBook(keypath, inpath, outpath):
|
def decryptBook(keypath, inpath, outpath):
|
||||||
with open(inpath, 'rb') as inf:
|
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
|
# hope this will fix the 'bad file descriptor' problem
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
# help construct to make sure the method runs to the end
|
# help construct to make sure the method runs to the end
|
||||||
serializer.dump(outf)
|
try:
|
||||||
|
serializer.dump(outf)
|
||||||
|
except:
|
||||||
|
print "error writing pdf."
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import with_statement
|
||||||
# and many many others
|
# and many many others
|
||||||
|
|
||||||
|
|
||||||
__version__ = '4.2'
|
__version__ = '4.4'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
|
@ -58,7 +58,7 @@ else:
|
||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# 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):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
|
@ -73,7 +73,7 @@ def cleanup_name(name):
|
||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
one = one.replace(' ','_')
|
one = one.replace('_',' ')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
|
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
title = mb.getBookTitle()
|
title = mb.getBookTitle()
|
||||||
print "Processing Book: ", title
|
print "Processing Book: ", title
|
||||||
filenametitle = cleanup_name(title)
|
filenametitle = cleanup_name(title)
|
||||||
outfilename = bookname
|
outfilename = cleanup_name(bookname)
|
||||||
if len(outfilename)<=8 or len(filenametitle)<=8:
|
|
||||||
outfilename = outfilename + "_" + filenametitle
|
# generate 'sensible' filename, that will sort with the original name,
|
||||||
elif outfilename[:8] != filenametitle[:8]:
|
# but is close to the name from the file.
|
||||||
outfilename = outfilename[:8] + "_" + filenametitle
|
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
|
# avoid excessively long file names
|
||||||
if len(outfilename)>150:
|
if len(outfilename)>150:
|
||||||
|
|
|
@ -385,17 +385,22 @@ def GetIDString():
|
||||||
if isNewInstall():
|
if isNewInstall():
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
sernum = GetVolumeSerialNumber()
|
sernum = GetVolumeSerialNumber()
|
||||||
if len(sernum) > 7:
|
if len(sernum) > 7:
|
||||||
|
print('Using Volume Serial Number for ID: '+sernum)
|
||||||
return sernum
|
return sernum
|
||||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||||
if len(uuidnum) > 7:
|
if len(uuidnum) > 7:
|
||||||
|
print('Using Disk Partition UUID for ID: '+uuidnum)
|
||||||
return uuidnum
|
return uuidnum
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
|
print('Using Fixed constant 9999999999 for ID.')
|
||||||
return '9999999999'
|
return '9999999999'
|
||||||
|
|
||||||
|
|
||||||
|
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found K4Mac kindle-info file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .rainier*-kinf files
|
# add any .rainier*-kinf files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||||
|
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .kinf2011 files
|
# add any .kinf2011 files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
||||||
|
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf2011 file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
print('No kindle-info files have been found.')
|
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
# determine type of kindle info provided and return a
|
# determine type of kindle info provided and return a
|
||||||
|
|
|
@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
|
||||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||||
|
|
||||||
def GetIDString():
|
def GetIDString():
|
||||||
return GetVolumeSerialNumber()
|
vsn = GetVolumeSerialNumber()
|
||||||
|
print('Using Volume Serial Number for ID: '+vsn)
|
||||||
|
return vsn
|
||||||
|
|
||||||
def getLastError():
|
def getLastError():
|
||||||
GetLastError = kernel32.GetLastError
|
GetLastError = kernel32.GetLastError
|
||||||
|
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
path = os.environ['LOCALAPPDATA']
|
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
|
# first look for older kindle-info files
|
||||||
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No kindle.info files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kindle.info file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
||||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.9.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kinf2011 file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -262,9 +262,15 @@ def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
|
||||||
if k4:
|
if k4:
|
||||||
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
||||||
for infoFile in 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:
|
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:
|
for pid in pids:
|
||||||
pidlst.append(pid)
|
pidlst.append(pid)
|
||||||
return pidlst
|
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())
|
Binary file not shown.
Binary file not shown.
|
@ -57,8 +57,11 @@
|
||||||
# 0.33 - Performance improvements for large files (concatenation)
|
# 0.33 - Performance improvements for large files (concatenation)
|
||||||
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
||||||
# 0.35 - add interface to get mobi_version
|
# 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
|
import sys
|
||||||
|
|
||||||
|
@ -168,9 +171,10 @@ class MobiBook:
|
||||||
off = self.sections[section][0]
|
off = self.sections[section][0]
|
||||||
return self.data_file[off:endoff]
|
return self.data_file[off:endoff]
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile, announce = True):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
if announce:
|
||||||
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
|
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = file(infile, 'rb').read()
|
self.data_file = file(infile, 'rb').read()
|
||||||
|
@ -198,6 +202,7 @@ class MobiBook:
|
||||||
print "Book has format: ", self.magic
|
print "Book has format: ", self.magic
|
||||||
self.extra_data_flags = 0
|
self.extra_data_flags = 0
|
||||||
self.mobi_length = 0
|
self.mobi_length = 0
|
||||||
|
self.mobi_codepage = 1252
|
||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
self.meta_array = {}
|
self.meta_array = {}
|
||||||
return
|
return
|
||||||
|
@ -248,18 +253,19 @@ class MobiBook:
|
||||||
65001 : 'utf-8',
|
65001 : 'utf-8',
|
||||||
}
|
}
|
||||||
title = ''
|
title = ''
|
||||||
if 503 in self.meta_array:
|
codec = 'windows-1252'
|
||||||
title = self.meta_array[503]
|
if self.magic == 'BOOKMOBI':
|
||||||
else :
|
if 503 in self.meta_array:
|
||||||
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
|
title = self.meta_array[503]
|
||||||
tend = toff + tlen
|
else:
|
||||||
title = self.sect[toff:tend]
|
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 == '':
|
if title == '':
|
||||||
title = self.header[:32]
|
title = self.header[:32]
|
||||||
title = title.split("\0")[0]
|
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')
|
return unicode(title, codec).encode('utf-8')
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
def getPIDMetaInfo(self):
|
||||||
|
@ -375,7 +381,7 @@ class MobiBook:
|
||||||
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
|
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)
|
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||||
if not found_key:
|
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
|
# kill the drm keys
|
||||||
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||||||
# kill the drm pointers
|
# kill the drm pointers
|
||||||
|
@ -411,26 +417,26 @@ class MobiBook:
|
||||||
print "done"
|
print "done"
|
||||||
return
|
return
|
||||||
|
|
||||||
def getUnencryptedBook(infile,pid):
|
def getUnencryptedBook(infile,pid,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile,announce)
|
||||||
book.processBook([pid])
|
book.processBook([pid])
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
def getUnencryptedBookWithList(infile,pidlist):
|
def getUnencryptedBookWithList(infile,pidlist,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile, announce)
|
||||||
book.processBook(pidlist)
|
book.processBook(pidlist)
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
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:
|
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 "Usage:"
|
||||||
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
||||||
return 1
|
return 1
|
||||||
|
@ -442,7 +448,7 @@ def main(argv=sys.argv):
|
||||||
else:
|
else:
|
||||||
pidlist = {}
|
pidlist = {}
|
||||||
try:
|
try:
|
||||||
stripped_file = getUnencryptedBookWithList(infile, pidlist)
|
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
|
||||||
file(outfile, 'wb').write(stripped_file)
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print "Error: %s" % 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
|
scale = self.pw
|
||||||
elif attr == 'line-space':
|
elif attr == 'line-space':
|
||||||
scale = self.fontsize * 2.0
|
scale = self.fontsize * 2.0
|
||||||
|
|
||||||
|
if val == "":
|
||||||
|
val = 0
|
||||||
|
|
||||||
if not ((attr == 'hang') and (int(val) == 0)) :
|
if not ((attr == 'hang') and (int(val) == 0)) :
|
||||||
pv = float(val)/scale
|
pv = float(val)/scale
|
||||||
|
|
|
@ -31,11 +31,8 @@ class TpzDRMError(Exception):
|
||||||
# local support routines
|
# local support routines
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.k4mobidedrm import kgenpids
|
from calibre_plugins.k4mobidedrm import kgenpids
|
||||||
from calibre_plugins.k4mobidedrm import genbook
|
|
||||||
else:
|
else:
|
||||||
import kgenpids
|
import kgenpids
|
||||||
import genbook
|
|
||||||
|
|
||||||
|
|
||||||
# recursive zip creation support routine
|
# recursive zip creation support routine
|
||||||
def zipUpDir(myzip, tdir, localname):
|
def zipUpDir(myzip, tdir, localname):
|
||||||
|
@ -271,6 +268,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
print "\nBook Successfully generated"
|
||||||
|
@ -300,6 +302,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
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.
|
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:
|
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
|
eInk Kindle: 16 digit Serial Number
|
||||||
Barnes & Noble key files (bnepubkey.b64)
|
Barnes & Noble: key file (bnepubkey.b64)
|
||||||
eReader Social DRM: (Name:Last 8 digits of CC number)
|
eReader Social DRM: Name:Last 8 digits of CC number
|
||||||
MobiPocket, Kindle for iPhone/iPad/iPodTouch 10 digit PID
|
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.
|
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.
|
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
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
1. From tools_vX.X\DeDRM_Applications\, right click on DeDRM_v_X.X_WinApp.zip and fully Extract its contents.
|
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.
|
||||||
|
|
||||||
2. Move the resulting DeDRM_vX.X_WinApp folder to whereever you keep you other programs.
|
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.
|
||||||
(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.
|
|
||||||
|
|
||||||
4. To set the preferences simply double-click on your just created short-cut.
|
|
||||||
|
|
||||||
|
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!
|
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
|
import simpleprefs
|
||||||
|
|
||||||
|
|
||||||
__version__ = '5.0'
|
__version__ = '5.2'
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -38,7 +38,7 @@ class MainApp(Tk):
|
||||||
['serials', 'seriallist.txt'],
|
['serials', 'seriallist.txt'],
|
||||||
['sdrms' , 'sdrmlist.txt' ],
|
['sdrms' , 'sdrmlist.txt' ],
|
||||||
['outdir' , 'outdir.txt' ]]
|
['outdir' , 'outdir.txt' ]]
|
||||||
self.po = simpleprefs.SimplePrefs('DeDRM',description)
|
self.po = simpleprefs.SimplePrefs("DeDRM",description)
|
||||||
if self.dnd:
|
if self.dnd:
|
||||||
self.cd = ConvDialog(self)
|
self.cd = ConvDialog(self)
|
||||||
prefs = self.getPreferences()
|
prefs = self.getPreferences()
|
||||||
|
@ -95,7 +95,7 @@ class PrefsDialog(Toplevel):
|
||||||
Toplevel.__init__(self, mainapp)
|
Toplevel.__init__(self, mainapp)
|
||||||
self.withdraw()
|
self.withdraw()
|
||||||
self.protocol("WM_DELETE_WINDOW", self.withdraw)
|
self.protocol("WM_DELETE_WINDOW", self.withdraw)
|
||||||
self.title("DeDRM")
|
self.title("DeDRM " + __version__)
|
||||||
self.prefs_array = prefs_array
|
self.prefs_array = prefs_array
|
||||||
self.status = Tkinter.Label(self, text='Setting Preferences')
|
self.status = Tkinter.Label(self, text='Setting Preferences')
|
||||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
|
@ -566,6 +566,7 @@ def main(argv=sys.argv):
|
||||||
infilelst = argv[1:]
|
infilelst = argv[1:]
|
||||||
filenames = []
|
filenames = []
|
||||||
for infile in infilelst:
|
for infile in infilelst:
|
||||||
|
infile = infile.decode(sys.getfilesystemencoding())
|
||||||
print infile
|
print infile
|
||||||
infile = infile.replace('"','')
|
infile = infile.replace('"','')
|
||||||
infile = os.path.abspath(infile)
|
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.title' : (1, 'text', 0, 0),
|
||||||
'links.href' : (1, 'text', 0, 0),
|
'links.href' : (1, 'text', 0, 0),
|
||||||
'links.type' : (1, 'text', 0, 0),
|
'links.type' : (1, 'text', 0, 0),
|
||||||
|
'links.id' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
'paraCont' : (0, 'number', 1, 1),
|
'paraCont' : (0, 'number', 1, 1),
|
||||||
'paraCont.rootID' : (1, 'number', 0, 0),
|
'paraCont.rootID' : (1, 'number', 0, 0),
|
||||||
|
@ -239,6 +240,7 @@ class PageParser(object):
|
||||||
'group' : (1, 'snippets', 1, 0),
|
'group' : (1, 'snippets', 1, 0),
|
||||||
'group.type' : (1, 'scalar_text', 0, 0),
|
'group.type' : (1, 'scalar_text', 0, 0),
|
||||||
'group._tag' : (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' : (1, 'snippets', 1, 0),
|
||||||
'region.type' : (1, 'scalar_text', 0, 0),
|
'region.type' : (1, 'scalar_text', 0, 0),
|
||||||
|
@ -246,7 +248,7 @@ class PageParser(object):
|
||||||
'region.y' : (1, 'scalar_number', 0, 0),
|
'region.y' : (1, 'scalar_number', 0, 0),
|
||||||
'region.h' : (1, 'scalar_number', 0, 0),
|
'region.h' : (1, 'scalar_number', 0, 0),
|
||||||
'region.w' : (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),
|
'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
|
#! /usr/bin/env python
|
||||||
# ineptpdf.pyw, version 7.9
|
# ineptpdf.pyw, version 7.11
|
||||||
|
|
||||||
from __future__ import with_statement
|
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.7 - On Windows try PyCrypto first and OpenSSL next
|
||||||
# 7.8 - Modify interface to allow use of import
|
# 7.8 - Modify interface to allow use of import
|
||||||
# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
|
# 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.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
|
@ -2200,11 +2202,19 @@ class DecryptionDialog(Tkinter.Frame):
|
||||||
|
|
||||||
def decryptBook(keypath, inpath, outpath):
|
def decryptBook(keypath, inpath, outpath):
|
||||||
with open(inpath, 'rb') as inf:
|
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
|
# hope this will fix the 'bad file descriptor' problem
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
# help construct to make sure the method runs to the end
|
# help construct to make sure the method runs to the end
|
||||||
serializer.dump(outf)
|
try:
|
||||||
|
serializer.dump(outf)
|
||||||
|
except:
|
||||||
|
print "error writing pdf."
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from __future__ import with_statement
|
||||||
# and many many others
|
# and many many others
|
||||||
|
|
||||||
|
|
||||||
__version__ = '4.2'
|
__version__ = '4.4'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
|
@ -58,7 +58,7 @@ else:
|
||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# 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):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
|
@ -73,7 +73,7 @@ def cleanup_name(name):
|
||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
one = one.replace(' ','_')
|
one = one.replace('_',' ')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
|
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
title = mb.getBookTitle()
|
title = mb.getBookTitle()
|
||||||
print "Processing Book: ", title
|
print "Processing Book: ", title
|
||||||
filenametitle = cleanup_name(title)
|
filenametitle = cleanup_name(title)
|
||||||
outfilename = bookname
|
outfilename = cleanup_name(bookname)
|
||||||
if len(outfilename)<=8 or len(filenametitle)<=8:
|
|
||||||
outfilename = outfilename + "_" + filenametitle
|
# generate 'sensible' filename, that will sort with the original name,
|
||||||
elif outfilename[:8] != filenametitle[:8]:
|
# but is close to the name from the file.
|
||||||
outfilename = outfilename[:8] + "_" + filenametitle
|
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
|
# avoid excessively long file names
|
||||||
if len(outfilename)>150:
|
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);
|
# 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,
|
# 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);
|
# const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc);
|
||||||
|
|
||||||
|
@ -385,17 +385,22 @@ def GetIDString():
|
||||||
if isNewInstall():
|
if isNewInstall():
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
sernum = GetVolumeSerialNumber()
|
sernum = GetVolumeSerialNumber()
|
||||||
if len(sernum) > 7:
|
if len(sernum) > 7:
|
||||||
|
print('Using Volume Serial Number for ID: '+sernum)
|
||||||
return sernum
|
return sernum
|
||||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||||
if len(uuidnum) > 7:
|
if len(uuidnum) > 7:
|
||||||
|
print('Using Disk Partition UUID for ID: '+uuidnum)
|
||||||
return uuidnum
|
return uuidnum
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
|
print('Using Fixed constant 9999999999 for ID.')
|
||||||
return '9999999999'
|
return '9999999999'
|
||||||
|
|
||||||
|
|
||||||
|
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found K4Mac kindle-info file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .rainier*-kinf files
|
# add any .rainier*-kinf files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||||
|
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .kinf2011 files
|
# add any .kinf2011 files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
||||||
|
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf2011 file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
print('No kindle-info files have been found.')
|
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
# determine type of kindle info provided and return a
|
# determine type of kindle info provided and return a
|
||||||
|
|
|
@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
|
||||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||||
|
|
||||||
def GetIDString():
|
def GetIDString():
|
||||||
return GetVolumeSerialNumber()
|
vsn = GetVolumeSerialNumber()
|
||||||
|
print('Using Volume Serial Number for ID: '+vsn)
|
||||||
|
return vsn
|
||||||
|
|
||||||
def getLastError():
|
def getLastError():
|
||||||
GetLastError = kernel32.GetLastError
|
GetLastError = kernel32.GetLastError
|
||||||
|
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
path = os.environ['LOCALAPPDATA']
|
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
|
# first look for older kindle-info files
|
||||||
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No kindle.info files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kindle.info file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
||||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.9.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kinf2011 file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -262,9 +262,15 @@ def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
|
||||||
if k4:
|
if k4:
|
||||||
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
||||||
for infoFile in 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:
|
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:
|
for pid in pids:
|
||||||
pidlst.append(pid)
|
pidlst.append(pid)
|
||||||
return pidlst
|
return pidlst
|
||||||
|
|
|
@ -57,8 +57,11 @@
|
||||||
# 0.33 - Performance improvements for large files (concatenation)
|
# 0.33 - Performance improvements for large files (concatenation)
|
||||||
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
||||||
# 0.35 - add interface to get mobi_version
|
# 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
|
import sys
|
||||||
|
|
||||||
|
@ -168,9 +171,10 @@ class MobiBook:
|
||||||
off = self.sections[section][0]
|
off = self.sections[section][0]
|
||||||
return self.data_file[off:endoff]
|
return self.data_file[off:endoff]
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile, announce = True):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
if announce:
|
||||||
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
|
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = file(infile, 'rb').read()
|
self.data_file = file(infile, 'rb').read()
|
||||||
|
@ -198,6 +202,7 @@ class MobiBook:
|
||||||
print "Book has format: ", self.magic
|
print "Book has format: ", self.magic
|
||||||
self.extra_data_flags = 0
|
self.extra_data_flags = 0
|
||||||
self.mobi_length = 0
|
self.mobi_length = 0
|
||||||
|
self.mobi_codepage = 1252
|
||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
self.meta_array = {}
|
self.meta_array = {}
|
||||||
return
|
return
|
||||||
|
@ -248,18 +253,19 @@ class MobiBook:
|
||||||
65001 : 'utf-8',
|
65001 : 'utf-8',
|
||||||
}
|
}
|
||||||
title = ''
|
title = ''
|
||||||
if 503 in self.meta_array:
|
codec = 'windows-1252'
|
||||||
title = self.meta_array[503]
|
if self.magic == 'BOOKMOBI':
|
||||||
else :
|
if 503 in self.meta_array:
|
||||||
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
|
title = self.meta_array[503]
|
||||||
tend = toff + tlen
|
else:
|
||||||
title = self.sect[toff:tend]
|
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 == '':
|
if title == '':
|
||||||
title = self.header[:32]
|
title = self.header[:32]
|
||||||
title = title.split("\0")[0]
|
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')
|
return unicode(title, codec).encode('utf-8')
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
def getPIDMetaInfo(self):
|
||||||
|
@ -375,7 +381,7 @@ class MobiBook:
|
||||||
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
|
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)
|
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||||
if not found_key:
|
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
|
# kill the drm keys
|
||||||
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||||||
# kill the drm pointers
|
# kill the drm pointers
|
||||||
|
@ -411,26 +417,26 @@ class MobiBook:
|
||||||
print "done"
|
print "done"
|
||||||
return
|
return
|
||||||
|
|
||||||
def getUnencryptedBook(infile,pid):
|
def getUnencryptedBook(infile,pid,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile,announce)
|
||||||
book.processBook([pid])
|
book.processBook([pid])
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
def getUnencryptedBookWithList(infile,pidlist):
|
def getUnencryptedBookWithList(infile,pidlist,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile, announce)
|
||||||
book.processBook(pidlist)
|
book.processBook(pidlist)
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
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:
|
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 "Usage:"
|
||||||
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
||||||
return 1
|
return 1
|
||||||
|
@ -442,7 +448,7 @@ def main(argv=sys.argv):
|
||||||
else:
|
else:
|
||||||
pidlist = {}
|
pidlist = {}
|
||||||
try:
|
try:
|
||||||
stripped_file = getUnencryptedBookWithList(infile, pidlist)
|
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
|
||||||
file(outfile, 'wb').write(stripped_file)
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print "Error: %s" % e
|
print "Error: %s" % e
|
||||||
|
|
|
@ -164,6 +164,9 @@ class DocParser(object):
|
||||||
scale = self.pw
|
scale = self.pw
|
||||||
elif attr == 'line-space':
|
elif attr == 'line-space':
|
||||||
scale = self.fontsize * 2.0
|
scale = self.fontsize * 2.0
|
||||||
|
|
||||||
|
if val == "":
|
||||||
|
val = 0
|
||||||
|
|
||||||
if not ((attr == 'hang') and (int(val) == 0)) :
|
if not ((attr == 'hang') and (int(val) == 0)) :
|
||||||
pv = float(val)/scale
|
pv = float(val)/scale
|
||||||
|
|
|
@ -31,11 +31,8 @@ class TpzDRMError(Exception):
|
||||||
# local support routines
|
# local support routines
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.k4mobidedrm import kgenpids
|
from calibre_plugins.k4mobidedrm import kgenpids
|
||||||
from calibre_plugins.k4mobidedrm import genbook
|
|
||||||
else:
|
else:
|
||||||
import kgenpids
|
import kgenpids
|
||||||
import genbook
|
|
||||||
|
|
||||||
|
|
||||||
# recursive zip creation support routine
|
# recursive zip creation support routine
|
||||||
def zipUpDir(myzip, tdir, localname):
|
def zipUpDir(myzip, tdir, localname):
|
||||||
|
@ -271,6 +268,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
print "\nBook Successfully generated"
|
||||||
|
@ -300,6 +302,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
print "\nBook Successfully generated"
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
# files reveals that a confusion has arisen because trailing data entries
|
# files reveals that a confusion has arisen because trailing data entries
|
||||||
# are not encrypted, but it turns out that the multibyte entries
|
# are not encrypted, but it turns out that the multibyte entries
|
||||||
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
||||||
# This knowledge leads to a simplification of the test for the
|
# This knowledge leads to a simplification of the test for the
|
||||||
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
# 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.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.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
|
# 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.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
|
||||||
# 0.21 - Added support for multiple pids
|
# 0.21 - Added support for multiple pids
|
||||||
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
|
# 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.24 - add support for type 1 encryption and 'TEXtREAd' books as well
|
||||||
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
|
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
|
||||||
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
|
# 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.31 - The multibyte encrytion info is true for version 7 files too.
|
||||||
# 0.32 - Added support for "Print Replica" Kindle ebooks
|
# 0.32 - Added support for "Print Replica" Kindle ebooks
|
||||||
# 0.33 - Performance improvements for large files (concatenation)
|
# 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
|
import sys
|
||||||
|
|
||||||
|
@ -73,6 +78,7 @@ sys.stdout=Unbuffered(sys.stdout)
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import binascii
|
import binascii
|
||||||
|
from alfcrypto import Pukall_Cipher
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -84,36 +90,37 @@ class DrmException(Exception):
|
||||||
|
|
||||||
# Implementation of Pukall Cipher 1
|
# Implementation of Pukall Cipher 1
|
||||||
def PC1(key, src, decryption=True):
|
def PC1(key, src, decryption=True):
|
||||||
sum1 = 0;
|
return Pukall_Cipher().PC1(key,src,decryption)
|
||||||
sum2 = 0;
|
# sum1 = 0;
|
||||||
keyXorVal = 0;
|
# sum2 = 0;
|
||||||
if len(key)!=16:
|
# keyXorVal = 0;
|
||||||
print "Bad key length!"
|
# if len(key)!=16:
|
||||||
return None
|
# print "Bad key length!"
|
||||||
wkey = []
|
# return None
|
||||||
for i in xrange(8):
|
# wkey = []
|
||||||
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
# for i in xrange(8):
|
||||||
dst = ""
|
# wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||||
for i in xrange(len(src)):
|
# dst = ""
|
||||||
temp1 = 0;
|
# for i in xrange(len(src)):
|
||||||
byteXorVal = 0;
|
# temp1 = 0;
|
||||||
for j in xrange(8):
|
# byteXorVal = 0;
|
||||||
temp1 ^= wkey[j]
|
# for j in xrange(8):
|
||||||
sum2 = (sum2+j)*20021 + sum1
|
# temp1 ^= wkey[j]
|
||||||
sum1 = (temp1*346)&0xFFFF
|
# sum2 = (sum2+j)*20021 + sum1
|
||||||
sum2 = (sum2+sum1)&0xFFFF
|
# sum1 = (temp1*346)&0xFFFF
|
||||||
temp1 = (temp1*20021+1)&0xFFFF
|
# sum2 = (sum2+sum1)&0xFFFF
|
||||||
byteXorVal ^= temp1 ^ sum2
|
# temp1 = (temp1*20021+1)&0xFFFF
|
||||||
curByte = ord(src[i])
|
# byteXorVal ^= temp1 ^ sum2
|
||||||
if not decryption:
|
# curByte = ord(src[i])
|
||||||
keyXorVal = curByte * 257;
|
# if not decryption:
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
# keyXorVal = curByte * 257;
|
||||||
if decryption:
|
# curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||||
keyXorVal = curByte * 257;
|
# if decryption:
|
||||||
for j in xrange(8):
|
# keyXorVal = curByte * 257;
|
||||||
wkey[j] ^= keyXorVal;
|
# for j in xrange(8):
|
||||||
dst+=chr(curByte)
|
# wkey[j] ^= keyXorVal;
|
||||||
return dst
|
# dst+=chr(curByte)
|
||||||
|
# return dst
|
||||||
|
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
@ -164,9 +171,10 @@ class MobiBook:
|
||||||
off = self.sections[section][0]
|
off = self.sections[section][0]
|
||||||
return self.data_file[off:endoff]
|
return self.data_file[off:endoff]
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile, announce = True):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
if announce:
|
||||||
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
|
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = file(infile, 'rb').read()
|
self.data_file = file(infile, 'rb').read()
|
||||||
|
@ -194,6 +202,7 @@ class MobiBook:
|
||||||
print "Book has format: ", self.magic
|
print "Book has format: ", self.magic
|
||||||
self.extra_data_flags = 0
|
self.extra_data_flags = 0
|
||||||
self.mobi_length = 0
|
self.mobi_length = 0
|
||||||
|
self.mobi_codepage = 1252
|
||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
self.meta_array = {}
|
self.meta_array = {}
|
||||||
return
|
return
|
||||||
|
@ -237,25 +246,26 @@ class MobiBook:
|
||||||
self.meta_array = {}
|
self.meta_array = {}
|
||||||
pass
|
pass
|
||||||
self.print_replica = False
|
self.print_replica = False
|
||||||
|
|
||||||
def getBookTitle(self):
|
def getBookTitle(self):
|
||||||
codec_map = {
|
codec_map = {
|
||||||
1252 : 'windows-1252',
|
1252 : 'windows-1252',
|
||||||
65001 : 'utf-8',
|
65001 : 'utf-8',
|
||||||
}
|
}
|
||||||
title = ''
|
title = ''
|
||||||
if 503 in self.meta_array:
|
codec = 'windows-1252'
|
||||||
title = self.meta_array[503]
|
if self.magic == 'BOOKMOBI':
|
||||||
else :
|
if 503 in self.meta_array:
|
||||||
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
|
title = self.meta_array[503]
|
||||||
tend = toff + tlen
|
else:
|
||||||
title = self.sect[toff:tend]
|
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 == '':
|
if title == '':
|
||||||
title = self.header[:32]
|
title = self.header[:32]
|
||||||
title = title.split("\0")[0]
|
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')
|
return unicode(title, codec).encode('utf-8')
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
def getPIDMetaInfo(self):
|
||||||
|
@ -320,6 +330,9 @@ class MobiBook:
|
||||||
|
|
||||||
def getMobiFile(self, outpath):
|
def getMobiFile(self, outpath):
|
||||||
file(outpath,'wb').write(self.mobi_data)
|
file(outpath,'wb').write(self.mobi_data)
|
||||||
|
|
||||||
|
def getMobiVersion(self):
|
||||||
|
return self.mobi_version
|
||||||
|
|
||||||
def getPrintReplica(self):
|
def getPrintReplica(self):
|
||||||
return self.print_replica
|
return self.print_replica
|
||||||
|
@ -356,9 +369,9 @@ class MobiBook:
|
||||||
if self.magic == 'TEXtREAd':
|
if self.magic == 'TEXtREAd':
|
||||||
bookkey_data = self.sect[0x0E:0x0E+16]
|
bookkey_data = self.sect[0x0E:0x0E+16]
|
||||||
elif self.mobi_version < 0:
|
elif self.mobi_version < 0:
|
||||||
bookkey_data = self.sect[0x90:0x90+16]
|
bookkey_data = self.sect[0x90:0x90+16]
|
||||||
else:
|
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"
|
pid = "00000000"
|
||||||
found_key = PC1(t1_keyvec, bookkey_data)
|
found_key = PC1(t1_keyvec, bookkey_data)
|
||||||
else :
|
else :
|
||||||
|
@ -368,12 +381,12 @@ class MobiBook:
|
||||||
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
|
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)
|
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||||
if not found_key:
|
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
|
# kill the drm keys
|
||||||
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||||||
# kill the drm pointers
|
# kill the drm pointers
|
||||||
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
|
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
|
||||||
|
|
||||||
if pid=="00000000":
|
if pid=="00000000":
|
||||||
print "File has default encryption, no specific PID."
|
print "File has default encryption, no specific PID."
|
||||||
else:
|
else:
|
||||||
|
@ -404,26 +417,26 @@ class MobiBook:
|
||||||
print "done"
|
print "done"
|
||||||
return
|
return
|
||||||
|
|
||||||
def getUnencryptedBook(infile,pid):
|
def getUnencryptedBook(infile,pid,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile,announce)
|
||||||
book.processBook([pid])
|
book.processBook([pid])
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
def getUnencryptedBookWithList(infile,pidlist):
|
def getUnencryptedBookWithList(infile,pidlist,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile, announce)
|
||||||
book.processBook(pidlist)
|
book.processBook(pidlist)
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
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:
|
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 "Usage:"
|
||||||
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
||||||
return 1
|
return 1
|
||||||
|
@ -435,7 +448,7 @@ def main(argv=sys.argv):
|
||||||
else:
|
else:
|
||||||
pidlist = {}
|
pidlist = {}
|
||||||
try:
|
try:
|
||||||
stripped_file = getUnencryptedBookWithList(infile, pidlist)
|
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
|
||||||
file(outfile, 'wb').write(stripped_file)
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print "Error: %s" % e
|
print "Error: %s" % e
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#! /usr/bin/env python
|
#! /usr/bin/env python
|
||||||
# ineptpdf.pyw, version 7.9
|
# ineptpdf.pyw, version 7.11
|
||||||
|
|
||||||
from __future__ import with_statement
|
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.7 - On Windows try PyCrypto first and OpenSSL next
|
||||||
# 7.8 - Modify interface to allow use of import
|
# 7.8 - Modify interface to allow use of import
|
||||||
# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
|
# 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.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
|
@ -2200,11 +2202,19 @@ class DecryptionDialog(Tkinter.Frame):
|
||||||
|
|
||||||
def decryptBook(keypath, inpath, outpath):
|
def decryptBook(keypath, inpath, outpath):
|
||||||
with open(inpath, 'rb') as inf:
|
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
|
# hope this will fix the 'bad file descriptor' problem
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
# help construct to make sure the method runs to the end
|
# help construct to make sure the method runs to the end
|
||||||
serializer.dump(outf)
|
try:
|
||||||
|
serializer.dump(outf)
|
||||||
|
except:
|
||||||
|
print "error writing pdf."
|
||||||
|
return 1
|
||||||
return 0
|
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.title' : (1, 'text', 0, 0),
|
||||||
'links.href' : (1, 'text', 0, 0),
|
'links.href' : (1, 'text', 0, 0),
|
||||||
'links.type' : (1, 'text', 0, 0),
|
'links.type' : (1, 'text', 0, 0),
|
||||||
|
'links.id' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
'paraCont' : (0, 'number', 1, 1),
|
'paraCont' : (0, 'number', 1, 1),
|
||||||
'paraCont.rootID' : (1, 'number', 0, 0),
|
'paraCont.rootID' : (1, 'number', 0, 0),
|
||||||
|
@ -239,6 +240,7 @@ class PageParser(object):
|
||||||
'group' : (1, 'snippets', 1, 0),
|
'group' : (1, 'snippets', 1, 0),
|
||||||
'group.type' : (1, 'scalar_text', 0, 0),
|
'group.type' : (1, 'scalar_text', 0, 0),
|
||||||
'group._tag' : (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' : (1, 'snippets', 1, 0),
|
||||||
'region.type' : (1, 'scalar_text', 0, 0),
|
'region.type' : (1, 'scalar_text', 0, 0),
|
||||||
|
@ -246,7 +248,7 @@ class PageParser(object):
|
||||||
'region.y' : (1, 'scalar_number', 0, 0),
|
'region.y' : (1, 'scalar_number', 0, 0),
|
||||||
'region.h' : (1, 'scalar_number', 0, 0),
|
'region.h' : (1, 'scalar_number', 0, 0),
|
||||||
'region.w' : (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),
|
'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
|
# and many many others
|
||||||
|
|
||||||
|
|
||||||
__version__ = '4.2'
|
__version__ = '4.4'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
|
@ -58,7 +58,7 @@ else:
|
||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
# added in removal of non-printing chars
|
# added in removal of non-printing chars
|
||||||
# and removal of . at start
|
# 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):
|
def cleanup_name(name):
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
|
||||||
substitute='_'
|
substitute='_'
|
||||||
|
@ -73,7 +73,7 @@ def cleanup_name(name):
|
||||||
# Mac and Unix don't like file names that begin with a full stop
|
# Mac and Unix don't like file names that begin with a full stop
|
||||||
if len(one) > 0 and one[0] == '.':
|
if len(one) > 0 and one[0] == '.':
|
||||||
one = substitute+one[1:]
|
one = substitute+one[1:]
|
||||||
one = one.replace(' ','_')
|
one = one.replace('_',' ')
|
||||||
return one
|
return one
|
||||||
|
|
||||||
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
|
@ -99,11 +99,20 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
|
||||||
title = mb.getBookTitle()
|
title = mb.getBookTitle()
|
||||||
print "Processing Book: ", title
|
print "Processing Book: ", title
|
||||||
filenametitle = cleanup_name(title)
|
filenametitle = cleanup_name(title)
|
||||||
outfilename = bookname
|
outfilename = cleanup_name(bookname)
|
||||||
if len(outfilename)<=8 or len(filenametitle)<=8:
|
|
||||||
outfilename = outfilename + "_" + filenametitle
|
# generate 'sensible' filename, that will sort with the original name,
|
||||||
elif outfilename[:8] != filenametitle[:8]:
|
# but is close to the name from the file.
|
||||||
outfilename = outfilename[:8] + "_" + filenametitle
|
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
|
# avoid excessively long file names
|
||||||
if len(outfilename)>150:
|
if len(outfilename)>150:
|
||||||
|
|
|
@ -385,17 +385,22 @@ def GetIDString():
|
||||||
if isNewInstall():
|
if isNewInstall():
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
sernum = GetVolumeSerialNumber()
|
sernum = GetVolumeSerialNumber()
|
||||||
if len(sernum) > 7:
|
if len(sernum) > 7:
|
||||||
|
print('Using Volume Serial Number for ID: '+sernum)
|
||||||
return sernum
|
return sernum
|
||||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||||
uuidnum = GetDiskPartitionUUID(diskpart)
|
uuidnum = GetDiskPartitionUUID(diskpart)
|
||||||
if len(uuidnum) > 7:
|
if len(uuidnum) > 7:
|
||||||
|
print('Using Disk Partition UUID for ID: '+uuidnum)
|
||||||
return uuidnum
|
return uuidnum
|
||||||
mungedmac = GetMACAddressMunged()
|
mungedmac = GetMACAddressMunged()
|
||||||
if len(mungedmac) > 7:
|
if len(mungedmac) > 7:
|
||||||
|
print('Using Munged MAC Address for ID: '+mungedmac)
|
||||||
return mungedmac
|
return mungedmac
|
||||||
|
print('Using Fixed constant 9999999999 for ID.')
|
||||||
return '9999999999'
|
return '9999999999'
|
||||||
|
|
||||||
|
|
||||||
|
@ -498,6 +503,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found K4Mac kindle-info file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .rainier*-kinf files
|
# add any .rainier*-kinf files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
|
||||||
|
@ -508,6 +514,7 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
# add any .kinf2011 files
|
# add any .kinf2011 files
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
|
||||||
|
@ -518,9 +525,10 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
for resline in reslst:
|
for resline in reslst:
|
||||||
if os.path.isfile(resline):
|
if os.path.isfile(resline):
|
||||||
kInfoFiles.append(resline)
|
kInfoFiles.append(resline)
|
||||||
|
print('Found k4Mac kinf2011 file: ' + resline)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
print('No kindle-info files have been found.')
|
print('No k4Mac kindle-info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
# determine type of kindle info provided and return a
|
# determine type of kindle info provided and return a
|
||||||
|
|
|
@ -151,7 +151,9 @@ def GetVolumeSerialNumber():
|
||||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||||
|
|
||||||
def GetIDString():
|
def GetIDString():
|
||||||
return GetVolumeSerialNumber()
|
vsn = GetVolumeSerialNumber()
|
||||||
|
print('Using Volume Serial Number for ID: '+vsn)
|
||||||
|
return vsn
|
||||||
|
|
||||||
def getLastError():
|
def getLastError():
|
||||||
GetLastError = kernel32.GetLastError
|
GetLastError = kernel32.GetLastError
|
||||||
|
@ -210,37 +212,40 @@ def getKindleInfoFiles(kInfoFiles):
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
path = os.environ['LOCALAPPDATA']
|
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
|
# first look for older kindle-info files
|
||||||
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No kindle.info files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kindle.info file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.5.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.5.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
# 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'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.6.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC 1.6.X kinf file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
# now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
|
||||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||||
if not os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
print('No K4PC 1.9.X .kinf files have not been found.')
|
found = True
|
||||||
else:
|
print('Found K4PC kinf2011 file: ' + kinfopath)
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
|
||||||
return kInfoFiles
|
return kInfoFiles
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ def encode(data, map):
|
||||||
result += map[Q]
|
result += map[Q]
|
||||||
result += map[R]
|
result += map[R]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
def encodeHash(data,map):
|
def encodeHash(data,map):
|
||||||
return encode(MD5(data),map)
|
return encode(MD5(data),map)
|
||||||
|
@ -78,11 +78,11 @@ def decode(data,map):
|
||||||
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
|
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
|
||||||
result += pack("B",value)
|
result += pack("B",value)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
#
|
#
|
||||||
# PID generation routines
|
# PID generation routines
|
||||||
#
|
#
|
||||||
|
|
||||||
# Returns two bit at offset from a bit field
|
# Returns two bit at offset from a bit field
|
||||||
def getTwoBitsFromBitField(bitField,offset):
|
def getTwoBitsFromBitField(bitField,offset):
|
||||||
byteNumber = offset // 4
|
byteNumber = offset // 4
|
||||||
|
@ -91,10 +91,10 @@ def getTwoBitsFromBitField(bitField,offset):
|
||||||
|
|
||||||
# Returns the six bits at offset from a bit field
|
# Returns the six bits at offset from a bit field
|
||||||
def getSixBitsFromBitField(bitField,offset):
|
def getSixBitsFromBitField(bitField,offset):
|
||||||
offset *= 3
|
offset *= 3
|
||||||
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
# 8 bits to six bits encoding from hash to generate PID string
|
# 8 bits to six bits encoding from hash to generate PID string
|
||||||
def encodePID(hash):
|
def encodePID(hash):
|
||||||
global charMap3
|
global charMap3
|
||||||
|
@ -121,8 +121,8 @@ def generatePidEncryptionTable() :
|
||||||
def generatePidSeed(table,dsn) :
|
def generatePidSeed(table,dsn) :
|
||||||
value = 0
|
value = 0
|
||||||
for counter in range (0,4) :
|
for counter in range (0,4) :
|
||||||
index = (ord(dsn[counter]) ^ value) &0xFF
|
index = (ord(dsn[counter]) ^ value) &0xFF
|
||||||
value = (value >> 8) ^ table[index]
|
value = (value >> 8) ^ table[index]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
# Generate the device PID
|
# Generate the device PID
|
||||||
|
@ -141,7 +141,7 @@ def generateDevicePID(table,dsn,nbRoll):
|
||||||
return pidAscii
|
return pidAscii
|
||||||
|
|
||||||
def crc32(s):
|
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
|
# convert from 8 digit PID to 10 digit PID with checksum
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
|
@ -204,10 +204,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
||||||
print(message)
|
print(message)
|
||||||
kindleDatabase = None
|
kindleDatabase = None
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if kindleDatabase == None :
|
if kindleDatabase == None :
|
||||||
return pidlst
|
return pidlst
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the Mazama Random number
|
# Get the Mazama Random number
|
||||||
MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
|
MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
|
||||||
|
@ -217,7 +217,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print "Keys not found in " + kInfoFile
|
print "Keys not found in " + kInfoFile
|
||||||
return pidlst
|
return pidlst
|
||||||
|
|
||||||
# Get the ID string used
|
# Get the ID string used
|
||||||
encodedIDString = encodeHash(GetIDString(),charMap1)
|
encodedIDString = encodeHash(GetIDString(),charMap1)
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
||||||
|
|
||||||
# concat, hash and encode to calculate the DSN
|
# concat, hash and encode to calculate the DSN
|
||||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||||
|
|
||||||
# Compute the device PID (for which I can tell, is used for nothing).
|
# Compute the device PID (for which I can tell, is used for nothing).
|
||||||
table = generatePidEncryptionTable()
|
table = generatePidEncryptionTable()
|
||||||
devicePID = generateDevicePID(table,DSN,4)
|
devicePID = generateDevicePID(table,DSN,4)
|
||||||
|
@ -258,13 +258,19 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
|
||||||
def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
|
def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
|
||||||
pidlst = []
|
pidlst = []
|
||||||
if kInfoFiles is None:
|
if kInfoFiles is None:
|
||||||
kInfoFiles = []
|
kInfoFiles = []
|
||||||
if k4:
|
if k4:
|
||||||
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
kInfoFiles = getKindleInfoFiles(kInfoFiles)
|
||||||
for infoFile in 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:
|
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:
|
for pid in pids:
|
||||||
pidlst.append(pid)
|
pidlst.append(pid)
|
||||||
return pidlst
|
return pidlst
|
||||||
|
|
|
@ -57,8 +57,11 @@
|
||||||
# 0.33 - Performance improvements for large files (concatenation)
|
# 0.33 - Performance improvements for large files (concatenation)
|
||||||
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
# 0.34 - Performance improvements in decryption (libalfcrypto)
|
||||||
# 0.35 - add interface to get mobi_version
|
# 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
|
import sys
|
||||||
|
|
||||||
|
@ -168,9 +171,10 @@ class MobiBook:
|
||||||
off = self.sections[section][0]
|
off = self.sections[section][0]
|
||||||
return self.data_file[off:endoff]
|
return self.data_file[off:endoff]
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile, announce = True):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
if announce:
|
||||||
'Copyright 2008-2011 The Dark Reverser et al.' % globals())
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
|
'Copyright 2008-2012 The Dark Reverser et al.' % globals())
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = file(infile, 'rb').read()
|
self.data_file = file(infile, 'rb').read()
|
||||||
|
@ -198,6 +202,7 @@ class MobiBook:
|
||||||
print "Book has format: ", self.magic
|
print "Book has format: ", self.magic
|
||||||
self.extra_data_flags = 0
|
self.extra_data_flags = 0
|
||||||
self.mobi_length = 0
|
self.mobi_length = 0
|
||||||
|
self.mobi_codepage = 1252
|
||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
self.meta_array = {}
|
self.meta_array = {}
|
||||||
return
|
return
|
||||||
|
@ -248,18 +253,19 @@ class MobiBook:
|
||||||
65001 : 'utf-8',
|
65001 : 'utf-8',
|
||||||
}
|
}
|
||||||
title = ''
|
title = ''
|
||||||
if 503 in self.meta_array:
|
codec = 'windows-1252'
|
||||||
title = self.meta_array[503]
|
if self.magic == 'BOOKMOBI':
|
||||||
else :
|
if 503 in self.meta_array:
|
||||||
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
|
title = self.meta_array[503]
|
||||||
tend = toff + tlen
|
else:
|
||||||
title = self.sect[toff:tend]
|
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 == '':
|
if title == '':
|
||||||
title = self.header[:32]
|
title = self.header[:32]
|
||||||
title = title.split("\0")[0]
|
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')
|
return unicode(title, codec).encode('utf-8')
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
def getPIDMetaInfo(self):
|
||||||
|
@ -375,7 +381,7 @@ class MobiBook:
|
||||||
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
|
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)
|
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||||
if not found_key:
|
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
|
# kill the drm keys
|
||||||
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||||||
# kill the drm pointers
|
# kill the drm pointers
|
||||||
|
@ -411,26 +417,26 @@ class MobiBook:
|
||||||
print "done"
|
print "done"
|
||||||
return
|
return
|
||||||
|
|
||||||
def getUnencryptedBook(infile,pid):
|
def getUnencryptedBook(infile,pid,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile,announce)
|
||||||
book.processBook([pid])
|
book.processBook([pid])
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
def getUnencryptedBookWithList(infile,pidlist):
|
def getUnencryptedBookWithList(infile,pidlist,announce=True):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException('Input File Not Found')
|
raise DrmException('Input File Not Found')
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile, announce)
|
||||||
book.processBook(pidlist)
|
book.processBook(pidlist)
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
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:
|
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 "Usage:"
|
||||||
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
|
||||||
return 1
|
return 1
|
||||||
|
@ -442,7 +448,7 @@ def main(argv=sys.argv):
|
||||||
else:
|
else:
|
||||||
pidlist = {}
|
pidlist = {}
|
||||||
try:
|
try:
|
||||||
stripped_file = getUnencryptedBookWithList(infile, pidlist)
|
stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
|
||||||
file(outfile, 'wb').write(stripped_file)
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print "Error: %s" % e
|
print "Error: %s" % e
|
||||||
|
|
|
@ -164,6 +164,9 @@ class DocParser(object):
|
||||||
scale = self.pw
|
scale = self.pw
|
||||||
elif attr == 'line-space':
|
elif attr == 'line-space':
|
||||||
scale = self.fontsize * 2.0
|
scale = self.fontsize * 2.0
|
||||||
|
|
||||||
|
if val == "":
|
||||||
|
val = 0
|
||||||
|
|
||||||
if not ((attr == 'hang') and (int(val) == 0)) :
|
if not ((attr == 'hang') and (int(val) == 0)) :
|
||||||
pv = float(val)/scale
|
pv = float(val)/scale
|
||||||
|
|
|
@ -31,11 +31,8 @@ class TpzDRMError(Exception):
|
||||||
# local support routines
|
# local support routines
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.k4mobidedrm import kgenpids
|
from calibre_plugins.k4mobidedrm import kgenpids
|
||||||
from calibre_plugins.k4mobidedrm import genbook
|
|
||||||
else:
|
else:
|
||||||
import kgenpids
|
import kgenpids
|
||||||
import genbook
|
|
||||||
|
|
||||||
|
|
||||||
# recursive zip creation support routine
|
# recursive zip creation support routine
|
||||||
def zipUpDir(myzip, tdir, localname):
|
def zipUpDir(myzip, tdir, localname):
|
||||||
|
@ -271,6 +268,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
print "\nBook Successfully generated"
|
||||||
|
@ -300,6 +302,11 @@ class TopazBook:
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print "Successfully Extracted Topaz contents"
|
print "Successfully Extracted Topaz contents"
|
||||||
|
if inCalibre:
|
||||||
|
from calibre_plugins.k4mobidedrm import genbook
|
||||||
|
else:
|
||||||
|
import genbook
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "\nBook Successfully generated"
|
print "\nBook Successfully generated"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Kindle for iPhone, iPod Touch, iPad
|
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, you’ll need the latest tools_vX.X.zip archive and
|
So, to remove the DRM from (Mobipocket) Kindle books downloaded to your iPhone/iPodTouch/iPad, you’ll 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.
|
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.
|
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 won’t), and you can then download ebooks from the store or library encrypted to work with your Kindle. Except they don’t. There’s a flag byte set in encrypted Kindle ebooks, and Kindles and the Kindle app won’t 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 Kindle’s 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, they’ll automatically become unreadable.
|
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 won’t), and you can then download ebooks from the store or library encrypted to work with your Kindle. Except they don’t. There’s a flag byte set in encrypted Kindle ebooks, and Kindles and the Kindle app won’t 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 Kindle’s 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, they’ll automatically become unreadable.
|
||||||
|
|
||||||
|
(2012 note: Few, if any, libraries now use this method of loaning ebooks.)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
Welcome to the tools!
|
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:
|
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
|
- Barnes and Noble ePubs
|
||||||
- Adobe Digitial Editions ePubs
|
- Adobe Digital Editions ePubs
|
||||||
- Adobe Digitial Editions PDFs
|
- Adobe Digital Editions PDFs
|
||||||
- Kindle/Mobipocket ebooks (including Topaz, Print Replica and KF8).
|
- Mobipocket ebooks
|
||||||
|
- eReader PDB books
|
||||||
|
|
||||||
These tools do NOT work with Apple's iBooks FairPlay DRM.
|
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 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.
|
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)
|
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***.
|
***This program requires that Python and PyCrypto be properly installed.***
|
||||||
See below for details on which versions are recommended.
|
***See below for details on recommended versions are where to get them.***
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
To use it simply drag ebooks or folders containing ebooks onto the DeDRM_Drop_Target short-cut, and it will process the ebooks.
|
To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM of the kinds listed above.
|
||||||
|
|
||||||
|
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
|
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:
|
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/)
|
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
|
2. input file: drmbook.epub
|
||||||
3. output file: name-ypu-want_for_free_book.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:
|
so if you want you can use calibre in Linux:
|
||||||
|
|
||||||
11. install the plugins from the tools as discribed in the readmes for win
|
11. install the plugins from the tools as discribed in the readmes for win
|
||||||
|
|
Loading…
Reference in New Issue